Skip to content

File point_cloud_framebuffer.cpp

File List > gui > point_cloud_framebuffer.cpp

Go to the documentation of this file

#include "gui/point_cloud_framebuffer.hpp"

#include <array>
#include <iostream>

#include "gui/gl_check.hpp"
#include "lib/assert/assert.hpp"

namespace {

const char* COMPOSITE_VERTEX_SHADER = R"(
    #version 330 core
    out vec2 texCoord;
    void main() {
        const vec2 positions[3] = vec2[](
            vec2(-1.0, -1.0),
            vec2( 3.0, -1.0),
            vec2(-1.0,  3.0));
        texCoord = 0.5 * positions[gl_VertexID] + 0.5;
        gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
    }
)";

const char* COMPOSITE_FRAGMENT_SHADER = R"(
    #version 330 core
    in vec2 texCoord;
    uniform sampler2D pointColor;
    uniform sampler2D pointDepth;
    uniform float layerAlpha;
    out vec4 fragColor;
    void main() {
        float depth = texture(pointDepth, texCoord).r;
        if (depth >= 0.99999) {
            discard;
        }
        vec4 point = texture(pointColor, texCoord);
        float alpha = clamp(point.a * layerAlpha, 0.0, 1.0);
        gl_FragDepth = depth;
        fragColor = vec4(point.rgb * alpha, alpha);
    }
)";

}  // namespace

void PointCloudFramebuffer::destroy() {
  // May run at teardown after the widget has called doneCurrent(); without a
  // current context there is nothing (and no safe way) to delete — the GL
  // objects are freed when the context itself is destroyed.
  auto* context = QOpenGLContext::currentContext();
  if (m_fbo != 0 && context) {
    auto* f = context->functions();
    CHECK_GL(f->glDeleteFramebuffers(1, &m_fbo));
    CHECK_GL(f->glDeleteTextures(1, &m_color_tex));
    CHECK_GL(f->glDeleteTextures(1, &m_depth_tex));
    CHECK_GL(f->glDeleteTextures(1, &m_pick_tex));
  }
  m_fbo = 0;
  m_color_tex = 0;
  m_depth_tex = 0;
  m_pick_tex = 0;
  m_width = 0;
  m_height = 0;
}

void PointCloudFramebuffer::ensure_size(int width, int height) {
  if (width <= 0 || height <= 0) {
    return;
  }
  if (valid() && m_width == width && m_height == height) {
    return;
  }
  destroy();

  auto* context = QOpenGLContext::currentContext();
  if (!context) {
    return;
  }
  auto* f = context->functions();
  m_width = width;
  m_height = height;

  CHECK_GL(f->glGenTextures(1, &m_color_tex));
  CHECK_GL(f->glBindTexture(GL_TEXTURE_2D, m_color_tex));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  CHECK_GL(f->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                           nullptr));

  CHECK_GL(f->glGenTextures(1, &m_depth_tex));
  CHECK_GL(f->glBindTexture(GL_TEXTURE_2D, m_depth_tex));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  CHECK_GL(f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  CHECK_GL(f->glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0,
                           GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));

  // Pick attachment: RG32UI — .r = global index + 1, .g = layer slot.
  auto* ef = context->extraFunctions();
  CHECK_GL(ef->glGenTextures(1, &m_pick_tex));
  CHECK_GL(ef->glBindTexture(GL_TEXTURE_2D, m_pick_tex));
  CHECK_GL(ef->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  CHECK_GL(ef->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
  CHECK_GL(ef->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  CHECK_GL(ef->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  CHECK_GL(ef->glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32UI, width, height, 0, GL_RG_INTEGER,
                            GL_UNSIGNED_INT, nullptr));

  CHECK_GL(f->glGenFramebuffers(1, &m_fbo));
  CHECK_GL(f->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo));
  CHECK_GL(f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                     m_color_tex, 0));
  CHECK_GL(f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
                                     m_depth_tex, 0));
  CHECK_GL(f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
                                     m_pick_tex, 0));
  // Attachment 1 is GL_RG32UI — compositor only samples attachment 0 + depth.
  GLenum bufs[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
  CHECK_GL(ef->glDrawBuffers(2, bufs));

  GLenum status = GL_FRAMEBUFFER_UNDEFINED;
  CHECK_GL(status = ef->glCheckFramebufferStatus(GL_FRAMEBUFFER));
  Assert(status == GL_FRAMEBUFFER_COMPLETE, "FBO not complete after adding pick attachment");

  CHECK_GL(f->glBindFramebuffer(GL_FRAMEBUFFER, 0));
  CHECK_GL(f->glBindTexture(GL_TEXTURE_2D, 0));
}

