| #include "rive/tess/sokol/sokol_tess_renderer.hpp" |
| #include "rive/tess/sokol/sokol_factory.hpp" |
| #include "rive/tess/tess_render_path.hpp" |
| #include "rive/tess/contour_stroke.hpp" |
| #include "generated/shader.h" |
| #include <unordered_set> |
| |
| using namespace rive; |
| |
| static void fillColorBuffer(float* buffer, ColorInt value) { |
| buffer[0] = (float)colorRed(value) / 0xFF; |
| buffer[1] = (float)colorGreen(value) / 0xFF; |
| buffer[2] = (float)colorBlue(value) / 0xFF; |
| buffer[3] = colorOpacity(value); |
| } |
| |
| class SokolRenderPath : public TessRenderPath { |
| public: |
| SokolRenderPath() {} |
| SokolRenderPath(RawPath& rawPath, FillRule fillRule) : TessRenderPath(rawPath, fillRule) {} |
| |
| ~SokolRenderPath() { |
| sg_destroy_buffer(m_vertexBuffer); |
| sg_destroy_buffer(m_indexBuffer); |
| } |
| |
| private: |
| std::vector<Vec2D> m_vertices; |
| std::vector<uint16_t> m_indices; |
| |
| sg_buffer m_vertexBuffer = {0}; |
| sg_buffer m_indexBuffer = {0}; |
| |
| std::size_t m_boundsIndex = 0; |
| |
| protected: |
| void addTriangles(rive::Span<const rive::Vec2D> vts, rive::Span<const uint16_t> idx) override { |
| m_vertices.insert(m_vertices.end(), vts.begin(), vts.end()); |
| m_indices.insert(m_indices.end(), idx.begin(), idx.end()); |
| } |
| |
| void setTriangulatedBounds(const AABB& value) override { |
| m_boundsIndex = m_vertices.size(); |
| m_vertices.emplace_back(Vec2D(value.minX, value.minY)); |
| m_vertices.emplace_back(Vec2D(value.maxX, value.minY)); |
| m_vertices.emplace_back(Vec2D(value.maxX, value.maxY)); |
| m_vertices.emplace_back(Vec2D(value.minX, value.maxY)); |
| } |
| |
| public: |
| void reset() override { |
| TessRenderPath::reset(); |
| m_vertices.clear(); |
| m_indices.clear(); |
| } |
| |
| void drawStroke(ContourStroke* stroke) { |
| if (isContainer()) { |
| for (auto& subPath : m_subPaths) { |
| reinterpret_cast<SokolRenderPath*>(subPath.path())->drawStroke(stroke); |
| } |
| return; |
| } |
| std::size_t start, end; |
| stroke->nextRenderOffset(start, end); |
| sg_draw(start < 2 ? 0 : (start - 2) * 3, end - start < 2 ? 0 : (end - start - 2) * 3, 1); |
| } |
| |
| void drawFill() { |
| if (triangulate()) { |
| sg_destroy_buffer(m_vertexBuffer); |
| sg_destroy_buffer(m_indexBuffer); |
| if (m_indices.size() == 0 || m_vertices.size() == 0) { |
| m_vertexBuffer = {0}; |
| m_indexBuffer = {0}; |
| return; |
| } |
| |
| m_vertexBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_VERTEXBUFFER, |
| .data = |
| { |
| m_vertices.data(), |
| m_vertices.size() * sizeof(Vec2D), |
| }, |
| }); |
| |
| m_indexBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_INDEXBUFFER, |
| .data = |
| { |
| m_indices.data(), |
| m_indices.size() * sizeof(uint16_t), |
| }, |
| }); |
| } |
| |
| if (m_vertexBuffer.id == 0) { |
| return; |
| } |
| |
| sg_bindings bind = { |
| .vertex_buffers[0] = m_vertexBuffer, |
| .index_buffer = m_indexBuffer, |
| }; |
| |
| sg_apply_bindings(&bind); |
| sg_draw(0, m_indices.size(), 1); |
| } |
| |
| void drawBounds(const sg_buffer& boundsIndexBuffer) { |
| if (m_vertexBuffer.id == 0) { |
| return; |
| } |
| sg_bindings bind = { |
| .vertex_buffers[0] = m_vertexBuffer, |
| .vertex_buffer_offsets[0] = (int)(m_boundsIndex * sizeof(Vec2D)), |
| .index_buffer = boundsIndexBuffer, |
| }; |
| |
| sg_apply_bindings(&bind); |
| sg_draw(0, 6, 1); |
| } |
| }; |
| |
| // Returns a full-formed RenderPath -- can be treated as immutable |
| std::unique_ptr<RenderPath> SokolFactory::makeRenderPath(RawPath& rawPath, FillRule rule) { |
| return std::make_unique<SokolRenderPath>(rawPath, rule); |
| } |
| |
| std::unique_ptr<RenderPath> SokolFactory::makeEmptyRenderPath() { |
| return std::make_unique<SokolRenderPath>(); |
| } |
| |
| class SokolBuffer : public RenderBuffer { |
| private: |
| sg_buffer m_Buffer; |
| |
| public: |
| SokolBuffer(size_t count, const sg_buffer_desc& desc) : |
| RenderBuffer(count), m_Buffer(sg_make_buffer(desc)) {} |
| ~SokolBuffer() { sg_destroy_buffer(m_Buffer); } |
| |
| sg_buffer buffer() { return m_Buffer; } |
| }; |
| |
| rcp<RenderBuffer> SokolFactory::makeBufferU16(Span<const uint16_t> span) { |
| return rcp<RenderBuffer>(new SokolBuffer(span.size(), |
| (sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_INDEXBUFFER, |
| .data = |
| { |
| span.data(), |
| span.size_bytes(), |
| }, |
| })); |
| } |
| |
| rcp<RenderBuffer> SokolFactory::makeBufferU32(Span<const uint32_t> span) { |
| return rcp<RenderBuffer>(new SokolBuffer(span.size(), |
| (sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_INDEXBUFFER, |
| .data = |
| { |
| span.data(), |
| span.size_bytes(), |
| }, |
| })); |
| } |
| rcp<RenderBuffer> SokolFactory::makeBufferF32(Span<const float> span) { |
| return rcp<RenderBuffer>(new SokolBuffer(span.size(), |
| (sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_VERTEXBUFFER, |
| .data = |
| { |
| span.data(), |
| span.size_bytes(), |
| }, |
| })); |
| } |
| |
| sg_pipeline vectorPipeline(sg_shader shader, |
| sg_blend_state blend, |
| sg_stencil_state stencil, |
| sg_color_mask colorMask = SG_COLORMASK_RGBA) { |
| return sg_make_pipeline((sg_pipeline_desc){ |
| .layout = |
| { |
| .attrs = |
| { |
| [ATTR_vs_path_position] = |
| { |
| .format = SG_VERTEXFORMAT_FLOAT2, |
| .buffer_index = 0, |
| }, |
| }, |
| }, |
| .shader = shader, |
| .index_type = SG_INDEXTYPE_UINT16, |
| .cull_mode = SG_CULLMODE_NONE, |
| .depth = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .write_enabled = false, |
| }, |
| .colors = |
| { |
| [0] = |
| { |
| .write_mask = colorMask, |
| .blend = blend, |
| }, |
| }, |
| .stencil = stencil, |
| .label = "path-pipeline", |
| }); |
| } |
| |
| SokolTessRenderer::SokolTessRenderer() { |
| m_meshPipeline = sg_make_pipeline((sg_pipeline_desc){ |
| .layout = |
| { |
| .attrs = |
| { |
| [ATTR_vs_position] = {.format = SG_VERTEXFORMAT_FLOAT2, .buffer_index = 0}, |
| [ATTR_vs_texcoord0] = {.format = SG_VERTEXFORMAT_FLOAT2, .buffer_index = 1}, |
| }, |
| }, |
| .shader = sg_make_shader(rive_tess_shader_desc(sg_query_backend())), |
| .index_type = SG_INDEXTYPE_UINT16, |
| .cull_mode = SG_CULLMODE_NONE, |
| .depth = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .write_enabled = false, |
| }, |
| .colors = |
| { |
| [0] = |
| { |
| .write_mask = SG_COLORMASK_RGBA, |
| .blend = |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| }, |
| }, |
| .label = "mesh-pipeline", |
| }); |
| |
| auto uberShader = sg_make_shader(rive_tess_path_shader_desc(sg_query_backend())); |
| |
| assert(maxClippingPaths < 256); |
| |
| // Src Over Pipelines |
| { |
| m_pathPipeline[0] = vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = false, |
| }); |
| |
| for (std::size_t i = 1; i <= maxClippingPaths; i++) { |
| m_pathPipeline[i] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = true, |
| .ref = (uint8_t)i, |
| .read_mask = 0xFF, |
| .write_mask = 0x00, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| }); |
| } |
| } |
| |
| // Screen Pipelines |
| { |
| m_pathScreenPipeline[0] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE, |
| .src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = false, |
| }); |
| |
| for (std::size_t i = 1; i <= maxClippingPaths; i++) { |
| m_pathScreenPipeline[i] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE, |
| .src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = true, |
| .ref = (uint8_t)i, |
| .read_mask = 0xFF, |
| .write_mask = 0x00, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| }); |
| } |
| } |
| |
| // Additive Pipelines |
| { |
| m_pathAdditivePipeline[0] = vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE, |
| }, |
| { |
| .enabled = false, |
| }); |
| |
| for (std::size_t i = 1; i <= maxClippingPaths; i++) { |
| m_pathAdditivePipeline[i] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE, |
| }, |
| { |
| .enabled = true, |
| .ref = (uint8_t)i, |
| .read_mask = 0xFF, |
| .write_mask = 0x00, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| }); |
| } |
| } |
| |
| // Multiply Pipelines |
| { |
| m_pathMultiplyPipeline[0] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_DST_COLOR, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = false, |
| }); |
| |
| for (std::size_t i = 1; i <= maxClippingPaths; i++) { |
| m_pathMultiplyPipeline[i] = |
| vectorPipeline(uberShader, |
| { |
| .enabled = true, |
| .src_factor_rgb = SG_BLENDFACTOR_DST_COLOR, |
| .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, |
| }, |
| { |
| .enabled = true, |
| .ref = (uint8_t)i, |
| .read_mask = 0xFF, |
| .write_mask = 0x00, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_EQUAL, |
| }, |
| }); |
| } |
| } |
| |
| m_incClipPipeline = vectorPipeline(uberShader, |
| { |
| .enabled = false, |
| }, |
| { |
| .enabled = true, |
| .read_mask = 0xFF, |
| .write_mask = 0xFF, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .depth_fail_op = SG_STENCILOP_KEEP, |
| .fail_op = SG_STENCILOP_KEEP, |
| .pass_op = SG_STENCILOP_INCR_CLAMP, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .depth_fail_op = SG_STENCILOP_KEEP, |
| .fail_op = SG_STENCILOP_KEEP, |
| .pass_op = SG_STENCILOP_INCR_CLAMP, |
| }, |
| }, |
| SG_COLORMASK_NONE); |
| |
| m_decClipPipeline = vectorPipeline(uberShader, |
| { |
| .enabled = false, |
| }, |
| { |
| .enabled = true, |
| .read_mask = 0xFF, |
| .write_mask = 0xFF, |
| .front = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .depth_fail_op = SG_STENCILOP_KEEP, |
| .fail_op = SG_STENCILOP_KEEP, |
| .pass_op = SG_STENCILOP_DECR_CLAMP, |
| }, |
| .back = |
| { |
| .compare = SG_COMPAREFUNC_ALWAYS, |
| .depth_fail_op = SG_STENCILOP_KEEP, |
| .fail_op = SG_STENCILOP_KEEP, |
| .pass_op = SG_STENCILOP_DECR_CLAMP, |
| }, |
| }, |
| SG_COLORMASK_NONE); |
| |
| uint16_t indices[] = {0, 1, 2, 0, 2, 3}; |
| |
| m_boundsIndices = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_INDEXBUFFER, |
| .data = SG_RANGE(indices), |
| }); |
| } |
| |
| SokolTessRenderer::~SokolTessRenderer() { |
| sg_destroy_buffer(m_boundsIndices); |
| sg_destroy_pipeline(m_meshPipeline); |
| sg_destroy_pipeline(m_incClipPipeline); |
| sg_destroy_pipeline(m_decClipPipeline); |
| for (std::size_t i = 0; i <= maxClippingPaths; i++) { |
| sg_destroy_pipeline(m_pathPipeline[i]); |
| sg_destroy_pipeline(m_pathScreenPipeline[i]); |
| } |
| } |
| |
| void SokolTessRenderer::orthographicProjection(float left, |
| float right, |
| float bottom, |
| float top, |
| float near, |
| float far) { |
| m_Projection[0] = 2.0f / (right - left); |
| m_Projection[1] = 0.0f; |
| m_Projection[2] = 0.0f; |
| m_Projection[3] = 0.0f; |
| |
| m_Projection[4] = 0.0f; |
| m_Projection[5] = 2.0f / (top - bottom); |
| m_Projection[6] = 0.0f; |
| m_Projection[7] = 0.0f; |
| |
| #ifdef SOKOL_GLCORE33 |
| m_Projection[8] = 0.0f; |
| m_Projection[9] = 0.0f; |
| m_Projection[10] = 2.0f / (near - far); |
| m_Projection[11] = 0.0f; |
| |
| m_Projection[12] = (right + left) / (left - right); |
| m_Projection[13] = (top + bottom) / (bottom - top); |
| m_Projection[14] = (far + near) / (near - far); |
| m_Projection[15] = 1.0f; |
| #else |
| // NDC are slightly different in Metal: |
| // https://metashapes.com/blog/opengl-metal-projection-matrix-problem/ |
| m_Projection[8] = 0.0f; |
| m_Projection[9] = 0.0f; |
| m_Projection[10] = 1.0f / (far - near); |
| m_Projection[11] = 0.0f; |
| |
| m_Projection[12] = (right + left) / (left - right); |
| m_Projection[13] = (top + bottom) / (bottom - top); |
| m_Projection[14] = near / (near - far); |
| m_Projection[15] = 1.0f; |
| #endif |
| // for (int i = 0; i < 4; i++) { |
| // int b = i * 4; |
| // printf("%f\t%f\t%f\t%f\n", |
| // m_Projection[b], |
| // m_Projection[b + 1], |
| // m_Projection[b + 2], |
| // m_Projection[b + 3]); |
| // } |
| } |
| |
| void SokolTessRenderer::drawImage(const RenderImage* image, BlendMode, float opacity) { |
| vs_params_t vs_params; |
| |
| const Mat2D& world = transform(); |
| vs_params.mvp = m_Projection * world; |
| |
| auto sokolImage = static_cast<const SokolRenderImage*>(image); |
| setPipeline(m_meshPipeline); |
| sg_bindings bind = { |
| .vertex_buffers[0] = sokolImage->vertexBuffer(), |
| .vertex_buffers[1] = sokolImage->uvBuffer(), |
| .index_buffer = m_boundsIndices, |
| .fs_images[SLOT_tex] = sokolImage->image(), |
| }; |
| |
| sg_apply_bindings(&bind); |
| sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_params, SG_RANGE_REF(vs_params)); |
| sg_draw(0, 6, 1); |
| } |
| |
| void SokolTessRenderer::drawImageMesh(const RenderImage* renderImage, |
| rcp<RenderBuffer> vertices_f32, |
| rcp<RenderBuffer> uvCoords_f32, |
| rcp<RenderBuffer> indices_u16, |
| BlendMode blendMode, |
| float opacity) { |
| vs_params_t vs_params; |
| |
| const Mat2D& world = transform(); |
| vs_params.mvp = m_Projection * world; |
| |
| setPipeline(m_meshPipeline); |
| sg_bindings bind = { |
| .vertex_buffers[0] = static_cast<SokolBuffer*>(vertices_f32.get())->buffer(), |
| .vertex_buffers[1] = static_cast<SokolBuffer*>(uvCoords_f32.get())->buffer(), |
| .index_buffer = static_cast<SokolBuffer*>(indices_u16.get())->buffer(), |
| .fs_images[SLOT_tex] = static_cast<const SokolRenderImage*>(renderImage)->image(), |
| }; |
| |
| sg_apply_bindings(&bind); |
| sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_params, SG_RANGE_REF(vs_params)); |
| sg_draw(0, indices_u16->count(), 1); |
| } |
| |
| class SokolGradient : public RenderShader { |
| private: |
| Vec2D m_start; |
| Vec2D m_end; |
| int m_type; |
| std::vector<float> m_colors; |
| std::vector<float> m_stops; |
| bool m_isVisible = false; |
| |
| private: |
| // General gradient |
| SokolGradient(int type, const ColorInt colors[], const float stops[], size_t count) : |
| m_type(type) { |
| m_stops.resize(count); |
| m_colors.resize(count * 4); |
| for (int i = 0, colorIndex = 0; i < count; i++, colorIndex += 4) { |
| fillColorBuffer(&m_colors[colorIndex], colors[i]); |
| m_stops[i] = stops[i]; |
| if (m_colors[colorIndex + 3] > 0.0f) { |
| m_isVisible = true; |
| } |
| } |
| } |
| |
| public: |
| // Linear gradient |
| SokolGradient(float sx, |
| float sy, |
| float ex, |
| float ey, |
| const ColorInt colors[], |
| const float stops[], |
| size_t count) : |
| SokolGradient(1, colors, stops, count) { |
| m_start = Vec2D(sx, sy); |
| m_end = Vec2D(ex, ey); |
| } |
| |
| SokolGradient(float cx, |
| float cy, |
| float radius, |
| const ColorInt colors[], // [count] |
| const float stops[], // [count] |
| size_t count) : |
| SokolGradient(2, colors, stops, count) { |
| m_start = Vec2D(cx, cy); |
| m_end = Vec2D(cx + radius, cy); |
| } |
| |
| void bind(vs_path_params_t& vertexUniforms, fs_path_uniforms_t& fragmentUniforms) { |
| auto stopCount = m_stops.size(); |
| vertexUniforms.fillType = fragmentUniforms.fillType = m_type; |
| vertexUniforms.gradientStart = m_start; |
| vertexUniforms.gradientEnd = m_end; |
| fragmentUniforms.stopCount = stopCount; |
| for (int i = 0; i < stopCount; i++) { |
| auto colorBufferIndex = i * 4; |
| for (int j = 0; j < 4; j++) { |
| fragmentUniforms.colors[i][j] = m_colors[colorBufferIndex + j]; |
| } |
| fragmentUniforms.stops[i / 4][i % 4] = m_stops[i]; |
| } |
| } |
| }; |
| |
| rcp<RenderShader> SokolFactory::makeLinearGradient(float sx, |
| float sy, |
| float ex, |
| float ey, |
| const ColorInt colors[], |
| const float stops[], |
| size_t count) { |
| return rcp<RenderShader>(new SokolGradient(sx, sy, ex, ey, colors, stops, count)); |
| } |
| |
| rcp<RenderShader> SokolFactory::makeRadialGradient(float cx, |
| float cy, |
| float radius, |
| const ColorInt colors[], // [count] |
| const float stops[], // [count] |
| size_t count) { |
| return rcp<RenderShader>(new SokolGradient(cx, cy, radius, colors, stops, count)); |
| } |
| |
| class SokolRenderPaint : public RenderPaint { |
| private: |
| fs_path_uniforms_t m_uniforms = {0}; |
| rcp<RenderShader> m_shader; |
| RenderPaintStyle m_style; |
| std::unique_ptr<ContourStroke> m_stroke; |
| bool m_strokeDirty = false; |
| float m_strokeThickness = 0.0f; |
| StrokeJoin m_strokeJoin; |
| StrokeCap m_strokeCap; |
| |
| sg_buffer m_strokeVertexBuffer = {0}; |
| sg_buffer m_strokeIndexBuffer = {0}; |
| std::vector<std::size_t> m_StrokeOffsets; |
| |
| BlendMode m_blendMode = BlendMode::srcOver; |
| |
| public: |
| ~SokolRenderPaint() override { |
| sg_destroy_buffer(m_strokeVertexBuffer); |
| sg_destroy_buffer(m_strokeIndexBuffer); |
| } |
| |
| void color(ColorInt value) override { |
| fillColorBuffer(m_uniforms.colors[0], value); |
| m_uniforms.fillType = 0; |
| } |
| |
| void style(RenderPaintStyle value) override { |
| m_style = value; |
| |
| switch (m_style) { |
| case RenderPaintStyle::fill: |
| m_stroke = nullptr; |
| m_strokeDirty = false; |
| break; |
| case RenderPaintStyle::stroke: |
| m_stroke = std::make_unique<ContourStroke>(); |
| m_strokeDirty = true; |
| break; |
| } |
| } |
| |
| RenderPaintStyle style() const { return m_style; } |
| |
| void thickness(float value) override { |
| m_strokeThickness = value; |
| m_strokeDirty = true; |
| } |
| |
| void join(StrokeJoin value) override { |
| m_strokeJoin = value; |
| m_strokeDirty = true; |
| } |
| |
| void cap(StrokeCap value) override { |
| m_strokeCap = value; |
| m_strokeDirty = true; |
| } |
| |
| void invalidateStroke() override { |
| if (m_stroke) { |
| m_strokeDirty = true; |
| } |
| } |
| |
| void blendMode(BlendMode value) override { m_blendMode = value; } |
| BlendMode blendMode() const { return m_blendMode; } |
| |
| void shader(rcp<RenderShader> shader) override { m_shader = shader; } |
| |
| void draw(vs_path_params_t& vertexUniforms, SokolRenderPath* path) { |
| if (m_shader) { |
| static_cast<SokolGradient*>(m_shader.get())->bind(vertexUniforms, m_uniforms); |
| } |
| |
| sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vertexUniforms)); |
| sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_path_uniforms, SG_RANGE_REF(m_uniforms)); |
| if (m_stroke != nullptr) { |
| if (m_strokeDirty) { |
| static Mat2D identity; |
| m_stroke->reset(); |
| path->extrudeStroke(m_stroke.get(), |
| m_strokeJoin, |
| m_strokeCap, |
| m_strokeThickness / 2.0f, |
| identity); |
| m_strokeDirty = false; |
| |
| const std::vector<Vec2D>& strip = m_stroke->triangleStrip(); |
| |
| sg_destroy_buffer(m_strokeVertexBuffer); |
| sg_destroy_buffer(m_strokeIndexBuffer); |
| auto size = strip.size(); |
| if (size <= 2) { |
| m_strokeVertexBuffer = {0}; |
| m_strokeIndexBuffer = {0}; |
| return; |
| } |
| |
| m_strokeVertexBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_VERTEXBUFFER, |
| .data = |
| { |
| strip.data(), |
| strip.size() * sizeof(Vec2D), |
| }, |
| }); |
| |
| // Let's use a tris index buffer so we can keep the same sokol pipeline. |
| std::vector<uint16_t> indices; |
| |
| // Build them by stroke offsets (where each offset represents a sub-path, or a move |
| // to) |
| m_stroke->resetRenderOffset(); |
| m_StrokeOffsets.clear(); |
| while (true) { |
| std::size_t strokeStart, strokeEnd; |
| if (!m_stroke->nextRenderOffset(strokeStart, strokeEnd)) { |
| break; |
| } |
| std::size_t length = strokeEnd - strokeStart; |
| if (length > 2) { |
| for (std::size_t i = 0, end = length - 2; i < end; i++) { |
| if ((i % 2) == 1) { |
| indices.push_back(i + strokeStart); |
| indices.push_back(i + 1 + strokeStart); |
| indices.push_back(i + 2 + strokeStart); |
| } else { |
| indices.push_back(i + strokeStart); |
| indices.push_back(i + 2 + strokeStart); |
| indices.push_back(i + 1 + strokeStart); |
| } |
| } |
| m_StrokeOffsets.push_back(indices.size()); |
| } |
| } |
| |
| m_strokeIndexBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_INDEXBUFFER, |
| .data = |
| { |
| indices.data(), |
| indices.size() * sizeof(uint16_t), |
| }, |
| }); |
| } |
| if (m_strokeVertexBuffer.id == 0) { |
| return; |
| } |
| |
| sg_bindings bind = { |
| .vertex_buffers[0] = m_strokeVertexBuffer, |
| .index_buffer = m_strokeIndexBuffer, |
| }; |
| |
| sg_apply_bindings(&bind); |
| |
| m_stroke->resetRenderOffset(); |
| // path->drawStroke(m_stroke.get()); |
| std::size_t start = 0; |
| for (auto end : m_StrokeOffsets) { |
| sg_draw(start, end - start, 1); |
| start = end; |
| } |
| |
| } else { |
| path->drawFill(); |
| } |
| } |
| }; |
| |
| std::unique_ptr<RenderPaint> SokolFactory::makeRenderPaint() { |
| return std::make_unique<SokolRenderPaint>(); |
| } |
| |
| void SokolTessRenderer::restore() { |
| TessRenderer::restore(); |
| if (m_Stack.size() == 1) { |
| // When we've fully restored, immediately update clip to not wait for next draw. |
| applyClipping(); |
| m_currentPipeline = {0}; |
| } |
| } |
| |
| void SokolTessRenderer::applyClipping() { |
| if (!m_IsClippingDirty) { |
| return; |
| } |
| 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; |
| } |
| } |
| |
| vs_path_params_t vs_params = {.fillType = 0}; |
| fs_path_uniforms_t uniforms = {0}; |
| |
| // Decr any paths from the last clip that are gone. |
| std::unordered_set<RenderPath*> alreadyApplied; |
| |
| for (auto appliedPath : m_ClipPaths) { |
| bool decr = true; |
| for (auto nextClipPath : state.clipPaths) { |
| if (nextClipPath.path() == appliedPath.path()) { |
| decr = false; |
| alreadyApplied.insert(appliedPath.path()); |
| break; |
| } |
| } |
| if (decr) { |
| // Draw appliedPath.path() with decr pipeline |
| setPipeline(m_decClipPipeline); |
| vs_params.mvp = m_Projection * appliedPath.transform(); |
| sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vs_params)); |
| sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_path_uniforms, SG_RANGE_REF(uniforms)); |
| auto sokolPath = static_cast<SokolRenderPath*>(appliedPath.path()); |
| sokolPath->drawFill(); |
| } |
| } |
| |
| // Incr any paths that are added. |
| for (auto nextClipPath : state.clipPaths) { |
| if (alreadyApplied.count(nextClipPath.path())) { |
| // Already applied. |
| continue; |
| } |
| // Draw nextClipPath.path() with incr pipeline |
| setPipeline(m_incClipPipeline); |
| vs_params.mvp = m_Projection * nextClipPath.transform(); |
| sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vs_params)); |
| sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_path_uniforms, SG_RANGE_REF(uniforms)); |
| auto sokolPath = static_cast<SokolRenderPath*>(nextClipPath.path()); |
| sokolPath->drawFill(); |
| } |
| |
| // Pick which pipeline to use for draw path operations. |
| // TODO: something similar for draw mesh. |
| m_clipCount = state.clipPaths.size(); |
| |
| m_ClipPaths = state.clipPaths; |
| } |
| |
| void SokolTessRenderer::reset() { m_currentPipeline = {0}; } |
| void SokolTessRenderer::setPipeline(sg_pipeline pipeline) { |
| if (m_currentPipeline.id == pipeline.id) { |
| return; |
| } |
| m_currentPipeline = pipeline; |
| sg_apply_pipeline(pipeline); |
| } |
| |
| void SokolTessRenderer::drawPath(RenderPath* path, RenderPaint* paint) { |
| auto sokolPaint = static_cast<SokolRenderPaint*>(paint); |
| |
| applyClipping(); |
| vs_path_params_t vs_params = {.fillType = 0}; |
| const Mat2D& world = transform(); |
| |
| vs_params.mvp = m_Projection * world; |
| switch (sokolPaint->blendMode()) { |
| case BlendMode::srcOver: setPipeline(m_pathPipeline[m_clipCount]); break; |
| case BlendMode::screen: setPipeline(m_pathScreenPipeline[m_clipCount]); break; |
| case BlendMode::colorDodge: setPipeline(m_pathAdditivePipeline[m_clipCount]); break; |
| case BlendMode::multiply: setPipeline(m_pathMultiplyPipeline[m_clipCount]); break; |
| default: setPipeline(m_pathScreenPipeline[m_clipCount]); break; |
| } |
| |
| static_cast<SokolRenderPaint*>(paint)->draw(vs_params, static_cast<SokolRenderPath*>(path)); |
| } |
| |
| SokolRenderImageResource::SokolRenderImageResource(const uint8_t* bytes, |
| uint32_t width, |
| uint32_t height) : |
| m_gpuResource(sg_make_image((sg_image_desc){ |
| .width = (int)width, |
| .height = (int)height, |
| .data.subimage[0][0] = {bytes, width * height * 4}, |
| .pixel_format = SG_PIXELFORMAT_RGBA8, |
| })) {} |
| SokolRenderImageResource::~SokolRenderImageResource() { sg_destroy_image(m_gpuResource); } |
| |
| SokolRenderImage::SokolRenderImage(rcp<SokolRenderImageResource> image, |
| uint32_t width, |
| uint32_t height, |
| const Mat2D& uvTransform) : |
| |
| RenderImage(uvTransform), |
| m_gpuImage(image) |
| |
| { |
| float halfWidth = width / 2.0f; |
| float halfHeight = height / 2.0f; |
| Vec2D points[] = { |
| Vec2D(-halfWidth, -halfHeight), |
| Vec2D(halfWidth, -halfHeight), |
| Vec2D(halfWidth, halfHeight), |
| Vec2D(-halfWidth, halfHeight), |
| }; |
| m_vertexBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_VERTEXBUFFER, |
| .data = SG_RANGE(points), |
| }); |
| |
| Vec2D uv[] = { |
| uvTransform * Vec2D(0.0f, 0.0f), |
| uvTransform * Vec2D(1.0f, 0.0f), |
| uvTransform * Vec2D(1.0f, 1.0f), |
| uvTransform * Vec2D(0.0f, 1.0f), |
| }; |
| |
| m_uvBuffer = sg_make_buffer((sg_buffer_desc){ |
| .type = SG_BUFFERTYPE_VERTEXBUFFER, |
| .data = SG_RANGE(uv), |
| }); |
| } |
| |
| SokolRenderImage::~SokolRenderImage() { |
| sg_destroy_buffer(m_vertexBuffer); |
| sg_destroy_buffer(m_uvBuffer); |
| } |