289 lines
8.3 KiB
C
289 lines
8.3 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "framebuffer.h"
|
|
|
|
#include "../glcommon/debug.h"
|
|
#include "gl33.h"
|
|
#include "texture.h"
|
|
|
|
GLuint r_attachment_to_gl_attachment[] = {
|
|
[FRAMEBUFFER_ATTACH_DEPTH] = GL_DEPTH_ATTACHMENT,
|
|
[FRAMEBUFFER_ATTACH_COLOR0] = GL_COLOR_ATTACHMENT0,
|
|
[FRAMEBUFFER_ATTACH_COLOR1] = GL_COLOR_ATTACHMENT1,
|
|
[FRAMEBUFFER_ATTACH_COLOR2] = GL_COLOR_ATTACHMENT2,
|
|
[FRAMEBUFFER_ATTACH_COLOR3] = GL_COLOR_ATTACHMENT3,
|
|
};
|
|
|
|
static_assert(sizeof(r_attachment_to_gl_attachment)/sizeof(GLuint) == FRAMEBUFFER_MAX_ATTACHMENTS, "");
|
|
|
|
Framebuffer *gl33_framebuffer_create(void) {
|
|
auto fb = ALLOC(Framebuffer);
|
|
glGenFramebuffers(1, &fb->gl_fbo);
|
|
snprintf(fb->debug_label, sizeof(fb->debug_label), "FBO #%i", fb->gl_fbo);
|
|
|
|
for(int i = 0; i < FRAMEBUFFER_MAX_OUTPUTS; ++i) {
|
|
fb->output_mapping[i] = FRAMEBUFFER_ATTACH_COLOR0 + i;
|
|
}
|
|
|
|
// According to the GL spec, the FBO is only actually created when it's first bound.
|
|
// Let's make sure this happens as early as possible.
|
|
Framebuffer *prev_fb = r_framebuffer_current();
|
|
r_framebuffer(fb);
|
|
gl33_sync_framebuffer();
|
|
r_framebuffer(prev_fb);
|
|
|
|
return fb;
|
|
}
|
|
|
|
attr_unused static inline char *attachment_str(FramebufferAttachment a) {
|
|
#define A(x) case x: return #x;
|
|
switch(a) {
|
|
A(FRAMEBUFFER_ATTACH_COLOR0)
|
|
A(FRAMEBUFFER_ATTACH_COLOR1)
|
|
A(FRAMEBUFFER_ATTACH_COLOR2)
|
|
A(FRAMEBUFFER_ATTACH_COLOR3)
|
|
A(FRAMEBUFFER_ATTACH_DEPTH)
|
|
default: UNREACHABLE;
|
|
}
|
|
#undef A
|
|
}
|
|
|
|
void gl33_framebuffer_attach(Framebuffer *framebuffer, Texture *tex, uint mipmap, FramebufferAttachment attachment) {
|
|
assert(attachment >= 0 && attachment < FRAMEBUFFER_MAX_ATTACHMENTS);
|
|
assert(!tex || mipmap < tex->params.mipmaps);
|
|
assert(tex || mipmap == 0);
|
|
|
|
GLuint gl_tex = tex ? tex->gl_handle : 0;
|
|
Framebuffer *prev_fb = r_framebuffer_current();
|
|
|
|
// gl33_sync_framebuffer will call gl33_framebuffer_prepare; we don't want it to touch draw buffers yet.
|
|
framebuffer->draw_buffers_dirty = false;
|
|
|
|
r_framebuffer(framebuffer);
|
|
gl33_sync_framebuffer();
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, r_attachment_to_gl_attachment[attachment], GL_TEXTURE_2D, gl_tex, mipmap);
|
|
r_framebuffer(prev_fb);
|
|
|
|
framebuffer->attachments[attachment] = tex;
|
|
framebuffer->attachment_mipmaps[attachment] = mipmap;
|
|
|
|
// need to update draw buffers
|
|
framebuffer->draw_buffers_dirty = true;
|
|
|
|
IF_DEBUG(if(tex) {
|
|
log_debug("%s %s = %s (%ux%u mip %u)", framebuffer->debug_label, attachment_str(attachment), tex->debug_label, tex->params.width, tex->params.height, mipmap);
|
|
} else {
|
|
log_debug("%s %s = NULL", framebuffer->debug_label, attachment_str(attachment));
|
|
});
|
|
}
|
|
|
|
FramebufferAttachmentQueryResult gl33_framebuffer_query_attachment(Framebuffer *framebuffer, FramebufferAttachment attachment) {
|
|
assert(attachment >= 0 && attachment < FRAMEBUFFER_MAX_ATTACHMENTS);
|
|
return (FramebufferAttachmentQueryResult) {
|
|
.texture = framebuffer->attachments[attachment],
|
|
.miplevel = framebuffer->attachment_mipmaps[attachment],
|
|
};
|
|
}
|
|
|
|
void gl33_framebuffer_outputs(
|
|
Framebuffer *framebuffer,
|
|
FramebufferAttachment config[FRAMEBUFFER_MAX_OUTPUTS],
|
|
uint8_t write_mask
|
|
) {
|
|
if(write_mask == 0x00) {
|
|
memcpy(config, framebuffer->output_mapping, sizeof(framebuffer->output_mapping));
|
|
return;
|
|
}
|
|
|
|
for(int i = 0; i < FRAMEBUFFER_MAX_OUTPUTS; ++i) {
|
|
if(write_mask & (1 << i) && config[i] != framebuffer->output_mapping[i]) {
|
|
if(config[i] != FRAMEBUFFER_ATTACH_NONE) {
|
|
assert(config[i] >= FRAMEBUFFER_ATTACH_COLOR0);
|
|
assert(config[i] < FRAMEBUFFER_ATTACH_COLOR0 + FRAMEBUFFER_MAX_COLOR_ATTACHMENTS);
|
|
}
|
|
|
|
framebuffer->output_mapping[i] = config[i];
|
|
framebuffer->draw_buffers_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gl33_framebuffer_destroy(Framebuffer *framebuffer) {
|
|
gl33_framebuffer_deleted(framebuffer);
|
|
glDeleteFramebuffers(1, &framebuffer->gl_fbo);
|
|
mem_free(framebuffer);
|
|
}
|
|
|
|
void gl33_framebuffer_taint(Framebuffer *framebuffer) {
|
|
for(uint i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) {
|
|
if(framebuffer->attachments[i] != NULL) {
|
|
gl33_texture_taint(framebuffer->attachments[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void gl33_framebuffer_prepare(Framebuffer *framebuffer) {
|
|
if(framebuffer->draw_buffers_dirty) {
|
|
// NOTE: this framebuffer is guaranteed to be bound at this point
|
|
|
|
if(glext.draw_buffers) {
|
|
GLenum drawbufs[FRAMEBUFFER_MAX_OUTPUTS];
|
|
|
|
for(int i = 0; i < FRAMEBUFFER_MAX_OUTPUTS; ++i) {
|
|
FramebufferAttachment a = framebuffer->output_mapping[i];
|
|
|
|
if(a == FRAMEBUFFER_ATTACH_NONE || framebuffer->attachments[a] == NULL) {
|
|
drawbufs[i] = GL_NONE;
|
|
} else {
|
|
drawbufs[i] = GL_COLOR_ATTACHMENT0 + (a - FRAMEBUFFER_ATTACH_COLOR0);
|
|
}
|
|
}
|
|
|
|
glDrawBuffers(FRAMEBUFFER_MAX_OUTPUTS, drawbufs);
|
|
} else {
|
|
// TODO: maybe simulate this by modifying framebuffer attachments?
|
|
}
|
|
|
|
framebuffer->draw_buffers_dirty = false;
|
|
}
|
|
}
|
|
|
|
void gl33_framebuffer_set_debug_label(Framebuffer *fb, const char *label) {
|
|
glcommon_set_debug_label(fb->debug_label, "FBO", GL_FRAMEBUFFER, fb->gl_fbo, label);
|
|
}
|
|
|
|
const char *gl33_framebuffer_get_debug_label(Framebuffer* fb) {
|
|
return fb->debug_label;
|
|
}
|
|
|
|
static GLuint buffer_flags_to_gl(BufferKindFlags flags) {
|
|
GLuint glflags = 0;
|
|
|
|
if(flags & BUFFER_COLOR) {
|
|
glflags |= GL_COLOR_BUFFER_BIT;
|
|
}
|
|
|
|
if(flags & BUFFER_DEPTH) {
|
|
glflags |= GL_DEPTH_BUFFER_BIT;
|
|
}
|
|
|
|
return glflags;
|
|
}
|
|
|
|
void gl33_framebuffer_clear(Framebuffer *framebuffer, BufferKindFlags flags, const Color *colorval, float depthval) {
|
|
GLuint glflags = buffer_flags_to_gl(flags);
|
|
|
|
if(flags & BUFFER_COLOR) {
|
|
assert(colorval != NULL);
|
|
gl33_set_clear_color(colorval);
|
|
}
|
|
|
|
if(flags & BUFFER_DEPTH) {
|
|
gl33_set_clear_depth(depthval);
|
|
}
|
|
|
|
r_flush_sprites();
|
|
|
|
Framebuffer *fb_saved = r_framebuffer_current();
|
|
r_framebuffer(framebuffer);
|
|
gl33_sync_framebuffer();
|
|
gl33_sync_scissor();
|
|
glClear(glflags);
|
|
r_framebuffer(fb_saved);
|
|
}
|
|
|
|
void gl33_framebuffer_copy(Framebuffer *dst, Framebuffer *src, BufferKindFlags flags) {
|
|
GLuint glflags = buffer_flags_to_gl(flags);
|
|
|
|
r_flush_sprites();
|
|
|
|
IntExtent size = r_framebuffer_get_size(dst);
|
|
GLint X0 = 0;
|
|
GLint X1 = size.w;
|
|
GLint Y0 = 0;
|
|
GLint Y1 = size.h;
|
|
|
|
Framebuffer *fb_saved = r_framebuffer_current();
|
|
r_framebuffer(dst);
|
|
gl33_sync_framebuffer();
|
|
gl33_sync_scissor();
|
|
// TODO track this?
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, src->gl_fbo);
|
|
glBlitFramebuffer(X0, Y0, X1, Y1, X0, Y0, X1, Y1, glflags, GL_NEAREST);
|
|
r_framebuffer(fb_saved);
|
|
}
|
|
|
|
IntExtent gl33_framebuffer_get_effective_size(Framebuffer *framebuffer) {
|
|
// According to the OpenGL wiki:
|
|
// "The effective size of the FBO is the intersection of all of the sizes of the bound images (ie: the smallest in each dimension)."
|
|
|
|
// TODO: do it once in gl33_framebuffer_prepare and cache the result
|
|
|
|
IntExtent fb_size = { 0, 0 };
|
|
|
|
for(int i = 0; i < FRAMEBUFFER_MAX_ATTACHMENTS; ++i) {
|
|
Texture *tex = framebuffer->attachments[i];
|
|
|
|
if(tex != NULL) {
|
|
uint mipmap = framebuffer->attachment_mipmaps[i];
|
|
uint tex_w, tex_h;
|
|
gl33_texture_get_size(tex, mipmap, &tex_w, &tex_h);
|
|
IntExtent tex_size = { tex_w, tex_h };
|
|
|
|
if(fb_size.w == 0 && fb_size.h == 0) {
|
|
fb_size = tex_size;
|
|
} else if(tex_size.w < fb_size.w) {
|
|
fb_size.w = tex_size.w;
|
|
} else if(tex_size.h < fb_size.h) {
|
|
fb_size.h = tex_size.h;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fb_size;
|
|
}
|
|
|
|
GLTextureFormatInfo *gl33_framebuffer_get_format(
|
|
Framebuffer *framebuffer, FramebufferAttachment attachment
|
|
) {
|
|
GLTextureFormatInfo *fmtinfo = NULL;
|
|
|
|
if(framebuffer) {
|
|
FramebufferAttachmentQueryResult aq = gl33_framebuffer_query_attachment(framebuffer, attachment);
|
|
fmtinfo = NOT_NULL(aq.texture)->fmt_info;
|
|
} else {
|
|
assert(attachment == FRAMEBUFFER_ATTACH_NONE);
|
|
|
|
GLenum ifmt = GL_RGBA8; // FIXME how to query this?
|
|
|
|
auto formats = glcommon_get_texture_formats();
|
|
dynarray_foreach_elem(formats, auto fmt, {
|
|
if(fmt->internal_format == ifmt) {
|
|
fmtinfo = fmt;
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
return NOT_NULL(fmtinfo);
|
|
}
|
|
|
|
void gl33_framebuffer_bind_for_read(
|
|
Framebuffer *framebuffer, FramebufferAttachment attachment
|
|
) {
|
|
if(framebuffer) {
|
|
assert(attachment >= 0 && attachment < FRAMEBUFFER_MAX_ATTACHMENTS);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer->gl_fbo);
|
|
glReadBuffer(r_attachment_to_gl_attachment[attachment]);
|
|
} else {
|
|
assert(attachment == FRAMEBUFFER_ATTACH_NONE);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
|
glReadBuffer(GL_BACK);
|
|
}
|
|
}
|