Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClément Foucault <foucault.clem@gmail.com>2020-08-29 02:13:54 +0300
committerClément Foucault <foucault.clem@gmail.com>2020-08-30 14:11:02 +0300
commitf3a65a1b4aa3c7da221acdbeee20afb7ebe3b221 (patch)
tree1b7b8c90ef0ecb891b35c8fa89cf25873035d784 /source/blender/gpu/opengl/gl_framebuffer.cc
parent0850afb34e21c073eb2a4c7475eeab2684f30e43 (diff)
GPUFrameBuffer: GL backend isolation
This is related to the Vulkan port T68990. This is a full cleanup of the Framebuffer module and a separation of OpenGL related functions. There is some changes with how the default framebuffers are handled. Now the default framebuffers are individually wrapped inside special GLFrameBuffers. This make it easier to keep track of the currently bound framebuffer state and have some specificity for operations on these framebuffers. Another change is dropping the optimisation of only configuring the changed attachements during framebuffers update. This does not give any benefits and add some complexity to the code. This might be brought back if it has a performance impact on some systems. This also adds support for naming framebuffers but it is currently not used.
Diffstat (limited to 'source/blender/gpu/opengl/gl_framebuffer.cc')
-rw-r--r--source/blender/gpu/opengl/gl_framebuffer.cc420
1 files changed, 420 insertions, 0 deletions
diff --git a/source/blender/gpu/opengl/gl_framebuffer.cc b/source/blender/gpu/opengl/gl_framebuffer.cc
new file mode 100644
index 00000000000..bf2a782b083
--- /dev/null
+++ b/source/blender/gpu/opengl/gl_framebuffer.cc
@@ -0,0 +1,420 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup gpu
+ */
+
+#include "BKE_global.h"
+
+#include "GPU_extensions.h"
+
+#include "gl_backend.hh"
+#include "gl_framebuffer.hh"
+#include "gl_texture.hh"
+
+namespace blender::gpu {
+
+/* -------------------------------------------------------------------- */
+/** \name Creation & Deletion
+ * \{ */
+
+GLFrameBuffer::GLFrameBuffer(const char *name) : FrameBuffer(name)
+{
+ /* Just-In-Time init. See GLFrameBuffer::init(). */
+ immutable_ = false;
+ fbo_id_ = 0;
+}
+
+GLFrameBuffer::GLFrameBuffer(
+ const char *name, GLContext *ctx, GLenum target, GLuint fbo, int w, int h)
+ : FrameBuffer(name)
+{
+ context_ = ctx;
+ immutable_ = true;
+ fbo_id_ = fbo;
+ gl_attachments_[0] = target;
+ /* Never update an internal framebuffer. */
+ dirty_attachments_ = false;
+ width_ = w;
+ height_ = h;
+ srgb_ = false;
+
+#ifndef __APPLE__
+ if (fbo_id_ && (G.debug & G_DEBUG_GPU) && (GLEW_VERSION_4_3 || GLEW_KHR_debug)) {
+ char sh_name[32];
+ SNPRINTF(sh_name, "FrameBuffer-%s", name);
+ glObjectLabel(GL_FRAMEBUFFER, fbo_id_, -1, sh_name);
+ }
+#endif
+}
+
+GLFrameBuffer::~GLFrameBuffer()
+{
+ if (context_ != NULL) {
+ context_->fbo_free(fbo_id_);
+ /* Restore default framebuffer if this framebuffer was bound. */
+ if (context_->active_fb == this && context_->back_left != this) {
+ /* If this assert triggers it means the framebuffer is being freed while in use by another
+ * context which, by the way, is TOTALLY UNSAFE!!! */
+ BLI_assert(context_ == GPU_context_active_get());
+ GPU_framebuffer_restore();
+ }
+ }
+}
+
+void GLFrameBuffer::init(void)
+{
+ context_ = static_cast<GLContext *>(GPU_context_active_get());
+ glGenFramebuffers(1, &fbo_id_);
+
+#ifndef __APPLE__
+ if ((G.debug & G_DEBUG_GPU) && (GLEW_VERSION_4_3 || GLEW_KHR_debug)) {
+ char sh_name[64];
+ SNPRINTF(sh_name, "FrameBuffer-%s", name_);
+ /* Binding before setting the label is needed on some drivers. */
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+ glObjectLabel(GL_FRAMEBUFFER, fbo_id_, -1, sh_name);
+ }
+#endif
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Config
+ * \{ */
+
+/* This is a rather slow operation. Don't check in normal cases. */
+bool GLFrameBuffer::check(char err_out[256])
+{
+ this->bind(true);
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+#define FORMAT_STATUS(X) \
+ case X: { \
+ err = #X; \
+ break; \
+ }
+
+ const char *err;
+ switch (status) {
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
+ FORMAT_STATUS(GL_FRAMEBUFFER_UNSUPPORTED);
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
+ FORMAT_STATUS(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS);
+ FORMAT_STATUS(GL_FRAMEBUFFER_UNDEFINED);
+ case GL_FRAMEBUFFER_COMPLETE:
+ return true;
+ default:
+ err = "unknown";
+ break;
+ }
+
+#undef FORMAT_STATUS
+
+ const char *format = "GPUFrameBuffer: framebuffer status %s\n";
+
+ if (err_out) {
+ BLI_snprintf(err_out, 256, format, err);
+ }
+ else {
+ fprintf(stderr, format, err);
+ }
+
+ return false;
+}
+
+void GLFrameBuffer::update_attachments(void)
+{
+ /* Default framebuffers cannot have attachements. */
+ BLI_assert(immutable_ == false);
+
+ /* First color texture OR the depth texture if no color is attached.
+ * Used to determine framebuffer colorspace and dimensions. */
+ GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHEMENT;
+ /* NOTE: Inverse iteration to get the first color texture. */
+ for (GPUAttachmentType type = GPU_FB_MAX_ATTACHEMENT - 1; type >= 0; --type) {
+ GPUAttachment &attach = attachments_[type];
+ GLenum gl_attachment = to_gl(type);
+
+ if (type >= GPU_FB_COLOR_ATTACHMENT0) {
+ gl_attachments_[type - GPU_FB_COLOR_ATTACHMENT0] = (attach.tex) ? gl_attachment : GL_NONE;
+ first_attachment = (attach.tex) ? type : first_attachment;
+ }
+ else if (first_attachment == GPU_FB_MAX_ATTACHEMENT) {
+ /* Only use depth texture to get infos if there is no color attachment. */
+ first_attachment = (attach.tex) ? type : first_attachment;
+ }
+
+ if (attach.tex == NULL) {
+ glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, 0, 0);
+ continue;
+ }
+ GLuint gl_tex = GPU_texture_opengl_bindcode(attach.tex);
+ if (attach.layer > -1 && GPU_texture_cube(attach.tex) && !GPU_texture_array(attach.tex)) {
+ /* Could be avoided if ARB_direct_state_access is required. In this case
+ * glFramebufferTextureLayer would bind the correct face. */
+ GLenum gl_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + attach.layer;
+ glFramebufferTexture2D(GL_FRAMEBUFFER, gl_attachment, gl_target, gl_tex, attach.mip);
+ }
+ else if (attach.layer > -1) {
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, gl_attachment, gl_tex, attach.mip, attach.layer);
+ }
+ else {
+ /* The whole texture level is attached. The framebuffer is potentially layered. */
+ glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, gl_tex, attach.mip);
+ }
+ /* We found one depth buffer type. Stop here, otherwise we would
+ * override it by setting GPU_FB_DEPTH_ATTACHMENT */
+ if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) {
+ break;
+ }
+ }
+
+ if (GPU_unused_fb_slot_workaround()) {
+ /* Fill normally un-occupied slots to avoid rendering artifacts on some hardware. */
+ GLuint gl_tex = 0;
+ /* NOTE: Inverse iteration to get the first color texture. */
+ for (int i = ARRAY_SIZE(gl_attachments_) - 1; i >= 0; --i) {
+ GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
+ GPUAttachment &attach = attachments_[type];
+ if (attach.tex != NULL) {
+ gl_tex = GPU_texture_opengl_bindcode(attach.tex);
+ }
+ else if (gl_tex != 0) {
+ GLenum gl_attachment = to_gl(type);
+ gl_attachments_[i] = gl_attachment;
+ glFramebufferTexture(GL_FRAMEBUFFER, gl_attachment, gl_tex, 0);
+ }
+ }
+ }
+
+ if (first_attachment != GPU_FB_MAX_ATTACHEMENT) {
+ GPUAttachment &attach = attachments_[first_attachment];
+ int size[3];
+ GPU_texture_get_mipmap_size(attach.tex, attach.mip, size);
+ width_ = size[0];
+ height_ = size[1];
+ srgb_ = (GPU_texture_format(attach.tex) == GPU_SRGB8_A8);
+ }
+
+ dirty_attachments_ = false;
+
+ glDrawBuffers(ARRAY_SIZE(gl_attachments_), gl_attachments_);
+
+ if (G.debug & G_DEBUG_GPU) {
+ BLI_assert(this->check(NULL));
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Binding
+ * \{ */
+
+void GLFrameBuffer::bind(bool enabled_srgb)
+{
+ GPUContext *ctx = GPU_context_active_get();
+ BLI_assert(ctx);
+
+ if (context_ != NULL && context_ != ctx) {
+ BLI_assert(!"Trying to use the same framebuffer in multiple context");
+ }
+
+ if (!immutable_ && fbo_id_ == 0) {
+ this->init();
+ }
+
+ if (ctx->active_fb != this) {
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+ /* Internal framebuffers have only one color output and needs to be set everytime. */
+ if (immutable_ && fbo_id_ == 0) {
+ glDrawBuffer(gl_attachments_[0]);
+ }
+ }
+
+ if (dirty_attachments_) {
+ this->update_attachments();
+ }
+
+ if (ctx->active_fb != this) {
+ ctx->active_fb = this;
+
+ if (enabled_srgb) {
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ }
+ else {
+ glDisable(GL_FRAMEBUFFER_SRGB);
+ }
+
+ GPU_shader_set_framebuffer_srgb_target(enabled_srgb && srgb_);
+ }
+
+ GPU_viewport(0, 0, width_, height_);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Operations.
+ * \{ */
+
+void GLFrameBuffer::clear(eGPUFrameBufferBits buffers,
+ const float clear_col[4],
+ float clear_depth,
+ uint clear_stencil)
+{
+ /* Save and restore the state. */
+ eGPUWriteMask write_mask = GPU_write_mask_get();
+ uint stencil_mask = GPU_stencil_mask_get();
+ eGPUStencilTest stencil_test = GPU_stencil_test_get();
+
+ if (buffers & GPU_COLOR_BIT) {
+ GPU_color_mask(true, true, true, true);
+ glClearColor(clear_col[0], clear_col[1], clear_col[2], clear_col[3]);
+ }
+ if (buffers & GPU_DEPTH_BIT) {
+ GPU_depth_mask(true);
+ glClearDepth(clear_depth);
+ }
+ if (buffers & GPU_STENCIL_BIT) {
+ GPU_stencil_write_mask_set(0xFFu);
+ GPU_stencil_test(GPU_STENCIL_ALWAYS);
+ glClearStencil(clear_stencil);
+ }
+
+ context_->state_manager->apply_state();
+
+ GLbitfield mask = to_gl(buffers);
+ glClear(mask);
+
+ if (buffers & (GPU_COLOR_BIT | GPU_DEPTH_BIT)) {
+ GPU_write_mask(write_mask);
+ }
+ if (buffers & GPU_STENCIL_BIT) {
+ GPU_stencil_write_mask_set(stencil_mask);
+ GPU_stencil_test(stencil_test);
+ }
+}
+
+void GLFrameBuffer::clear_multi(const float (*clear_cols)[4])
+{
+ /* Save and restore the state. */
+ eGPUWriteMask write_mask = GPU_write_mask_get();
+ GPU_color_mask(true, true, true, true);
+
+ context_->state_manager->apply_state();
+
+ /* WATCH: This can easilly access clear_cols out of bounds it clear_cols is not big enough for
+ * all attachments.
+ * TODO(fclem) fix this insecurity? */
+ int type = GPU_FB_COLOR_ATTACHMENT0;
+ for (int i = 0; type < GPU_FB_MAX_ATTACHEMENT; i++, type++) {
+ if (attachments_[type].tex != NULL) {
+ glClearBufferfv(GL_COLOR, i, clear_cols[i]);
+ }
+ }
+
+ GPU_write_mask(write_mask);
+}
+
+void GLFrameBuffer::read(eGPUFrameBufferBits plane,
+ eGPUDataFormat data_format,
+ const int area[4],
+ int channel_len,
+ int slot,
+ void *r_data)
+{
+ GLenum format, type, mode;
+ mode = gl_attachments_[slot];
+ type = to_gl(data_format);
+
+ switch (plane) {
+ case GPU_DEPTH_BIT:
+ format = GL_DEPTH_COMPONENT;
+ break;
+ case GPU_COLOR_BIT:
+ format = channel_len_to_gl(channel_len);
+ /* TODO: needed for selection buffers to work properly, this should be handled better. */
+ if (format == GL_RED && type == GL_UNSIGNED_INT) {
+ format = GL_RED_INTEGER;
+ }
+ break;
+ case GPU_STENCIL_BIT:
+ fprintf(stderr, "GPUFramebuffer: Error: Trying to read stencil bit. Unsupported.");
+ return;
+ default:
+ fprintf(stderr, "GPUFramebuffer: Error: Trying to read more than one framebuffer plane.");
+ return;
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_id_);
+ glReadBuffer(mode);
+ glReadPixels(UNPACK4(area), format, type, r_data);
+}
+
+/* Copy src at the give offset inside dst. */
+void GLFrameBuffer::blit_to(
+ eGPUFrameBufferBits planes, int src_slot, FrameBuffer *dst_, int dst_slot, int x, int y)
+{
+ GLFrameBuffer *src = this;
+ GLFrameBuffer *dst = static_cast<GLFrameBuffer *>(dst_);
+
+ /* Framebuffers must be up to date. This simplify this function. */
+ if (src->dirty_attachments_) {
+ src->bind(true);
+ }
+ if (dst->dirty_attachments_) {
+ dst->bind(true);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, src->fbo_id_);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->fbo_id_);
+
+ if (planes & GPU_COLOR_BIT) {
+ BLI_assert(src->immutable_ == false || src_slot == 0);
+ BLI_assert(dst->immutable_ == false || dst_slot == 0);
+ BLI_assert(src->gl_attachments_[src_slot] != GL_NONE);
+ BLI_assert(dst->gl_attachments_[dst_slot] != GL_NONE);
+ glReadBuffer(src->gl_attachments_[src_slot]);
+ glDrawBuffer(dst->gl_attachments_[dst_slot]);
+ }
+
+ GPU_context_active_get()->state_manager->apply_state();
+
+ int w = src->width_;
+ int h = src->height_;
+ GLbitfield mask = to_gl(planes);
+ glBlitFramebuffer(0, 0, w, h, x, y, x + w, y + h, mask, GL_NEAREST);
+
+ if (!dst->immutable_) {
+ /* Restore the draw buffers. */
+ glDrawBuffers(ARRAY_SIZE(dst->gl_attachments_), dst->gl_attachments_);
+ }
+}
+
+/** \} */
+
+} // namespace blender::gpu \ No newline at end of file