| /* |
| * Copyright 2023 Rive |
| */ |
| |
| #include "rive/pls/gl/gl_state.hpp" |
| |
| #include "shaders/constants.glsl" |
| |
| namespace rive::pls |
| { |
| 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); |
| |
| // We always blend with premultiplied src-over when glBlendFunc is relevant. |
| glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
| |
| // ANGLE_shader_pixel_local_storage doesn't allow dither. |
| glDisable(GL_DITHER); |
| |
| #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 |
| |
| // 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); |
| } |
| |
| constexpr static GLenum blend_mode_to_gl_equation(BlendMode blendMode) |
| { |
| switch (blendMode) |
| { |
| case BlendMode::srcOver: |
| return GL_FUNC_ADD; |
| case BlendMode::screen: |
| return GL_SCREEN_KHR; |
| case BlendMode::overlay: |
| return GL_OVERLAY_KHR; |
| case BlendMode::darken: |
| return GL_DARKEN_KHR; |
| case BlendMode::lighten: |
| return GL_LIGHTEN_KHR; |
| case BlendMode::colorDodge: |
| return GL_COLORDODGE_KHR; |
| case BlendMode::colorBurn: |
| return GL_COLORBURN_KHR; |
| case BlendMode::hardLight: |
| return GL_HARDLIGHT_KHR; |
| case BlendMode::softLight: |
| return GL_SOFTLIGHT_KHR; |
| case BlendMode::difference: |
| return GL_DIFFERENCE_KHR; |
| case BlendMode::exclusion: |
| return GL_EXCLUSION_KHR; |
| case BlendMode::multiply: |
| return GL_MULTIPLY_KHR; |
| case BlendMode::hue: |
| return GL_HSL_HUE_KHR; |
| case BlendMode::saturation: |
| return GL_HSL_SATURATION_KHR; |
| case BlendMode::color: |
| return GL_HSL_COLOR_KHR; |
| case BlendMode::luminosity: |
| return GL_HSL_LUMINOSITY_KHR; |
| } |
| RIVE_UNREACHABLE(); |
| } |
| |
| void GLState::setBlendEquation(BlendMode blendMode) |
| { |
| GLenum blendEquation = blend_mode_to_gl_equation(blendMode); |
| if (!m_validState.blendEquation || blendEquation != m_blendEquation) |
| { |
| if (!m_validState.blendEquation || m_blendEquation == GL_NONE) |
| { |
| glEnable(GL_BLEND); |
| } |
| glBlendEquation(blendEquation); |
| m_blendEquation = blendEquation; |
| m_validState.blendEquation = true; |
| } |
| } |
| |
| void GLState::disableBlending() |
| { |
| if (!m_validState.blendEquation || m_blendEquation != GL_NONE) |
| { |
| glDisable(GL_BLEND); |
| m_blendEquation = GL_NONE; |
| m_validState.blendEquation = true; |
| } |
| } |
| |
| void GLState::setWriteMasks(bool colorWriteMask, bool depthWriteMask, GLuint 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::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; |
| } |
| } |
| |
| 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; |
| case GL_PIXEL_UNPACK_BUFFER: |
| if (!m_validState.boundPixelUnpackBufferID || bufferID != m_boundPixelUnpackBufferID) |
| { |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, bufferID); |
| m_boundPixelUnpackBufferID = bufferID; |
| m_validState.boundPixelUnpackBufferID = 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; |
| if (m_validState.boundPixelUnpackBufferID && m_boundPixelUnpackBufferID == bufferID) |
| m_boundPixelUnpackBufferID = 0; |
| } |
| } // namespace rive::pls |