| /* | |
| * Copyright 2022 Rive | |
| */ | |
| #include "rive/pls/gl/pls_render_context_gl_impl.hpp" | |
| #include "rive/pls/gl/gl_utils.hpp" | |
| #include "rive/pls/gl/pls_render_target_gl.hpp" | |
| #include "shaders/constants.glsl" | |
| #include "shaders/out/generated/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; | |
| gl.pls = gl["getExtension"]("WEBGL_shader_pixel_local_storage"); | |
| return Boolean(gl.pls && gl.pls["isCoherent"]()); | |
| }); | |
| EM_JS(void, | |
| framebufferTexturePixelLocalStorageWEBGL, | |
| (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, | |
| GLint plane, | |
| GLuint backingtexture, | |
| GLint level, | |
| GLint layer), | |
| { | |
| const pls = GL.getContext(gl).GLctx.pls; | |
| if (pls) | |
| { | |
| pls["framebufferTexturePixelLocalStorageWEBGL"](plane, | |
| GL.textures[backingtexture], | |
| level, | |
| layer); | |
| } | |
| }); | |
| 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"]( | |
| Module["HEAPU32"].subarray(loadopsIdx, loadopsIdx + 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"]( | |
| Module["HEAPU32"].subarray(storeopsIdx, storeopsIdx + n)); | |
| } | |
| }); | |
| 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()); | |
| } | |
| bool webgl_enable_WEBGL_provoking_vertex() | |
| { | |
| return enable_WEBGL_provoking_vertex(emscripten_webgl_get_current_context()); | |
| } | |
| void glFramebufferTexturePixelLocalStorageANGLE(GLint plane, | |
| GLuint backingtexture, | |
| GLint level, | |
| GLint layer) | |
| { | |
| framebufferTexturePixelLocalStorageWEBGL(emscripten_webgl_get_current_context(), | |
| plane, | |
| backingtexture, | |
| level, | |
| layer); | |
| } | |
| 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 glProvokingVertexANGLE(GLenum provokeMode) | |
| { | |
| provokingVertexWEBGL(emscripten_webgl_get_current_context(), provokeMode); | |
| } | |
| #endif // RIVE_WEBGL | |
| namespace rive::pls | |
| { | |
| using DrawBufferMask = PLSRenderTargetGL::DrawBufferMask; | |
| static GLenum webgl_load_op(pls::LoadAction loadAction) | |
| { | |
| switch (loadAction) | |
| { | |
| case pls::LoadAction::clear: | |
| return GL_LOAD_OP_CLEAR_ANGLE; | |
| case pls::LoadAction::preserveRenderTarget: | |
| return GL_LOAD_OP_LOAD_ANGLE; | |
| case pls::LoadAction::dontCare: | |
| return GL_LOAD_OP_ZERO_ANGLE; | |
| } | |
| RIVE_UNREACHABLE(); | |
| } | |
| class PLSRenderContextGLImpl::PLSImplWebGL : public PLSRenderContextGLImpl::PLSImpl | |
| { | |
| bool supportsRasterOrdering(const GLCapabilities& capabilities) const override | |
| { | |
| return capabilities.ANGLE_shader_pixel_local_storage_coherent; | |
| } | |
| void activatePixelLocalStorage(PLSRenderContextGLImpl* plsContextImpl, | |
| const FlushDescriptor& desc) override | |
| { | |
| auto renderTarget = static_cast<PLSRenderTargetGL*>(desc.renderTarget); | |
| renderTarget->allocateInternalPLSTextures(desc.interlockMode); | |
| 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->bindInternalFramebuffer(GL_DRAW_FRAMEBUFFER, | |
| DrawBufferMask::color); | |
| glutils::BlitFramebuffer(desc.renderTargetUpdateBounds, renderTarget->height()); | |
| } | |
| } | |
| // Begin pixel local storage. | |
| renderTarget->bindHeadlessFramebuffer(plsContextImpl->m_capabilities); | |
| if (desc.colorLoadAction == LoadAction::clear) | |
| { | |
| float clearColor4f[4]; | |
| UnpackColorToRGBA32F(desc.clearColor, clearColor4f); | |
| glFramebufferPixelLocalClearValuefvANGLE(FRAMEBUFFER_PLANE_IDX, clearColor4f); | |
| } | |
| GLenum clipLoadAction = (desc.combinedShaderFeatures & pls::ShaderFeatures::ENABLE_CLIPPING) | |
| ? GL_LOAD_OP_ZERO_ANGLE | |
| : GL_DONT_CARE; | |
| GLenum loadOps[4] = {webgl_load_op(desc.colorLoadAction), | |
| GL_LOAD_OP_ZERO_ANGLE, | |
| clipLoadAction, | |
| GL_DONT_CARE}; | |
| static_assert(FRAMEBUFFER_PLANE_IDX == 0); | |
| static_assert(COVERAGE_PLANE_IDX == 1); | |
| static_assert(CLIP_PLANE_IDX == 2); | |
| static_assert(ORIGINAL_DST_COLOR_PLANE_IDX == 3); | |
| glBeginPixelLocalStorageANGLE(4, loadOps); | |
| } | |
| void deactivatePixelLocalStorage(PLSRenderContextGLImpl*, 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(FRAMEBUFFER_PLANE_IDX == 0); | |
| static_assert(COVERAGE_PLANE_IDX == 1); | |
| static_assert(CLIP_PLANE_IDX == 2); | |
| static_assert(ORIGINAL_DST_COLOR_PLANE_IDX == 3); | |
| glEndPixelLocalStorageANGLE(4, kStoreOps); | |
| if (auto framebufferRenderTarget = lite_rtti_cast<FramebufferRenderTargetGL*>( | |
| static_cast<PLSRenderTargetGL*>(desc.renderTarget))) | |
| { | |
| // We rendered to an offscreen texture. Copy back to the external target FBO. | |
| framebufferRenderTarget->bindInternalFramebuffer(GL_READ_FRAMEBUFFER, | |
| DrawBufferMask::color); | |
| framebufferRenderTarget->bindDestinationFramebuffer(GL_DRAW_FRAMEBUFFER); | |
| glutils::BlitFramebuffer(desc.renderTargetUpdateBounds, | |
| framebufferRenderTarget->height()); | |
| } | |
| } | |
| void pushShaderDefines(pls::InterlockMode, std::vector<const char*>* defines) const override | |
| { | |
| defines->push_back(GLSL_PLS_IMPL_ANGLE); | |
| } | |
| }; | |
| std::unique_ptr<PLSRenderContextGLImpl::PLSImpl> PLSRenderContextGLImpl::MakePLSImplWebGL() | |
| { | |
| return std::make_unique<PLSImplWebGL>(); | |
| } | |
| } // namespace rive::pls |