Adding clipping support.
diff --git a/renderer/library/include/low_level/low_level_renderer.hpp b/renderer/library/include/low_level/low_level_renderer.hpp index 6f5dbfb..9c377a4 100644 --- a/renderer/library/include/low_level/low_level_renderer.hpp +++ b/renderer/library/include/low_level/low_level_renderer.hpp
@@ -6,36 +6,55 @@ #include "math/mat2d.hpp" #include <stdint.h> #include <list> +#include <vector> namespace rive { - struct SubPath + class SubPath { - RenderPath* path; - Mat2D transform; + private: + RenderPath* m_Path; + Mat2D m_Transform; + + public: + SubPath(RenderPath* path, const Mat2D& transform); + + RenderPath* path(); + const Mat2D& transform(); }; struct RenderState { Mat2D transform; - std::list<SubPath> clipPaths; + std::vector<SubPath> clipPaths; }; /// /// Low level implementation of a generalized rive::Renderer. It's /// specifically tailored for use with low level graphics apis like Metal, /// OpenGL, Vulkan, D3D, etc. + /// class LowLevelRenderer : public Renderer { protected: float m_ModelViewProjection[16] = {0.0f}; std::list<RenderState> m_Stack; + bool m_IsClippingDirty = false; + std::vector<SubPath> m_ClipPaths; public: LowLevelRenderer(); + + /// + /// Checks if clipping is dirty and clears the clipping flag. Hard + /// expectation for whoever checks this to also apply it. That's why + /// it's not marked const. + /// + bool isClippingDirty(); + virtual GraphicsApi::Type type() const = 0; - virtual void startFrame() = 0; + virtual void startFrame(); virtual void endFrame() = 0; virtual RenderPaint* makeRenderPaint() = 0; @@ -57,6 +76,7 @@ void restore() override; void transform(const Mat2D& transform) override; const Mat2D& transform(); + void clipPath(RenderPath* path) override; }; } // namespace rive
diff --git a/renderer/library/include/opengl/opengl_renderer.hpp b/renderer/library/include/opengl/opengl_renderer.hpp index 149c0fc..cbb538c 100644 --- a/renderer/library/include/opengl/opengl_renderer.hpp +++ b/renderer/library/include/opengl/opengl_renderer.hpp
@@ -25,6 +25,8 @@ GLint m_GradientPositionUniformIndex = -1; GLint m_ShapeTransformUniformIndex = -1; GLuint m_VertexArray = 0; + GLuint m_BlitBuffer = 0; + bool m_IsClipping = false; /// Indices for the max sized contour, prepended with 2 triangles for /// bounding boxes. @@ -36,7 +38,6 @@ OpenGLRenderer(); ~OpenGLRenderer(); void drawPath(RenderPath* path, RenderPaint* paint) override; - void clipPath(RenderPath* path) override; void startFrame() override; void endFrame() override;
diff --git a/renderer/library/src/low_level_renderer/low_level_renderer.cpp b/renderer/library/src/low_level_renderer/low_level_renderer.cpp index fdfad07..d64bf05 100644 --- a/renderer/library/src/low_level_renderer/low_level_renderer.cpp +++ b/renderer/library/src/low_level_renderer/low_level_renderer.cpp
@@ -1,8 +1,17 @@ #include "low_level/low_level_renderer.hpp" #include <cstring> +#include <cassert> using namespace rive; +SubPath::SubPath(RenderPath* path, const Mat2D& transform) : + m_Path(path), m_Transform(transform) +{ +} + +RenderPath* SubPath::path() { return m_Path; } +const Mat2D& SubPath::transform() { return m_Transform; } + LowLevelRenderer::LowLevelRenderer() { m_Stack.emplace_back(RenderState()); } void LowLevelRenderer::modelViewProjection(float value[16]) @@ -44,7 +53,13 @@ void LowLevelRenderer::restore() { assert(m_Stack.size() > 1); + RenderState& state = m_Stack.back(); m_Stack.pop_back(); + + // We can only add clipping paths so if they're still the same, nothing has + // changed. + m_IsClippingDirty = + state.clipPaths.size() != m_Stack.back().clipPaths.size(); } void LowLevelRenderer::transform(const Mat2D& transform) @@ -52,4 +67,50 @@ Mat2D& stackMat = m_Stack.back().transform; Mat2D::multiply(stackMat, stackMat, transform); } -const Mat2D& LowLevelRenderer::transform() { return m_Stack.back().transform; } \ No newline at end of file +const Mat2D& LowLevelRenderer::transform() { return m_Stack.back().transform; } + +void LowLevelRenderer::clipPath(RenderPath* path) +{ + RenderState& state = m_Stack.back(); + state.clipPaths.emplace_back(SubPath(path, state.transform)); + m_IsClippingDirty = true; +} + +void LowLevelRenderer::startFrame() +{ + assert(m_Stack.size() == 1); + m_ClipPaths.clear(); + m_IsClippingDirty = false; +} + +bool LowLevelRenderer::isClippingDirty() +{ + if (!m_IsClippingDirty) + { + return false; + } + + m_IsClippingDirty = false; + RenderState& state = m_Stack.back(); + auto currentClipLength = m_ClipPaths.size(); + if (currentClipLength == state.clipPaths.size()) + { + // Same length so now check if they're all the same. + bool allSame = true; + for (std::size_t i = 0; i < currentClipLength; i++) + { + if (state.clipPaths[i].path() != m_ClipPaths[i].path()) + { + allSame = false; + break; + } + } + if (allSame) + { + return false; + } + } + m_ClipPaths = state.clipPaths; + + return true; +} \ No newline at end of file
diff --git a/renderer/library/src/opengl/opengl_renderer.cpp b/renderer/library/src/opengl/opengl_renderer.cpp index 30f0e08..a722fc0 100644 --- a/renderer/library/src/opengl/opengl_renderer.cpp +++ b/renderer/library/src/opengl/opengl_renderer.cpp
@@ -15,6 +15,7 @@ glDeleteShader(m_VertexShader); glDeleteShader(m_FragmentShader); glDeleteBuffers(1, &m_IndexBuffer); + glDeleteBuffers(1, &m_BlitBuffer); glDeleteVertexArrays(1, &m_VertexArray); } @@ -57,6 +58,25 @@ glGenBuffers(1, &m_IndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer); + // Create vertex buffer for blitting to full viewport coordinates. + float blitBuffer[8] = { + -1.0f, + 1.0f, + + 1.0f, + 1.0f, + + 1.0f, + -1.0f, + + -1.0f, + -1.0f, + }; + glGenBuffers(1, &m_BlitBuffer); + glBindBuffer(GL_ARRAY_BUFFER, m_BlitBuffer); + glBufferData( + GL_ARRAY_BUFFER, 8 * sizeof(float), &blitBuffer[0], GL_STATIC_DRAW); + // Two triangles for bounds. m_Indices.emplace_back(0); m_Indices.emplace_back(1); @@ -103,29 +123,157 @@ return; } + glColorMask(false, false, false, false); + // Set fill type to 0 so we don't perform any gradient fragment calcs. + glUniform1i(fillTypeUniformIndex(), 0); + + if (isClippingDirty()) + { + if (m_IsClipping) + { + // Clear previous clip. + glStencilMask(0xFF); + glClear(GL_STENCIL_BUFFER_BIT); + + // TODO: instead of clearing the entire buffer, as we clip we could + // compute the combined clipping area set and clear that here. + } + auto clipLength = m_ClipPaths.size(); + if (clipLength > 0) + { + m_IsClipping = true; + SubPath& firstClipPath = m_ClipPaths[0]; + + glStencilMask(0xFF); + glStencilFunc(GL_ALWAYS, 0x0, 0xFF); + + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); + static_cast<OpenGLRenderPath*>(firstClipPath.path()) + ->stencil(this, firstClipPath.transform()); + + // Fail when not equal to 0 and replace with 0x80 (mark high bit as + // included in clip). Require stencil mask (write mask) of 0xFF and + // stencil func mask of 0x7F such that the comparison looks for 0 + // but write 0x80. + glStencilMask(0xFF); + glStencilFunc(GL_NOTEQUAL, 0x80, 0x7F); + glStencilOp(GL_ZERO, GL_ZERO, GL_REPLACE); + + glBindBuffer(GL_ARRAY_BUFFER, m_BlitBuffer); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * 4, (void*)0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer); + + float m4[16] = {1.0, + 0.0, + 0.0, + 0.0, + + 0.0, + 1.0, + 0.0, + 0.0, + + 0.0, + 0.0, + 1.0, + 0.0, + + 0.0, + 0.0, + 0.0, + 1.0}; + + glUniformMatrix4fv(transformUniformIndex(), 1, GL_FALSE, m4); + glUniformMatrix4fv(m_ProjectionUniformIndex, 1, GL_FALSE, m4); + + // Draw bounds. + glDrawElements(GL_TRIANGLES, 2 * 3, GL_UNSIGNED_SHORT, (void*)(0)); + + glUniformMatrix4fv( + m_ProjectionUniformIndex, 1, GL_FALSE, m_ModelViewProjection); + for (int i = 1; i < clipLength; i++) + // for (int i = 1; i < 0; i++) + { + + // When already clipping we want to write only to the last/lower + // 7 bits as our high 8th bit is used to mark clipping + // inclusion. + glStencilMask(0x7F); + // Pass only if that 8th bit is set. This allows us to write our + // new winding into the lower 7 bits. + glStencilFunc(GL_EQUAL, 0x80, 0x80); + SubPath& nextClipPath = m_ClipPaths[i]; + + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); + static_cast<OpenGLRenderPath*>(nextClipPath.path()) + ->stencil(this, nextClipPath.transform()); + + // Fail when not equal to 0 and replace with 0x80 (mark high bit + // as included in clip). Require stencil mask (write mask) of + // 0xFF and stencil func mask of 0x7F such that the comparison + // looks for 0 but write 0x80. + glStencilMask(0xFF); + glStencilFunc(GL_NOTEQUAL, 0x80, 0x7F); + glStencilOp(GL_ZERO, GL_ZERO, GL_REPLACE); + + glBindBuffer(GL_ARRAY_BUFFER, m_BlitBuffer); + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, 2, GL_FLOAT, GL_FALSE, 2 * 4, (void*)0); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer); + + glUniformMatrix4fv(transformUniformIndex(), 1, GL_FALSE, m4); + glUniformMatrix4fv(m_ProjectionUniformIndex, 1, GL_FALSE, m4); + // Draw bounds. + glDrawElements( + GL_TRIANGLES, 2 * 3, GL_UNSIGNED_SHORT, (void*)(0)); + + glUniformMatrix4fv(m_ProjectionUniformIndex, + 1, + GL_FALSE, + m_ModelViewProjection); + } + } + else + { + m_IsClipping = false; + } + } + auto glPath = static_cast<OpenGLRenderPath*>(path); // Set up stencil buffer. - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 0x0, 0xFF); - glColorMask(false, false, false, false); + if (m_IsClipping) + { + glStencilMask(0x7F); + glStencilFunc(GL_EQUAL, 0x80, 0x80); + } + else + { + glStencilMask(0xFF); + glStencilFunc(GL_ALWAYS, 0x0, 0xFF); + } + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); glPath->stencil(this, transform()); glColorMask(true, true, true, true); - glStencilFunc(GL_NOTEQUAL, 0, 0xFF); + glStencilFunc(GL_NOTEQUAL, 0, m_IsClipping ? 0x7F : 0xFF); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); glPaint->draw(this, transform(), glPath); // glPath->cover(this, transform()); } -void OpenGLRenderer::clipPath(RenderPath* path) {} - void OpenGLRenderer::startFrame() { + LowLevelRenderer::startFrame(); glUseProgram(m_Program); glEnableVertexAttribArray(0); glUniformMatrix4fv(
diff --git a/renderer/viewer/assets/clip.riv b/renderer/viewer/assets/clip.riv new file mode 100644 index 0000000..eb9a735 --- /dev/null +++ b/renderer/viewer/assets/clip.riv Binary files differ
diff --git a/renderer/viewer/assets/clipped_circle_star_2.riv b/renderer/viewer/assets/clipped_circle_star_2.riv new file mode 100644 index 0000000..4172640 --- /dev/null +++ b/renderer/viewer/assets/clipped_circle_star_2.riv Binary files differ
diff --git a/renderer/viewer/assets/marty.riv b/renderer/viewer/assets/marty.riv new file mode 100644 index 0000000..abc309f --- /dev/null +++ b/renderer/viewer/assets/marty.riv Binary files differ
diff --git a/renderer/viewer/assets/marty_v2.riv b/renderer/viewer/assets/marty_v2.riv new file mode 100644 index 0000000..774fee6 --- /dev/null +++ b/renderer/viewer/assets/marty_v2.riv Binary files differ
diff --git a/renderer/viewer/src/viewer.cpp b/renderer/viewer/src/viewer.cpp index 86dca8d..829c986 100644 --- a/renderer/viewer/src/viewer.cpp +++ b/renderer/viewer/src/viewer.cpp
@@ -107,7 +107,10 @@ // std::string filename = "assets/polygon_party.riv"; // std::string filename = "assets/triangle.riv"; - std::string filename = "assets/juice.riv"; + // std::string filename = "assets/juice.riv"; + // std::string filename = "assets/clip.riv"; + // std::string filename = "assets/clipped_circle_star_2.riv"; + std::string filename = "assets/marty.riv"; FILE* fp = fopen(filename.c_str(), "r"); fseek(fp, 0, SEEK_END); fileBytesLength = ftell(fp);