| /* |
| * 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 |