This is to improve debugging on older hardware that may not support 4.3 debug capabilities (like Macs). This avoids sprinkling glGetErrors manually. This might still be needed to find the root cause since not all functions are covered. This overrides the functions pointers that GLEW have already init. This is only enabled if using --debug-gpu option and the debug extension are not available. This also cleanup the usage of GLContext::debug_layer_support and use wrapper to set object labels.
497 lines
14 KiB
C++
497 lines
14 KiB
C++
/*
|
|
* 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_capabilities.h"
|
|
|
|
#include "gl_backend.hh"
|
|
#include "gl_debug.hh"
|
|
#include "gl_state.hh"
|
|
#include "gl_texture.hh"
|
|
|
|
#include "gl_framebuffer.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;
|
|
state_manager_ = static_cast<GLStateManager *>(ctx->state_manager);
|
|
immutable_ = true;
|
|
fbo_id_ = fbo;
|
|
gl_attachments_[0] = target;
|
|
/* Never update an internal frame-buffer. */
|
|
dirty_attachments_ = false;
|
|
width_ = w;
|
|
height_ = h;
|
|
srgb_ = false;
|
|
|
|
viewport_[0] = scissor_[0] = 0;
|
|
viewport_[1] = scissor_[1] = 0;
|
|
viewport_[2] = scissor_[2] = w;
|
|
viewport_[3] = scissor_[3] = h;
|
|
|
|
if (fbo_id_) {
|
|
debug::object_label(GL_FRAMEBUFFER, fbo_id_, name_);
|
|
}
|
|
}
|
|
|
|
GLFrameBuffer::~GLFrameBuffer()
|
|
{
|
|
if (context_ == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Context might be partially freed. This happens when destroying the window frame-buffers. */
|
|
if (context_ == Context::get()) {
|
|
glDeleteFramebuffers(1, &fbo_id_);
|
|
}
|
|
else {
|
|
context_->fbo_free(fbo_id_);
|
|
}
|
|
/* Restore default frame-buffer if this frame-buffer was bound. */
|
|
if (context_->active_fb == this && context_->back_left != this) {
|
|
/* If this assert triggers it means the frame-buffer is being freed while in use by another
|
|
* context which, by the way, is TOTALLY UNSAFE!!! */
|
|
BLI_assert(context_ == Context::get());
|
|
GPU_framebuffer_restore();
|
|
}
|
|
}
|
|
|
|
void GLFrameBuffer::init(void)
|
|
{
|
|
context_ = GLContext::get();
|
|
state_manager_ = static_cast<GLStateManager *>(context_->state_manager);
|
|
glGenFramebuffers(1, &fbo_id_);
|
|
/* Binding before setting the label is needed on some drivers.
|
|
* This is not an issue since we call this function only before binding. */
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
|
|
|
|
debug::object_label(GL_FRAMEBUFFER, fbo_id_, name_);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \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: frame-buffer 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 frame-buffers cannot have attachments. */
|
|
BLI_assert(immutable_ == false);
|
|
|
|
/* First color texture OR the depth texture if no color is attached.
|
|
* Used to determine frame-buffer color-space and dimensions. */
|
|
GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT;
|
|
/* NOTE: Inverse iteration to get the first color texture. */
|
|
for (GPUAttachmentType type = GPU_FB_MAX_ATTACHMENT - 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_ATTACHMENT) {
|
|
/* Only use depth texture to get information 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 = static_cast<GLTexture *>(unwrap(attach.tex))->tex_id_;
|
|
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 frame-buffer 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 (GLContext::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 = static_cast<GLTexture *>(unwrap(attach.tex))->tex_id_;
|
|
}
|
|
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_ATTACHMENT) {
|
|
GPUAttachment &attach = attachments_[first_attachment];
|
|
int size[3];
|
|
GPU_texture_get_mipmap_size(attach.tex, attach.mip, size);
|
|
this->size_set(size[0], 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));
|
|
}
|
|
}
|
|
|
|
void GLFrameBuffer::apply_state(void)
|
|
{
|
|
if (dirty_state_ == false) {
|
|
return;
|
|
}
|
|
|
|
glViewport(UNPACK4(viewport_));
|
|
glScissor(UNPACK4(scissor_));
|
|
|
|
if (scissor_test_) {
|
|
glEnable(GL_SCISSOR_TEST);
|
|
}
|
|
else {
|
|
glDisable(GL_SCISSOR_TEST);
|
|
}
|
|
|
|
dirty_state_ = false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Binding
|
|
* \{ */
|
|
|
|
void GLFrameBuffer::bind(bool enabled_srgb)
|
|
{
|
|
if (!immutable_ && fbo_id_ == 0) {
|
|
this->init();
|
|
}
|
|
|
|
if (context_ != GLContext::get()) {
|
|
BLI_assert(!"Trying to use the same frame-buffer in multiple context");
|
|
return;
|
|
}
|
|
|
|
if (context_->active_fb != this) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
|
|
/* Internal frame-buffers have only one color output and needs to be set every time. */
|
|
if (immutable_ && fbo_id_ == 0) {
|
|
glDrawBuffer(gl_attachments_[0]);
|
|
}
|
|
}
|
|
|
|
if (dirty_attachments_) {
|
|
this->update_attachments();
|
|
this->viewport_reset();
|
|
this->scissor_reset();
|
|
}
|
|
|
|
if (context_->active_fb != this) {
|
|
context_->active_fb = this;
|
|
state_manager_->active_fb = this;
|
|
dirty_state_ = true;
|
|
|
|
if (enabled_srgb) {
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
else {
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
|
|
GPU_shader_set_framebuffer_srgb_target(enabled_srgb && srgb_);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Operations.
|
|
* \{ */
|
|
|
|
void GLFrameBuffer::clear(eGPUFrameBufferBits buffers,
|
|
const float clear_col[4],
|
|
float clear_depth,
|
|
uint clear_stencil)
|
|
{
|
|
BLI_assert(GLContext::get() == context_);
|
|
BLI_assert(context_->active_fb == this);
|
|
|
|
/* 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_attachment(GPUAttachmentType type,
|
|
eGPUDataFormat data_format,
|
|
const void *clear_value)
|
|
{
|
|
BLI_assert(GLContext::get() == context_);
|
|
BLI_assert(context_->active_fb == this);
|
|
|
|
/* Save and restore the state. */
|
|
eGPUWriteMask write_mask = GPU_write_mask_get();
|
|
GPU_color_mask(true, true, true, true);
|
|
|
|
context_->state_manager->apply_state();
|
|
|
|
if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) {
|
|
BLI_assert(data_format == GPU_DATA_UNSIGNED_INT_24_8);
|
|
float depth = ((*(uint32_t *)clear_value) & 0x00FFFFFFu) / (float)0x00FFFFFFu;
|
|
int stencil = ((*(uint32_t *)clear_value) >> 24);
|
|
glClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil);
|
|
}
|
|
else if (type == GPU_FB_DEPTH_ATTACHMENT) {
|
|
if (data_format == GPU_DATA_FLOAT) {
|
|
glClearBufferfv(GL_DEPTH, 0, (GLfloat *)clear_value);
|
|
}
|
|
else if (data_format == GPU_DATA_UNSIGNED_INT) {
|
|
float depth = *(uint32_t *)clear_value / (float)0xFFFFFFFFu;
|
|
glClearBufferfv(GL_DEPTH, 0, &depth);
|
|
}
|
|
else {
|
|
BLI_assert(!"Unhandled data format");
|
|
}
|
|
}
|
|
else {
|
|
int slot = type - GPU_FB_COLOR_ATTACHMENT0;
|
|
switch (data_format) {
|
|
case GPU_DATA_FLOAT:
|
|
glClearBufferfv(GL_COLOR, slot, (GLfloat *)clear_value);
|
|
break;
|
|
case GPU_DATA_UNSIGNED_INT:
|
|
glClearBufferuiv(GL_COLOR, slot, (GLuint *)clear_value);
|
|
break;
|
|
case GPU_DATA_INT:
|
|
glClearBufferiv(GL_COLOR, slot, (GLint *)clear_value);
|
|
break;
|
|
default:
|
|
BLI_assert(!"Unhandled data format");
|
|
break;
|
|
}
|
|
}
|
|
|
|
GPU_write_mask(write_mask);
|
|
}
|
|
|
|
void GLFrameBuffer::clear_multi(const float (*clear_cols)[4])
|
|
{
|
|
/* WATCH: This can easily 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_ATTACHMENT; i++, type++) {
|
|
if (attachments_[type].tex != NULL) {
|
|
this->clear_attachment(GPU_FB_COLOR_ATTACHMENT0 + i, GPU_DATA_FLOAT, clear_cols[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 frame-buffer plane.");
|
|
return;
|
|
}
|
|
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_id_);
|
|
glReadBuffer(mode);
|
|
glReadPixels(UNPACK4(area), format, type, r_data);
|
|
}
|
|
|
|
/**
|
|
* Copy \a src at the give offset inside \a 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_);
|
|
|
|
/* Frame-buffers 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]);
|
|
}
|
|
|
|
context_->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
|