void PointCloudFramebuffer::bind() const {
  if (m_fbo == 0) {
    return;
  }
  auto* f = QOpenGLContext::currentContext()->functions();
  CHECK_GL(f->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo));
  // Ensure both draw buffers are active so MRT writes to both colour
  // attachments (0 = RGBA8 colour, 1 = RG32UI pick ID).
  auto* ef = QOpenGLContext::currentContext()->extraFunctions();
  if (ef) {
    GLenum bufs[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
    CHECK_GL(ef->glDrawBuffers(2, bufs));
  }
  // Blending with an integer colour attachment is INVALID_OPERATION.
  CHECK_GL(f->glDisable(GL_BLEND));
}

void PointCloudFramebuffer::bind_read() const {
  if (m_fbo != 0) {
    auto* f = QOpenGLContext::currentContext()->functions();
    CHECK_GL(f->glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo));
  }
}

void PointCloudFramebuffer::clear() const {
  if (m_fbo == 0) {
    return;
  }
  auto* f = QOpenGLContext::currentContext()->functions();
  auto* ef = QOpenGLContext::currentContext()->extraFunctions();
  if (!ef) {
    return;
  }

  // Caller should have bound this FBO; ensure draw-buffer state is correct.
  GLenum bufs[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
  CHECK_GL(ef->glDrawBuffers(2, bufs));
  CHECK_GL(f->glDisable(GL_BLEND));

  // Clear each colour attachment individually.  glClear(GL_COLOR_BUFFER_BIT)
  // would generate GL_INVALID_OPERATION because attachment 1 is GL_RG32UI
  // (integer) — the spec forbids glClear when any draw buffer is integer.
  const GLfloat zeros[4] = {0.0f, 0.0f, 0.0f, 0.0f};
  CHECK_GL(ef->glClearBufferfv(GL_COLOR, 0, zeros));
  const GLuint zero_u[4] = {0, 0, 0, 0};
  CHECK_GL(ef->glClearBufferuiv(GL_COLOR, 1, zero_u));

  CHECK_GL(f->glClearDepthf(1.0f));
  CHECK_GL(f->glClear(GL_DEPTH_BUFFER_BIT));
}

void PointCloudCompositor::ensure_initialized(QOpenGLFunctions* f) {
  if (m_ready) {
    return;
  }
  m_shader = std::make_unique<QOpenGLShaderProgram>();
  if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, COMPOSITE_VERTEX_SHADER) ||
      !m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, COMPOSITE_FRAGMENT_SHADER) ||
      !m_shader->link()) {
    std::cerr << "Point compositor shader error: " << m_shader->log().toStdString() << std::endl;
    m_shader.reset();
    return;
  }
  m_color_loc = m_shader->uniformLocation("pointColor");
  m_depth_loc = m_shader->uniformLocation("pointDepth");
  m_alpha_loc = m_shader->uniformLocation("layerAlpha");
  m_vao.create();
  (void)f;
  m_ready = true;
}

void PointCloudCompositor::composite(QOpenGLExtraFunctions* gl, GLuint dest_fbo,
                                     GLuint point_color_tex, GLuint point_depth_tex,
                                     float layer_alpha, int width, int height) {
  if (point_color_tex == 0 || point_depth_tex == 0 || layer_alpha <= 0.0f) {
    return;
  }
  ensure_initialized(gl);
  if (!m_ready || !m_shader) {
    return;
  }

  CHECK_GL(gl->glBindFramebuffer(GL_FRAMEBUFFER, dest_fbo));
  CHECK_GL(gl->glViewport(0, 0, width, height));
  CHECK_GL(gl->glEnable(GL_BLEND));
  CHECK_GL(gl->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
  CHECK_GL(gl->glEnable(GL_DEPTH_TEST));
  CHECK_GL(gl->glDepthFunc(GL_LESS));
  CHECK_GL(gl->glDepthMask(GL_TRUE));

  m_shader->bind();
  CHECK_GL_AFTER();
  CHECK_GL(gl->glActiveTexture(GL_TEXTURE0));
  CHECK_GL(gl->glBindTexture(GL_TEXTURE_2D, point_color_tex));
  m_shader->setUniformValue(m_color_loc, 0);
  CHECK_GL(gl->glActiveTexture(GL_TEXTURE1));
  CHECK_GL(gl->glBindTexture(GL_TEXTURE_2D, point_depth_tex));
  m_shader->setUniformValue(m_depth_loc, 1);
  m_shader->setUniformValue(m_alpha_loc, layer_alpha);

  QOpenGLVertexArrayObject::Binder vao_binder(&m_vao);
  CHECK_GL(gl->glDrawArrays(GL_TRIANGLES, 0, 3));

  m_shader->release();
  CHECK_GL_AFTER();
  CHECK_GL(gl->glBindTexture(GL_TEXTURE_2D, 0));
}