| /* | |
| * Copyright 2022 Rive | |
| */ | |
| #include "rive/renderer/gl/render_context_gl_impl.hpp" | |
| #include "rive/renderer/gl/gl_utils.hpp" | |
| #include "rive/renderer/gl/render_target_gl.hpp" | |
| #include "shaders/constants.glsl" | |
| #include "generated/shaders/glsl.glsl.exports.h" | |
| #ifdef RIVE_WEBGL | |
| #include <emscripten/emscripten.h> | |
| #include <emscripten/html5.h> | |
| EM_JS( | |
| bool, | |
| enable_WEBGL_shader_pixel_local_storage_coherent, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl), | |
| { | |
| gl = GL.getContext(gl).GLctx; | |
| const pls = gl["getExtension"]("WEBGL_shader_pixel_local_storage"); | |
| if (Boolean(pls) && pls["isCoherent"]()) | |
| { | |
| // WEBGL_shader_pixel_local_storage has breaking changes from | |
| // time to time, while it's still a draft extension. A 5th | |
| // argument was added to this function in 2026. Only use the | |
| // extension if we are on the latest spec (with 5 arguments). | |
| if (pls["framebufferTexturePixelLocalStorageWEBGL"].length == 5) | |
| { | |
| gl.pls = pls; | |
| return true; | |
| } | |
| else | |
| { | |
| console.warn( | |
| "WEBGL_shader_pixel_local_storage is advertised, but a deprecated version has been detected. Disabling."); | |
| } | |
| } | |
| return false; | |
| }); | |
| EM_JS(void, | |
| framebufferTexturePixelLocalStorageWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, | |
| GLint plane, | |
| GLuint backingtexture, | |
| GLint level, | |
| GLint layer, | |
| GLenum usage), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| pls["framebufferTexturePixelLocalStorageWEBGL"]( | |
| plane, | |
| GL.textures[backingtexture], | |
| level, | |
| layer, | |
| usage); | |
| } | |
| }); | |
| EM_JS(void, | |
| framebufferPixelLocalClearValuefvWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, | |
| GLint plane, | |
| float r, | |
| float g, | |
| float b, | |
| float a), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| pls["framebufferPixelLocalClearValuefvWEBGL"](plane, | |
| [ r, g, b, a ]); | |
| } | |
| }); | |
| EM_JS(void, | |
| beginPixelLocalStorageWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, uint32_t n, uint32_t loadopsIdx), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| pls["beginPixelLocalStorageWEBGL"]( | |
| new Uint32Array(wasmMemory.buffer, loadopsIdx * 4, n)); | |
| } | |
| }); | |
| EM_JS(void, | |
| endPixelLocalStorageWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, uint32_t n, uint32_t storeopsIdx), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| pls["endPixelLocalStorageWEBGL"]( | |
| new Uint32Array(wasmMemory.buffer, storeopsIdx * 4, n)); | |
| } | |
| }); | |
| EM_JS(int, | |
| getFramebufferPixelLocalStorageParameterivWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, int plane, int pname), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| return pls["getFramebufferPixelLocalStorageParameterWEBGL"]( | |
| plane, | |
| pname); | |
| } | |
| return 0; | |
| }); | |
| EM_JS(bool, | |
| enable_WEBGL_provoking_vertex, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl), | |
| { | |
| gl = GL.getContext(gl).GLctx; | |
| gl.pv = gl["getExtension"]("WEBGL_provoking_vertex"); | |
| return Boolean(gl.pv); | |
| }); | |
| EM_JS(void, | |
| provokingVertexWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, GLenum provokeMode), | |
| { | |
| const pv = GL.getContext(gl).GLctx.pv; | |
| if (pv) | |
| { | |
| pv["provokingVertexWEBGL"](provokeMode); | |
| } | |
| }); | |
| bool webgl_enable_WEBGL_shader_pixel_local_storage_coherent() | |
| { | |
| return enable_WEBGL_shader_pixel_local_storage_coherent( | |
| emscripten_webgl_get_current_context()); | |
| } | |
| void glFramebufferTexturePixelLocalStorageANGLE(GLint plane, | |
| GLuint backingtexture, | |
| GLint level, | |
| GLint layer, | |
| GLenum usage) | |
| { | |
| framebufferTexturePixelLocalStorageWEBGL( | |
| emscripten_webgl_get_current_context(), | |
| plane, | |
| backingtexture, | |
| level, | |
| layer, | |
| usage); | |
| } | |
| void glFramebufferPixelLocalClearValuefvANGLE(GLint plane, | |
| const GLfloat value[4]) | |
| { | |
| framebufferPixelLocalClearValuefvWEBGL( | |
| emscripten_webgl_get_current_context(), | |
| plane, | |
| value[0], | |
| value[1], | |
| value[2], | |
| value[3]); | |
| } | |
| void glBeginPixelLocalStorageANGLE(GLsizei n, const uint32_t loadops[]) | |
| { | |
| beginPixelLocalStorageWEBGL(emscripten_webgl_get_current_context(), | |
| n, | |
| reinterpret_cast<uintptr_t>(loadops) / | |
| sizeof(uint32_t)); | |
| } | |
| void glEndPixelLocalStorageANGLE(GLsizei n, const uint32_t storeops[]) | |
| { | |
| endPixelLocalStorageWEBGL(emscripten_webgl_get_current_context(), | |
| n, | |
| reinterpret_cast<uintptr_t>(storeops) / | |
| sizeof(uint32_t)); | |
| } | |
| void glGetFramebufferPixelLocalStorageParameterivANGLE(GLint plane, | |
| GLenum pname, | |
| GLint* param) | |
| { | |
| *param = getFramebufferPixelLocalStorageParameterivWEBGL( | |
| emscripten_webgl_get_current_context(), | |
| plane, | |
| pname); | |
| } | |
| bool webgl_enable_WEBGL_provoking_vertex() | |
| { | |
| return enable_WEBGL_provoking_vertex( | |
| emscripten_webgl_get_current_context()); | |
| } | |
| void glProvokingVertexANGLE(GLenum provokeMode) | |
| { | |
| provokingVertexWEBGL(emscripten_webgl_get_current_context(), provokeMode); | |
| } | |
| #endif // RIVE_WEBGL | |
| namespace rive::gpu | |
| { | |
| static GLenum webgl_load_op(gpu::LoadAction loadAction) | |
| { | |
| switch (loadAction) | |
| { | |
| case gpu::LoadAction::clear: | |
| return GL_LOAD_OP_CLEAR_ANGLE; | |
| case gpu::LoadAction::preserveRenderTarget: | |
| return GL_LOAD_OP_LOAD_ANGLE; | |
| case gpu::LoadAction::dontCare: | |
| return GL_LOAD_OP_ZERO_ANGLE; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| class RenderContextGLImpl::PLSImplWebGL | |
| : public RenderContextGLImpl::PixelLocalStorageImpl | |
| { | |
| void getSupportedInterlockModes( | |
| const GLCapabilities& capabilities, | |
| PlatformFeatures* platformFeatures) const override | |
| { | |
| assert(capabilities.ANGLE_shader_pixel_local_storage); | |
| if (capabilities.ANGLE_shader_pixel_local_storage_coherent) | |
| { | |
| platformFeatures->supportsRasterOrderingMode = true; | |
| } | |
| } | |
| void activatePixelLocalStorage(RenderContextGLImpl* renderContextImpl, | |
| const FlushDescriptor& desc) override | |
| { | |
| auto renderTarget = static_cast<RenderTargetGL*>(desc.renderTarget); | |
| renderTarget->allocateWebGLPLSBacking( | |
| renderContextImpl->capabilities()); | |
| auto framebufferRenderTarget = | |
| lite_rtti_cast<FramebufferRenderTargetGL*>(renderTarget); | |
| if (framebufferRenderTarget != nullptr) | |
| { | |
| // We're targeting an external FBO directly. Make sure to allocate | |
| // and attach an offscreen target texture. | |
| framebufferRenderTarget->allocateOffscreenTargetTexture(); | |
| if (desc.colorLoadAction == LoadAction::preserveRenderTarget) | |
| { | |
| // Copy the framebuffer's contents to our offscreen texture. | |
| framebufferRenderTarget->bindDestinationFramebuffer( | |
| GL_READ_FRAMEBUFFER); | |
| framebufferRenderTarget->bindTextureFramebuffer( | |
| GL_DRAW_FRAMEBUFFER); | |
| renderContextImpl->state()->setPipelineState( | |
| gpu::COLOR_ONLY_PIPELINE_STATE); | |
| glutils::BlitFramebuffer(desc.renderTargetUpdateBounds, | |
| renderTarget->height()); | |
| } | |
| } | |
| // Begin pixel local storage. | |
| renderTarget->bindHeadlessFramebuffer( | |
| renderContextImpl->m_capabilities); | |
| if (desc.colorLoadAction == LoadAction::clear) | |
| { | |
| float clearColor4f[4]; | |
| UnpackColorToRGBA32FPremul(desc.colorClearValue, clearColor4f); | |
| glFramebufferPixelLocalClearValuefvANGLE(COLOR_PLANE_IDX, | |
| clearColor4f); | |
| } | |
| GLenum clipLoadAction = | |
| enums::is_flag_set(desc.combinedShaderFeatures, | |
| gpu::ShaderFeatures::ENABLE_CLIPPING) | |
| ? GL_LOAD_OP_ZERO_ANGLE | |
| : GL_DONT_CARE; | |
| GLenum loadOps[4] = {webgl_load_op(desc.colorLoadAction), | |
| clipLoadAction, | |
| GL_DONT_CARE, | |
| GL_LOAD_OP_ZERO_ANGLE}; | |
| static_assert(COLOR_PLANE_IDX == 0); | |
| static_assert(CLIP_PLANE_IDX == 1); | |
| static_assert(SCRATCH_COLOR_PLANE_IDX == 2); | |
| static_assert(COVERAGE_PLANE_IDX == 3); | |
| glBeginPixelLocalStorageANGLE(4, loadOps); | |
| } | |
| void deactivatePixelLocalStorage(RenderContextGLImpl* renderContextImpl, | |
| const FlushDescriptor& desc) override | |
| { | |
| constexpr static GLenum kStoreOps[4] = {GL_STORE_OP_STORE_ANGLE, | |
| GL_DONT_CARE, | |
| GL_DONT_CARE, | |
| GL_DONT_CARE}; | |
| static_assert(COLOR_PLANE_IDX == 0); | |
| static_assert(CLIP_PLANE_IDX == 1); | |
| static_assert(SCRATCH_COLOR_PLANE_IDX == 2); | |
| static_assert(COVERAGE_PLANE_IDX == 3); | |
| glEndPixelLocalStorageANGLE(4, kStoreOps); | |
| if (auto framebufferRenderTarget = | |
| lite_rtti_cast<FramebufferRenderTargetGL*>( | |
| static_cast<RenderTargetGL*>(desc.renderTarget))) | |
| { | |
| // We rendered to an offscreen texture. Copy back to the external | |
| // target FBO. | |
| framebufferRenderTarget->bindTextureFramebuffer( | |
| GL_READ_FRAMEBUFFER); | |
| framebufferRenderTarget->bindDestinationFramebuffer( | |
| GL_DRAW_FRAMEBUFFER); | |
| renderContextImpl->state()->setPipelineState( | |
| gpu::COLOR_ONLY_PIPELINE_STATE); | |
| glutils::BlitFramebuffer(desc.renderTargetUpdateBounds, | |
| framebufferRenderTarget->height()); | |
| } | |
| } | |
| void pushShaderDefines(gpu::InterlockMode, | |
| std::vector<const char*>* defines) const override | |
| { | |
| defines->push_back(GLSL_PLS_IMPL_ANGLE); | |
| } | |
| }; | |
| std::unique_ptr<RenderContextGLImpl::PixelLocalStorageImpl> | |
| RenderContextGLImpl::MakePLSImplWebGL() | |
| { | |
| return std::make_unique<PLSImplWebGL>(); | |
| } | |
| } // namespace rive::gpu |