diff options
Diffstat (limited to 'source/blender/gpu/intern/gpu_framebuffer.c')
-rw-r--r-- | source/blender/gpu/intern/gpu_framebuffer.c | 940 |
1 files changed, 575 insertions, 365 deletions
diff --git a/source/blender/gpu/intern/gpu_framebuffer.c b/source/blender/gpu/intern/gpu_framebuffer.c index ff637b5b4de..ffc72718e42 100644 --- a/source/blender/gpu/intern/gpu_framebuffer.c +++ b/source/blender/gpu/intern/gpu_framebuffer.c @@ -28,308 +28,433 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_threads.h" #include "BLI_utildefines.h" +#include "BLI_math_base.h" #include "BKE_global.h" -#include "GPU_debug.h" -#include "GPU_glew.h" +#include "GPU_batch.h" +#include "GPU_draw.h" +#include "GPU_extensions.h" #include "GPU_framebuffer.h" +#include "GPU_matrix.h" #include "GPU_shader.h" #include "GPU_texture.h" -static struct GPUFrameBufferGlobal { - GLuint currentfb; -} GG = {0}; +#include "intern/gpu_private.h" -/* Number of maximum output slots. - * We support 4 outputs for now (usually we wouldn't need more to preserve fill rate) */ -#define GPU_FB_MAX_SLOTS 4 +static ThreadLocal(void *) g_currentfb; + +typedef enum { + GPU_FB_DEPTH_ATTACHMENT = 0, + GPU_FB_DEPTH_STENCIL_ATTACHMENT, + GPU_FB_COLOR_ATTACHMENT0, + GPU_FB_COLOR_ATTACHMENT1, + GPU_FB_COLOR_ATTACHMENT2, + GPU_FB_COLOR_ATTACHMENT3, + GPU_FB_COLOR_ATTACHMENT4, + /* Number of maximum output slots. + * We support 5 outputs for now (usually we wouldn't need more to preserve fill rate). */ + /* Keep in mind that GL max is GL_MAX_DRAW_BUFFERS and is at least 8, corresponding to + * the maximum number of COLOR attachments specified by glDrawBuffers. */ + GPU_FB_MAX_ATTACHEMENT +} GPUAttachmentType; + +#define GPU_FB_MAX_COLOR_ATTACHMENT (GPU_FB_MAX_ATTACHEMENT - GPU_FB_COLOR_ATTACHMENT0) + +#define GPU_FB_DIRTY_DRAWBUFFER (1 << 15) + +#define GPU_FB_ATTACHEMENT_IS_DIRTY(flag, type) ((flag & (1 << type)) != 0) +#define GPU_FB_ATTACHEMENT_SET_DIRTY(flag, type) (flag |= (1 << type)) struct GPUFrameBuffer { GLuint object; - GPUTexture *colortex[GPU_FB_MAX_SLOTS]; - GPUTexture *depthtex; + GPUAttachment attachments[GPU_FB_MAX_ATTACHEMENT]; + uint16_t dirty_flag; + int width, height; + bool multisample; + /* TODO Check that we always use the right context when binding + * (FBOs are not shared accross ogl contexts). */ + // void *ctx; }; +static GLenum convert_attachment_type_to_gl(GPUAttachmentType type) +{ + static const GLenum table[] = { + [GPU_FB_DEPTH_ATTACHMENT] = GL_DEPTH_ATTACHMENT, + [GPU_FB_DEPTH_STENCIL_ATTACHMENT] = GL_DEPTH_STENCIL_ATTACHMENT, + [GPU_FB_COLOR_ATTACHMENT0] = GL_COLOR_ATTACHMENT0, + [GPU_FB_COLOR_ATTACHMENT1] = GL_COLOR_ATTACHMENT1, + [GPU_FB_COLOR_ATTACHMENT2] = GL_COLOR_ATTACHMENT2, + [GPU_FB_COLOR_ATTACHMENT3] = GL_COLOR_ATTACHMENT3, + [GPU_FB_COLOR_ATTACHMENT4] = GL_COLOR_ATTACHMENT4 + }; + return table[type]; +} + +static GPUAttachmentType attachment_type_from_tex(GPUTexture *tex, int slot) +{ + switch (GPU_texture_format(tex)) { + case GPU_DEPTH_COMPONENT32F: + case GPU_DEPTH_COMPONENT24: + case GPU_DEPTH_COMPONENT16: + return GPU_FB_DEPTH_ATTACHMENT; + case GPU_DEPTH24_STENCIL8: + return GPU_FB_DEPTH_STENCIL_ATTACHMENT; + default: + return GPU_FB_COLOR_ATTACHMENT0 + slot; + } +} + +static GLenum convert_buffer_bits_to_gl(GPUFrameBufferBits bits) +{ + GLbitfield mask = 0; + mask |= (bits & GPU_DEPTH_BIT) ? GL_DEPTH_BUFFER_BIT : 0; + mask |= (bits & GPU_STENCIL_BIT) ? GL_STENCIL_BUFFER_BIT : 0; + mask |= (bits & GPU_COLOR_BIT) ? GL_COLOR_BUFFER_BIT : 0; + return mask; +} + +static GPUTexture *framebuffer_get_depth_tex(GPUFrameBuffer *fb) +{ + if (fb->attachments[GPU_FB_DEPTH_ATTACHMENT].tex) + return fb->attachments[GPU_FB_DEPTH_ATTACHMENT].tex; + else + return fb->attachments[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex;; +} + +static GPUTexture *framebuffer_get_color_tex(GPUFrameBuffer *fb, int slot) +{ + return fb->attachments[GPU_FB_COLOR_ATTACHMENT0 + slot].tex; +} + static void gpu_print_framebuffer_error(GLenum status, char err_out[256]) { + const char *format = "GPUFrameBuffer: framebuffer status %s\n"; const char *err = "unknown"; +#define format_status(X) \ + case GL_FRAMEBUFFER_##X: err = "GL_FRAMEBUFFER_"#X; \ + break; + switch (status) { - case GL_FRAMEBUFFER_COMPLETE_EXT: - break; - case GL_INVALID_OPERATION: - err = "Invalid operation"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: - err = "Incomplete attachment"; - break; - case GL_FRAMEBUFFER_UNSUPPORTED_EXT: - err = "Unsupported framebuffer format"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: - err = "Missing attachment"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: - err = "Attached images must have same dimensions"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: - err = "Attached images must have same format"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: - err = "Missing draw buffer"; - break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: - err = "Missing read buffer"; - break; + /* success */ + format_status(COMPLETE) + /* errors shared by OpenGL desktop & ES */ + format_status(INCOMPLETE_ATTACHMENT) + format_status(INCOMPLETE_MISSING_ATTACHMENT) + format_status(UNSUPPORTED) +#if 0 /* for OpenGL ES only */ + format_status(INCOMPLETE_DIMENSIONS) +#else /* for desktop GL only */ + format_status(INCOMPLETE_DRAW_BUFFER) + format_status(INCOMPLETE_READ_BUFFER) + format_status(INCOMPLETE_MULTISAMPLE) + format_status(UNDEFINED) +#endif } +#undef format_status + if (err_out) { - BLI_snprintf(err_out, 256, "GPUFrameBuffer: framebuffer incomplete error %d '%s'", - (int)status, err); + BLI_snprintf(err_out, 256, format, err); } else { - fprintf(stderr, "GPUFrameBuffer: framebuffer incomplete error %d '%s'\n", - (int)status, err); + fprintf(stderr, format, err); } } -/* GPUFrameBuffer */ - -GPUFrameBuffer *GPU_framebuffer_create(void) +void gpu_framebuffer_module_init(void) { - GPUFrameBuffer *fb; + BLI_thread_local_create(g_currentfb); +} - if (!(GLEW_VERSION_3_0 || GLEW_ARB_framebuffer_object || - (GLEW_EXT_framebuffer_object && GLEW_EXT_framebuffer_blit))) - { - return NULL; - } +void gpu_framebuffer_module_exit(void) +{ + BLI_thread_local_delete(g_currentfb); +} - fb = MEM_callocN(sizeof(GPUFrameBuffer), "GPUFrameBuffer"); - glGenFramebuffersEXT(1, &fb->object); +static uint gpu_framebuffer_current_get(void) +{ + return GET_UINT_FROM_POINTER(BLI_thread_local_get(g_currentfb)); +} - if (!fb->object) { - fprintf(stderr, "GPUFFrameBuffer: framebuffer gen failed. %d\n", - (int)glGetError()); - GPU_framebuffer_free(fb); - return NULL; - } +static void gpu_framebuffer_current_set(uint object) +{ + BLI_thread_local_set(g_currentfb, SET_UINT_IN_POINTER(object)); +} - /* make sure no read buffer is enabled, so completeness check will not fail. We set those at binding time */ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - glReadBuffer(GL_NONE); - glDrawBuffer(GL_NONE); - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +/* GPUFrameBuffer */ - return fb; +GPUFrameBuffer *GPU_framebuffer_create(void) +{ + /* We generate the FB object later at first use in order to + * create the framebuffer in the right opengl context. */ + return MEM_callocN(sizeof(GPUFrameBuffer), "GPUFrameBuffer");; } -int GPU_framebuffer_texture_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, char err_out[256]) +static void gpu_framebuffer_init(GPUFrameBuffer *fb) { - GLenum attachment; - GLenum error; + glGenFramebuffers(1, &fb->object); +} - if (slot >= GPU_FB_MAX_SLOTS) { - fprintf(stderr, - "Attaching to index %d framebuffer slot unsupported. " - "Use at most %d\n", slot, GPU_FB_MAX_SLOTS); - return 0; +void GPU_framebuffer_free(GPUFrameBuffer *fb) +{ + for (GPUAttachmentType type = 0; type < GPU_FB_MAX_ATTACHEMENT; type++) { + if (fb->attachments[type].tex != NULL) { + GPU_framebuffer_texture_detach(fb, fb->attachments[type].tex); + } } - if ((G.debug & G_DEBUG)) { - if (GPU_texture_bound_number(tex) != -1) { - fprintf(stderr, - "Feedback loop warning!: " - "Attempting to attach texture to framebuffer while still bound to texture unit for drawing!\n"); - } + /* This restores the framebuffer if it was bound */ + glDeleteFramebuffers(1, &fb->object); + + if (gpu_framebuffer_current_get() == fb->object) { + gpu_framebuffer_current_set(0); } - if (GPU_texture_depth(tex)) - attachment = GL_DEPTH_ATTACHMENT_EXT; - else - attachment = GL_COLOR_ATTACHMENT0_EXT + slot; + MEM_freeN(fb); +} - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - GG.currentfb = fb->object; +/* ---------- Attach ----------- */ - /* Clean glError buffer. */ - while (glGetError() != GL_NO_ERROR) {} +static void gpu_framebuffer_texture_attach_ex(GPUFrameBuffer *fb, GPUTexture *tex, int slot, int layer, int mip) +{ + if (slot >= GPU_FB_MAX_COLOR_ATTACHMENT) { + fprintf(stderr, + "Attaching to index %d framebuffer slot unsupported. " + "Use at most %d\n", slot, GPU_FB_MAX_COLOR_ATTACHMENT); + return; + } - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, - GPU_texture_target(tex), GPU_texture_opengl_bindcode(tex), 0); + GPUAttachmentType type = attachment_type_from_tex(tex, slot); + GPUAttachment *attachment = &fb->attachments[type]; - error = glGetError(); + if ((attachment->tex == tex) && + (attachment->mip == mip) && + (attachment->layer == layer)) + { + return; /* Exact same texture already bound here. */ + } + else if (attachment->tex != NULL) { + GPU_framebuffer_texture_detach(fb, attachment->tex); + } - if (error == GL_INVALID_OPERATION) { - GPU_framebuffer_restore(); - gpu_print_framebuffer_error(error, err_out); - return 0; + if (attachment->tex == NULL) { + GPU_texture_attach_framebuffer(tex, fb, type); } - if (GPU_texture_depth(tex)) - fb->depthtex = tex; - else - fb->colortex[slot] = tex; + attachment->tex = tex; + attachment->mip = mip; + attachment->layer = layer; + GPU_FB_ATTACHEMENT_SET_DIRTY(fb->dirty_flag, type); +} - GPU_texture_framebuffer_set(tex, fb, slot); +void GPU_framebuffer_texture_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, int mip) +{ + gpu_framebuffer_texture_attach_ex(fb, tex, slot, -1, mip); +} - return 1; +void GPU_framebuffer_texture_layer_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, int layer, int mip) +{ + /* NOTE: We could support 1D ARRAY texture. */ + BLI_assert(GPU_texture_target(tex) == GL_TEXTURE_2D_ARRAY); + gpu_framebuffer_texture_attach_ex(fb, tex, slot, layer, mip); } -void GPU_framebuffer_texture_detach(GPUTexture *tex) +void GPU_framebuffer_texture_cubeface_attach(GPUFrameBuffer *fb, GPUTexture *tex, int slot, int face, int mip) { - GLenum attachment; - GPUFrameBuffer *fb = GPU_texture_framebuffer(tex); - int fb_attachment = GPU_texture_framebuffer_attachment(tex); + BLI_assert(GPU_texture_cube(tex)); + gpu_framebuffer_texture_attach_ex(fb, tex, slot, face, mip); +} - if (!fb) - return; +/* ---------- Detach ----------- */ - if (GG.currentfb != fb->object) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - GG.currentfb = fb->object; - } +void GPU_framebuffer_texture_detach_slot(GPUFrameBuffer *fb, GPUTexture *tex, int type) +{ + GPUAttachment *attachment = &fb->attachments[type]; - if (GPU_texture_depth(tex)) { - fb->depthtex = NULL; - attachment = GL_DEPTH_ATTACHMENT_EXT; - } - else { - BLI_assert(fb->colortex[fb_attachment] == tex); - fb->colortex[fb_attachment] = NULL; - attachment = GL_COLOR_ATTACHMENT0_EXT + fb_attachment; + if (attachment->tex != tex) { + fprintf(stderr, + "Warning, attempting to detach Texture %p from framebuffer %p " + "but texture is not attached.\n", tex, fb); + return; } - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment, GPU_texture_target(tex), 0, 0); + attachment->tex = NULL; + GPU_FB_ATTACHEMENT_SET_DIRTY(fb->dirty_flag, type); +} - GPU_texture_framebuffer_set(tex, NULL, -1); +void GPU_framebuffer_texture_detach(GPUFrameBuffer *fb, GPUTexture *tex) +{ + GPUAttachmentType type = GPU_texture_detach_framebuffer(tex, fb); + GPU_framebuffer_texture_detach_slot(fb, tex, type); } -void GPU_texture_bind_as_framebuffer(GPUTexture *tex) +/* ---------- Config (Attach & Detach) ----------- */ + +/** + * First GPUAttachment in *config is always the depth/depth_stencil buffer. + * Following GPUAttachments are color buffers. + * Setting GPUAttachment.mip to -1 will leave the texture in this slot. + * Setting GPUAttachment.tex to NULL will detach the texture in this slot. + **/ +void GPU_framebuffer_config_array(GPUFrameBuffer *fb, const GPUAttachment *config, int config_len) { - GPUFrameBuffer *fb = GPU_texture_framebuffer(tex); - int fb_attachment = GPU_texture_framebuffer_attachment(tex); + if (config[0].tex) { + BLI_assert(GPU_texture_depth(config[0].tex)); + gpu_framebuffer_texture_attach_ex(fb, config[0].tex, 0, config[0].layer, config[0].mip); + } + else if (config[0].mip == -1) { + /* Leave texture attached */ + } + else if (fb->attachments[GPU_FB_DEPTH_ATTACHMENT].tex != NULL) { + GPU_framebuffer_texture_detach(fb, fb->attachments[GPU_FB_DEPTH_ATTACHMENT].tex); + } + else if (fb->attachments[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex != NULL) { + GPU_framebuffer_texture_detach(fb, fb->attachments[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex); + } - if (!fb) { - fprintf(stderr, "Error, texture not bound to framebuffer!\n"); - return; + int slot = 0; + for (int i = 1; i < config_len; ++i, ++slot) { + if (config[i].tex != NULL) { + BLI_assert(GPU_texture_depth(config[i].tex) == false); + gpu_framebuffer_texture_attach_ex(fb, config[i].tex, slot, config[i].layer, config[i].mip); + } + else if (config[i].mip != -1) { + GPUTexture *tex = framebuffer_get_color_tex(fb, slot); + if (tex != NULL) { + GPU_framebuffer_texture_detach(fb, tex); + } + } } +} - /* push attributes */ - glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT); - glDisable(GL_SCISSOR_TEST); +/* ---------- Bind / Restore ----------- */ - /* bind framebuffer */ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); +static void gpu_framebuffer_attachment_attach(GPUAttachment *attach, GPUAttachmentType attach_type) +{ + int tex_bind = GPU_texture_opengl_bindcode(attach->tex); + GLenum gl_attachment = convert_attachment_type_to_gl(attach_type); - if (GPU_texture_depth(tex)) { - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); + if (attach->layer > -1) { + if (GPU_texture_cube(attach->tex)) { + glFramebufferTexture2D(GL_FRAMEBUFFER, gl_attachment, GL_TEXTURE_CUBE_MAP_POSITIVE_X + attach->layer, + tex_bind, attach->mip); + } + else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_attachment, tex_bind, attach->mip, attach->layer); + } } else { - /* last bound prevails here, better allow explicit control here too */ - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment); - glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + fb_attachment); + glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, tex_bind, attach->mip); } +} - if (GPU_texture_target(tex) == GL_TEXTURE_2D_MULTISAMPLE) { - glEnable(GL_MULTISAMPLE); - } - - /* push matrices and set default viewport and matrix */ - glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex)); - GG.currentfb = fb->object; - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); +static void gpu_framebuffer_attachment_detach(GPUAttachment *UNUSED(attachment), GPUAttachmentType attach_type) +{ + GLenum gl_attachment = convert_attachment_type_to_gl(attach_type); + glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, 0, 0); } -void GPU_framebuffer_slots_bind(GPUFrameBuffer *fb, int slot) +static void gpu_framebuffer_update_attachments(GPUFrameBuffer *fb) { - int numslots = 0, i; - GLenum attachments[4]; + GLenum gl_attachments[GPU_FB_MAX_COLOR_ATTACHMENT]; + int numslots = 0; - if (!fb->colortex[slot]) { - fprintf(stderr, "Error, framebuffer slot empty!\n"); - return; - } + BLI_assert(gpu_framebuffer_current_get() == fb->object); - for (i = 0; i < 4; i++) { - if (fb->colortex[i]) { - attachments[numslots] = GL_COLOR_ATTACHMENT0_EXT + i; + /* Update attachments */ + for (GPUAttachmentType type = 0; type < GPU_FB_MAX_ATTACHEMENT; ++type) { + + if (type >= GPU_FB_COLOR_ATTACHMENT0) { + if (fb->attachments[type].tex) { + gl_attachments[numslots] = convert_attachment_type_to_gl(type); + } + else { + gl_attachments[numslots] = GL_NONE; + } numslots++; } + + if (GPU_FB_ATTACHEMENT_IS_DIRTY(fb->dirty_flag, type) == false) { + continue; + } + else if (fb->attachments[type].tex != NULL) { + gpu_framebuffer_attachment_attach(&fb->attachments[type], type); + + fb->multisample = (GPU_texture_samples(fb->attachments[type].tex) > 0); + fb->width = GPU_texture_width(fb->attachments[type].tex); + fb->height = GPU_texture_height(fb->attachments[type].tex); + } + else { + gpu_framebuffer_attachment_detach(&fb->attachments[type], type); + } } + fb->dirty_flag = 0; - /* push attributes */ - glPushAttrib(GL_ENABLE_BIT | GL_VIEWPORT_BIT); - glDisable(GL_SCISSOR_TEST); + /* Update draw buffers (color targets) + * This state is saved in the FBO */ + if (numslots) + glDrawBuffers(numslots, gl_attachments); + else + glDrawBuffer(GL_NONE); +} - /* bind framebuffer */ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); +void GPU_framebuffer_bind(GPUFrameBuffer *fb) +{ + if (fb->object == 0) + gpu_framebuffer_init(fb); - /* last bound prevails here, better allow explicit control here too */ - glDrawBuffers(numslots, attachments); - glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot); + if (gpu_framebuffer_current_get() != fb->object) + glBindFramebuffer(GL_FRAMEBUFFER, fb->object); - /* push matrices and set default viewport and matrix */ - glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot])); - GG.currentfb = fb->object; + gpu_framebuffer_current_set(fb->object); - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); -} + if (fb->dirty_flag != 0) + gpu_framebuffer_update_attachments(fb); + /* TODO manually check for errors? */ +#if 0 + char err_out[256]; + if (!GPU_framebuffer_check_valid(fb, err_out)) { + printf("Invalid %s\n", err_out); + } +#endif -void GPU_framebuffer_texture_unbind(GPUFrameBuffer *UNUSED(fb), GPUTexture *UNUSED(tex)) -{ - /* restore matrix */ - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); + if (fb->multisample) + glEnable(GL_MULTISAMPLE); - /* restore attributes */ - glPopAttrib(); + glViewport(0, 0, fb->width, fb->height); } -void GPU_framebuffer_bind_no_save(GPUFrameBuffer *fb, int slot) +void GPU_framebuffer_restore(void) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - /* last bound prevails here, better allow explicit control here too */ - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT + slot); - glReadBuffer(GL_COLOR_ATTACHMENT0_EXT + slot); - - /* push matrices and set default viewport and matrix */ - glViewport(0, 0, GPU_texture_width(fb->colortex[slot]), GPU_texture_height(fb->colortex[slot])); - GG.currentfb = fb->object; - GG.currentfb = fb->object; + if (gpu_framebuffer_current_get() != 0) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + gpu_framebuffer_current_set(0); + } } bool GPU_framebuffer_bound(GPUFrameBuffer *fb) { - return fb->object == GG.currentfb; + return (fb->object == gpu_framebuffer_current_get()) && (fb->object != 0); } -bool GPU_framebuffer_check_valid(GPUFrameBuffer *fb, char err_out[256]) +unsigned int GPU_framebuffer_current_get(void) { - GLenum status; - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - GG.currentfb = fb->object; + return gpu_framebuffer_current_get(); +} - /* Clean glError buffer. */ - while (glGetError() != GL_NO_ERROR) {} +bool GPU_framebuffer_check_valid(GPUFrameBuffer *fb, char err_out[256]) +{ + if (!GPU_framebuffer_bound(fb)) + GPU_framebuffer_bind(fb); - status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { + if (status != GL_FRAMEBUFFER_COMPLETE) { GPU_framebuffer_restore(); gpu_print_framebuffer_error(status, err_out); return false; @@ -338,109 +463,206 @@ bool GPU_framebuffer_check_valid(GPUFrameBuffer *fb, char err_out[256]) return true; } -void GPU_framebuffer_free(GPUFrameBuffer *fb) +/* ---------- Framebuffer Operations ----------- */ + +#define CHECK_FRAMEBUFFER_IS_BOUND(_fb) \ + BLI_assert(GPU_framebuffer_bound(_fb)); \ + UNUSED_VARS_NDEBUG(_fb); + +/* Needs to be done after binding. */ +void GPU_framebuffer_viewport_set(GPUFrameBuffer *fb, int x, int y, int w, int h) { - int i; - if (fb->depthtex) - GPU_framebuffer_texture_detach(fb->depthtex); + CHECK_FRAMEBUFFER_IS_BOUND(fb); - for (i = 0; i < GPU_FB_MAX_SLOTS; i++) { - if (fb->colortex[i]) { - GPU_framebuffer_texture_detach(fb->colortex[i]); - } - } + glViewport(x, y, w, h); +} - if (fb->object) { - glDeleteFramebuffersEXT(1, &fb->object); +void GPU_framebuffer_clear( + GPUFrameBuffer *fb, GPUFrameBufferBits buffers, + const float clear_col[4], float clear_depth, unsigned int clear_stencil) +{ + CHECK_FRAMEBUFFER_IS_BOUND(fb); - if (GG.currentfb == fb->object) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - GG.currentfb = 0; - } + if (buffers & GPU_COLOR_BIT) { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(clear_col[0], clear_col[1], clear_col[2], clear_col[3]); + } + if (buffers & GPU_DEPTH_BIT) { + glDepthMask(GL_TRUE); + glClearDepth(clear_depth); + } + if (buffers & GPU_STENCIL_BIT) { + glStencilMask(clear_stencil); } - MEM_freeN(fb); + GLbitfield mask = convert_buffer_bits_to_gl(buffers); + glClear(mask); } -void GPU_framebuffer_restore(void) +void GPU_framebuffer_read_depth(GPUFrameBuffer *fb, int x, int y, int w, int h, float *data) +{ + CHECK_FRAMEBUFFER_IS_BOUND(fb); + + GLenum type = GL_DEPTH_COMPONENT; + glReadBuffer(GL_COLOR_ATTACHMENT0); /* This is OK! */ + glReadPixels(x, y, w, h, type, GL_FLOAT, data); +} + +void GPU_framebuffer_read_color( + GPUFrameBuffer *fb, int x, int y, int w, int h, int channels, int slot, float *data) { - if (GG.currentfb != 0) { - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - GG.currentfb = 0; + CHECK_FRAMEBUFFER_IS_BOUND(fb); + + GLenum type; + switch (channels) { + case 1: type = GL_RED; break; + case 2: type = GL_RG; break; + case 3: type = GL_RGB; break; + case 4: type = GL_RGBA; break; + default: + BLI_assert(false && "wrong number of read channels"); + return; } + glReadBuffer(GL_COLOR_ATTACHMENT0 + slot); + glReadPixels(x, y, w, h, type, GL_FLOAT, data); } -void GPU_framebuffer_blur( - GPUFrameBuffer *fb, GPUTexture *tex, - GPUFrameBuffer *blurfb, GPUTexture *blurtex) +/* read_slot and write_slot are only used for color buffers. */ +void GPU_framebuffer_blit( + GPUFrameBuffer *fb_read, int read_slot, + GPUFrameBuffer *fb_write, int write_slot, + GPUFrameBufferBits blit_buffers) { - const float scaleh[2] = {1.0f / GPU_texture_width(blurtex), 0.0f}; - const float scalev[2] = {0.0f, 1.0f / GPU_texture_height(tex)}; + BLI_assert(blit_buffers != 0); - GPUShader *blur_shader = GPU_shader_get_builtin_shader(GPU_SHADER_SEP_GAUSSIAN_BLUR); - int scale_uniform, texture_source_uniform; + GLuint prev_fb = gpu_framebuffer_current_get(); - if (!blur_shader) - return; + /* Framebuffers must be up to date. This simplify this function. */ + if (fb_read->dirty_flag != 0 || fb_read->object == 0) { + GPU_framebuffer_bind(fb_read); + } + if (fb_write->dirty_flag != 0 || fb_write->object == 0) { + GPU_framebuffer_bind(fb_write); + } - scale_uniform = GPU_shader_get_uniform(blur_shader, "ScaleU"); - texture_source_uniform = GPU_shader_get_uniform(blur_shader, "textureSource"); + const bool do_color = (blit_buffers & GPU_COLOR_BIT); + const bool do_depth = (blit_buffers & GPU_DEPTH_BIT); + const bool do_stencil = (blit_buffers & GPU_STENCIL_BIT); - /* Blurring horizontally */ + GPUTexture *read_tex = (do_depth || do_stencil) + ? framebuffer_get_depth_tex(fb_read) + : framebuffer_get_color_tex(fb_read, read_slot); + GPUTexture *write_tex = (do_depth || do_stencil) + ? framebuffer_get_depth_tex(fb_write) + : framebuffer_get_color_tex(fb_write, read_slot); - /* We do the bind ourselves rather than using GPU_framebuffer_texture_bind() to avoid - * pushing unnecessary matrices onto the OpenGL stack. */ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, blurfb->object); - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); + if (do_depth) { + BLI_assert(GPU_texture_depth(read_tex) && GPU_texture_depth(write_tex)); + BLI_assert(GPU_texture_format(read_tex) == GPU_texture_format(write_tex)); + } + if (do_stencil) { + BLI_assert(GPU_texture_stencil(read_tex) && GPU_texture_stencil(write_tex)); + BLI_assert(GPU_texture_format(read_tex) == GPU_texture_format(write_tex)); + } + if (GPU_texture_samples(write_tex) != 0 || + GPU_texture_samples(read_tex) != 0) + { + /* Can only blit multisample textures to another texture of the same size. */ + BLI_assert((fb_read->width == fb_write->width) && + (fb_read->height == fb_write->height)); + } - /* avoid warnings from texture binding */ - GG.currentfb = blurfb->object; + glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_read->object); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb_write->object); - GPU_shader_bind(blur_shader); - GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scaleh); - GPU_shader_uniform_texture(blur_shader, texture_source_uniform, tex); - glViewport(0, 0, GPU_texture_width(blurtex), GPU_texture_height(blurtex)); + if (do_color) { + glReadBuffer(GL_COLOR_ATTACHMENT0 + read_slot); + glDrawBuffer(GL_COLOR_ATTACHMENT0 + write_slot); + /* XXX we messed with the glDrawBuffer, this will reset the + * glDrawBuffers the next time we bind fb_write. */ + fb_write->dirty_flag = GPU_FB_DIRTY_DRAWBUFFER; + } - /* Preparing to draw quad */ - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + GLbitfield mask = convert_buffer_bits_to_gl(blit_buffers); - glDisable(GL_DEPTH_TEST); + glBlitFramebuffer(0, 0, fb_read->width, fb_read->height, + 0, 0, fb_write->width, fb_write->height, + mask, GL_NEAREST); - GPU_texture_bind(tex, 0); + /* Restore previous framebuffer */ + if (fb_write->object == prev_fb) { + GPU_framebuffer_bind(fb_write); /* To update drawbuffers */ + } + else { + glBindFramebuffer(GL_FRAMEBUFFER, prev_fb); + gpu_framebuffer_current_set(prev_fb); + } +} - /* Drawing quad */ - glBegin(GL_QUADS); - glTexCoord2d(0, 0); glVertex2f(1, 1); - glTexCoord2d(1, 0); glVertex2f(-1, 1); - glTexCoord2d(1, 1); glVertex2f(-1, -1); - glTexCoord2d(0, 1); glVertex2f(1, -1); - glEnd(); +/** + * Use this if you need to custom downsample your texture and use the previous mip level as input. + * This function only takes care of the correct texture handling. It execute the callback for each texture level. + **/ +void GPU_framebuffer_recursive_downsample( + GPUFrameBuffer *fb, int max_lvl, + void (*callback)(void *userData, int level), void *userData) +{ + /* Framebuffer must be up to date and bound. This simplify this function. */ + if (gpu_framebuffer_current_get() != fb->object || fb->dirty_flag != 0 || fb->object == 0) { + GPU_framebuffer_bind(fb); + } + /* HACK: We make the framebuffer appear not bound in order to + * not trigger any error in GPU_texture_bind(). */ + GLuint prev_fb = gpu_framebuffer_current_get(); + gpu_framebuffer_current_set(0); - /* Blurring vertically */ + int i; + int current_dim[2] = {fb->width, fb->height}; + for (i = 1; i < max_lvl + 1; i++) { + /* calculate next viewport size */ + current_dim[0] = max_ii(current_dim[0] / 2, 1); + current_dim[1] = max_ii(current_dim[1] / 2, 1); + + for (GPUAttachmentType type = 0; type < GPU_FB_MAX_ATTACHEMENT; ++type) { + if (fb->attachments[type].tex != NULL) { + /* bind next level for rendering but first restrict fetches only to previous level */ + GPUTexture *tex = fb->attachments[type].tex; + GPU_texture_bind(tex, 0); + glTexParameteri(GPU_texture_target(tex), GL_TEXTURE_BASE_LEVEL, i - 1); + glTexParameteri(GPU_texture_target(tex), GL_TEXTURE_MAX_LEVEL, i - 1); + GPU_texture_unbind(tex); + /* copy attachment and replace miplevel. */ + GPUAttachment attachment = fb->attachments[type]; + attachment.mip = i; + gpu_framebuffer_attachment_attach(&attachment, type); + } + } - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->object); - glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); + BLI_assert(GL_FRAMEBUFFER_COMPLETE == glCheckFramebufferStatus(GL_FRAMEBUFFER)); - GG.currentfb = fb->object; + glViewport(0, 0, current_dim[0], current_dim[1]); + callback(userData, i); - glViewport(0, 0, GPU_texture_width(tex), GPU_texture_height(tex)); - GPU_shader_uniform_vector(blur_shader, scale_uniform, 2, 1, scalev); - GPU_shader_uniform_texture(blur_shader, texture_source_uniform, blurtex); - GPU_texture_bind(blurtex, 0); + if (current_dim[0] == 1 && current_dim[1] == 1) + break; + } - glBegin(GL_QUADS); - glTexCoord2d(0, 0); glVertex2f(1, 1); - glTexCoord2d(1, 0); glVertex2f(-1, 1); - glTexCoord2d(1, 1); glVertex2f(-1, -1); - glTexCoord2d(0, 1); glVertex2f(1, -1); - glEnd(); + for (GPUAttachmentType type = 0; type < GPU_FB_MAX_ATTACHEMENT; ++type) { + if (fb->attachments[type].tex != NULL) { + /* reset mipmap level range */ + GPUTexture *tex = fb->attachments[type].tex; + GPU_texture_bind(tex, 0); + glTexParameteri(GPU_texture_target(tex), GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GPU_texture_target(tex), GL_TEXTURE_MAX_LEVEL, i - 1); + GPU_texture_unbind(tex); + /* Reattach original level */ + /* NOTE: This is not necessary but this makes the FBO config + * remain in sync with the GPUFrameBuffer config. */ + gpu_framebuffer_attachment_attach(&fb->attachments[type], type); + } + } - GPU_shader_unbind(); + gpu_framebuffer_current_set(prev_fb); } /* GPUOffScreen */ @@ -451,63 +673,42 @@ struct GPUOffScreen { GPUTexture *depth; }; -GPUOffScreen *GPU_offscreen_create(int width, int height, int samples, char err_out[256]) +GPUOffScreen *GPU_offscreen_create(int width, int height, int samples, bool depth, bool high_bitdepth, char err_out[256]) { GPUOffScreen *ofs; ofs = MEM_callocN(sizeof(GPUOffScreen), "GPUOffScreen"); - ofs->fb = GPU_framebuffer_create(); - if (!ofs->fb) { - GPU_offscreen_free(ofs); - return NULL; - } - - if (samples) { - if (!GLEW_EXT_framebuffer_multisample || - !GLEW_ARB_texture_multisample || - /* Only needed for GPU_offscreen_read_pixels. - * We could add an arg if we intend to use multi-sample - * offscreen buffers w/o reading their pixels */ - !GLEW_EXT_framebuffer_blit || - /* This is required when blitting from a multi-sampled buffers, - * even though we're not scaling. */ - !GLEW_EXT_framebuffer_multisample_blit_scaled) - { - samples = 0; - } - } + ofs->color = GPU_texture_create_2D_multisample(width, height, + (high_bitdepth) ? GPU_RGBA16F : GPU_RGBA8, NULL, samples, err_out); - ofs->depth = GPU_texture_create_depth_multisample(width, height, samples, err_out); - if (!ofs->depth) { - GPU_offscreen_free(ofs); - return NULL; + if (depth) { + ofs->depth = GPU_texture_create_2D_multisample(width, height, GPU_DEPTH24_STENCIL8, NULL, samples, err_out); } - if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->depth, 0, err_out)) { + if ((depth && !ofs->depth) || !ofs->color) { GPU_offscreen_free(ofs); return NULL; } - ofs->color = GPU_texture_create_2D_multisample(width, height, NULL, GPU_HDR_NONE, samples, err_out); - if (!ofs->color) { - GPU_offscreen_free(ofs); - return NULL; - } + gpuPushAttrib(GPU_VIEWPORT_BIT); - if (!GPU_framebuffer_texture_attach(ofs->fb, ofs->color, 0, err_out)) { - GPU_offscreen_free(ofs); - return NULL; - } + GPU_framebuffer_ensure_config(&ofs->fb, { + GPU_ATTACHMENT_TEXTURE(ofs->depth), + GPU_ATTACHMENT_TEXTURE(ofs->color) + }); /* check validity at the very end! */ if (!GPU_framebuffer_check_valid(ofs->fb, err_out)) { GPU_offscreen_free(ofs); + gpuPopAttrib(); return NULL; } GPU_framebuffer_restore(); + gpuPopAttrib(); + return ofs; } @@ -525,20 +726,37 @@ void GPU_offscreen_free(GPUOffScreen *ofs) void GPU_offscreen_bind(GPUOffScreen *ofs, bool save) { - glDisable(GL_SCISSOR_TEST); - if (save) - GPU_texture_bind_as_framebuffer(ofs->color); - else { - GPU_framebuffer_bind_no_save(ofs->fb, 0); + if (save) { + gpuPushAttrib(GPU_SCISSOR_BIT | GPU_VIEWPORT_BIT); } + glDisable(GL_SCISSOR_TEST); + GPU_framebuffer_bind(ofs->fb); } -void GPU_offscreen_unbind(GPUOffScreen *ofs, bool restore) +void GPU_offscreen_unbind(GPUOffScreen *UNUSED(ofs), bool restore) { - if (restore) - GPU_framebuffer_texture_unbind(ofs->fb, ofs->color); GPU_framebuffer_restore(); - glEnable(GL_SCISSOR_TEST); + if (restore) { + gpuPopAttrib(); + } +} + +void GPU_offscreen_draw_to_screen(GPUOffScreen *ofs, int x, int y) +{ + const int w = GPU_texture_width(ofs->color); + const int h = GPU_texture_height(ofs->color); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, ofs->fb->object); + GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + + if (status == GL_FRAMEBUFFER_COMPLETE) { + glBlitFramebuffer(0, 0, w, h, x, y, x + w, y + h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else { + gpu_print_framebuffer_error(status, NULL); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); } void GPU_offscreen_read_pixels(GPUOffScreen *ofs, int type, void *pixels) @@ -546,74 +764,46 @@ void GPU_offscreen_read_pixels(GPUOffScreen *ofs, int type, void *pixels) const int w = GPU_texture_width(ofs->color); const int h = GPU_texture_height(ofs->color); + BLI_assert(type == GL_UNSIGNED_BYTE || type == GL_FLOAT); + if (GPU_texture_target(ofs->color) == GL_TEXTURE_2D_MULTISAMPLE) { /* For a multi-sample texture, * we need to create an intermediate buffer to blit to, * before its copied using 'glReadPixels' */ - - /* not needed since 'ofs' needs to be bound to the framebuffer already */ -// #define USE_FBO_CTX_SWITCH - GLuint fbo_blit = 0; GLuint tex_blit = 0; - GLenum status; /* create texture for new 'fbo_blit' */ glGenTextures(1, &tex_blit); - if (!tex_blit) { - goto finally; - } - glBindTexture(GL_TEXTURE_2D, tex_blit); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, type, 0); - -#ifdef USE_FBO_CTX_SWITCH - /* read from multi-sample buffer */ - glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, ofs->color->fb->object); - glFramebufferTexture2DEXT( - GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + ofs->color->fb_attachment, - GL_TEXTURE_2D_MULTISAMPLE, ofs->color->bindcode, 0); - status = glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT); - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { - goto finally; - } -#endif + glTexImage2D(GL_TEXTURE_2D, 0, (type == GL_FLOAT) ? GL_RGBA16F : GL_RGBA8, + w, h, 0, GL_RGBA, type, 0); /* write into new single-sample buffer */ - glGenFramebuffersEXT(1, &fbo_blit); - glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbo_blit); - glFramebufferTexture2DEXT( - GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_TEXTURE_2D, tex_blit, 0); - status = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT); - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { + glGenFramebuffers(1, &fbo_blit); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_blit); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, tex_blit, 0); + + GLenum status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { goto finally; } /* perform the copy */ - glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST); /* read the results */ - glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, fbo_blit); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_blit); glReadPixels(0, 0, w, h, GL_RGBA, type, pixels); -#ifdef USE_FBO_CTX_SWITCH /* restore the original frame-bufer */ - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, ofs->color->fb->object); -#undef USE_FBO_CTX_SWITCH -#endif - + glBindFramebuffer(GL_FRAMEBUFFER, ofs->fb->object); finally: /* cleanup */ - if (tex_blit) { - glDeleteTextures(1, &tex_blit); - } - if (fbo_blit) { - glDeleteFramebuffersEXT(1, &fbo_blit); - } - - GPU_ASSERT_NO_GL_ERRORS("Read Multi-Sample Pixels"); + glDeleteTextures(1, &tex_blit); + glDeleteFramebuffers(1, &fbo_blit); } else { glReadPixels(0, 0, w, h, GL_RGBA, type, pixels); @@ -630,7 +820,27 @@ int GPU_offscreen_height(const GPUOffScreen *ofs) return GPU_texture_height(ofs->color); } -int GPU_offscreen_color_texture(const GPUOffScreen *ofs) +GPUTexture *GPU_offscreen_color_texture(const GPUOffScreen *ofs) +{ + return ofs->color; +} + +/* only to be used by viewport code! */ +void GPU_offscreen_viewport_data_get( + GPUOffScreen *ofs, + GPUFrameBuffer **r_fb, GPUTexture **r_color, GPUTexture **r_depth) +{ + *r_fb = ofs->fb; + *r_color = ofs->color; + *r_depth = ofs->depth; +} + +void GPU_clear_color(float red, float green, float blue, float alpha) +{ + glClearColor(red, green, blue, alpha); +} + +void GPU_clear(GPUFrameBufferBits flags) { - return GPU_texture_opengl_bindcode(ofs->color); + glClear(convert_buffer_bits_to_gl(flags)); } |