Improving stroking for closed paths.
diff --git a/include/contour_render_path.hpp b/include/contour_render_path.hpp index 9911f99..ee0a35b 100644 --- a/include/contour_render_path.hpp +++ b/include/contour_render_path.hpp
@@ -66,6 +66,7 @@ const Mat2D& transform() const; }; + class ContourStroke; /// /// Segments curves into line segments and computes the bounds of the /// segmented curve. @@ -79,6 +80,7 @@ std::vector<PathCommand> m_Commands; bool m_IsDirty = true; float m_ContourThreshold = 1.0f; + bool m_IsClosed = false; public: std::size_t contourLength() const { return m_ContourVertices.size(); } @@ -86,6 +88,7 @@ { return m_ContourVertices; } + bool isClosed() const { return m_IsClosed; } bool isContainer() const; void addRenderPath(RenderPath* path, const Mat2D& transform) override; @@ -99,6 +102,11 @@ void computeContour(); bool isDirty() const { return m_IsDirty; } + + void extrudeStroke(ContourStroke* stroke, + StrokeJoin join, + StrokeCap cap, + float strokeWidth); }; } // namespace rive #endif \ No newline at end of file
diff --git a/include/contour_stroke.hpp b/include/contour_stroke.hpp index 118974a..65f30d6 100644 --- a/include/contour_stroke.hpp +++ b/include/contour_stroke.hpp
@@ -17,8 +17,19 @@ { protected: std::vector<Vec2D> m_TriangleStrip; + std::vector<std::size_t> m_Offsets; + uint32_t m_RenderOffset = 0; public: + const std::vector<Vec2D>& triangleStrip() const + { + return m_TriangleStrip; + } + + void reset(); + void resetRenderOffset(); + void nextRenderOffset(std::size_t& start, std::size_t& end); + void extrude(const ContourRenderPath* renderPath, bool isClosed, StrokeJoin join,
diff --git a/renderer/library/include/opengl/opengl_render_paint.hpp b/renderer/library/include/opengl/opengl_render_paint.hpp index 1d017e4..bc2e3a1 100644 --- a/renderer/library/include/opengl/opengl_render_paint.hpp +++ b/renderer/library/include/opengl/opengl_render_paint.hpp
@@ -3,12 +3,14 @@ #include "renderer.hpp" #include <vector> +#include "opengl/opengl.h" namespace rive { class OpenGLRenderer; class OpenGLRenderPaint; class OpenGLRenderPath; + class ContourStroke; class OpenGLGradient { @@ -34,6 +36,11 @@ RenderPaintStyle m_PaintStyle; float m_Color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; OpenGLGradient* m_Gradient = nullptr; + ContourStroke* m_Stroke = nullptr; + StrokeJoin m_StrokeJoin = StrokeJoin::miter; + StrokeCap m_StrokeCap = StrokeCap::butt; + float m_StrokeThickness = 0.0f; + GLuint m_StrokeBuffer = 0; public: void style(RenderPaintStyle style) override;
diff --git a/renderer/library/include/opengl/opengl_render_path.hpp b/renderer/library/include/opengl/opengl_render_path.hpp index afd968f..fa4242c 100644 --- a/renderer/library/include/opengl/opengl_render_path.hpp +++ b/renderer/library/include/opengl/opengl_render_path.hpp
@@ -24,6 +24,10 @@ void cover(OpenGLRenderer* renderer, const Mat2D& transform, const Mat2D& localTransform = Mat2D::identity()); + void renderStroke(ContourStroke* stroke, + OpenGLRenderer* renderer, + const Mat2D& transform, + const Mat2D& localTransform = Mat2D::identity()); }; } // namespace rive #endif \ No newline at end of file
diff --git a/renderer/library/src/opengl/opengl_render_paint.cpp b/renderer/library/src/opengl/opengl_render_paint.cpp index f4d3260..32a2302 100644 --- a/renderer/library/src/opengl/opengl_render_paint.cpp +++ b/renderer/library/src/opengl/opengl_render_paint.cpp
@@ -2,6 +2,7 @@ #include "shapes/paint/color.hpp" #include "opengl/opengl_renderer.hpp" #include "opengl/opengl_render_path.hpp" +#include "contour_stroke.hpp" using namespace rive; @@ -13,18 +14,35 @@ buffer[3] = colorAlpha(value) / 255.0f; } -void OpenGLRenderPaint::style(RenderPaintStyle style) { m_PaintStyle = style; } +void OpenGLRenderPaint::style(RenderPaintStyle style) +{ + m_PaintStyle = style; + delete m_Stroke; + if (m_PaintStyle == RenderPaintStyle::stroke) + { + m_Stroke = new ContourStroke(); + if (m_StrokeBuffer != 0) + { + glDeleteBuffers(1, &m_StrokeBuffer); + } + glGenBuffers(1, &m_StrokeBuffer); + } + else + { + m_Stroke = nullptr; + } +} void OpenGLRenderPaint::color(unsigned int value) { fillColorBuffer(m_Color, value); } -void OpenGLRenderPaint::thickness(float value) {} +void OpenGLRenderPaint::thickness(float value) { m_StrokeThickness = value; } -void OpenGLRenderPaint::join(StrokeJoin value) {} +void OpenGLRenderPaint::join(StrokeJoin value) { m_StrokeJoin = value; } -void OpenGLRenderPaint::cap(StrokeCap value) {} +void OpenGLRenderPaint::cap(StrokeCap value) { m_StrokeCap = value; } void OpenGLRenderPaint::blendMode(BlendMode value) {} @@ -53,7 +71,15 @@ void OpenGLRenderPaint::completeGradient() {} -OpenGLRenderPaint::~OpenGLRenderPaint() { delete m_Gradient; } +OpenGLRenderPaint::~OpenGLRenderPaint() +{ + if (m_StrokeBuffer != 0) + { + glDeleteBuffers(1, &m_StrokeBuffer); + } + delete m_Gradient; + delete m_Stroke; +} bool OpenGLRenderPaint::doesDraw() const { @@ -76,7 +102,35 @@ glUniform1i(renderer->fillTypeUniformIndex(), type); glUniform4fv(renderer->colorUniformIndex(), 1, m_Color); - path->cover(renderer, transform); + if (m_Stroke != nullptr) + { + m_Stroke->reset(); + path->extrudeStroke( + m_Stroke, m_StrokeJoin, m_StrokeCap, m_StrokeThickness / 2.0f); + + const std::vector<Vec2D>& strip = m_Stroke->triangleStrip(); + auto size = strip.size(); + if (size == 0) + { + return; + } + + glBindBuffer(GL_ARRAY_BUFFER, m_StrokeBuffer); + glBufferData(GL_ARRAY_BUFFER, + size * 2 * sizeof(float), + &strip[0][0], + GL_DYNAMIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * 4, (void*)0); + + m_Stroke->resetRenderOffset(); + path->renderStroke(m_Stroke, renderer, transform); + } + else + { + path->cover(renderer, transform); + } } OpenGLGradient::OpenGLGradient(int type) : m_Type(type) {}
diff --git a/renderer/library/src/opengl/opengl_render_path.cpp b/renderer/library/src/opengl/opengl_render_path.cpp index 1c13c01..6e72e68 100644 --- a/renderer/library/src/opengl/opengl_render_path.cpp +++ b/renderer/library/src/opengl/opengl_render_path.cpp
@@ -1,6 +1,7 @@ #include "opengl/opengl_render_path.hpp" #include "opengl/opengl_renderer.hpp" #include "opengl/opengl.h" +#include "contour_stroke.hpp" using namespace rive; @@ -182,4 +183,71 @@ // Draw bounds. glDrawElements(GL_TRIANGLES, 2 * 3, GL_UNSIGNED_SHORT, (void*)(0)); +} + +void OpenGLRenderPath::renderStroke(ContourStroke* stroke, + OpenGLRenderer* renderer, + const Mat2D& transform, + const Mat2D& localTransform) +{ + if (isContainer()) + { + for (auto& subPath : m_SubPaths) + { + const Mat2D& subPathTransform = subPath.transform(); + Mat2D pathTransform; + Mat2D::multiply(pathTransform, transform, subPathTransform); + reinterpret_cast<OpenGLRenderPath*>(subPath.path()) + ->renderStroke( + stroke, renderer, pathTransform, subPathTransform); + } + return; + } + + { + float m4[16] = {transform[0], + transform[1], + 0.0, + 0.0, + transform[2], + transform[3], + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + transform[4], + transform[5], + 0.0, + 1.0}; + + glUniformMatrix4fv(renderer->transformUniformIndex(), 1, GL_FALSE, m4); + } + { + float m4[16] = {localTransform[0], + localTransform[1], + 0.0, + 0.0, + localTransform[2], + localTransform[3], + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + localTransform[4], + localTransform[5], + 0.0, + 1.0}; + + glUniformMatrix4fv( + renderer->shapeTransformUniformIndex(), 1, GL_FALSE, m4); + } + + std::size_t start, end; + stroke->nextRenderOffset(start, end); + + glDrawArrays(GL_TRIANGLE_STRIP, start, end - start); } \ 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 a722fc0..83a89ca 100644 --- a/renderer/library/src/opengl/opengl_renderer.cpp +++ b/renderer/library/src/opengl/opengl_renderer.cpp
@@ -118,10 +118,13 @@ void OpenGLRenderer::drawPath(RenderPath* path, RenderPaint* paint) { auto glPaint = static_cast<OpenGLRenderPaint*>(paint); - if (glPaint->style() == RenderPaintStyle::stroke || !glPaint->doesDraw()) + // if (glPaint->style() == RenderPaintStyle::stroke || !glPaint->doesDraw()) + + if (!glPaint->doesDraw()) { return; } + bool needsStencil = glPaint->style() == RenderPaintStyle::fill; glColorMask(false, false, false, false); // Set fill type to 0 so we don't perform any gradient fragment calcs. @@ -246,28 +249,49 @@ auto glPath = static_cast<OpenGLRenderPath*>(path); - // Set up stencil buffer. - if (m_IsClipping) + if (needsStencil) { - glStencilMask(0x7F); - glStencilFunc(GL_EQUAL, 0x80, 0x80); + // Set up stencil buffer. + 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, m_IsClipping ? 0x7F : 0xFF); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); } else { - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 0x0, 0xFF); + 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_KEEP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP); + glColorMask(true, true, true, true); + // glStencilFunc(GL_ALWAYS, 0x0, 0xFF); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); } - - 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, m_IsClipping ? 0x7F : 0xFF); - glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - glPaint->draw(this, transform(), glPath); + // glPath->cover(this, transform()); }
diff --git a/renderer/viewer/assets/404.riv b/renderer/viewer/assets/404.riv new file mode 100644 index 0000000..c7a33a1 --- /dev/null +++ b/renderer/viewer/assets/404.riv Binary files differ
diff --git a/renderer/viewer/assets/control.riv b/renderer/viewer/assets/control.riv new file mode 100644 index 0000000..3f7b044 --- /dev/null +++ b/renderer/viewer/assets/control.riv Binary files differ
diff --git a/renderer/viewer/assets/leg_issues.riv b/renderer/viewer/assets/leg_issues.riv new file mode 100644 index 0000000..7c76170 --- /dev/null +++ b/renderer/viewer/assets/leg_issues.riv Binary files differ
diff --git a/renderer/viewer/assets/off_road_car.riv b/renderer/viewer/assets/off_road_car.riv new file mode 100644 index 0000000..81202cb --- /dev/null +++ b/renderer/viewer/assets/off_road_car.riv Binary files differ
diff --git a/renderer/viewer/assets/simple_stroke.riv b/renderer/viewer/assets/simple_stroke.riv new file mode 100644 index 0000000..28eb071 --- /dev/null +++ b/renderer/viewer/assets/simple_stroke.riv Binary files differ
diff --git a/renderer/viewer/assets/simple_stroke_only.riv b/renderer/viewer/assets/simple_stroke_only.riv new file mode 100644 index 0000000..8fc84f1 --- /dev/null +++ b/renderer/viewer/assets/simple_stroke_only.riv Binary files differ
diff --git a/renderer/viewer/assets/zombie_leg.riv b/renderer/viewer/assets/zombie_leg.riv new file mode 100644 index 0000000..5df4bdf --- /dev/null +++ b/renderer/viewer/assets/zombie_leg.riv Binary files differ
diff --git a/renderer/viewer/src/gl.cpp b/renderer/viewer/src/gl.cpp index e2339d0..2919423 100644 --- a/renderer/viewer/src/gl.cpp +++ b/renderer/viewer/src/gl.cpp
@@ -7,7 +7,7 @@ public: void startFrame() override { - glClearColor(0.0f, 1.0f, 1.0f, 1.0f); + glClearColor(0.05f, 0.05f, 0.05f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); OpenGLRenderer::startFrame(); }
diff --git a/renderer/viewer/src/viewer.cpp b/renderer/viewer/src/viewer.cpp index 829c986..f4804a5 100644 --- a/renderer/viewer/src/viewer.cpp +++ b/renderer/viewer/src/viewer.cpp
@@ -110,7 +110,11 @@ // 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"; + // std::string filename = "assets/marty.riv"; + // std::string filename = "assets/off_road_car.riv"; + // std::string filename = "assets/simple_stroke.riv"; + // std::string filename = "assets/leg_issues.riv"; + std::string filename = "assets/control.riv"; FILE* fp = fopen(filename.c_str(), "r"); fseek(fp, 0, SEEK_END); fileBytesLength = ftell(fp);
diff --git a/src/contour_render_path.cpp b/src/contour_render_path.cpp index cfe7a01..7211492 100644 --- a/src/contour_render_path.cpp +++ b/src/contour_render_path.cpp
@@ -1,5 +1,6 @@ #ifdef LOW_LEVEL_RENDERING #include "contour_render_path.hpp" +#include "contour_stroke.hpp" using namespace rive; @@ -34,6 +35,7 @@ void ContourRenderPath::reset() { + m_IsClosed = false; m_SubPaths.clear(); m_ContourVertices.clear(); m_Commands.clear(); @@ -59,8 +61,31 @@ void ContourRenderPath::close() { m_Commands.emplace_back(PathCommand(PathCommandType::close)); + m_IsClosed = true; } bool ContourRenderPath::isContainer() const { return !m_SubPaths.empty(); } +void ContourRenderPath::extrudeStroke(ContourStroke* stroke, + StrokeJoin join, + StrokeCap cap, + float strokeWidth) +{ + if (isContainer()) + { + for (auto& subPath : m_SubPaths) + { + static_cast<ContourRenderPath*>(subPath.path()) + ->extrudeStroke(stroke, join, cap, strokeWidth); + } + return; + } + + if (isDirty()) + { + computeContour(); + } + + stroke->extrude(this, m_IsClosed, join, cap, strokeWidth); +} #endif \ No newline at end of file
diff --git a/src/contour_stroke.cpp b/src/contour_stroke.cpp index 2c0a4d8..a0e70eb 100644 --- a/src/contour_stroke.cpp +++ b/src/contour_stroke.cpp
@@ -6,6 +6,22 @@ using namespace rive; static const int subdivisionArcLength = 4.0f; +bool first = true; + +void ContourStroke::reset() +{ + m_TriangleStrip.clear(); + m_Offsets.clear(); +} + +void ContourStroke::resetRenderOffset() { m_RenderOffset = 0; } + +void ContourStroke::nextRenderOffset(std::size_t& start, std::size_t& end) +{ + assert(m_RenderOffset < m_Offsets.size()); + start = m_RenderOffset == 0 ? 0 : m_Offsets[m_RenderOffset - 1]; + end = m_Offsets[m_RenderOffset++]; +} void ContourStroke::extrude(const ContourRenderPath* renderPath, bool isClosed, @@ -13,17 +29,16 @@ StrokeCap cap, float strokeWidth) { - m_TriangleStrip.clear(); - const std::vector<Vec2D>& points = renderPath->contourVertices(); auto pointCount = points.size(); - if (pointCount < 2) + if (pointCount < 6) { return; } - Vec2D lastPoint = points[0]; + auto startOffset = m_TriangleStrip.size(); + Vec2D lastPoint = points[4]; Vec2D lastDiff; - Vec2D::subtract(lastDiff, points[1], lastPoint); + Vec2D::subtract(lastDiff, points[5], lastPoint); float lastLength = Vec2D::length(lastDiff); Vec2D lastDiffNormalized; Vec2D::scale(lastDiffNormalized, lastDiff, 1.0f / lastLength); @@ -76,16 +91,18 @@ m_TriangleStrip.push_back(lastA); m_TriangleStrip.push_back(lastB); + pointCount -= isClosed ? 6 : 5; std::size_t adjustedPointCount = isClosed ? pointCount + 1 : pointCount; for (std::size_t i = 1; i < adjustedPointCount; i++) { - const Vec2D& point = points[i % pointCount]; + const Vec2D& point = points[(i % pointCount) + 4]; Vec2D diff, diffNormalized, next; float length; if (i < adjustedPointCount - 1 || isClosed) { - Vec2D::subtract(diff, (next = points[(i + 1) % pointCount]), point); + Vec2D::subtract( + diff, (next = points[((i + 1) % pointCount) + 4]), point); length = Vec2D::length(diff); Vec2D::scale(diffNormalized, diff, 1.0f / length); } @@ -258,6 +275,7 @@ else { Vec2D::subtract(b1, point, bisector); + b2 = b1; } Vec2D a; @@ -307,5 +325,30 @@ lastDiff = diff; lastDiffNormalized = diffNormalized; } + + if (isClosed) + { + auto last = m_TriangleStrip.size() - 1; + m_TriangleStrip[startOffset] = m_TriangleStrip[last - 1]; + m_TriangleStrip[startOffset + 1] = m_TriangleStrip[last]; + } + + m_Offsets.push_back(m_TriangleStrip.size()); + + if (first) + { + printf("CLOSED: %i %i\n", isClosed, points.size() - 4); + for (auto pt : points) + { + printf("P: %f %f\n", pt[0], pt[1]); + } + printf("---\n"); + first = false; + + for (auto v : m_TriangleStrip) + { + printf("%f %f\n", v[0], v[1]); + } + } } -#endif \ No newline at end of file +#endif