blob: da95920cc473d5a4e9eeeaaffcaa746de7cf3012 [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#include "rive/pls/gl/pls_render_target_gl.hpp"
#include "rive/pls/pls.hpp"
#include "rive/pls/gl/pls_render_context_gl_impl.hpp"
#include "shaders/constants.glsl"
namespace rive::pls
{
TextureRenderTargetGL::~TextureRenderTargetGL() {}
static glutils::Texture make_backing_texture(GLenum internalformat, uint32_t width, uint32_t height)
{
glutils::Texture texture;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, internalformat, width, height);
return texture;
}
void TextureRenderTargetGL::allocateInternalPLSTextures(pls::InterlockMode interlockMode)
{
if (m_coverageTexture == 0)
{
m_coverageTexture = make_backing_texture(GL_R32UI, width(), height());
m_framebufferInternalAttachmentsDirty = true;
m_framebufferInternalPLSBindingsDirty = true;
}
if (m_clipTexture == 0)
{
m_clipTexture = make_backing_texture(GL_R32UI, width(), height());
m_framebufferInternalAttachmentsDirty = true;
m_framebufferInternalPLSBindingsDirty = true;
}
if (interlockMode == InterlockMode::rasterOrdering && m_originalDstColorTexture == 0)
{
m_originalDstColorTexture = make_backing_texture(GL_RGBA8, width(), height());
m_framebufferInternalAttachmentsDirty = true;
m_framebufferInternalPLSBindingsDirty = true;
}
}
void TextureRenderTargetGL::bindInternalFramebuffer(GLenum target, DrawBufferMask drawBufferMask)
{
if (m_framebufferID == 0)
{
m_framebufferID = glutils::Framebuffer();
}
glBindFramebuffer(target, m_framebufferID);
if (target != GL_READ_FRAMEBUFFER && m_internalDrawBufferMask != drawBufferMask)
{
GLenum drawBufferList[4];
for (size_t i = 0; i < 4; ++i)
{
drawBufferList[i] = (drawBufferMask & static_cast<DrawBufferMask>(1 << i))
? GL_COLOR_ATTACHMENT0 + i
: GL_NONE;
static_assert((int)DrawBufferMask::color == 1 << FRAMEBUFFER_PLANE_IDX);
static_assert((int)DrawBufferMask::coverage == 1 << COVERAGE_PLANE_IDX);
static_assert((int)DrawBufferMask::clip == 1 << CLIP_PLANE_IDX);
static_assert((int)DrawBufferMask::originalDstColor ==
1 << ORIGINAL_DST_COLOR_PLANE_IDX);
}
glDrawBuffers(4, drawBufferList);
m_internalDrawBufferMask = drawBufferMask;
}
if (m_framebufferTargetAttachmentDirty)
{
glFramebufferTexture2D(target,
GL_COLOR_ATTACHMENT0 + FRAMEBUFFER_PLANE_IDX,
GL_TEXTURE_2D,
m_externalTextureID,
0);
m_framebufferTargetAttachmentDirty = false;
}
if (m_framebufferInternalAttachmentsDirty)
{
glFramebufferTexture2D(target,
GL_COLOR_ATTACHMENT0 + COVERAGE_PLANE_IDX,
GL_TEXTURE_2D,
m_coverageTexture,
0);
glFramebufferTexture2D(target,
GL_COLOR_ATTACHMENT0 + CLIP_PLANE_IDX,
GL_TEXTURE_2D,
m_clipTexture,
0);
glFramebufferTexture2D(target,
GL_COLOR_ATTACHMENT0 + ORIGINAL_DST_COLOR_PLANE_IDX,
GL_TEXTURE_2D,
m_originalDstColorTexture,
0);
m_framebufferInternalAttachmentsDirty = false;
}
}
void TextureRenderTargetGL::bindHeadlessFramebuffer(const GLCapabilities& capabilities)
{
if (m_headlessFramebuffer == 0)
{
m_headlessFramebuffer = glutils::Framebuffer();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_headlessFramebuffer);
#ifndef RIVE_WEBGL
if (capabilities.ARB_shader_image_load_store)
{
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, width());
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, height());
}
#endif
glDrawBuffers(0, nullptr);
}
else
{
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_headlessFramebuffer);
}
#ifdef GL_ANGLE_shader_pixel_local_storage
if (capabilities.ANGLE_shader_pixel_local_storage)
{
if (m_framebufferTargetPLSBindingDirty)
{
glFramebufferTexturePixelLocalStorageANGLE(FRAMEBUFFER_PLANE_IDX,
m_externalTextureID,
0,
0);
m_framebufferTargetPLSBindingDirty = false;
}
if (m_framebufferInternalPLSBindingsDirty)
{
glFramebufferTexturePixelLocalStorageANGLE(COVERAGE_PLANE_IDX, m_coverageTexture, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(CLIP_PLANE_IDX, m_clipTexture, 0, 0);
glFramebufferTexturePixelLocalStorageANGLE(ORIGINAL_DST_COLOR_PLANE_IDX,
m_originalDstColorTexture,
0,
0);
m_framebufferInternalPLSBindingsDirty = false;
}
}
#endif
}
void TextureRenderTargetGL::bindAsImageTextures(DrawBufferMask drawBufferMask)
{
#ifndef RIVE_WEBGL
if (drawBufferMask & DrawBufferMask::color)
{
assert(m_externalTextureID != 0);
glBindImageTexture(FRAMEBUFFER_PLANE_IDX,
m_externalTextureID,
0,
GL_FALSE,
0,
GL_READ_WRITE,
GL_RGBA8);
}
if (drawBufferMask & DrawBufferMask::coverage)
{
assert(m_coverageTexture != 0);
glBindImageTexture(COVERAGE_PLANE_IDX,
m_coverageTexture,
0,
GL_FALSE,
0,
GL_READ_WRITE,
GL_R32UI);
}
if (drawBufferMask & DrawBufferMask::clip)
{
assert(m_clipTexture != 0);
glBindImageTexture(CLIP_PLANE_IDX, m_clipTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
}
if (drawBufferMask & DrawBufferMask::originalDstColor)
{
assert(m_originalDstColorTexture != 0);
glBindImageTexture(ORIGINAL_DST_COLOR_PLANE_IDX,
m_originalDstColorTexture,
0,
GL_FALSE,
0,
GL_READ_WRITE,
GL_RGBA8);
}
#endif
}
PLSRenderTargetGL::MSAAResolveAction TextureRenderTargetGL::bindMSAAFramebuffer(
PLSRenderContextGLImpl* plsContextGL,
int sampleCount,
const IAABB* preserveBounds,
bool* isFBO0)
{
assert(sampleCount > 0);
if (m_msaaFramebuffer == 0)
{
m_msaaFramebuffer = glutils::Framebuffer();
}
if (isFBO0 != nullptr)
{
*isFBO0 = false;
}
sampleCount = std::max(sampleCount, 1);
if (m_msaaFramebufferSampleCount != sampleCount)
{
m_msaaDepthStencilBuffer = glutils::Renderbuffer();
glBindRenderbuffer(GL_RENDERBUFFER, m_msaaDepthStencilBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_msaaFramebuffer);
#ifndef RIVE_WEBGL
if (plsContextGL->capabilities().EXT_multisampled_render_to_texture)
{
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER,
sampleCount,
GL_DEPTH24_STENCIL8,
width(),
height());
// With EXT_multisampled_render_to_texture we can render directly to the target texture.
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
m_externalTextureID,
0,
sampleCount);
}
else
#endif
{
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
sampleCount,
GL_DEPTH24_STENCIL8,
width(),
height());
// Render to an offscreen renderbuffer that gets resolved into the target texture.
m_msaaColorBuffer = glutils::Renderbuffer();
glBindRenderbuffer(GL_RENDERBUFFER, m_msaaColorBuffer);
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
sampleCount,
GL_RGBA8,
width(),
height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER,
m_msaaColorBuffer);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_DEPTH_STENCIL_ATTACHMENT,
GL_RENDERBUFFER,
m_msaaDepthStencilBuffer);
m_msaaFramebufferSampleCount = sampleCount;
}
glBindFramebuffer(GL_FRAMEBUFFER, m_msaaFramebuffer);
if (plsContextGL->capabilities().EXT_multisampled_render_to_texture)
{
return MSAAResolveAction::automatic; // MSAA render-to-texture resolves automatically.
}
else
{
if (preserveBounds != nullptr)
{
// The MSAA render target is offscreen. In order to preserve, we need to draw the target
// texture into the MSAA buffer. (glBlitFramebuffer() doesn't support texture -> MSAA.)
plsContextGL->blitTextureToFramebufferAsDraw(m_externalTextureID,
*preserveBounds,
height());
}
return MSAAResolveAction::framebufferBlit; // Caller must resolve this framebuffer.
}
}
void TextureRenderTargetGL::bindInternalDstTexture(GLenum activeTexture)
{
glActiveTexture(activeTexture);
glBindTexture(GL_TEXTURE_2D, m_externalTextureID);
}
FramebufferRenderTargetGL::~FramebufferRenderTargetGL() {}
void FramebufferRenderTargetGL::bindDestinationFramebuffer(GLenum target)
{
glBindFramebuffer(target, m_externalFramebufferID);
}
void FramebufferRenderTargetGL::allocateOffscreenTargetTexture()
{
if (m_offscreenTargetTexture == 0)
{
m_offscreenTargetTexture = make_backing_texture(GL_RGBA8, width(), height());
m_textureRenderTarget.setTargetTexture(m_offscreenTargetTexture);
}
}
void FramebufferRenderTargetGL::allocateInternalPLSTextures(pls::InterlockMode interlockMode)
{
m_textureRenderTarget.allocateInternalPLSTextures(interlockMode);
}
void FramebufferRenderTargetGL::bindInternalFramebuffer(GLenum target,
DrawBufferMask drawBufferMask)
{
m_textureRenderTarget.bindInternalFramebuffer(target, drawBufferMask);
}
void FramebufferRenderTargetGL::bindHeadlessFramebuffer(const GLCapabilities& capabilities)
{
m_textureRenderTarget.bindHeadlessFramebuffer(capabilities);
}
void FramebufferRenderTargetGL::bindAsImageTextures(DrawBufferMask drawBufferMask)
{
m_textureRenderTarget.bindAsImageTextures(drawBufferMask);
}
PLSRenderTargetGL::MSAAResolveAction FramebufferRenderTargetGL::bindMSAAFramebuffer(
PLSRenderContextGLImpl* plsContextGL,
int sampleCount,
const IAABB* preserveBounds,
bool* isFBO0)
{
assert(sampleCount > 0);
if (m_sampleCount > 1)
{
// Just bind the destination framebuffer it's already msaa, even if its sampleCount doesn't
// match the desired count.
bindDestinationFramebuffer(GL_FRAMEBUFFER);
if (isFBO0 != nullptr)
{
*isFBO0 = m_externalFramebufferID == 0;
}
return MSAAResolveAction::automatic;
}
else
{
// The destination framebuffer is not multisampled. Bind the offscreen one.
if (preserveBounds != nullptr)
{
// API support for copying a non-msaa framebuffer into an msaa framebuffer (for
// preservation) is awful. It needs to be done in 2 steps:
// 1. Blit non-msaa framebuffer -> texture.
// 2. Draw texture -> msaa framebuffer.
// (NOTE: step 2 gets skipped when we have EXT_multisampled_render_to_texture.)
allocateOffscreenTargetTexture();
m_textureRenderTarget.bindInternalFramebuffer(GL_DRAW_FRAMEBUFFER,
DrawBufferMask::color);
bindDestinationFramebuffer(GL_READ_FRAMEBUFFER);
glutils::BlitFramebuffer(*preserveBounds, height()); // Step 1.
// Step 2 will happen when we bind.
}
else if (plsContextGL->capabilities().EXT_multisampled_render_to_texture)
{
// When we have EXT_multisampled_render_to_texture, the "msaa buffer" is just the target
// texture.
allocateOffscreenTargetTexture();
}
m_textureRenderTarget.bindMSAAFramebuffer(plsContextGL,
sampleCount,
preserveBounds,
isFBO0);
// Since we're rendering to an offscreen framebuffer, the client has to resolve this buffer
// even if we have EXT_multisampled_render_to_texture.
return MSAAResolveAction::framebufferBlit;
}
}
void FramebufferRenderTargetGL::bindInternalDstTexture(GLenum activeTexture)
{
allocateOffscreenTargetTexture();
m_textureRenderTarget.bindInternalDstTexture(activeTexture);
}
} // namespace rive::pls