blob: 9899b7f88301f44743d7cde378bd0b6b6496bc26 [file] [log] [blame] [edit]
/*
* Copyright 2023 Rive
*/
#include "rive/renderer/gl/gl_state.hpp"
#include "shaders/constants.glsl"
namespace rive::gpu
{
void GLState::invalidate()
{
// Invalidate all cached state.
memset(&m_validState, 0, sizeof(m_validState));
// PLS only ever culls the CCW face when culling is enabled.
glFrontFace(GL_CW);
glDepthRangef(0, 1);
glDepthFunc(GL_LESS);
glClearDepthf(1);
glClearStencil(0);
// ANGLE_shader_pixel_local_storage doesn't allow dither.
glDisable(GL_DITHER);
// Low-effort attempt to reset core state we don't use to default values.
glDisable(GL_DEPTH_TEST);
glDisable(GL_POLYGON_OFFSET_FILL);
#ifndef RIVE_WEBGL
// https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18
// WebGL 2.0 behaves as though PRIMITIVE_RESTART_FIXED_INDEX were always
// enabled.
glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
#endif
glDisable(GL_RASTERIZER_DISCARD);
glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
glDisable(GL_SAMPLE_COVERAGE);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
// glDisable(GL_COLOR_LOGIC_OP);
// glDisable(GL_INDEX_LOGIC_OP);
// glDisable(GL_ALPHA_TEST);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
#ifndef RIVE_ANDROID
// D3D and Metal both have a provoking vertex convention of "first" for flat
// varyings, and it's very costly for ANGLE to implement the OpenGL
// convention of "last" on these backends. To workaround this, ANGLE
// provides the ANGLE_provoking_vertex extension. When this extension is
// present, we can just set the provoking vertex to "first" and trust that
// it will be fast.
if (m_capabilities.ANGLE_provoking_vertex)
{
glProvokingVertexANGLE(GL_FIRST_VERTEX_CONVENTION_ANGLE);
}
#endif
// WebGL doesn't support glMaxShaderCompilerThreadsKHR.
#ifndef RIVE_WEBGL
if (m_capabilities.KHR_parallel_shader_compile)
{
// Allow GL's shader compilation to use 2 background threads.
//
// Parallel compilation is documented to be enabled by default, but on
// some drivers the parallel compilation does not actually activate
// without explicitly setting this.
//
// NOTE: the spec states that apps may use "0xffffffff" to mean "use the
// maximum number of threads", but that seems like an easy invitation
// for a driver bug, so we are explicit about the number of threads.
//
// FIXME: When AsyncPipelineManager starts using >1 thread, we should
// incorporate its same logic here.
glMaxShaderCompilerThreadsKHR(2);
}
#endif
}
static void gl_enable_disable(GLenum state, bool enabled)
{
if (enabled)
glEnable(state);
else
glDisable(state);
}
void GLState::setDepthStencilEnabled(bool depthEnabled, bool stencilEnabled)
{
if (!m_validState.depthStencilEnabled || m_depthTestEnabled != depthEnabled)
{
gl_enable_disable(GL_DEPTH_TEST, depthEnabled);
m_depthTestEnabled = depthEnabled;
}
if (!m_validState.depthStencilEnabled ||
m_stencilTestEnabled != stencilEnabled)
{
gl_enable_disable(GL_STENCIL_TEST, stencilEnabled);
m_stencilTestEnabled = stencilEnabled;
}
m_validState.depthStencilEnabled = true;
}
void GLState::setCullFace(GLenum cullFace)
{
if (!m_validState.cullFace || cullFace != m_cullFace)
{
if (cullFace == GL_NONE)
{
glDisable(GL_CULL_FACE);
}
else
{
if (!m_validState.cullFace || m_cullFace == GL_NONE)
{
glEnable(GL_CULL_FACE);
}
glCullFace(cullFace);
}
m_cullFace = cullFace;
m_validState.cullFace = true;
}
}
static GLenum gl_stencil_op(StencilOp op)
{
switch (op)
{
case StencilOp::keep:
return GL_KEEP;
case StencilOp::replace:
return GL_REPLACE;
case StencilOp::zero:
return GL_ZERO;
case StencilOp::decrClamp:
return GL_DECR;
case StencilOp::incrWrap:
return GL_INCR_WRAP;
case StencilOp::decrWrap:
return GL_DECR_WRAP;
}
RIVE_UNREACHABLE();
}
static GLenum gl_stencil_func(gpu::StencilCompareOp compareOp)
{
switch (compareOp)
{
case gpu::StencilCompareOp::less:
return GL_LESS;
case gpu::StencilCompareOp::equal:
return GL_EQUAL;
case gpu::StencilCompareOp::lessOrEqual:
return GL_LEQUAL;
case gpu::StencilCompareOp::notEqual:
return GL_NOTEQUAL;
case gpu::StencilCompareOp::always:
return GL_ALWAYS;
}
RIVE_UNREACHABLE();
}
static GLenum gl_cull_face(CullFace riveCullFace)
{
switch (riveCullFace)
{
case CullFace::none:
return GL_NONE;
case CullFace::clockwise:
return GL_FRONT;
case CullFace::counterclockwise:
return GL_BACK;
}
RIVE_UNREACHABLE();
}
void GLState::setBlendEquation(gpu::BlendEquation blendEquation)
{
if (m_validState.blendEquation && blendEquation == m_blendEquation)
{
return;
}
if (!m_validState.blendEquation ||
m_blendEquation == gpu::BlendEquation::none)
{
glEnable(GL_BLEND);
}
switch (blendEquation)
{
case gpu::BlendEquation::none:
glDisable(GL_BLEND);
break;
case gpu::BlendEquation::srcOver:
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
break;
case gpu::BlendEquation::screen:
glBlendEquation(GL_SCREEN_KHR);
break;
case gpu::BlendEquation::overlay:
glBlendEquation(GL_OVERLAY_KHR);
break;
case gpu::BlendEquation::darken:
glBlendEquation(GL_DARKEN_KHR);
break;
case gpu::BlendEquation::lighten:
glBlendEquation(GL_LIGHTEN_KHR);
break;
case gpu::BlendEquation::colorDodge:
glBlendEquation(GL_COLORDODGE_KHR);
break;
case gpu::BlendEquation::colorBurn:
glBlendEquation(GL_COLORBURN_KHR);
break;
case gpu::BlendEquation::hardLight:
glBlendEquation(GL_HARDLIGHT_KHR);
break;
case gpu::BlendEquation::softLight:
glBlendEquation(GL_SOFTLIGHT_KHR);
break;
case gpu::BlendEquation::difference:
glBlendEquation(GL_DIFFERENCE_KHR);
break;
case gpu::BlendEquation::exclusion:
glBlendEquation(GL_EXCLUSION_KHR);
break;
case gpu::BlendEquation::multiply:
glBlendEquation(GL_MULTIPLY_KHR);
break;
case gpu::BlendEquation::hue:
glBlendEquation(GL_HSL_HUE_KHR);
break;
case gpu::BlendEquation::saturation:
glBlendEquation(GL_HSL_SATURATION_KHR);
break;
case gpu::BlendEquation::color:
glBlendEquation(GL_HSL_COLOR_KHR);
break;
case gpu::BlendEquation::luminosity:
glBlendEquation(GL_HSL_LUMINOSITY_KHR);
break;
case gpu::BlendEquation::plus:
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
break;
case gpu::BlendEquation::max:
glBlendEquation(GL_MAX);
glBlendFunc(GL_ONE, GL_ONE);
break;
}
m_blendEquation = blendEquation;
m_validState.blendEquation = true;
}
void GLState::setWriteMasks(bool colorWriteMask,
bool depthWriteMask,
uint8_t stencilWriteMask)
{
if (!m_validState.writeMasks)
{
glColorMask(colorWriteMask,
colorWriteMask,
colorWriteMask,
colorWriteMask);
glDepthMask(depthWriteMask);
glStencilMask(stencilWriteMask);
m_colorWriteMask = colorWriteMask;
m_depthWriteMask = depthWriteMask;
m_stencilWriteMask = stencilWriteMask;
m_validState.writeMasks = true;
}
else
{
if (colorWriteMask != m_colorWriteMask)
{
glColorMask(colorWriteMask,
colorWriteMask,
colorWriteMask,
colorWriteMask);
m_colorWriteMask = colorWriteMask;
}
if (depthWriteMask != m_depthWriteMask)
{
glDepthMask(depthWriteMask);
m_depthWriteMask = depthWriteMask;
}
if (stencilWriteMask != m_stencilWriteMask)
{
glStencilMask(stencilWriteMask);
m_stencilWriteMask = stencilWriteMask;
}
}
}
void GLState::setPipelineState(const gpu::PipelineState& pipelineState)
{
setDepthStencilEnabled(pipelineState.depthTestEnabled,
pipelineState.stencilTestEnabled);
if (pipelineState.stencilTestEnabled)
{
if (!pipelineState.stencilDoubleSided)
{
glStencilFunc(
gl_stencil_func(pipelineState.stencilFrontOps.compareOp),
pipelineState.stencilReference,
pipelineState.stencilCompareMask);
glStencilOp(
gl_stencil_op(pipelineState.stencilFrontOps.failOp),
gl_stencil_op(pipelineState.stencilFrontOps.depthFailOp),
gl_stencil_op(pipelineState.stencilFrontOps.passOp));
}
else
{
glStencilFuncSeparate(
GL_FRONT,
gl_stencil_func(pipelineState.stencilFrontOps.compareOp),
pipelineState.stencilReference,
pipelineState.stencilCompareMask);
glStencilOpSeparate(
GL_FRONT,
gl_stencil_op(pipelineState.stencilFrontOps.failOp),
gl_stencil_op(pipelineState.stencilFrontOps.depthFailOp),
gl_stencil_op(pipelineState.stencilFrontOps.passOp));
glStencilFuncSeparate(
GL_BACK,
gl_stencil_func(pipelineState.stencilBackOps.compareOp),
pipelineState.stencilReference,
pipelineState.stencilCompareMask);
glStencilOpSeparate(
GL_BACK,
gl_stencil_op(pipelineState.stencilBackOps.failOp),
gl_stencil_op(pipelineState.stencilBackOps.depthFailOp),
gl_stencil_op(pipelineState.stencilBackOps.passOp));
}
}
setCullFace(gl_cull_face(pipelineState.cullFace));
setBlendEquation(pipelineState.blendEquation);
setWriteMasks(pipelineState.colorWriteEnabled,
pipelineState.depthWriteEnabled,
pipelineState.stencilWriteMask);
}
void GLState::bindProgram(GLuint programID)
{
if (!m_validState.boundProgramID || programID != m_boundProgramID)
{
glUseProgram(programID);
m_boundProgramID = programID;
m_validState.boundProgramID = true;
}
}
void GLState::bindVAO(GLuint vao)
{
if (!m_validState.boundVAO || vao != m_boundVAO)
{
glBindVertexArray(vao);
m_boundVAO = vao;
m_validState.boundVAO = true;
}
}
void GLState::bindBuffer(GLenum target, GLuint bufferID)
{
switch (target)
{
default:
// Don't track GL_ELEMENT_ARRAY_BUFFER, since it is tied to the VAO
// state.
glBindBuffer(target, bufferID);
return;
case GL_ARRAY_BUFFER:
if (!m_validState.boundArrayBufferID ||
bufferID != m_boundArrayBufferID)
{
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
m_boundArrayBufferID = bufferID;
m_validState.boundArrayBufferID = true;
}
break;
case GL_UNIFORM_BUFFER:
if (!m_validState.boundUniformBufferID ||
bufferID != m_boundUniformBufferID)
{
glBindBuffer(GL_UNIFORM_BUFFER, bufferID);
m_boundUniformBufferID = bufferID;
m_validState.boundUniformBufferID = true;
}
break;
}
}
void GLState::deleteProgram(GLuint programID)
{
glDeleteProgram(programID);
if (m_validState.boundProgramID && m_boundProgramID == programID)
m_boundProgramID = 0;
}
void GLState::deleteVAO(GLuint vao)
{
glDeleteVertexArrays(1, &vao);
if (m_validState.boundVAO && m_boundVAO == vao)
m_boundVAO = 0;
}
void GLState::deleteBuffer(GLuint bufferID)
{
glDeleteBuffers(1, &bufferID);
if (m_validState.boundArrayBufferID && m_boundArrayBufferID == bufferID)
m_boundArrayBufferID = 0;
if (m_validState.boundUniformBufferID && m_boundUniformBufferID == bufferID)
m_boundUniformBufferID = 0;
}
} // namespace rive::gpu