blob: 40ed1769f9978d80ff69b5ed3746d744ee3f8dbf [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/renderer/gl/render_context_gl_impl.hpp"
#include "rive/renderer/gl/render_buffer_gl_impl.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"
#include "rive/renderer/draw.hpp"
#include "rive/renderer/texture.hpp"
#include "shaders/constants.glsl"
#include "generated/shaders/advanced_blend.glsl.hpp"
#include "generated/shaders/color_ramp.glsl.hpp"
#include "generated/shaders/constants.glsl.hpp"
#include "generated/shaders/common.glsl.hpp"
#include "generated/shaders/draw_path_common.glsl.hpp"
#include "generated/shaders/draw_path.glsl.hpp"
#include "generated/shaders/draw_image_mesh.glsl.hpp"
#include "generated/shaders/tessellate.glsl.hpp"
#include "generated/shaders/blit_texture_as_draw.glsl.hpp"
#include "generated/shaders/stencil_draw.glsl.hpp"
#ifdef RIVE_WEBGL
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
// In an effort to save space on web, and since web doesn't have ES 3.1 level
// support, don't include the atomic sources.
namespace rive::gpu::glsl
{
const char atomic_draw[] = "";
}
#define DISABLE_PLS_ATOMICS
#else
#include "generated/shaders/atomic_draw.glsl.hpp"
#endif
// Offset all PLS texture indices by 1 so we, and others who share our GL
// context, can use GL_TEXTURE0 as a scratch texture index.
constexpr static int kPLSTexIdxOffset = 1;
namespace rive::gpu
{
RenderContextGLImpl::RenderContextGLImpl(
const char* rendererString,
GLCapabilities capabilities,
std::unique_ptr<PixelLocalStorageImpl> plsImpl) :
m_capabilities(capabilities),
m_plsImpl(std::move(plsImpl)),
m_state(make_rcp<GLState>(m_capabilities))
{
if (m_plsImpl != nullptr)
{
m_platformFeatures.supportsRasterOrdering =
m_plsImpl->supportsRasterOrdering(m_capabilities);
m_platformFeatures.supportsFragmentShaderAtomics =
m_plsImpl->supportsFragmentShaderAtomics(m_capabilities);
}
if (m_capabilities.KHR_blend_equation_advanced_coherent)
{
m_platformFeatures.supportsKHRBlendEquations = true;
}
if (m_capabilities.EXT_clip_cull_distance)
{
m_platformFeatures.supportsClipPlanes = true;
}
if (strstr(rendererString, "Apple") && strstr(rendererString, "Metal"))
{
// In Metal, non-flat varyings preserve their exact value if all
// vertices in the triangle emit the same value, and we also see a small
// (5-10%) improvement from not using flat varyings.
m_platformFeatures.avoidFlatVaryings = true;
}
m_platformFeatures.fragCoordBottomUp = true;
std::vector<const char*> generalDefines;
if (!m_capabilities.ARB_shader_storage_buffer_object)
{
generalDefines.push_back(GLSL_DISABLE_SHADER_STORAGE_BUFFERS);
}
const char* colorRampSources[] = {glsl::constants,
glsl::common,
glsl::color_ramp};
m_colorRampProgram.compileAndAttachShader(GL_VERTEX_SHADER,
generalDefines.data(),
generalDefines.size(),
colorRampSources,
std::size(colorRampSources),
m_capabilities);
m_colorRampProgram.compileAndAttachShader(GL_FRAGMENT_SHADER,
generalDefines.data(),
generalDefines.size(),
colorRampSources,
std::size(colorRampSources),
m_capabilities);
m_colorRampProgram.link();
glUniformBlockBinding(
m_colorRampProgram,
glGetUniformBlockIndex(m_colorRampProgram, GLSL_FlushUniforms),
FLUSH_UNIFORM_BUFFER_IDX);
m_state->bindVAO(m_colorRampVAO);
glEnableVertexAttribArray(0);
glVertexAttribDivisor(0, 1);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + FEATHER_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_featherTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R16F, gpu::GAUSSIAN_TABLE_SIZE, 1);
m_state->bindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glTexSubImage2D(GL_TEXTURE_2D,
0,
0,
0,
gpu::GAUSSIAN_TABLE_SIZE,
1,
GL_RED,
GL_HALF_FLOAT,
gpu::g_gaussianIntegralTableF16);
glutils::SetTexture2DSamplingParams(GL_LINEAR, GL_LINEAR);
const char* tessellateSources[] = {glsl::constants,
glsl::common,
glsl::tessellate};
m_tessellateProgram.compileAndAttachShader(GL_VERTEX_SHADER,
generalDefines.data(),
generalDefines.size(),
tessellateSources,
std::size(tessellateSources),
m_capabilities);
m_tessellateProgram.compileAndAttachShader(GL_FRAGMENT_SHADER,
generalDefines.data(),
generalDefines.size(),
tessellateSources,
std::size(tessellateSources),
m_capabilities);
m_tessellateProgram.link();
m_state->bindProgram(m_tessellateProgram);
glUniformBlockBinding(
m_tessellateProgram,
glGetUniformBlockIndex(m_tessellateProgram, GLSL_FlushUniforms),
FLUSH_UNIFORM_BUFFER_IDX);
if (!m_capabilities.ARB_shader_storage_buffer_object)
{
// Our GL driver doesn't support storage buffers. We polyfill these
// buffers as textures.
glutils::Uniform1iByName(m_tessellateProgram,
GLSL_pathBuffer,
kPLSTexIdxOffset + PATH_BUFFER_IDX);
glutils::Uniform1iByName(m_tessellateProgram,
GLSL_contourBuffer,
kPLSTexIdxOffset + CONTOUR_BUFFER_IDX);
}
m_state->bindVAO(m_tessellateVAO);
for (int i = 0; i < 4; ++i)
{
glEnableVertexAttribArray(i);
// Draw two instances per TessVertexSpan: one normal and one optional
// reflection.
glVertexAttribDivisor(i, 1);
}
m_state->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_tessSpanIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(gpu::kTessSpanIndices),
gpu::kTessSpanIndices,
GL_STATIC_DRAW);
m_state->bindVAO(m_drawVAO);
PatchVertex patchVertices[kPatchVertexBufferCount];
uint16_t patchIndices[kPatchIndexBufferCount];
GeneratePatchBufferData(patchVertices, patchIndices);
m_state->bindBuffer(GL_ARRAY_BUFFER, m_patchVerticesBuffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(patchVertices),
patchVertices,
GL_STATIC_DRAW);
m_state->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_patchIndicesBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(patchIndices),
patchIndices,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,
4,
GL_FLOAT,
GL_FALSE,
sizeof(PatchVertex),
nullptr);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,
4,
GL_FLOAT,
GL_FALSE,
sizeof(PatchVertex),
reinterpret_cast<const void*>(sizeof(float) * 4));
m_state->bindVAO(m_trianglesVAO);
glEnableVertexAttribArray(0);
// We draw imageRects when in atomic mode.
m_state->bindVAO(m_imageRectVAO);
m_state->bindBuffer(GL_ARRAY_BUFFER, m_imageRectVertexBuffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(gpu::kImageRectVertices),
gpu::kImageRectVertices,
GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,
4,
GL_FLOAT,
GL_FALSE,
sizeof(gpu::ImageRectVertex),
nullptr);
m_state->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_imageRectIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(gpu::kImageRectIndices),
gpu::kImageRectIndices,
GL_STATIC_DRAW);
m_state->bindVAO(m_imageMeshVAO);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
if (m_plsImpl != nullptr)
{
m_plsImpl->init(m_state);
}
}
RenderContextGLImpl::~RenderContextGLImpl()
{
glDeleteTextures(1, &m_gradientTexture);
glDeleteTextures(1, &m_tessVertexTexture);
// Because glutils wrappers delete GL objects that might affect bindings.
m_state->invalidate();
}
void RenderContextGLImpl::invalidateGLState()
{
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + TESS_VERTEX_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_tessVertexTexture);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + GRAD_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_gradientTexture);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + FEATHER_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_featherTexture);
m_state->invalidate();
}
void RenderContextGLImpl::unbindGLInternalResources()
{
m_state->bindVAO(0);
m_state->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
m_state->bindBuffer(GL_ARRAY_BUFFER, 0);
m_state->bindBuffer(GL_UNIFORM_BUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
for (int i = 0; i <= CONTOUR_BUFFER_IDX; ++i)
{
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + i);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
rcp<RenderBuffer> RenderContextGLImpl::makeRenderBuffer(RenderBufferType type,
RenderBufferFlags flags,
size_t sizeInBytes)
{
return make_rcp<RenderBufferGLImpl>(type, flags, sizeInBytes, m_state);
}
class TextureGLImpl : public Texture
{
public:
TextureGLImpl(uint32_t width,
uint32_t height,
GLuint textureID,
const GLCapabilities& capabilities) :
Texture(width, height), m_textureID(textureID)
{}
GLuint textureID() const { return m_textureID; }
private:
GLuint m_textureID = 0;
};
rcp<Texture> RenderContextGLImpl::makeImageTexture(
uint32_t width,
uint32_t height,
uint32_t mipLevelCount,
const uint8_t imageDataRGBA[])
{
GLuint textureID;
glGenTextures(1, &textureID);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + IMAGE_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexStorage2D(GL_TEXTURE_2D, mipLevelCount, GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D,
0,
0,
0,
width,
height,
GL_RGBA,
GL_UNSIGNED_BYTE,
imageDataRGBA);
glutils::SetTexture2DSamplingParams(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
return adoptImageTexture(width, height, textureID);
}
rcp<Texture> RenderContextGLImpl::adoptImageTexture(uint32_t width,
uint32_t height,
GLuint textureID)
{
return make_rcp<TextureGLImpl>(width, height, textureID, m_capabilities);
}
// BufferRingImpl in GL on a given buffer target. In order to support WebGL2, we
// don't do hardware mapping.
class BufferRingGLImpl : public BufferRing
{
public:
static std::unique_ptr<BufferRingGLImpl> Make(size_t capacityInBytes,
GLenum target,
rcp<GLState> state)
{
return capacityInBytes != 0
? std::unique_ptr<BufferRingGLImpl>(
new BufferRingGLImpl(target,
capacityInBytes,
std::move(state)))
: nullptr;
}
~BufferRingGLImpl()
{
for (int i = 0; i < kBufferRingSize; ++i)
{
m_state->deleteBuffer(m_ids[i]);
}
}
GLuint submittedBufferID() const { return m_ids[submittedBufferIdx()]; }
protected:
BufferRingGLImpl(GLenum target,
size_t capacityInBytes,
rcp<GLState> state) :
BufferRing(capacityInBytes), m_target(target), m_state(std::move(state))
{
glGenBuffers(kBufferRingSize, m_ids);
for (int i = 0; i < kBufferRingSize; ++i)
{
m_state->bindBuffer(m_target, m_ids[i]);
glBufferData(m_target, capacityInBytes, nullptr, GL_DYNAMIC_DRAW);
}
}
void* onMapBuffer(int bufferIdx, size_t mapSizeInBytes) override
{
#ifdef RIVE_WEBGL
// WebGL doesn't support buffer mapping.
return shadowBuffer();
#else
m_state->bindBuffer(m_target, m_ids[bufferIdx]);
return glMapBufferRange(m_target,
0,
mapSizeInBytes,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT |
GL_MAP_UNSYNCHRONIZED_BIT);
#endif
}
void onUnmapAndSubmitBuffer(int bufferIdx, size_t mapSizeInBytes) override
{
m_state->bindBuffer(m_target, m_ids[bufferIdx]);
#ifdef RIVE_WEBGL
// WebGL doesn't support buffer mapping.
glBufferSubData(m_target, 0, mapSizeInBytes, shadowBuffer());
#else
glUnmapBuffer(m_target);
#endif
}
const GLenum m_target;
GLuint m_ids[kBufferRingSize];
const rcp<GLState> m_state;
};
// GL internalformat to use for a texture that polyfills a storage buffer.
static GLenum storage_texture_internalformat(
gpu::StorageBufferStructure bufferStructure)
{
switch (bufferStructure)
{
case gpu::StorageBufferStructure::uint32x4:
return GL_RGBA32UI;
case gpu::StorageBufferStructure::uint32x2:
return GL_RG32UI;
case gpu::StorageBufferStructure::float32x4:
return GL_RGBA32F;
}
RIVE_UNREACHABLE();
}
// GL format to use for a texture that polyfills a storage buffer.
static GLenum storage_texture_format(
gpu::StorageBufferStructure bufferStructure)
{
switch (bufferStructure)
{
case gpu::StorageBufferStructure::uint32x4:
return GL_RGBA_INTEGER;
case gpu::StorageBufferStructure::uint32x2:
return GL_RG_INTEGER;
case gpu::StorageBufferStructure::float32x4:
return GL_RGBA;
}
RIVE_UNREACHABLE();
}
// GL type to use for a texture that polyfills a storage buffer.
static GLenum storage_texture_type(gpu::StorageBufferStructure bufferStructure)
{
switch (bufferStructure)
{
case gpu::StorageBufferStructure::uint32x4:
return GL_UNSIGNED_INT;
case gpu::StorageBufferStructure::uint32x2:
return GL_UNSIGNED_INT;
case gpu::StorageBufferStructure::float32x4:
return GL_FLOAT;
}
RIVE_UNREACHABLE();
}
class StorageBufferRingGLImpl : public BufferRingGLImpl
{
public:
StorageBufferRingGLImpl(size_t capacityInBytes,
gpu::StorageBufferStructure bufferStructure,
rcp<GLState> state) :
BufferRingGLImpl(
// If we don't support storage buffers, instead make a pixel-unpack
// buffer that will be used to copy data into the polyfill texture.
GL_SHADER_STORAGE_BUFFER,
capacityInBytes,
std::move(state)),
m_bufferStructure(bufferStructure)
{}
void bindToRenderContext(uint32_t bindingIdx,
size_t bindingSizeInBytes,
size_t offsetSizeInBytes) const
{
glBindBufferRange(GL_SHADER_STORAGE_BUFFER,
bindingIdx,
submittedBufferID(),
offsetSizeInBytes,
bindingSizeInBytes);
}
protected:
const gpu::StorageBufferStructure m_bufferStructure;
};
class TexelBufferRingWebGL : public BufferRing
{
public:
TexelBufferRingWebGL(size_t capacityInBytes,
gpu::StorageBufferStructure bufferStructure,
rcp<GLState> state) :
BufferRing(
gpu::StorageTextureBufferSize(capacityInBytes, bufferStructure)),
m_bufferStructure(bufferStructure),
m_state(std::move(state))
{
auto [width, height] =
gpu::StorageTextureSize(capacityInBytes, m_bufferStructure);
GLenum internalformat =
storage_texture_internalformat(m_bufferStructure);
glGenTextures(gpu::kBufferRingSize, m_textures);
glActiveTexture(GL_TEXTURE0);
for (size_t i = 0; i < gpu::kBufferRingSize; ++i)
{
glBindTexture(GL_TEXTURE_2D, m_textures[i]);
glTexStorage2D(GL_TEXTURE_2D, 1, internalformat, width, height);
glutils::SetTexture2DSamplingParams(GL_NEAREST, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
}
~TexelBufferRingWebGL()
{
glDeleteTextures(gpu::kBufferRingSize, m_textures);
}
void* onMapBuffer(int bufferIdx, size_t mapSizeInBytes) override
{
return shadowBuffer();
}
void onUnmapAndSubmitBuffer(int bufferIdx, size_t mapSizeInBytes) override
{}
void bindToRenderContext(uint32_t bindingIdx,
size_t bindingSizeInBytes,
size_t offsetSizeInBytes) const
{
auto [updateWidth, updateHeight] =
gpu::StorageTextureSize(bindingSizeInBytes, m_bufferStructure);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + bindingIdx);
glBindTexture(GL_TEXTURE_2D, m_textures[submittedBufferIdx()]);
glTexSubImage2D(GL_TEXTURE_2D,
0,
0,
0,
updateWidth,
updateHeight,
storage_texture_format(m_bufferStructure),
storage_texture_type(m_bufferStructure),
shadowBuffer() + offsetSizeInBytes);
}
protected:
const gpu::StorageBufferStructure m_bufferStructure;
const rcp<GLState> m_state;
GLuint m_textures[gpu::kBufferRingSize];
};
std::unique_ptr<BufferRing> RenderContextGLImpl::makeUniformBufferRing(
size_t capacityInBytes)
{
return BufferRingGLImpl::Make(capacityInBytes, GL_UNIFORM_BUFFER, m_state);
}
std::unique_ptr<BufferRing> RenderContextGLImpl::makeStorageBufferRing(
size_t capacityInBytes,
gpu::StorageBufferStructure bufferStructure)
{
if (capacityInBytes == 0)
{
return nullptr;
}
else if (m_capabilities.ARB_shader_storage_buffer_object)
{
return std::make_unique<StorageBufferRingGLImpl>(capacityInBytes,
bufferStructure,
m_state);
}
else
{
return std::make_unique<TexelBufferRingWebGL>(capacityInBytes,
bufferStructure,
m_state);
}
}
std::unique_ptr<BufferRing> RenderContextGLImpl::makeVertexBufferRing(
size_t capacityInBytes)
{
return BufferRingGLImpl::Make(capacityInBytes, GL_ARRAY_BUFFER, m_state);
}
void RenderContextGLImpl::resizeGradientTexture(uint32_t width, uint32_t height)
{
glDeleteTextures(1, &m_gradientTexture);
if (width == 0 || height == 0)
{
m_gradientTexture = 0;
return;
}
glGenTextures(1, &m_gradientTexture);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + GRAD_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_gradientTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
glutils::SetTexture2DSamplingParams(GL_LINEAR, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, m_colorRampFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
m_gradientTexture,
0);
}
void RenderContextGLImpl::resizeTessellationTexture(uint32_t width,
uint32_t height)
{
glDeleteTextures(1, &m_tessVertexTexture);
if (width == 0 || height == 0)
{
m_tessVertexTexture = 0;
return;
}
glGenTextures(1, &m_tessVertexTexture);
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + TESS_VERTEX_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, m_tessVertexTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32UI, width, height);
glutils::SetTexture2DSamplingParams(GL_NEAREST, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, m_tessellateFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
m_tessVertexTexture,
0);
}
RenderContextGLImpl::DrawShader::DrawShader(
RenderContextGLImpl* renderContextImpl,
GLenum shaderType,
gpu::DrawType drawType,
ShaderFeatures shaderFeatures,
gpu::InterlockMode interlockMode,
gpu::ShaderMiscFlags shaderMiscFlags)
{
#ifdef DISABLE_PLS_ATOMICS
if (interlockMode == gpu::InterlockMode::atomics)
{
// Don't draw anything in atomic mode if support for it isn't compiled
// in.
return;
}
#endif
std::vector<const char*> defines;
if (renderContextImpl->m_plsImpl != nullptr)
{
renderContextImpl->m_plsImpl->pushShaderDefines(interlockMode,
&defines);
}
if (interlockMode == gpu::InterlockMode::atomics)
{
// Atomics are currently always done on storage textures.
defines.push_back(GLSL_USING_PLS_STORAGE_TEXTURES);
}
if (shaderMiscFlags & gpu::ShaderMiscFlags::fixedFunctionColorOutput)
{
defines.push_back(GLSL_FIXED_FUNCTION_COLOR_OUTPUT);
}
if (shaderMiscFlags & gpu::ShaderMiscFlags::clockwiseFill)
{
defines.push_back(GLSL_CLOCKWISE_FILL);
}
for (size_t i = 0; i < kShaderFeatureCount; ++i)
{
ShaderFeatures feature = static_cast<ShaderFeatures>(1 << i);
if (shaderFeatures & feature)
{
assert((kVertexShaderFeaturesMask & feature) ||
shaderType == GL_FRAGMENT_SHADER);
if (interlockMode == gpu::InterlockMode::msaa &&
feature == gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND &&
renderContextImpl->m_capabilities
.KHR_blend_equation_advanced_coherent)
{
defines.push_back(GLSL_ENABLE_KHR_BLEND);
}
else
{
defines.push_back(GetShaderFeatureGLSLName(feature));
}
}
}
if (interlockMode == gpu::InterlockMode::msaa)
{
defines.push_back(GLSL_RENDER_MODE_MSAA);
}
std::vector<const char*> sources;
sources.push_back(glsl::constants);
sources.push_back(glsl::common);
if (shaderType == GL_FRAGMENT_SHADER &&
(shaderFeatures & ShaderFeatures::ENABLE_ADVANCED_BLEND))
{
sources.push_back(glsl::advanced_blend);
}
if (renderContextImpl->platformFeatures().avoidFlatVaryings)
{
sources.push_back("#define " GLSL_OPTIONALLY_FLAT "\n");
}
else
{
sources.push_back("#define " GLSL_OPTIONALLY_FLAT " flat\n");
}
switch (drawType)
{
case gpu::DrawType::midpointFanPatches:
case gpu::DrawType::midpointFanCenterAAPatches:
case gpu::DrawType::outerCurvePatches:
if (shaderType == GL_VERTEX_SHADER)
{
defines.push_back(GLSL_ENABLE_INSTANCE_INDEX);
if (!renderContextImpl->m_capabilities
.ANGLE_base_vertex_base_instance_shader_builtin)
{
defines.push_back(GLSL_ENABLE_SPIRV_CROSS_BASE_INSTANCE);
}
}
defines.push_back(GLSL_DRAW_PATH);
sources.push_back(gpu::glsl::draw_path_common);
sources.push_back(interlockMode == gpu::InterlockMode::atomics
? gpu::glsl::atomic_draw
: gpu::glsl::draw_path);
break;
case gpu::DrawType::stencilClipReset:
assert(interlockMode == gpu::InterlockMode::msaa);
sources.push_back(gpu::glsl::stencil_draw);
break;
case gpu::DrawType::interiorTriangulation:
defines.push_back(GLSL_DRAW_INTERIOR_TRIANGLES);
sources.push_back(gpu::glsl::draw_path_common);
sources.push_back(interlockMode == gpu::InterlockMode::atomics
? gpu::glsl::atomic_draw
: gpu::glsl::draw_path);
break;
case gpu::DrawType::imageRect:
assert(interlockMode == gpu::InterlockMode::atomics);
defines.push_back(GLSL_DRAW_IMAGE);
defines.push_back(GLSL_DRAW_IMAGE_RECT);
sources.push_back(gpu::glsl::atomic_draw);
break;
case gpu::DrawType::imageMesh:
defines.push_back(GLSL_DRAW_IMAGE);
defines.push_back(GLSL_DRAW_IMAGE_MESH);
sources.push_back(interlockMode == gpu::InterlockMode::atomics
? gpu::glsl::atomic_draw
: gpu::glsl::draw_image_mesh);
break;
case gpu::DrawType::atomicResolve:
assert(interlockMode == gpu::InterlockMode::atomics);
defines.push_back(GLSL_DRAW_RENDER_TARGET_UPDATE_BOUNDS);
defines.push_back(GLSL_RESOLVE_PLS);
if (shaderMiscFlags &
gpu::ShaderMiscFlags::coalescedResolveAndTransfer)
{
assert(shaderType == GL_FRAGMENT_SHADER);
defines.push_back(GLSL_COALESCED_PLS_RESOLVE_AND_TRANSFER);
}
sources.push_back(gpu::glsl::atomic_draw);
break;
case gpu::DrawType::atomicInitialize:
assert(interlockMode == gpu::InterlockMode::atomics);
RIVE_UNREACHABLE();
}
if (!renderContextImpl->m_capabilities.ARB_shader_storage_buffer_object)
{
defines.push_back(GLSL_DISABLE_SHADER_STORAGE_BUFFERS);
}
m_id = glutils::CompileShader(shaderType,
defines.data(),
defines.size(),
sources.data(),
sources.size(),
renderContextImpl->m_capabilities);
}
RenderContextGLImpl::DrawProgram::DrawProgram(
RenderContextGLImpl* renderContextImpl,
gpu::DrawType drawType,
gpu::ShaderFeatures shaderFeatures,
gpu::InterlockMode interlockMode,
gpu::ShaderMiscFlags fragmentShaderMiscFlags) :
m_fragmentShader(renderContextImpl,
GL_FRAGMENT_SHADER,
drawType,
shaderFeatures,
interlockMode,
fragmentShaderMiscFlags),
m_state(renderContextImpl->m_state)
{
// Not every vertex shader is unique. Cache them by just the vertex features
// and reuse when possible.
ShaderFeatures vertexShaderFeatures =
shaderFeatures & kVertexShaderFeaturesMask;
uint32_t vertexShaderKey = gpu::ShaderUniqueKey(drawType,
vertexShaderFeatures,
interlockMode,
gpu::ShaderMiscFlags::none);
const DrawShader& vertexShader =
renderContextImpl->m_vertexShaders
.try_emplace(vertexShaderKey,
renderContextImpl,
GL_VERTEX_SHADER,
drawType,
vertexShaderFeatures,
interlockMode,
gpu::ShaderMiscFlags::none)
.first->second;
m_id = glCreateProgram();
glAttachShader(m_id, vertexShader.id());
glAttachShader(m_id, m_fragmentShader.id());
glutils::LinkProgram(m_id);
m_state->bindProgram(m_id);
glUniformBlockBinding(m_id,
glGetUniformBlockIndex(m_id, GLSL_FlushUniforms),
FLUSH_UNIFORM_BUFFER_IDX);
const bool isImageDraw = gpu::DrawTypeIsImageDraw(drawType);
const bool isTessellationDraw =
drawType == gpu::DrawType::midpointFanPatches ||
drawType == gpu::DrawType::midpointFanCenterAAPatches ||
drawType == gpu::DrawType::outerCurvePatches;
const bool isPathDraw =
isTessellationDraw || drawType == gpu::DrawType::interiorTriangulation;
if (isImageDraw)
{
glUniformBlockBinding(
m_id,
glGetUniformBlockIndex(m_id, GLSL_ImageDrawUniforms),
IMAGE_DRAW_UNIFORM_BUFFER_IDX);
}
if (isTessellationDraw)
{
glutils::Uniform1iByName(m_id,
GLSL_tessVertexTexture,
kPLSTexIdxOffset + TESS_VERTEX_TEXTURE_IDX);
}
// Since atomic mode emits the color of the *previous* path, it needs the
// gradient texture bound for every draw.
if (isPathDraw || interlockMode == gpu::InterlockMode::atomics)
{
glutils::Uniform1iByName(m_id,
GLSL_gradTexture,
kPLSTexIdxOffset + GRAD_TEXTURE_IDX);
}
if (shaderFeatures & gpu::ShaderFeatures::ENABLE_FEATHER)
{
assert(isPathDraw || interlockMode == gpu::InterlockMode::atomics);
glUniform1i(glGetUniformLocation(m_id, GLSL_featherTexture),
kPLSTexIdxOffset + FEATHER_TEXTURE_IDX);
}
// Atomic mode doesn't support image paints on paths.
if (isImageDraw ||
(isPathDraw && interlockMode != gpu::InterlockMode::atomics))
{
glutils::Uniform1iByName(m_id,
GLSL_imageTexture,
kPLSTexIdxOffset + IMAGE_TEXTURE_IDX);
}
if (!renderContextImpl->m_capabilities.ARB_shader_storage_buffer_object)
{
// Our GL driver doesn't support storage buffers. We polyfill these
// buffers as textures.
if (isPathDraw)
{
glutils::Uniform1iByName(m_id,
GLSL_pathBuffer,
kPLSTexIdxOffset + PATH_BUFFER_IDX);
}
if (isPathDraw || interlockMode == gpu::InterlockMode::atomics)
{
glutils::Uniform1iByName(m_id,
GLSL_paintBuffer,
kPLSTexIdxOffset + PAINT_BUFFER_IDX);
glutils::Uniform1iByName(m_id,
GLSL_paintAuxBuffer,
kPLSTexIdxOffset + PAINT_AUX_BUFFER_IDX);
}
if (isTessellationDraw)
{
glutils::Uniform1iByName(m_id,
GLSL_contourBuffer,
kPLSTexIdxOffset + CONTOUR_BUFFER_IDX);
}
}
if (interlockMode == gpu::InterlockMode::msaa &&
(shaderFeatures & gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND) &&
!renderContextImpl->m_capabilities.KHR_blend_equation_advanced_coherent)
{
glutils::Uniform1iByName(m_id,
GLSL_dstColorTexture,
kPLSTexIdxOffset + DST_COLOR_TEXTURE_IDX);
}
if (!renderContextImpl->m_capabilities
.ANGLE_base_vertex_base_instance_shader_builtin)
{
// This uniform is specifically named "SPIRV_Cross_BaseInstance" for
// compatibility with SPIRV-Cross sytems.
m_spirvCrossBaseInstanceLocation =
glGetUniformLocation(m_id, "SPIRV_Cross_BaseInstance");
}
}
RenderContextGLImpl::DrawProgram::~DrawProgram()
{
m_state->deleteProgram(m_id);
}
static GLuint gl_buffer_id(const BufferRing* bufferRing)
{
return static_cast<const BufferRingGLImpl*>(bufferRing)
->submittedBufferID();
}
static void bind_storage_buffer(const GLCapabilities& capabilities,
const BufferRing* bufferRing,
uint32_t bindingIdx,
size_t bindingSizeInBytes,
size_t offsetSizeInBytes)
{
assert(bufferRing != nullptr);
assert(bindingSizeInBytes > 0);
if (capabilities.ARB_shader_storage_buffer_object)
{
static_cast<const StorageBufferRingGLImpl*>(bufferRing)
->bindToRenderContext(bindingIdx,
bindingSizeInBytes,
offsetSizeInBytes);
}
else
{
static_cast<const TexelBufferRingWebGL*>(bufferRing)
->bindToRenderContext(bindingIdx,
bindingSizeInBytes,
offsetSizeInBytes);
}
}
void RenderContextGLImpl::PixelLocalStorageImpl::ensureRasterOrderingEnabled(
RenderContextGLImpl* renderContextImpl,
const gpu::FlushDescriptor& desc,
bool enabled)
{
assert(!enabled ||
supportsRasterOrdering(renderContextImpl->m_capabilities));
auto rasterOrderState = enabled ? gpu::TriState::yes : gpu::TriState::no;
if (m_rasterOrderingEnabled != rasterOrderState)
{
onEnableRasterOrdering(enabled);
m_rasterOrderingEnabled = rasterOrderState;
// We only need a barrier when turning raster ordering OFF, because PLS
// already inserts the necessary barriers after draws when it's
// disabled.
if (m_rasterOrderingEnabled == gpu::TriState::no)
{
onBarrier(desc);
}
}
}
// Wraps calls to glDrawElementsInstanced*(), as appropriate for the current
// platform. Also includes simple helpers for working with stencil.
class DrawIndexedInstancedHelper
{
public:
DrawIndexedInstancedHelper(const GLCapabilities& capabilities,
GLint spirvCrossBaseInstanceLocation,
GLenum primitiveTopology,
uint32_t instanceCount,
uint32_t baseInstance) :
m_hasBaseInstanceInShader(
capabilities.ANGLE_base_vertex_base_instance_shader_builtin),
m_spirvCrossBaseInstanceLocation(spirvCrossBaseInstanceLocation),
m_primitiveTopology(primitiveTopology),
m_instanceCount(instanceCount),
m_baseInstance(baseInstance)
{
assert(m_hasBaseInstanceInShader ==
(m_spirvCrossBaseInstanceLocation < 0));
}
void setIndexRange(uint32_t indexCount, uint32_t baseIndex)
{
m_indexCount = indexCount;
m_indexOffset =
reinterpret_cast<const void*>(baseIndex * sizeof(uint16_t));
}
void draw() const
{
#ifndef RIVE_WEBGL
if (m_hasBaseInstanceInShader)
{
glDrawElementsInstancedBaseInstanceEXT(m_primitiveTopology,
m_indexCount,
GL_UNSIGNED_SHORT,
m_indexOffset,
m_instanceCount,
m_baseInstance);
}
else
#endif
{
glUniform1i(m_spirvCrossBaseInstanceLocation, m_baseInstance);
glDrawElementsInstanced(m_primitiveTopology,
m_indexCount,
GL_UNSIGNED_SHORT,
m_indexOffset,
m_instanceCount);
}
}
void drawWithStencilSettings(GLenum comparator,
GLint ref,
GLuint mask,
GLenum stencilFailOp,
GLenum depthPassStencilFailOp,
GLenum depthPassStencilPassOp) const
{
glStencilFunc(comparator, ref, mask);
glStencilOp(stencilFailOp,
depthPassStencilFailOp,
depthPassStencilPassOp);
draw();
}
private:
const bool m_hasBaseInstanceInShader;
const GLint m_spirvCrossBaseInstanceLocation;
const GLenum m_primitiveTopology;
const uint32_t m_instanceCount;
const uint32_t m_baseInstance;
uint32_t m_indexCount = 0;
const void* m_indexOffset = nullptr;
};
void RenderContextGLImpl::flush(const FlushDescriptor& desc)
{
assert(desc.interlockMode != gpu::InterlockMode::clockwiseAtomic);
auto renderTarget = static_cast<RenderTargetGL*>(desc.renderTarget);
m_state->setWriteMasks(true, true, 0xff);
m_state->disableBlending();
// All programs use the same set of per-flush uniforms.
glBindBufferRange(GL_UNIFORM_BUFFER,
FLUSH_UNIFORM_BUFFER_IDX,
gl_buffer_id(flushUniformBufferRing()),
desc.flushUniformDataOffsetInBytes,
sizeof(gpu::FlushUniforms));
// All programs use the same storage buffers.
if (desc.pathCount > 0)
{
bind_storage_buffer(m_capabilities,
pathBufferRing(),
PATH_BUFFER_IDX,
desc.pathCount * sizeof(gpu::PathData),
desc.firstPath * sizeof(gpu::PathData));
bind_storage_buffer(m_capabilities,
paintBufferRing(),
PAINT_BUFFER_IDX,
desc.pathCount * sizeof(gpu::PaintData),
desc.firstPaint * sizeof(gpu::PaintData));
bind_storage_buffer(m_capabilities,
paintAuxBufferRing(),
PAINT_AUX_BUFFER_IDX,
desc.pathCount * sizeof(gpu::PaintAuxData),
desc.firstPaintAux * sizeof(gpu::PaintAuxData));
}
if (desc.contourCount > 0)
{
bind_storage_buffer(m_capabilities,
contourBufferRing(),
CONTOUR_BUFFER_IDX,
desc.contourCount * sizeof(gpu::ContourData),
desc.firstContour * sizeof(gpu::ContourData));
}
// Render the complex color ramps into the gradient texture.
if (desc.gradSpanCount > 0)
{
if (m_capabilities.isPowerVR)
{
// PowerVR needs an extra little update to the gradient texture to
// help with synchronization.
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + GRAD_TEXTURE_IDX);
uint32_t nullData = 0;
glTexSubImage2D(GL_TEXTURE_2D,
0,
0,
0,
1,
1,
GL_RGBA,
GL_UNSIGNED_BYTE,
&nullData);
}
m_state->bindBuffer(GL_ARRAY_BUFFER,
gl_buffer_id(gradSpanBufferRing()));
m_state->bindVAO(m_colorRampVAO);
m_state->setCullFace(GL_BACK);
glVertexAttribIPointer(
0,
4,
GL_UNSIGNED_INT,
0,
reinterpret_cast<const void*>(desc.firstGradSpan *
sizeof(gpu::GradientSpan)));
glViewport(0, 0, kGradTextureWidth, desc.gradDataHeight);
glBindFramebuffer(GL_FRAMEBUFFER, m_colorRampFBO);
m_state->bindProgram(m_colorRampProgram);
GLenum colorAttachment0 = GL_COLOR_ATTACHMENT0;
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &colorAttachment0);
glDrawArraysInstanced(GL_TRIANGLE_STRIP,
0,
gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT,
desc.gradSpanCount);
}
// Tessellate all curves into vertices in the tessellation texture.
if (desc.tessVertexSpanCount > 0)
{
m_state->bindBuffer(GL_ARRAY_BUFFER,
gl_buffer_id(tessSpanBufferRing()));
m_state->bindVAO(m_tessellateVAO);
m_state->setCullFace(GL_BACK);
size_t tessSpanOffsetInBytes =
desc.firstTessVertexSpan * sizeof(gpu::TessVertexSpan);
for (GLuint i = 0; i < 3; ++i)
{
glVertexAttribPointer(i,
4,
GL_FLOAT,
GL_FALSE,
sizeof(TessVertexSpan),
reinterpret_cast<const void*>(
tessSpanOffsetInBytes + i * 4 * 4));
}
glVertexAttribIPointer(
3,
4,
GL_UNSIGNED_INT,
sizeof(TessVertexSpan),
reinterpret_cast<const void*>(tessSpanOffsetInBytes +
offsetof(TessVertexSpan, x0x1)));
glViewport(0, 0, gpu::kTessTextureWidth, desc.tessDataHeight);
glBindFramebuffer(GL_FRAMEBUFFER, m_tessellateFBO);
m_state->bindProgram(m_tessellateProgram);
GLenum colorAttachment0 = GL_COLOR_ATTACHMENT0;
glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &colorAttachment0);
glDrawElementsInstanced(GL_TRIANGLES,
std::size(gpu::kTessSpanIndices),
GL_UNSIGNED_SHORT,
0,
desc.tessVertexSpanCount);
}
// Compile the draw programs before activating pixel local storage.
// Cache specific compilations by DrawType and ShaderFeatures.
// (ANGLE_shader_pixel_local_storage doesn't allow shader compilation while
// active.)
for (const DrawBatch& batch : *desc.drawList)
{
auto shaderFeatures = desc.interlockMode == gpu::InterlockMode::atomics
? desc.combinedShaderFeatures
: batch.shaderFeatures;
auto fragmentShaderMiscFlags =
m_plsImpl != nullptr
? m_plsImpl->shaderMiscFlags(desc, batch.drawType)
: gpu::ShaderMiscFlags::none;
if (desc.interlockMode == gpu::InterlockMode::rasterOrdering &&
(batch.drawContents & gpu::DrawContents::clockwiseFill))
{
fragmentShaderMiscFlags |= gpu::ShaderMiscFlags::clockwiseFill;
}
uint32_t fragmentShaderKey =
gpu::ShaderUniqueKey(batch.drawType,
shaderFeatures,
desc.interlockMode,
fragmentShaderMiscFlags);
m_drawPrograms.try_emplace(fragmentShaderKey,
this,
batch.drawType,
shaderFeatures,
desc.interlockMode,
fragmentShaderMiscFlags);
}
// Bind the currently-submitted buffer in the triangleBufferRing to its
// vertex array.
if (desc.hasTriangleVertices)
{
m_state->bindVAO(m_trianglesVAO);
m_state->bindBuffer(GL_ARRAY_BUFFER,
gl_buffer_id(triangleBufferRing()));
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
}
glViewport(0, 0, renderTarget->width(), renderTarget->height());
#ifdef RIVE_DESKTOP_GL
if (m_capabilities.ANGLE_polygon_mode && desc.wireframe)
{
glPolygonModeANGLE(GL_FRONT_AND_BACK, GL_LINE_ANGLE);
glLineWidth(2);
}
#endif
auto msaaResolveAction = RenderTargetGL::MSAAResolveAction::automatic;
std::array<GLenum, 3> msaaDepthStencilColor;
if (desc.interlockMode != gpu::InterlockMode::msaa)
{
assert(desc.msaaSampleCount == 0);
m_plsImpl->activatePixelLocalStorage(this, desc);
}
else
{
assert(desc.msaaSampleCount > 0);
bool preserveRenderTarget =
desc.colorLoadAction == gpu::LoadAction::preserveRenderTarget;
bool isFBO0;
msaaResolveAction = renderTarget->bindMSAAFramebuffer(
this,
desc.msaaSampleCount,
preserveRenderTarget ? &desc.renderTargetUpdateBounds : nullptr,
&isFBO0);
// Hint to tilers to not load unnecessary buffers from memory.
if (isFBO0)
{
msaaDepthStencilColor = {GL_DEPTH, GL_STENCIL, GL_COLOR};
}
else
{
msaaDepthStencilColor = {GL_DEPTH_ATTACHMENT,
GL_STENCIL_ATTACHMENT,
GL_COLOR_ATTACHMENT0};
}
glInvalidateFramebuffer(GL_FRAMEBUFFER,
preserveRenderTarget ? 2 : 3,
msaaDepthStencilColor.data());
GLbitfield buffersToClear = GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
if (desc.colorLoadAction == gpu::LoadAction::clear)
{
float cc[4];
UnpackColorToRGBA32FPremul(desc.clearColor, cc);
glClearColor(cc[0], cc[1], cc[2], cc[3]);
buffersToClear |= GL_COLOR_BUFFER_BIT;
}
glClear(buffersToClear);
glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
if (desc.combinedShaderFeatures &
gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND)
{
if (m_capabilities.KHR_blend_equation_advanced_coherent)
{
glEnable(GL_BLEND_ADVANCED_COHERENT_KHR);
}
else
{
// Set up an internal texture to copy the framebuffer into, for
// in-shader blending.
renderTarget->bindInternalDstTexture(
GL_TEXTURE0 + kPLSTexIdxOffset + DST_COLOR_TEXTURE_IDX);
}
}
}
bool clipPlanesEnabled = false;
// Execute the DrawList.
for (const DrawBatch& batch : *desc.drawList)
{
if (batch.elementCount == 0)
{
continue;
}
auto shaderFeatures = desc.interlockMode == gpu::InterlockMode::atomics
? desc.combinedShaderFeatures
: batch.shaderFeatures;
auto fragmentShaderMiscFlags =
m_plsImpl != nullptr
? m_plsImpl->shaderMiscFlags(desc, batch.drawType)
: gpu::ShaderMiscFlags::none;
if (desc.interlockMode == gpu::InterlockMode::rasterOrdering &&
(batch.drawContents & gpu::DrawContents::clockwiseFill))
{
fragmentShaderMiscFlags |= gpu::ShaderMiscFlags::clockwiseFill;
}
uint32_t fragmentShaderKey =
gpu::ShaderUniqueKey(batch.drawType,
shaderFeatures,
desc.interlockMode,
fragmentShaderMiscFlags);
const DrawProgram& drawProgram =
m_drawPrograms.find(fragmentShaderKey)->second;
if (drawProgram.id() == 0)
{
fprintf(stderr,
"WARNING: skipping draw due to missing GL program.\n");
continue;
}
m_state->bindProgram(drawProgram.id());
if (auto imageTextureGL =
static_cast<const TextureGLImpl*>(batch.imageTexture))
{
glActiveTexture(GL_TEXTURE0 + kPLSTexIdxOffset + IMAGE_TEXTURE_IDX);
glBindTexture(GL_TEXTURE_2D, imageTextureGL->textureID());
}
if (desc.interlockMode == gpu::InterlockMode::msaa)
{
// Set up the next blend.
if (batch.drawContents & gpu::DrawContents::opaquePaint)
{
m_state->disableBlending();
}
else if (!(batch.drawContents & gpu::DrawContents::advancedBlend))
{
assert(batch.firstBlendMode == BlendMode::srcOver);
m_state->setBlendEquation(BlendMode::srcOver);
}
else if (m_capabilities.KHR_blend_equation_advanced_coherent)
{
// When m_platformFeatures.supportsKHRBlendEquations is true in
// msaa mode, the renderContext does not combine draws when they
// have different blend modes.
m_state->setBlendEquation(batch.firstBlendMode);
}
else
{
if (batch.dstReadList != nullptr)
{
// Read back the framebuffer where we need a dstColor for
// blending.
renderTarget->bindInternalFramebuffer(
GL_DRAW_FRAMEBUFFER,
RenderTargetGL::DrawBufferMask::color);
for (const Draw* draw = batch.dstReadList; draw != nullptr;
draw = draw->nextDstRead())
{
assert(draw->blendMode() != BlendMode::srcOver);
glutils::BlitFramebuffer(draw->pixelBounds(),
renderTarget->height());
}
renderTarget->bindMSAAFramebuffer(this,
desc.msaaSampleCount);
}
m_state->disableBlending(); // Blend in the shader instead.
}
// Set up the next clipRect.
bool needsClipPlanes =
(shaderFeatures & gpu::ShaderFeatures::ENABLE_CLIP_RECT);
if (needsClipPlanes != clipPlanesEnabled)
{
auto toggleEnableOrDisable =
needsClipPlanes ? glEnable : glDisable;
toggleEnableOrDisable(GL_CLIP_DISTANCE0_EXT);
toggleEnableOrDisable(GL_CLIP_DISTANCE1_EXT);
toggleEnableOrDisable(GL_CLIP_DISTANCE2_EXT);
toggleEnableOrDisable(GL_CLIP_DISTANCE3_EXT);
clipPlanesEnabled = needsClipPlanes;
}
}
switch (gpu::DrawType drawType = batch.drawType)
{
case DrawType::midpointFanPatches:
case DrawType::midpointFanCenterAAPatches:
case DrawType::outerCurvePatches:
{
// Draw PLS patches that connect the tessellation vertices.
m_state->bindVAO(m_drawVAO);
DrawIndexedInstancedHelper drawHelper(
m_capabilities,
drawProgram.spirvCrossBaseInstanceLocation(),
GL_TRIANGLES,
batch.elementCount,
batch.baseElement);
if (desc.interlockMode != gpu::InterlockMode::msaa)
{
m_plsImpl->ensureRasterOrderingEnabled(
this,
desc,
desc.interlockMode ==
gpu::InterlockMode::rasterOrdering);
drawHelper.setIndexRange(gpu::PatchIndexCount(drawType),
gpu::PatchBaseIndex(drawType));
m_state->setCullFace(GL_BACK);
drawHelper.draw();
break;
}
// MSAA path draws require different stencil settings, depending
// on their drawContents.
bool hasActiveClip =
((batch.drawContents & gpu::DrawContents::activeClip));
bool isClipUpdate =
((batch.drawContents & gpu::DrawContents::clipUpdate));
bool isNestedClipUpdate =
(batch.drawContents & gpu::kNestedClipUpdateMask) ==
gpu::kNestedClipUpdateMask;
bool isEvenOddFill =
(batch.drawContents & gpu::DrawContents::evenOddFill);
bool isStroke =
(batch.drawContents & gpu::DrawContents::stroke);
if (isStroke)
{
// MSAA strokes only use the "border" section of the patch.
// (The depth test prevents double hits.)
assert(drawType == gpu::DrawType::midpointFanPatches);
drawHelper.setIndexRange(
gpu::kMidpointFanPatchBorderIndexCount,
gpu::kMidpointFanPatchBaseIndex);
m_state->setWriteMasks(true, true, 0xff);
m_state->setCullFace(GL_BACK);
drawHelper.drawWithStencilSettings(
hasActiveClip ? GL_EQUAL : GL_ALWAYS,
0x80,
0xff,
GL_KEEP,
GL_KEEP,
GL_KEEP);
break;
}
// MSAA fills only use the "fan" section of the patch (they
// don't need AA borders).
drawHelper.setIndexRange(gpu::PatchFanIndexCount(drawType),
gpu::PatchFanBaseIndex(drawType));
// "nonZero" fill rules (that aren't nested clip updates) can be
// optimized to render directly instead of using a "stencil then
// cover" approach.
if (!isEvenOddFill && !isNestedClipUpdate)
{
// Count backward triangle hits (negative coverage) in the
// stencil buffer.
m_state->setWriteMasks(false, false, 0x7f);
m_state->setCullFace(GL_FRONT);
drawHelper.drawWithStencilSettings(
hasActiveClip ? GL_LEQUAL : GL_ALWAYS,
0x80,
0xff,
GL_KEEP,
GL_KEEP,
GL_INCR_WRAP);
// (Configure both stencil faces before issuing the next
// draws, so GL can give them the same internal pipeline
// under the hood.)
GLuint stencilReadMask = hasActiveClip ? 0xff : 0x7f;
glStencilFuncSeparate(GL_FRONT,
GL_EQUAL,
0x80,
stencilReadMask);
glStencilOpSeparate(
GL_FRONT,
GL_DECR, // Don't wrap; 0 must stay 0 outside the clip.
GL_KEEP,
isClipUpdate ? GL_REPLACE : GL_KEEP);
glStencilFuncSeparate(GL_BACK,
GL_LESS,
0x80,
stencilReadMask);
glStencilOpSeparate(GL_BACK,
GL_KEEP,
GL_KEEP,
isClipUpdate ? GL_REPLACE : GL_ZERO);
m_state->setWriteMasks(!isClipUpdate,
!isClipUpdate,
isClipUpdate ? 0xff : 0x7f);
// Draw forward triangles, clipped by the backward triangle
// counts. (The depth test prevents double hits.)
m_state->setCullFace(GL_BACK);
drawHelper.draw();
// Clean up backward triangles in the stencil buffer, (also
// filling negative winding numbers for nonZero fill).
m_state->setCullFace(GL_FRONT);
if (batch.drawContents & gpu::DrawContents::clockwiseFill)
{
// For clockwise fill, disable the color mask when
// cleaning up backward triangles. This mode only fills
// in forward triangles.
m_state->setWriteMasks(false,
false,
isClipUpdate ? 0xff : 0x7f);
}
drawHelper.draw();
break;
}
// Fall back on stencil-then-cover.
glStencilFunc(hasActiveClip ? GL_LEQUAL : GL_ALWAYS,
0x80,
0xff);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP);
m_state->setWriteMasks(false,
false,
isEvenOddFill ? 0x1 : 0x7f);
m_state->setCullFace(GL_NONE);
drawHelper.draw(); // Stencil the path!
// Nested clip updates do the "cover" operation during
// DrawType::stencilClipReset.
if (!isNestedClipUpdate)
{
assert(isEvenOddFill);
m_state->setWriteMasks(!isClipUpdate,
!isClipUpdate,
isClipUpdate ? 0xff : 0x1);
drawHelper.drawWithStencilSettings(
GL_NOTEQUAL, // Cover the path!
0x80,
0x7f,
GL_KEEP,
GL_KEEP,
isClipUpdate ? GL_REPLACE : GL_ZERO);
}
break;
}
case gpu::DrawType::stencilClipReset:
{
assert(desc.interlockMode == gpu::InterlockMode::msaa);
m_state->bindVAO(m_trianglesVAO);
bool isNestedClipUpdate =
(batch.drawContents & gpu::kNestedClipUpdateMask) ==
gpu::kNestedClipUpdateMask;
if (isNestedClipUpdate)
{
// The nested clip just got stencilled and left in the
// stencil buffer. Intersect it with the existing clip.
// (Erasing regions of the existing clip that are outside
// the nested clip.)
glStencilFunc(GL_LESS, 0x80, 0xff);
glStencilOp(GL_ZERO, GL_KEEP, GL_REPLACE);
}
else
{
// Clear the entire previous clip.
glStencilFunc(GL_NOTEQUAL, 0, 0xff);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
}
m_state->setWriteMasks(false, false, 0xff);
m_state->setCullFace(GL_BACK);
glDrawArrays(GL_TRIANGLES,
batch.baseElement,
batch.elementCount);
break;
}
case gpu::DrawType::interiorTriangulation:
{
assert(desc.interlockMode != gpu::InterlockMode::msaa); // TODO!
m_plsImpl->ensureRasterOrderingEnabled(this, desc, false);
m_state->bindVAO(m_trianglesVAO);
m_state->setCullFace(GL_BACK);
glDrawArrays(GL_TRIANGLES,
batch.baseElement,
batch.elementCount);
if (desc.interlockMode == gpu::InterlockMode::rasterOrdering)
{
// We turned off raster ordering even though we're in
// "rasterOrdering" mode because it improves performance and
// we know the interior triangles don't overlap. But now we
// have to insert a barrier before we draw anything else.
m_plsImpl->barrier(desc);
}
break;
}
case gpu::DrawType::imageRect:
{
assert(desc.interlockMode == gpu::InterlockMode::atomics);
// m_imageRectVAO should have gotten lazily allocated by now.
assert(m_imageRectVAO != 0);
m_plsImpl->ensureRasterOrderingEnabled(this, desc, false);
m_state->bindVAO(m_imageRectVAO);
glBindBufferRange(GL_UNIFORM_BUFFER,
IMAGE_DRAW_UNIFORM_BUFFER_IDX,
gl_buffer_id(imageDrawUniformBufferRing()),
batch.imageDrawDataOffset,
sizeof(gpu::ImageDrawUniforms));
m_state->setCullFace(GL_NONE);
glDrawElements(GL_TRIANGLES,
std::size(gpu::kImageRectIndices),
GL_UNSIGNED_SHORT,
nullptr);
break;
}
case gpu::DrawType::imageMesh:
{
LITE_RTTI_CAST_OR_BREAK(vertexBuffer,
RenderBufferGLImpl*,
batch.vertexBuffer);
LITE_RTTI_CAST_OR_BREAK(uvBuffer,
RenderBufferGLImpl*,
batch.uvBuffer);
LITE_RTTI_CAST_OR_BREAK(indexBuffer,
RenderBufferGLImpl*,
batch.indexBuffer);
m_state->bindVAO(m_imageMeshVAO);
m_state->bindBuffer(GL_ARRAY_BUFFER,
vertexBuffer->frontBufferID());
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
m_state->bindBuffer(GL_ARRAY_BUFFER, uvBuffer->frontBufferID());
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
m_state->bindBuffer(GL_ELEMENT_ARRAY_BUFFER,
indexBuffer->frontBufferID());
glBindBufferRange(GL_UNIFORM_BUFFER,
IMAGE_DRAW_UNIFORM_BUFFER_IDX,
gl_buffer_id(imageDrawUniformBufferRing()),
batch.imageDrawDataOffset,
sizeof(gpu::ImageDrawUniforms));
if (desc.interlockMode != gpu::InterlockMode::msaa)
{
// Try to enable raster ordering for image meshes in
// rasterOrdering and atomic mode both; we have no control
// over whether the internal geometry has self overlaps.
m_plsImpl->ensureRasterOrderingEnabled(
this,
desc,
m_platformFeatures.supportsRasterOrdering);
}
else
{
bool hasActiveClip =
((batch.drawContents & gpu::DrawContents::activeClip));
glStencilFunc(hasActiveClip ? GL_EQUAL : GL_ALWAYS,
0x80,
0xff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
m_state->setWriteMasks(true, true, 0xff);
}
m_state->setCullFace(GL_NONE);
glDrawElements(GL_TRIANGLES,
batch.elementCount,
GL_UNSIGNED_SHORT,
reinterpret_cast<const void*>(batch.baseElement *
sizeof(uint16_t)));
break;
}
case gpu::DrawType::atomicResolve:
{
assert(desc.interlockMode == gpu::InterlockMode::atomics);
m_plsImpl->ensureRasterOrderingEnabled(this, desc, false);
m_state->bindVAO(m_emptyVAO);
m_plsImpl->setupAtomicResolve(this, desc);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
break;
}
case gpu::DrawType::atomicInitialize:
{
assert(desc.interlockMode == gpu::InterlockMode::atomics);
RIVE_UNREACHABLE();
}
}
if (desc.interlockMode != gpu::InterlockMode::msaa &&
batch.needsBarrier)
{
assert(desc.interlockMode == gpu::InterlockMode::atomics);
m_plsImpl->barrier(desc);
}
}
if (desc.interlockMode != gpu::InterlockMode::msaa)
{
m_plsImpl->deactivatePixelLocalStorage(this, desc);
}
else
{
// Depth/stencil can be discarded.
glInvalidateFramebuffer(GL_FRAMEBUFFER,
2,
msaaDepthStencilColor.data());
if (msaaResolveAction ==
RenderTargetGL::MSAAResolveAction::framebufferBlit)
{
renderTarget->bindDestinationFramebuffer(GL_DRAW_FRAMEBUFFER);
glutils::BlitFramebuffer(desc.renderTargetUpdateBounds,
renderTarget->height(),
GL_COLOR_BUFFER_BIT);
// Now that color is resolved elsewhere we can discard the MSAA
// color buffer as well.
glInvalidateFramebuffer(GL_READ_FRAMEBUFFER,
1,
msaaDepthStencilColor.data() + 2);
}
if ((desc.combinedShaderFeatures &
gpu::ShaderFeatures::ENABLE_ADVANCED_BLEND) &&
m_capabilities.KHR_blend_equation_advanced_coherent)
{
glDisable(GL_BLEND_ADVANCED_COHERENT_KHR);
}
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
if (clipPlanesEnabled)
{
glDisable(GL_CLIP_DISTANCE0_EXT);
glDisable(GL_CLIP_DISTANCE1_EXT);
glDisable(GL_CLIP_DISTANCE2_EXT);
glDisable(GL_CLIP_DISTANCE3_EXT);
}
}
#ifdef RIVE_DESKTOP_GL
if (m_capabilities.ANGLE_polygon_mode && desc.wireframe)
{
glPolygonModeANGLE(GL_FRONT_AND_BACK, GL_FILL_ANGLE);
}
#endif
if (m_capabilities.isAdreno)
{
// Qualcomm experiences synchronization issues with multiple flushes per
// frame if we don't call glFlush in between.
glFlush();
}
}
void RenderContextGLImpl::blitTextureToFramebufferAsDraw(
GLuint textureID,
const IAABB& bounds,
uint32_t renderTargetHeight)
{
if (m_blitAsDrawProgram == 0)
{
const char* blitSources[] = {glsl::constants,
glsl::common,
glsl::blit_texture_as_draw};
m_blitAsDrawProgram = glutils::Program();
m_blitAsDrawProgram.compileAndAttachShader(GL_VERTEX_SHADER,
nullptr,
0,
blitSources,
std::size(blitSources),
m_capabilities);
m_blitAsDrawProgram.compileAndAttachShader(GL_FRAGMENT_SHADER,
nullptr,
0,
blitSources,
std::size(blitSources),
m_capabilities);
m_blitAsDrawProgram.link();
m_state->bindProgram(m_blitAsDrawProgram);
glutils::Uniform1iByName(m_blitAsDrawProgram,
GLSL_blitTextureSource,
0);
}
m_state->bindProgram(m_blitAsDrawProgram);
m_state->bindVAO(m_emptyVAO);
m_state->setWriteMasks(true, true, 0xff);
m_state->disableBlending();
m_state->setCullFace(GL_NONE);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID);
glEnable(GL_SCISSOR_TEST);
glScissor(bounds.left,
renderTargetHeight - bounds.bottom,
bounds.width(),
bounds.height());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_SCISSOR_TEST);
}
std::unique_ptr<RenderContext> RenderContextGLImpl::MakeContext(
const ContextOptions& contextOptions)
{
GLCapabilities capabilities{};
const char* glVersionStr = (const char*)glGetString(GL_VERSION);
#ifdef RIVE_WEBGL
capabilities.isGLES = true;
#else
capabilities.isGLES = strstr(glVersionStr, "OpenGL ES") != NULL;
#endif
if (capabilities.isGLES)
{
#ifdef RIVE_ANDROID
capabilities.isAndroidANGLE = strstr(glVersionStr, "ANGLE") != NULL;
#endif
#ifdef _MSC_VER
sscanf_s(
#else
sscanf(
#endif
glVersionStr,
"OpenGL ES %d.%d",
&capabilities.contextVersionMajor,
&capabilities.contextVersionMinor);
}
else
{
#ifdef _MSC_VER
sscanf_s(
#else
sscanf(
#endif
glVersionStr,
"%d.%d",
&capabilities.contextVersionMajor,
&capabilities.contextVersionMinor);
}
#ifdef RIVE_DESKTOP_GL
assert(capabilities.contextVersionMajor == GLAD_GL_version_major);
assert(capabilities.contextVersionMinor == GLAD_GL_version_minor);
assert(capabilities.isGLES == static_cast<bool>(GLAD_GL_version_es));
#endif
if (capabilities.isGLES)
{
if (!capabilities.isContextVersionAtLeast(3, 0))
{
fprintf(stderr,
"OpenGL ES %i.%i not supported. Minimum supported version "
"is 3.0.\n",
capabilities.contextVersionMajor,
capabilities.contextVersionMinor);
return nullptr;
}
}
else
{
if (!capabilities.isContextVersionAtLeast(4, 2))
{
fprintf(stderr,
"OpenGL %i.%i not supported. Minimum supported version is "
"4.2.\n",
capabilities.contextVersionMajor,
capabilities.contextVersionMinor);
return nullptr;
}
}
// Our baseline feature set is GLES 3.0. Capabilities from newer context
// versions are reported as extensions.
if (capabilities.isGLES)
{
if (capabilities.isContextVersionAtLeast(3, 1))
{
// Don't use storage buffers if we're on Android ANGLE. ANGLE
// doesn't have strong support for 3.1+ functionality, and storage
// buffers get rendering artifacts on Galaxy S22
// (OpenGL Samsung Electronics Co., Ltd.;
// ANGLE (Samsung Xclipse 920) on Vulkan 1.1.179;
// OpenGL ES 3.2 ANGLE git hash: c7c78c41d520).
if (!capabilities.isAndroidANGLE)
{
capabilities.ARB_shader_storage_buffer_object = true;
}
}
}
else
{
if (capabilities.isContextVersionAtLeast(4, 2))
{
capabilities.ARB_shader_image_load_store = true;
}
if (capabilities.isContextVersionAtLeast(4, 3))
{
capabilities.ARB_shader_storage_buffer_object = true;
}
capabilities.EXT_clip_cull_distance = true;
}
#ifndef RIVE_WEBGL
GLint extensionCount;
glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount);
for (int i = 0; i < extensionCount; ++i)
{
auto* ext =
reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
if (strcmp(ext, "GL_ANGLE_base_vertex_base_instance_shader_builtin") ==
0)
{
capabilities.ANGLE_base_vertex_base_instance_shader_builtin = true;
}
else if (strcmp(ext, "GL_ANGLE_shader_pixel_local_storage") == 0)
{
capabilities.ANGLE_shader_pixel_local_storage = true;
}
else if (strcmp(ext, "GL_ANGLE_shader_pixel_local_storage_coherent") ==
0)
{
capabilities.ANGLE_shader_pixel_local_storage_coherent = true;
}
else if (strcmp(ext, "GL_ANGLE_provoking_vertex") == 0)
{
capabilities.ANGLE_provoking_vertex = true;
}
else if (strcmp(ext, "GL_ANGLE_polygon_mode") == 0)
{
capabilities.ANGLE_polygon_mode = true;
}
else if (strcmp(ext, "GL_ARM_shader_framebuffer_fetch") == 0)
{
capabilities.ARM_shader_framebuffer_fetch = true;
}
else if (strcmp(ext, "GL_ARB_fragment_shader_interlock") == 0)
{
capabilities.ARB_fragment_shader_interlock = true;
}
else if (strcmp(ext, "GL_ARB_shader_image_load_store") == 0)
{
capabilities.ARB_shader_image_load_store = true;
}
else if (strcmp(ext, "GL_ARB_shader_storage_buffer_object") == 0)
{
capabilities.ARB_shader_storage_buffer_object = true;
}
else if (strcmp(ext, "GL_KHR_blend_equation_advanced") == 0)
{
capabilities.KHR_blend_equation_advanced = true;
}
else if (strcmp(ext, "GL_KHR_blend_equation_advanced_coherent") == 0)
{
capabilities.KHR_blend_equation_advanced_coherent = true;
}
else if (strcmp(ext, "GL_EXT_base_instance") == 0)
{
capabilities.EXT_base_instance = true;
}
// Don't use EXT_clip_cull_distance if we're on ANGLE. Galaxy S22
// (OpenGL Samsung Electronics Co., Ltd.;
// ANGLE (Samsung Xclipse 920) on Vulkan 1.1.179;
// OpenGL ES 3.2 ANGLE git hash: c7c78c41d520) advertises support for
// this extension but then doesn't support gl_ClipDistance in the
// shader. Only use clip planes on ANGLE if ANGLE_clip_cull_distance is
// supported.
else if (!capabilities.isAndroidANGLE &&
strcmp(ext, "GL_EXT_clip_cull_distance") == 0)
{
capabilities.EXT_clip_cull_distance = true;
}
else if (strcmp(ext, "GL_EXT_multisampled_render_to_texture") == 0)
{
capabilities.EXT_multisampled_render_to_texture = true;
}
else if (strcmp(ext, "GL_ANGLE_clip_cull_distance") == 0)
{
capabilities.EXT_clip_cull_distance = true;
}
else if (strcmp(ext, "GL_INTEL_fragment_shader_ordering") == 0)
{
capabilities.INTEL_fragment_shader_ordering = true;
}
else if (strcmp(ext, "GL_EXT_shader_framebuffer_fetch") == 0)
{
capabilities.EXT_shader_framebuffer_fetch = true;
}
else if (strcmp(ext, "GL_EXT_shader_pixel_local_storage") == 0)
{
capabilities.EXT_shader_pixel_local_storage = true;
}
else if (strcmp(ext, "GL_QCOM_shader_framebuffer_fetch_noncoherent") ==
0)
{
capabilities.QCOM_shader_framebuffer_fetch_noncoherent = true;
}
}
#else // !RIVE_WEBGL -> RIVE_WEBGL
if (webgl_enable_WEBGL_shader_pixel_local_storage_coherent())
{
capabilities.ANGLE_shader_pixel_local_storage = true;
capabilities.ANGLE_shader_pixel_local_storage_coherent = true;
}
if (webgl_enable_WEBGL_provoking_vertex())
{
capabilities.ANGLE_provoking_vertex = true;
}
if (emscripten_webgl_enable_extension(
emscripten_webgl_get_current_context(),
"WEBGL_clip_cull_distance"))
{
capabilities.EXT_clip_cull_distance = true;
}
#endif // RIVE_WEBGL
#ifdef RIVE_DESKTOP_GL
if (GLAD_GL_ANGLE_base_vertex_base_instance_shader_builtin)
{
capabilities.ANGLE_base_vertex_base_instance_shader_builtin = true;
}
if (GLAD_GL_ANGLE_polygon_mode)
{
capabilities.ANGLE_polygon_mode = true;
}
if (GLAD_GL_EXT_base_instance)
{
capabilities.EXT_base_instance = true;
}
#endif
// We need four storage buffers in the vertex shader. Disable the extension
// if this isn't supported.
if (capabilities.ARB_shader_storage_buffer_object)
{
int maxVertexShaderStorageBlocks;
glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS,
&maxVertexShaderStorageBlocks);
if (maxVertexShaderStorageBlocks < gpu::kMaxStorageBuffers)
{
capabilities.ARB_shader_storage_buffer_object = false;
}
}
// Now disable the extensions we don't want to use internally.
if (contextOptions.disableFragmentShaderInterlock)
{
capabilities.ARB_fragment_shader_interlock = false;
capabilities.INTEL_fragment_shader_ordering = false;
}
GLenum rendererToken = GL_RENDERER;
#ifdef RIVE_WEBGL
if (emscripten_webgl_enable_extension(
emscripten_webgl_get_current_context(),
"WEBGL_debug_renderer_info"))
{
rendererToken = GL_UNMASKED_RENDERER_WEBGL;
}
#endif
const char* rendererString =
reinterpret_cast<const char*>(glGetString(rendererToken));
capabilities.isPowerVR = strstr(rendererString, "PowerVR");
capabilities.isAdreno = strstr(rendererString, "Adreno");
if (strstr(rendererString, "Direct3D") != nullptr)
{
// Disable ANGLE_base_vertex_base_instance_shader_builtin on ANGLE/D3D.
// This extension is polyfilled on D3D anyway, and we need to test our
// fallback.
capabilities.ANGLE_base_vertex_base_instance_shader_builtin = false;
// Our use of EXT_multisampled_render_to_texture causes a segfault in
// the Microsoft WARP (software) renderer. Just don't use this extension
// on D3D since it's polyfilled anyway.
capabilities.EXT_multisampled_render_to_texture = false;
}
#ifdef RIVE_ANDROID
LoadGLESExtensions(
capabilities); // Android doesn't load extension functions for us.
#endif
if (!contextOptions.disablePixelLocalStorage)
{
#ifdef RIVE_ANDROID
if (capabilities.EXT_shader_pixel_local_storage &&
(capabilities.ARM_shader_framebuffer_fetch ||
capabilities.EXT_shader_framebuffer_fetch))
{
return MakeContext(rendererString,
capabilities,
MakePLSImplEXTNative(capabilities));
}
#else
if (capabilities.ANGLE_shader_pixel_local_storage_coherent)
{
// EXT_shader_framebuffer_fetch is costly on Qualcomm, with or
// without the "noncoherent" extension. Use MSAA on Adreno.
if (!capabilities.isAdreno)
{
return MakeContext(rendererString,
capabilities,
MakePLSImplWebGL());
}
}
#endif
#ifdef RIVE_DESKTOP_GL
if (capabilities.ARB_shader_image_load_store)
{
return MakeContext(rendererString,
capabilities,
MakePLSImplRWTexture());
}
#endif
}
return MakeContext(rendererString, capabilities, nullptr);
}
std::unique_ptr<RenderContext> RenderContextGLImpl::MakeContext(
const char* rendererString,
GLCapabilities capabilities,
std::unique_ptr<PixelLocalStorageImpl> plsImpl)
{
auto renderContextImpl = std::unique_ptr<RenderContextGLImpl>(
new RenderContextGLImpl(rendererString,
capabilities,
std::move(plsImpl)));
return std::make_unique<RenderContext>(std::move(renderContextImpl));
}
} // namespace rive::gpu