diff --git a/src/renderer/wg_engine/meson.build b/src/renderer/wg_engine/meson.build index ff831e70..e5aa2f2c 100644 --- a/src/renderer/wg_engine/meson.build +++ b/src/renderer/wg_engine/meson.build @@ -10,6 +10,7 @@ source_file = [ 'tvgWgRenderTask.h', 'tvgWgShaderSrc.h', 'tvgWgShaderTypes.h', + 'tvgWgTessellator.h', 'tvgWgBindGroups.cpp', 'tvgWgCommon.cpp', 'tvgWgCompositor.cpp', @@ -20,7 +21,8 @@ source_file = [ 'tvgWgRenderTarget.cpp', 'tvgWgRenderTask.cpp', 'tvgWgShaderSrc.cpp', - 'tvgWgShaderTypes.cpp' + 'tvgWgShaderTypes.cpp', + 'tvgWgTessellator.cpp' ] wgpu_dep = [] diff --git a/src/renderer/wg_engine/tvgWgCommon.cpp b/src/renderer/wg_engine/tvgWgCommon.cpp index a56a56a0..6dc914e8 100644 --- a/src/renderer/wg_engine/tvgWgCommon.cpp +++ b/src/renderer/wg_engine/tvgWgCommon.cpp @@ -39,7 +39,6 @@ void WgContext::initialize(WGPUInstance instance, WGPUDevice device) assert(queue); // create shared webgpu assets - allocateBufferIndexFan(32768); samplerNearestRepeat = createSampler(WGPUFilterMode_Nearest, WGPUMipmapFilterMode_Nearest, WGPUAddressMode_Repeat); samplerLinearRepeat = createSampler(WGPUFilterMode_Linear, WGPUMipmapFilterMode_Linear, WGPUAddressMode_Repeat, 4); samplerLinearMirror = createSampler(WGPUFilterMode_Linear, WGPUMipmapFilterMode_Linear, WGPUAddressMode_MirrorRepeat, 4); @@ -61,7 +60,6 @@ void WgContext::release() releaseSampler(samplerLinearMirror); releaseSampler(samplerLinearRepeat); releaseSampler(samplerNearestRepeat); - releaseBuffer(bufferIndexFan); releaseQueue(queue); } @@ -222,26 +220,6 @@ bool WgContext::allocateBufferIndex(WGPUBuffer& buffer, const uint32_t* data, ui } -bool WgContext::allocateBufferIndexFan(uint64_t vertexCount) -{ - uint64_t indexCount = (vertexCount - 2) * 3; - if ((!bufferIndexFan) || (wgpuBufferGetSize(bufferIndexFan) < indexCount * sizeof(uint32_t))) { - tvg::Array indexes(indexCount); - for (size_t i = 0; i < vertexCount - 2; i++) { - indexes.push(0); - indexes.push(i + 1); - indexes.push(i + 2); - } - releaseBuffer(bufferIndexFan); - WGPUBufferDescriptor bufferDesc{ .usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, .size = indexCount * sizeof(uint32_t) }; - bufferIndexFan = wgpuDeviceCreateBuffer(device, &bufferDesc); - wgpuQueueWriteBuffer(queue, bufferIndexFan, 0, &indexes[0], indexCount * sizeof(uint32_t)); - return true; - } - return false; -} - - void WgContext::releaseBuffer(WGPUBuffer& buffer) { if (buffer) { diff --git a/src/renderer/wg_engine/tvgWgCommon.h b/src/renderer/wg_engine/tvgWgCommon.h index 16825ca6..0729fdb9 100644 --- a/src/renderer/wg_engine/tvgWgCommon.h +++ b/src/renderer/wg_engine/tvgWgCommon.h @@ -34,7 +34,6 @@ struct WgContext { WGPUQueue queue{}; WGPUTextureFormat preferredFormat{}; // shared webgpu assets - WGPUBuffer bufferIndexFan{}; WGPUSampler samplerNearestRepeat{}; WGPUSampler samplerLinearRepeat{}; WGPUSampler samplerLinearMirror{}; @@ -63,7 +62,6 @@ struct WgContext { bool allocateBufferUniform(WGPUBuffer& buffer, const void* data, uint64_t size); bool allocateBufferVertex(WGPUBuffer& buffer, const float* data, uint64_t size); bool allocateBufferIndex(WGPUBuffer& buffer, const uint32_t* data, uint64_t size); - bool allocateBufferIndexFan(uint64_t vertexCount); // release buffer objects void releaseBuffer(WGPUBuffer& buffer); diff --git a/src/renderer/wg_engine/tvgWgCompositor.cpp b/src/renderer/wg_engine/tvgWgCompositor.cpp index 81a01ff0..c27d827d 100644 --- a/src/renderer/wg_engine/tvgWgCompositor.cpp +++ b/src/renderer/wg_engine/tvgWgCompositor.cpp @@ -347,19 +347,6 @@ void WgCompositor::drawMesh(WgContext& context, WgMeshData* meshData) }; -void WgCompositor::drawMeshFan(WgContext& context, WgMeshData* meshData) -{ - assert(meshData); - assert(renderPassEncoder); - uint64_t icount = (meshData->vbuffer.count - 2) * 3; - uint64_t vsize = meshData->vbuffer.count * sizeof(Point); - uint64_t isize = icount * sizeof(uint32_t); - wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, stageBufferGeometry.vbuffer_gpu, meshData->voffset, vsize); - wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, context.bufferIndexFan, WGPUIndexFormat_Uint32, 0, isize); - wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, icount, 1, 0, 0, 0); -}; - - void WgCompositor::drawMeshImage(WgContext& context, WgMeshData* meshData) { assert(meshData); @@ -378,8 +365,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData) { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count); - if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return; + if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsShape; wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h()); // setup stencil rules @@ -389,8 +375,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData) wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline); // draw to stencil (first pass) - ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes) - drawMeshFan(context, (*p)); + drawMesh(context, &renderData->meshShape); // setup fill rules wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); @@ -406,7 +391,7 @@ void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData) wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); } // draw to color (second pass) - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); } @@ -414,8 +399,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData, { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count); - if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return; + if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsShape; // copy current render target data to dst target WgRenderTarget *target = currentTarget; @@ -431,8 +415,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData, wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline); // draw to stencil (first pass) - ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes) - drawMeshFan(context, (*p)); + drawMesh(context, &renderData->meshShape); // setup fill rules wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); @@ -450,7 +433,7 @@ void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData, wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]); } // draw to color (second pass) - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); } @@ -458,8 +441,7 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData) { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count); - if (renderData->renderSettingsShape.skip || renderData->meshGroupShapes.meshes.count == 0 || renderData->viewport.invalid()) return; + if (renderData->renderSettingsShape.skip || renderData->meshShape.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsShape; wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h()); // setup stencil rules @@ -469,13 +451,12 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData) wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline); // draw to stencil (first pass) - ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes) - drawMeshFan(context, (*p)); + drawMesh(context, &renderData->meshShape); // merge depth and stencil buffer wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); // setup fill rules wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); @@ -491,7 +472,7 @@ void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData) wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); } // draw to color (second pass) - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); } @@ -499,36 +480,33 @@ void WgCompositor::drawStrokes(WgContext& context, WgRenderDataShape* renderData { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count); - if (renderData->renderSettingsStroke.skip || renderData->meshGroupStrokes.meshes.count == 0 || renderData->viewport.invalid()) return; + if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsStroke; wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h()); // draw strokes to stencil (first pass) - for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) { - // setup stencil rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); - // draw to stencil (first pass) - drawMesh(context, renderData->meshGroupStrokes.meshes[i]); - // setup fill rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - if (settings.fillType == WgRenderSettingsType::Solid) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid); - } else if (settings.fillType == WgRenderSettingsType::Linear) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear); - } else if (settings.fillType == WgRenderSettingsType::Radial) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); - } - // draw to color (second pass) - drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]); + // setup stencil rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); + // draw to stencil (first pass) + drawMesh(context, &renderData->meshStrokes); + // setup fill rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + if (settings.fillType == WgRenderSettingsType::Solid) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid); + } else if (settings.fillType == WgRenderSettingsType::Linear) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear); + } else if (settings.fillType == WgRenderSettingsType::Radial) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); } + // draw to color (second pass) + drawMesh(context, &renderData->meshStrokesBBox); } @@ -536,8 +514,7 @@ void WgCompositor::blendStrokes(WgContext& context, WgRenderDataShape* renderDat { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count); - if (renderData->renderSettingsStroke.skip || renderData->meshGroupStrokes.meshes.count == 0 || renderData->viewport.invalid()) return; + if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsStroke; // copy current render target data to dst target WgRenderTarget *target = currentTarget; @@ -546,33 +523,31 @@ void WgCompositor::blendStrokes(WgContext& context, WgRenderDataShape* renderDat beginRenderPass(commandEncoder, target, false); wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h()); // draw strokes to stencil (first pass) - for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) { - // setup stencil rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); - // draw to stencil (first pass) - drawMesh(context, renderData->meshGroupStrokes.meshes[i]); - // setup fill rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, targetTemp0.bindGroupTexure, 0, nullptr); - uint32_t blendMethodInd = (uint32_t)blendMethod; - if (settings.fillType == WgRenderSettingsType::Solid) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid_blend[blendMethodInd]); - } else if (settings.fillType == WgRenderSettingsType::Linear) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear_blend[blendMethodInd]); - } else if (settings.fillType == WgRenderSettingsType::Radial) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]); - } - // draw to color (second pass) - drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]); + // setup stencil rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); + // draw to stencil (first pass) + drawMesh(context, &renderData->meshStrokes); + // setup fill rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, targetTemp0.bindGroupTexure, 0, nullptr); + uint32_t blendMethodInd = (uint32_t)blendMethod; + if (settings.fillType == WgRenderSettingsType::Solid) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid_blend[blendMethodInd]); + } else if (settings.fillType == WgRenderSettingsType::Linear) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear_blend[blendMethodInd]); + } else if (settings.fillType == WgRenderSettingsType::Radial) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial_blend[blendMethodInd]); } + // draw to color (second pass) + drawMesh(context, &renderData->meshStrokesBBox); }; @@ -580,43 +555,38 @@ void WgCompositor::clipStrokes(WgContext& context, WgRenderDataShape* renderData { assert(renderData); assert(renderPassEncoder); - assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count); - if (renderData->renderSettingsStroke.skip) return; - if (renderData->meshGroupStrokes.meshes.count == 0) return; - if (renderData->viewport.invalid()) return; + if (renderData->renderSettingsStroke.skip || renderData->meshStrokes.vbuffer.count == 0 || renderData->viewport.invalid()) return; WgRenderSettings& settings = renderData->renderSettingsStroke; wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x(), renderData->viewport.y(), renderData->viewport.w(), renderData->viewport.h()); // draw strokes to stencil (first pass) - for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) { - // setup stencil rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); - // draw to stencil (first pass) - drawMesh(context, renderData->meshGroupStrokes.meshes[i]); - // merge depth and stencil buffer - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil); - drawMeshFan(context, &renderData->meshDataBBox); - // setup fill rules - wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - if (settings.fillType == WgRenderSettingsType::Solid) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid); - } else if (settings.fillType == WgRenderSettingsType::Linear) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear); - } else if (settings.fillType == WgRenderSettingsType::Radial) { - wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); - wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); - } - // draw to color (second pass) - drawMeshFan(context, renderData->meshGroupStrokesBBox.meshes[i]); + // setup stencil rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); + // draw to stencil (first pass) + drawMesh(context, &renderData->meshStrokes); + // merge depth and stencil buffer + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.merge_depth_stencil); + drawMesh(context, &renderData->meshBBox); + // setup fill rules + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + if (settings.fillType == WgRenderSettingsType::Solid) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, stageBufferPaint[settings.bindGroupInd], 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.solid); + } else if (settings.fillType == WgRenderSettingsType::Linear) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.linear); + } else if (settings.fillType == WgRenderSettingsType::Radial) { + wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.gradientData.bindGroup, 0, nullptr); + wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.radial); } + // draw to color (second pass) + drawMesh(context, &renderData->meshStrokesBBox); } @@ -744,21 +714,19 @@ void WgCompositor::markupClipPath(WgContext& context, WgRenderDataShape* renderD { wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr); // markup stencil - if (renderData->meshGroupStrokes.meshes.count > 0) { + if (renderData->meshStrokes.vbuffer.count > 0) { WgRenderSettings& settings = renderData->renderSettingsStroke; wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.direct); - ARRAY_FOREACH(p, renderData->meshGroupStrokes.meshes) - drawMesh(context, (*p)); + drawMesh(context, &renderData->meshStrokes); } else { WGPURenderPipeline stencilPipeline = (renderData->fillRule == FillRule::NonZero) ? pipelines.nonzero : pipelines.evenodd; WgRenderSettings& settings = renderData->renderSettingsShape; wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline); - ARRAY_FOREACH(p, renderData->meshGroupShapes.meshes) - drawMeshFan(context, (*p)); + drawMesh(context, &renderData->meshShape); } } @@ -781,7 +749,7 @@ void WgCompositor::renderClipPath(WgContext& context, WgRenderDataPaint* paint) wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings0.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth); - drawMeshFan(context, &renderData0->meshDataBBox); + drawMesh(context, &renderData0->meshBBox); // merge clip pathes with AND logic for (auto p = paint->clips.begin() + 1; p < paint->clips.end(); ++p) { // get render data @@ -794,31 +762,31 @@ void WgCompositor::renderClipPath(WgContext& context, WgRenderDataPaint* paint) wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[190], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth_interm); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); // copy depth to stencil wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 1); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[190], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_depth_to_stencil); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); // clear depth current (keep stencil) wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); // clear depth original (keep stencil) wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings0.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth); - drawMeshFan(context, &renderData0->meshDataBBox); + drawMesh(context, &renderData0->meshBBox); // copy stencil to depth (clear stencil) wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[128], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.copy_stencil_to_depth); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); } } @@ -840,7 +808,7 @@ void WgCompositor::clearClipPath(WgContext& context, WgRenderDataPaint* paint) wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, stageBufferPaint[settings.bindGroupInd], 0, nullptr); wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[255], 0, nullptr); wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines.clear_depth); - drawMeshFan(context, &renderData->meshDataBBox); + drawMesh(context, &renderData->meshBBox); } } @@ -1019,4 +987,4 @@ bool WgCompositor::tritoneEffect(WgContext& context, WgRenderTarget* dst, const wgpuComputePassEncoderRelease(computePassEncoder); return true; -} \ No newline at end of file +} diff --git a/src/renderer/wg_engine/tvgWgCompositor.h b/src/renderer/wg_engine/tvgWgCompositor.h index dbc75f0c..e7612672 100644 --- a/src/renderer/wg_engine/tvgWgCompositor.h +++ b/src/renderer/wg_engine/tvgWgCompositor.h @@ -73,7 +73,6 @@ private: // base meshes draw void drawMesh(WgContext& context, WgMeshData* meshData); - void drawMeshFan(WgContext& context, WgMeshData* meshData); void drawMeshImage(WgContext& context, WgMeshData* meshData); // shapes diff --git a/src/renderer/wg_engine/tvgWgGeometry.cpp b/src/renderer/wg_engine/tvgWgGeometry.cpp index 210d47c2..56505fd1 100644 --- a/src/renderer/wg_engine/tvgWgGeometry.cpp +++ b/src/renderer/wg_engine/tvgWgGeometry.cpp @@ -22,62 +22,73 @@ #include "tvgWgGeometry.h" +//*********************************************************************** +// WgMeshData +//*********************************************************************** -/************************************************************************/ -/* Internal Class Implementation */ -/************************************************************************/ - -static WgGeometryBufferPool _pool; - -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ - -WgVertexBuffer* WgGeometryBufferPool::reqVertexBuffer(float scale) +void WgMeshData::bbox(const Point pmin, const Point pmax) { - ARRAY_FOREACH(p, vbuffers) { - if ((*p)->count == 0) { - (*p)->scale = scale; - return (*p); - } - } - vbuffers.push(new WgVertexBuffer(scale)); - return vbuffers.last(); -} - -void WgGeometryBufferPool::retVertexBuffer(WgVertexBuffer* buffer) -{ - buffer->reset(1.0f); -} - -WgIndexedVertexBuffer* WgGeometryBufferPool::reqIndexedVertexBuffer(float scale) -{ - ARRAY_FOREACH(p, ibuffers) { - if ((*p)->vcount == 0) { - (*p)->scale = scale; - return (*p); - } - } - ibuffers.push(new WgIndexedVertexBuffer(this, scale)); - return ibuffers.last(); -} - -void WgGeometryBufferPool::retIndexedVertexBuffer(WgIndexedVertexBuffer* buffer) -{ - buffer->reset(1.0f); + const float vdata[] = {pmin.x, pmin.y, pmax.x, pmin.y, pmax.x, pmax.y, pmin.x, pmax.y}; + const uint32_t idata[] = {0, 1, 2, 0, 2, 3}; + // setup vertex data + vbuffer.reserve(4); + vbuffer.count = 4; + memcpy(vbuffer.data, vdata, sizeof(vdata)); + // setup tex coords data + tbuffer.clear(); + // setup indexes data + ibuffer.reserve(6); + ibuffer.count = 6; + memcpy(ibuffer.data, idata, sizeof(idata)); } -WgGeometryBufferPool* WgGeometryBufferPool::instance() +void WgMeshData::imageBox(float w, float h) { - /* TODO: These could be easily addressed per threads. i.e _pool[thread_cnt]; */ - return &_pool; + const float vdata[] = {0.0f, 0.0f, w, 0.0f, w, h, 0.0f, h}; + const float tdata[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; + const uint32_t idata[] = {0, 1, 2, 0, 2, 3}; + // setup vertex data + vbuffer.reserve(4); + vbuffer.count = 4; + memcpy(vbuffer.data, vdata, sizeof(vdata)); + // setup tex coords data + tbuffer.reserve(4); + tbuffer.count = 4; + memcpy(tbuffer.data, tdata, sizeof(tdata)); + // setup indexes data + ibuffer.reserve(6); + ibuffer.count = 6; + memcpy(ibuffer.data, idata, sizeof(idata)); } -WgGeometryBufferPool::~WgGeometryBufferPool() +void WgMeshData::blitBox() { - //The indexed buffer may contain the vertex buffer, so free the memory in reverse order. - ARRAY_FOREACH(p, ibuffers) delete(*p); - ARRAY_FOREACH(p, vbuffers) delete(*p); + const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f}; + const float tdata[] = {+0.0f, +0.0f, +1.0f, +0.0f, +1.0f, +1.0f, +0.0f, +1.0f}; + const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 }; + // setup vertex data + vbuffer.reserve(4); + vbuffer.count = 4; + memcpy(vbuffer.data, vdata, sizeof(vdata)); + // setup tex coords data + tbuffer.reserve(4); + tbuffer.count = 4; + memcpy(tbuffer.data, tdata, sizeof(tdata)); + // setup indexes data + ibuffer.reserve(6); + ibuffer.count = 6; + memcpy(ibuffer.data, idata, sizeof(idata)); +} + + +void WgMeshData::clear() +{ + vbuffer.clear(); + tbuffer.clear(); + ibuffer.clear(); + voffset = 0; + toffset = 0; + ioffset = 0; } \ No newline at end of file diff --git a/src/renderer/wg_engine/tvgWgGeometry.h b/src/renderer/wg_engine/tvgWgGeometry.h index 35a66c67..62363476 100644 --- a/src/renderer/wg_engine/tvgWgGeometry.h +++ b/src/renderer/wg_engine/tvgWgGeometry.h @@ -25,523 +25,20 @@ #include #include "tvgMath.h" -#include "tvgRender.h" +#include "tvgArray.h" +struct WgMeshData { + Array vbuffer; + Array tbuffer; + Array ibuffer; + size_t voffset{}; + size_t toffset{}; + size_t ioffset{}; -// default size of vertex and index buffers -#define WG_DEFAULT_BUFFER_SIZE 2048 - -struct WgVertexBuffer; -struct WgIndexedVertexBuffer; - -struct WgGeometryBufferPool -{ -private: - Array vbuffers; - Array ibuffers; - -public: - ~WgGeometryBufferPool(); - - WgVertexBuffer* reqVertexBuffer(float scale = 1.0f); - WgIndexedVertexBuffer* reqIndexedVertexBuffer(float scale = 1.0f); - void retVertexBuffer(WgVertexBuffer* buffer); - void retIndexedVertexBuffer(WgIndexedVertexBuffer* buffer); - - static WgGeometryBufferPool* instance(); //return the shared buffer pool -}; - - -// simple vertex buffer -struct WgVertexBuffer -{ - Point* data; // vertex buffer - struct Distance { - float interval; // distance to previous point - float length; // distance to the first point through all previous points - } *dist; - uint32_t count = 0; - uint32_t reserved = WG_DEFAULT_BUFFER_SIZE; - float scale; // tesselation scale - bool closed = false; - - // callback for external process of polyline - using onPolylineFn = std::function; - - WgVertexBuffer(float scale = 1.0f) : scale(scale) - { - data = tvg::malloc(sizeof(Point) * reserved); - dist = tvg::malloc(sizeof(Distance) * reserved); - } - - ~WgVertexBuffer() - { - tvg::free(data); - tvg::free(dist); - } - - // reset buffer - void reset(float scale) - { - count = 0; - closed = false; - this->scale = scale; - } - - // get the last point with optional index offset from the end - Point last(size_t offset = 0) const - { - return data[count - offset - 1]; - } - - // get the last distance with optional index offset from the end - float lastDist(size_t offset = 0) const - { - return dist[count - offset - 1].interval; - } - - // get total length - float total() const - { - return (count == 0) ? 0.0f : dist[count-1].length; - } - - // get next vertex index by length using binary search - size_t getIndexByLength(float len) const - { - if (count <= 1) return 0; - size_t left = 0; - size_t right = count - 1; - while (left <= right) { - size_t mid = left + (right - left) / 2; - if (dist[mid].length == len) return mid; - else if (dist[mid].length < len) left = mid + 1; - else right = mid - 1; - } - return right + 1; - } - - // get min and max values of the buffer - void getMinMax(Point& pmin, Point& pmax) const - { - if (count == 0) return; - pmax = pmin = data[0]; - for (size_t i = 1; i < count; i++) { - pmin = min(pmin, data[i]); - pmax = max(pmax, data[i]); - } - } - - // update points distancess to the prev point and total length - void updateDistances() - { - if (count == 0) return; - dist[0].interval = 0.0f; - dist[0].length = 0.0f; - for (size_t i = 1; i < count; i++) { - dist[i].interval = tvg::length(data[i-1] - data[i]); - dist[i].length = dist[i-1].length + dist[i].interval; - } - } - - // close vertex buffer - void close() - { - // check if last point is not to close to the first point - if (length(data[0] - last()) > 0.015625f) { - append(data[0]); - } - closed = true; - } - - // append point - void append(const Point& p) - { - if (count >= reserved) { - reserved *= 2; - data = tvg::realloc(data, reserved * sizeof(Point)); - dist = tvg::realloc(dist, reserved * sizeof(Distance)); - } - data[count++] = p; - } - - // append source vertex buffer in index range from start to end (end not included) - void appendRange(const WgVertexBuffer& buff, size_t start_index, size_t end_index) - { - for (size_t i = start_index; i < end_index; i++) { - append(buff.data[i]); - } - } - - // append circle (list of triangles) - void appendCircle(float radius) - { - // get approx circle length - float clen = 2.0f * radius * MATH_PI; - size_t nsegs = std::max((uint32_t)(clen * scale / 8), 16U); - // append circle^ - Point prev { std::sin(0.0f) * radius, std::cos(0.0f) * radius }; - for (size_t i = 1; i <= nsegs; i++) { - float t = (2.0f * MATH_PI * i) / nsegs; - Point curr { std::sin(t) * radius, std::cos(t) * radius }; - append(Point{0.0f, 0.0f}); - append(prev); - append(curr); - prev = curr; - } - } - - // append cubic spline - void appendCubic(const Point& v0, const Point& v1, const Point& v2, const Point& v3) - { - // get approx cubic length - float clen = (tvg::length(v0 - v1) + tvg::length(v1 - v2) + tvg::length(v2 - v3)); - size_t nsegs = std::max((uint32_t)(clen * scale / 16), 16U); - // append cubic - Bezier bezier{v0, v1, v2, v3}; - for (size_t i = 1; i <= nsegs; i++) { - append(bezier.at((float)i / nsegs)); - } - } - - // decode path with callback for external prcesses - void decodePath(const RenderShape& rshape, bool update_dist, onPolylineFn onPolyline, bool trim = false) - { - // decode path - reset(scale); - - PathCommand *cmds, *trimmedCmds = nullptr; - Point *pts, *trimmedPts = nullptr; - uint32_t cmdCnt{}; - - if (trim) { - RenderPath trimmedPath; - if (!rshape.stroke->trim.trim(rshape.path, trimmedPath)) return; - - cmds = trimmedCmds = trimmedPath.cmds.data; - cmdCnt = trimmedPath.cmds.count; - pts = trimmedPts = trimmedPath.pts.data; - - trimmedPath.cmds.data = nullptr; - trimmedPath.pts.data = nullptr; - } else { - cmds = rshape.path.cmds.data; - cmdCnt = rshape.path.cmds.count; - pts = rshape.path.pts.data; - } - - size_t pntIndex = 0; - for (uint32_t i = 0; i < cmdCnt; i++) { - auto& cmd = cmds[i]; - if (cmd == PathCommand::MoveTo) { - // after path decoding we need to update distances and total length - if (update_dist) updateDistances(); - if ((onPolyline) && (count > 0)) onPolyline(*this); - reset(scale); - append(pts[pntIndex]); - pntIndex++; - } else if (cmd == PathCommand::LineTo) { - append(pts[pntIndex]); - pntIndex++; - } else if (cmd == PathCommand::Close) { - close(); - // proceed path if close command is not the last command and next command is LineTo or CubicTo - if (i + 1 < cmdCnt && (cmds[i + 1] == PathCommand::LineTo || cmds[i + 1] == PathCommand::CubicTo)) { - // proceed current path - if (update_dist) updateDistances(); - if ((count > 0) && (onPolyline)) onPolyline(*this); - // append closing point of current path as a first point of the new path - Point last_pt = last(); - reset(scale); - append(last_pt); - } - } else if (cmd == PathCommand::CubicTo) { - // append tesselated cubic spline with scale param - appendCubic(data[count - 1], pts[pntIndex + 0], pts[pntIndex + 1], pts[pntIndex + 2]); - pntIndex += 3; - } - } - - tvg::free(trimmedCmds); - tvg::free(trimmedPts); - - // after path decoding we need to update distances and total length - if (update_dist) updateDistances(); - if ((count > 0) && (onPolyline)) onPolyline(*this); - reset(scale); - } -}; - - -struct WgIndexedVertexBuffer -{ - Point* vbuff; - uint32_t* ibuff; - uint32_t vcount = 0, icount = 0; - size_t vreserved = WG_DEFAULT_BUFFER_SIZE; - size_t ireserved = WG_DEFAULT_BUFFER_SIZE * 2; - WgGeometryBufferPool* pool; - WgVertexBuffer* dashed; // intermediate buffer for stroke dashing - float scale; - - WgIndexedVertexBuffer(WgGeometryBufferPool* pool, float scale = 1.0f) : pool(pool), scale(scale) - { - vbuff = tvg::malloc(sizeof(Point) * vreserved); - ibuff = tvg::malloc(sizeof(uint32_t) * ireserved); - dashed = pool->reqVertexBuffer(); - } - - ~WgIndexedVertexBuffer() - { - pool->retVertexBuffer(dashed); - tvg::free(vbuff); - tvg::free(ibuff); - } - - // reset buffer - void reset(float scale) - { - icount = vcount = 0; - this->scale = scale; - } - - void growIndex(size_t grow) - { - if (icount + grow >= ireserved) { - ireserved *= 2; - ibuff = tvg::realloc(ibuff, ireserved * sizeof(uint32_t)); - } - } - - void growVertex(size_t grow) - { - if (vcount + grow >= vreserved) { - vreserved *= 2; - vbuff = tvg::realloc(vbuff, vreserved * sizeof(Point)); - } - } - - // get min and max values of the buffer - void getMinMax(Point& pmin, Point& pmax) const - { - if (vcount == 0) return; - pmax = pmin = vbuff[0]; - for (size_t i = 1; i < vcount; i++) { - pmin = min(pmin, vbuff[i]); - pmax = max(pmax, vbuff[i]); - } - } - - // append quad - two triangles formed from four points - void appendQuad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) - { - growVertex(4); - vbuff[vcount+0] = p0; - vbuff[vcount+1] = p1; - vbuff[vcount+2] = p2; - vbuff[vcount+3] = p3; - - growIndex(6); - ibuff[icount+0] = vcount + 0; - ibuff[icount+1] = vcount + 1; - ibuff[icount+2] = vcount + 2; - ibuff[icount+3] = vcount + 1; - ibuff[icount+4] = vcount + 3; - ibuff[icount+5] = vcount + 2; - - vcount += 4; - icount += 6; - } - - - void appendStrokesDashed(const WgVertexBuffer& buff, const RenderStroke* rstroke) - { - dashed->reset(scale); - auto& dash = rstroke->dash; - - if (buff.count < 2) return; - - uint32_t index = 0; - auto total = dash.pattern[index]; - auto length = (dash.count % 2) ? dash.length * 2 : dash.length; - - // normalize dash offset - auto dashOffset = dash.offset; - while(dashOffset < 0) dashOffset += length; - while(dashOffset > length) dashOffset -= length; - auto gap = false; - - // scip dashes by offset - if (dashOffset > 0.0f) { - while(total <= dashOffset) { - index = (index + 1) % dash.count; - total += dash.pattern[index]; - gap = !gap; - } - total -= dashOffset; - } - - // iterate by polyline points - for (uint32_t i = 0; i < buff.count - 1; i++) { - if (!gap) dashed->append(buff.data[i]); - // move inside polyline segment - while(total < buff.dist[i+1].interval) { - // get current point - dashed->append(tvg::lerp(buff.data[i], buff.data[i+1], total / buff.dist[i+1].interval)); - // update current state - index = (index + 1) % dash.count; - total += dash.pattern[index]; - // preceed stroke if dash - if (!gap) { - dashed->updateDistances(); - appendStrokes(*dashed, rstroke); - dashed->reset(scale); - } - gap = !gap; - } - // update current subline length - total -= buff.dist[i+1].interval; - } - // draw last subline - if (!gap) { - dashed->append(buff.last()); - dashed->updateDistances(); - appendStrokes(*dashed, rstroke); - } - } - - // append buffer with optional offset - void appendBuffer(const WgVertexBuffer& buff, Point offset = Point{0.0f, 0.0f}) - { - growVertex(buff.count); - growIndex(buff.count); - - for (uint32_t i = 0; i < buff.count; i++) { - vbuff[vcount + i] = buff.data[i] + offset; - ibuff[icount + i] = vcount + i; - } - vcount += buff.count; - icount += buff.count; - }; - - void appendLine(const Point& v0, const Point& v1, float dist, float halfWidth) - { - if(tvg::zero(dist)) return; - Point sub = v1 - v0; - Point nrm = {sub.y / dist * halfWidth, -sub.x / dist * halfWidth}; - appendQuad(v0 - nrm, v0 + nrm, v1 - nrm, v1 + nrm); - } - - void appendBevel(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth) - { - if(tvg::zero(dist1) || tvg::zero(dist2)) return; - Point sub1 = v1 - v0; - Point sub2 = v2 - v1; - Point nrm1 {sub1.y / dist1 * halfWidth, -sub1.x / dist1 * halfWidth}; - Point nrm2 {sub2.y / dist2 * halfWidth, -sub2.x / dist2 * halfWidth}; - appendQuad(v1 - nrm1, v1 + nrm1, v1 - nrm2, v1 + nrm2); - } - - void appendMiter(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth, float miterLimit) - { - if(tvg::zero(dist1) || tvg::zero(dist2)) return; - auto sub1 = v1 - v0; - auto sub2 = v2 - v1; - auto nrm1 = Point{+sub1.y / dist1, -sub1.x / dist1}; - auto nrm2 = Point{+sub2.y / dist2, -sub2.x / dist2}; - auto offset1 = nrm1 * halfWidth; - auto offset2 = nrm2 * halfWidth; - auto nrm = nrm1 + nrm2; - normalize(nrm); - float cosine = dot(nrm, nrm1); - if (tvg::zero(cosine)) return; - float angle = std::acos(dot(nrm1, -nrm2)); - if (tvg::zero(angle) || tvg::equal(angle, MATH_PI)) return; - float miterRatio = 1.0f / (std::sin(angle) * 0.5f); - if (miterRatio <= miterLimit) { - appendQuad(v1 + nrm * (halfWidth / cosine), v1 + offset2, v1 + offset1, v1); - appendQuad(v1 - nrm * (halfWidth / cosine), v1 - offset2, v1 - offset1, v1); - } else { - appendQuad(v1 - offset1, v1 + offset2, v1 - offset2, v1 + offset1); - } - } - - void appendSquare(Point v0, Point v1, float dist, float halfWidth) - { - // zero length segment with square cap still should be rendered as a point - only the caps are visible - if(tvg::zero(dist)) { - appendQuad(v1 + Point{-halfWidth, -halfWidth}, v1 + Point{-halfWidth, halfWidth}, v1 + Point{halfWidth, -halfWidth}, v1 + Point{halfWidth, halfWidth}); - return; - } - Point sub = v1 - v0; - Point offset = sub / dist * halfWidth; - Point nrm = {+offset.y, -offset.x}; - appendQuad(v1 - nrm, v1 + nrm, v1 + offset - nrm, v1 + offset + nrm); - } - - void appendStrokes(const WgVertexBuffer& buff, const RenderStroke* rstroke) - { - assert(rstroke); - // empty buffer gueard - if (buff.count < 2) return; - - float halfWidth = rstroke->width * 0.5f; - - // append core lines - for (size_t i = 1; i < buff.count; i++) { - appendLine(buff.data[i-1], buff.data[i], buff.dist[i].interval, halfWidth); - } - - // append caps (square) - if ((rstroke->cap == StrokeCap::Square) && !buff.closed) { - appendSquare(buff.data[1], buff.data[0], buff.dist[1].interval, halfWidth); - appendSquare(buff.last(1), buff.last(0), buff.lastDist(0), halfWidth); - } - - // append round joints and caps - if ((rstroke->join == StrokeJoin::Round) || (rstroke->cap == StrokeCap::Round)) { - // create mesh for circle - WgVertexBuffer circle; - circle.reset(buff.scale); - circle.appendCircle(halfWidth); - // append caps (round) - if (rstroke->cap == StrokeCap::Round) { - appendBuffer(circle, buff.data[0]); - // append ending cap if polyline is not closed - if (!buff.closed) appendBuffer(circle, buff.last()); - } - // append joints (round) - if (rstroke->join == StrokeJoin::Round) { - for (size_t i = 1; i < buff.count - 1; i++) { - appendBuffer(circle, buff.data[i]); - } - if (buff.closed) appendBuffer(circle, buff.last()); - } - } - - // append closed endings - if (buff.closed) { - // close by bevel - if (rstroke->join == StrokeJoin::Bevel) { - appendBevel(buff.last(1), buff.data[0], buff.data[1], buff.lastDist(0), buff.dist[1].interval, halfWidth); - // close by mitter - } else if (rstroke->join == StrokeJoin::Miter) { - appendMiter(buff.last(1), buff.data[0], buff.data[1], buff.lastDist(0), buff.dist[1].interval, halfWidth, rstroke->miterlimit); - } - } - - // append joints (bevel) - if (rstroke->join == StrokeJoin::Bevel) { - for (size_t i = 1; i < buff.count - 1; i++) { - appendBevel(buff.data[i-1], buff.data[i], buff.data[i+1], buff.dist[i].interval, buff.dist[i+1].interval, halfWidth); - } - // append joints (mitter) - } else if (rstroke->join == StrokeJoin::Miter) { - for (size_t i = 1; i < buff.count - 1; i++) { - appendMiter(buff.data[i-1], buff.data[i], buff.data[i+1], buff.dist[i].interval, buff.dist[i+1].interval, halfWidth, rstroke->miterlimit); - } - } - } + void bbox(const Point pmin, const Point pmax); + void imageBox(float w, float h); + void blitBox(); + void clear(); }; #endif // _TVG_WG_GEOMETRY_H_ \ No newline at end of file diff --git a/src/renderer/wg_engine/tvgWgRenderData.cpp b/src/renderer/wg_engine/tvgWgRenderData.cpp index 6e646777..1a449aa1 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.cpp +++ b/src/renderer/wg_engine/tvgWgRenderData.cpp @@ -22,127 +22,10 @@ */ #include -#include "tvgMath.h" +#include "tvgWgTessellator.h" #include "tvgWgRenderData.h" #include "tvgWgShaderTypes.h" -//*********************************************************************** -// WgMeshData -//*********************************************************************** - -void WgMeshData::update(const WgVertexBuffer& vertexBuffer) -{ - assert(vertexBuffer.count > 2); - // setup vertex data - vbuffer.reserve(vertexBuffer.count); - vbuffer.count = vertexBuffer.count; - memcpy(vbuffer.data, vertexBuffer.data, sizeof(vertexBuffer.data[0])*vertexBuffer.count); - // setup tex coords data - tbuffer.clear(); -} - - -void WgMeshData::update(const WgIndexedVertexBuffer& vertexBufferInd) -{ - assert(vertexBufferInd.vcount > 2); - // setup vertex data - vbuffer.reserve(vertexBufferInd.vcount); - vbuffer.count = vertexBufferInd.vcount; - memcpy(vbuffer.data, vertexBufferInd.vbuff, sizeof(vertexBufferInd.vbuff[0])*vertexBufferInd.vcount); - // setup tex coords data - tbuffer.clear(); - // copy index data - ibuffer.reserve(vertexBufferInd.icount); - ibuffer.count = vertexBufferInd.icount; - memcpy(ibuffer.data, vertexBufferInd.ibuff, sizeof(vertexBufferInd.ibuff[0])*vertexBufferInd.icount); -}; - - -void WgMeshData::bbox(const Point pmin, const Point pmax) -{ - const float data[] = {pmin.x, pmin.y, pmax.x, pmin.y, pmax.x, pmax.y, pmin.x, pmax.y}; - // setup vertex data - vbuffer.reserve(4); - vbuffer.count = 4; - memcpy(vbuffer.data, data, sizeof(data)); - // setup tex coords data - tbuffer.clear(); -} - - -void WgMeshData::imageBox(float w, float h) -{ - const float vdata[] = {0.0f, 0.0f, w, 0.0f, w, h, 0.0f, h}; - const float tdata[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; - const uint32_t idata[] = {0, 1, 2, 0, 2, 3}; - // setup vertex data - vbuffer.reserve(4); - vbuffer.count = 4; - memcpy(vbuffer.data, vdata, sizeof(vdata)); - // setup tex coords data - tbuffer.reserve(4); - tbuffer.count = 4; - memcpy(tbuffer.data, tdata, sizeof(tdata)); - // setup indexes data - ibuffer.reserve(6); - ibuffer.count = 6; - memcpy(ibuffer.data, idata, sizeof(idata)); -} - - -void WgMeshData::blitBox() -{ - const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f}; - const float tdata[] = {+0.0f, +0.0f, +1.0f, +0.0f, +1.0f, +1.0f, +0.0f, +1.0f}; - const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 }; - // setup vertex data - vbuffer.reserve(4); - vbuffer.count = 4; - memcpy(vbuffer.data, vdata, sizeof(vdata)); - // setup tex coords data - tbuffer.reserve(4); - tbuffer.count = 4; - memcpy(tbuffer.data, tdata, sizeof(tdata)); - // setup indexes data - ibuffer.reserve(6); - ibuffer.count = 6; - memcpy(ibuffer.data, idata, sizeof(idata)); -} - -//*********************************************************************** -// WgMeshDataGroup -//*********************************************************************** - -void WgMeshDataGroup::append(const WgVertexBuffer& vertexBuffer) -{ - assert(vertexBuffer.count >= 3); - meshes.push(new WgMeshData()); - meshes.last()->update(vertexBuffer); -} - - -void WgMeshDataGroup::append(const WgIndexedVertexBuffer& vertexBufferInd) -{ - assert(vertexBufferInd.vcount >= 3); - meshes.push(new WgMeshData()); - meshes.last()->update(vertexBufferInd); -} - - -void WgMeshDataGroup::append(const Point pmin, const Point pmax) -{ - meshes.push(new WgMeshData()); - meshes.last()->bbox(pmin, pmax); -} - - -void WgMeshDataGroup::release() -{ - ARRAY_FOREACH(p, meshes) delete *p; - meshes.clear(); -}; - - //*********************************************************************** // WgImageData //*********************************************************************** @@ -257,95 +140,75 @@ void WgRenderDataPaint::updateClips(tvg::Array &clips) { // WgRenderDataShape //*********************************************************************** -void WgRenderDataShape::appendShape(const WgVertexBuffer& vertexBuffer) +void WgRenderDataShape::updateBBox(BBox bb) { - if (vertexBuffer.count < 3) return; - Point pmin{}, pmax{}; - vertexBuffer.getMinMax(pmin, pmax); - meshGroupShapes.append(vertexBuffer); - meshGroupShapesBBox.append(pmin, pmax); - updateBBox(pmin, pmax); + bbox.min = tvg::min(bbox.min, bb.min); + bbox.max = tvg::max(bbox.max, bb.max); } -void WgRenderDataShape::appendStroke(const WgIndexedVertexBuffer& vertexBufferInd) +void WgRenderDataShape::updateAABB(const Matrix& matrix) { - if (vertexBufferInd.vcount < 3) return; - Point pmin{}, pmax{}; - vertexBufferInd.getMinMax(pmin, pmax); - meshGroupStrokes.append(vertexBufferInd); - meshGroupStrokesBBox.append(pmin, pmax); - updateBBox(pmin, pmax); -} - - -void WgRenderDataShape::updateBBox(Point pmin, Point pmax) -{ - pMin.x = std::min(pMin.x, pmin.x); - pMin.y = std::min(pMin.y, pmin.y); - pMax.x = std::max(pMax.x, pmax.x); - pMax.y = std::max(pMax.y, pmax.y); -} - - -void WgRenderDataShape::updateAABB(const Matrix& tr) { - auto p0 = Point{pMin.x, pMin.y} * tr; - auto p1 = Point{pMax.x, pMin.y} * tr; - auto p2 = Point{pMin.x, pMax.y} * tr; - auto p3 = Point{pMax.x, pMax.y} * tr; + auto p0 = Point{bbox.min.x, bbox.min.y} * matrix; + auto p1 = Point{bbox.max.x, bbox.min.y} * matrix; + auto p2 = Point{bbox.min.x, bbox.max.y} * matrix; + auto p3 = Point{bbox.max.x, bbox.max.y} * matrix; aabb.min = {std::min(std::min(p0.x, p1.x), std::min(p2.x, p3.x)), std::min(std::min(p0.y, p1.y), std::min(p2.y, p3.y))}; aabb.max = {std::max(std::max(p0.x, p1.x), std::max(p2.x, p3.x)), std::max(std::max(p0.y, p1.y), std::max(p2.y, p3.y))}; } -void WgRenderDataShape::updateMeshes(const RenderShape &rshape, const Matrix& tr, WgGeometryBufferPool* pool) +void WgRenderDataShape::updateMeshes(const RenderShape &rshape, RenderUpdateFlag flag, const Matrix& matrix) { releaseMeshes(); strokeFirst = rshape.strokeFirst(); - // get object scale - float scale = std::max(std::min(length(Point{tr.e11 + tr.e12,tr.e21 + tr.e22}), 8.0f), 1.0f); + // update fill shapes + if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Path)) { + meshShape.clear(); - // path decoded vertex buffer - auto pbuff = pool->reqVertexBuffer(scale); + WgBWTessellator bwTess{&meshShape}; + if (rshape.trimpath()) { + RenderPath trimmedPath; + if (rshape.stroke->trim.trim(rshape.path, trimmedPath)) + bwTess.tessellate(trimmedPath, matrix); + } else bwTess.tessellate(rshape.path, matrix); - pbuff->decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) { - appendShape(path_buff); - if ((rshape.stroke) && (rshape.stroke->width > 0)) proceedStrokes(rshape.stroke, path_buff, pool); - }, rshape.trimpath()); + if (meshShape.ibuffer.count > 0) {; + auto bbox = bwTess.getBBox(); + meshShapeBBox.bbox(bbox.min, bbox.max); + updateBBox(bbox); + } else meshShape.clear(); + } + + // update strokes shapes + if (rshape.stroke && (flag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke | RenderUpdateFlag::Transform))) { + meshStrokes.clear(); + + WgStroker stroker{&meshStrokes, matrix}; + stroker.stroke(&rshape); + + if (meshStrokes.ibuffer.count > 0) { + auto bbox = stroker.getBBox(); + meshStrokesBBox.bbox(bbox.min, bbox.max); + updateBBox(bbox); + } else meshStrokes.clear(); + } // update shapes bbox (with empty path handling) - if ((this->meshGroupShapesBBox.meshes.count > 0 ) || - (this->meshGroupStrokesBBox.meshes.count > 0)) { - updateAABB(tr); + if ((meshShape.vbuffer.count > 0 ) || (meshStrokes.vbuffer.count > 0)) { + updateAABB(matrix); } else aabb = {{0, 0}, {0, 0}}; - meshDataBBox.bbox(pMin, pMax); - - pool->retVertexBuffer(pbuff); -} - - -void WgRenderDataShape::proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool) -{ - assert(rstroke); - auto strokesGenerator = pool->reqIndexedVertexBuffer(buff.scale); - if (rstroke->dash.length < DASH_PATTERN_THRESHOLD) strokesGenerator->appendStrokes(buff, rstroke); - else strokesGenerator->appendStrokesDashed(buff, rstroke); - - appendStroke(*strokesGenerator); - - pool->retIndexedVertexBuffer(strokesGenerator); + meshBBox.bbox(bbox.min, bbox.max); } void WgRenderDataShape::releaseMeshes() { - meshGroupStrokesBBox.release(); - meshGroupStrokes.release(); - meshGroupShapesBBox.release(); - meshGroupShapes.release(); - pMin = {FLT_MAX, FLT_MAX}; - pMax = {0.0f, 0.0f}; + meshStrokes.clear(); + meshShape.clear(); + bbox.min = {FLT_MAX, FLT_MAX}; + bbox.max = {0.0f, 0.0f}; aabb = {{0, 0}, {0, 0}}; clips.clear(); } @@ -379,10 +242,7 @@ WgRenderDataShape* WgRenderDataShapePool::allocate(WgContext& context) void WgRenderDataShapePool::free(WgContext& context, WgRenderDataShape* renderData) { - renderData->meshGroupShapes.release(); - renderData->meshGroupShapesBBox.release(); - renderData->meshGroupStrokes.release(); - renderData->meshGroupStrokesBBox.release(); + renderData->releaseMeshes(); renderData->clips.clear(); mPool.push(renderData); } @@ -620,7 +480,6 @@ void WgStageBufferGeometry::append(WgMeshData* meshData) uint32_t vsize = meshData->vbuffer.count * sizeof(meshData->vbuffer[0]); uint32_t tsize = meshData->tbuffer.count * sizeof(meshData->tbuffer[0]); uint32_t isize = meshData->ibuffer.count * sizeof(meshData->ibuffer[0]); - vmaxcount = std::max(vmaxcount, meshData->vbuffer.count); // append vertex data if (vbuffer.reserved < vbuffer.count + vsize) vbuffer.grow(std::max(vsize, vbuffer.reserved)); @@ -648,19 +507,13 @@ void WgStageBufferGeometry::append(WgMeshData* meshData) } -void WgStageBufferGeometry::append(WgMeshDataGroup* meshDataGroup) -{ - ARRAY_FOREACH(p, meshDataGroup->meshes) append(*p); -} - - void WgStageBufferGeometry::append(WgRenderDataShape* renderDataShape) { - append(&renderDataShape->meshGroupShapes); - append(&renderDataShape->meshGroupShapesBBox); - append(&renderDataShape->meshGroupStrokes); - append(&renderDataShape->meshGroupStrokesBBox); - append(&renderDataShape->meshDataBBox); + append(&renderDataShape->meshShape); + append(&renderDataShape->meshShapeBBox); + append(&renderDataShape->meshStrokes); + append(&renderDataShape->meshStrokesBBox); + append(&renderDataShape->meshBBox); } @@ -681,7 +534,6 @@ void WgStageBufferGeometry::clear() { vbuffer.clear(); ibuffer.clear(); - vmaxcount = 0; } @@ -689,5 +541,4 @@ void WgStageBufferGeometry::flush(WgContext& context) { context.allocateBufferVertex(vbuffer_gpu, (float *)vbuffer.data, vbuffer.count); context.allocateBufferIndex(ibuffer_gpu, (uint32_t *)ibuffer.data, ibuffer.count); - context.allocateBufferIndexFan(vmaxcount); } diff --git a/src/renderer/wg_engine/tvgWgRenderData.h b/src/renderer/wg_engine/tvgWgRenderData.h index 59687de2..fe65a027 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.h +++ b/src/renderer/wg_engine/tvgWgRenderData.h @@ -27,30 +27,6 @@ #include "tvgWgGeometry.h" #include "tvgWgShaderTypes.h" -struct WgMeshData { - Array vbuffer; - Array tbuffer; - Array ibuffer; - size_t voffset{}; - size_t toffset{}; - size_t ioffset{}; - - void update(const WgVertexBuffer& vertexBuffer); - void update(const WgIndexedVertexBuffer& vertexBufferInd); - void bbox(const Point pmin, const Point pmax); - void imageBox(float w, float h); - void blitBox(); -}; - -struct WgMeshDataGroup { - Array meshes{}; - - void append(const WgVertexBuffer& vertexBuffer); - void append(const WgIndexedVertexBuffer& vertexBufferInd); - void append(const Point pmin, const Point pmax); - void release(); -}; - struct WgImageData { WGPUTexture texture{}; WGPUTextureView textureView{}; @@ -96,22 +72,18 @@ struct WgRenderDataShape: public WgRenderDataPaint { WgRenderSettings renderSettingsShape{}; WgRenderSettings renderSettingsStroke{}; - WgMeshDataGroup meshGroupShapes{}; - WgMeshDataGroup meshGroupShapesBBox{}; - WgMeshData meshDataBBox{}; - WgMeshDataGroup meshGroupStrokes{}; - WgMeshDataGroup meshGroupStrokesBBox{}; - Point pMin{}; - Point pMax{}; + WgMeshData meshBBox{}; + WgMeshData meshShape{}; + WgMeshData meshShapeBBox{}; + WgMeshData meshStrokes{}; + WgMeshData meshStrokesBBox{}; bool strokeFirst{}; FillRule fillRule{}; + BBox bbox; - void appendShape(const WgVertexBuffer& vertexBuffer); - void appendStroke(const WgIndexedVertexBuffer& vertexBufferInd); - void updateBBox(Point pmin, Point pmax); - void updateAABB(const Matrix& tr); - void updateMeshes(const RenderShape& rshape, const Matrix& tr, WgGeometryBufferPool* pool); - void proceedStrokes(const RenderStroke* rstroke, const WgVertexBuffer& buff, WgGeometryBufferPool* pool); + void updateBBox(BBox bb); + void updateAABB(const Matrix& matrix); + void updateMeshes(const RenderShape& rshape, RenderUpdateFlag flag, const Matrix& matrix); void releaseMeshes(); void release(WgContext& context) override; Type type() override { return Type::Shape; }; @@ -207,13 +179,11 @@ class WgStageBufferGeometry { private: Array vbuffer; Array ibuffer; - uint32_t vmaxcount{}; public: WGPUBuffer vbuffer_gpu{}; WGPUBuffer ibuffer_gpu{}; void append(WgMeshData* meshData); - void append(WgMeshDataGroup* meshDataGroup); void append(WgRenderDataShape* renderDataShape); void append(WgRenderDataPicture* renderDataPicture); void initialize(WgContext& context){}; diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index 513a43d1..8fe347ab 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -135,7 +135,7 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const // update geometry if (!data || (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke))) { - renderDataShape->updateMeshes(rshape, transform, mBufferPool.pool); + renderDataShape->updateMeshes(rshape, flags, transform); } // update paint settings @@ -408,10 +408,6 @@ WgRenderer::WgRenderer() { if (TaskScheduler::onthread()) { TVGLOG("WG_RENDERER", "Running on a non-dominant thread!, Renderer(%p)", this); - mBufferPool.pool = new WgGeometryBufferPool; - mBufferPool.individual = true; - } else { - mBufferPool.pool = WgGeometryBufferPool::instance(); } ++rendererCnt; @@ -422,8 +418,6 @@ WgRenderer::~WgRenderer() { release(); - if (mBufferPool.individual) delete(mBufferPool.pool); - --rendererCnt; } diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index 4ead7075..5cc1c84e 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -107,11 +107,6 @@ private: WGPUTexture targetTexture{}; // external handle WGPUSurfaceTexture surfaceTexture{}; WGPUSurface surface{}; // external handle - - struct { - WgGeometryBufferPool* pool; //private buffer pool - bool individual = false; //buffer-pool sharing policy - } mBufferPool; }; #endif /* _TVG_WG_RENDERER_H_ */ diff --git a/src/renderer/wg_engine/tvgWgTessellator.cpp b/src/renderer/wg_engine/tvgWgTessellator.cpp new file mode 100644 index 00000000..6e9cacff --- /dev/null +++ b/src/renderer/wg_engine/tvgWgTessellator.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2025 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgWgTessellator.h" +#include "tvgMath.h" + + +WgStroker::WgStroker(WgMeshData* buffer, const Matrix& matrix) : mBuffer(buffer), mMatrix(matrix) +{ +} + + +void WgStroker::stroke(const RenderShape *rshape) +{ + mMiterLimit = rshape->strokeMiterlimit(); + mStrokeCap = rshape->strokeCap(); + mStrokeJoin = rshape->strokeJoin(); + mStrokeWidth = rshape->strokeWidth(); + + if (isinf(mMatrix.e11)) { + auto strokeWidth = rshape->strokeWidth() * scaling(mMatrix); + if (strokeWidth <= MIN_WG_STROKE_WIDTH) strokeWidth = MIN_WG_STROKE_WIDTH; + mStrokeWidth = strokeWidth / mMatrix.e11; + } + + RenderPath dashed; + if (rshape->strokeDash(dashed)) doStroke(dashed); + else if (rshape->trimpath()) { + RenderPath trimmedPath; + if (rshape->stroke->trim.trim(rshape->path, trimmedPath)) doStroke(trimmedPath); + } else + doStroke(rshape->path); +} + + +RenderRegion WgStroker::bounds() const +{ + return {{int32_t(floor(mLeftTop.x)), int32_t(floor(mLeftTop.y))}, {int32_t(ceil(mRightBottom.x)), int32_t(ceil(mRightBottom.y))}}; +} + + +BBox WgStroker::getBBox() const +{ + return {mLeftTop, mRightBottom}; +} + +void WgStroker::doStroke(const RenderPath& path) +{ + mBuffer->vbuffer.reserve(path.pts.count * 4 + 16); + mBuffer->ibuffer.reserve(path.pts.count * 3); + + auto validStrokeCap = false; + auto pts = path.pts.data; + + ARRAY_FOREACH(cmd, path.cmds) { + switch (*cmd) { + case PathCommand::MoveTo: { + if (validStrokeCap) { // check this, so we can skip if path only contains move instruction + strokeCap(); + validStrokeCap = false; + } + mStrokeState.firstPt = *pts; + mStrokeState.firstPtDir = {0.0f, 0.0f}; + mStrokeState.prevPt = *pts; + mStrokeState.prevPtDir = {0.0f, 0.0f}; + pts++; + validStrokeCap = false; + } break; + case PathCommand::LineTo: { + validStrokeCap = true; + this->strokeLineTo(*pts); + pts++; + } break; + case PathCommand::CubicTo: { + validStrokeCap = true; + this->strokeCubicTo(pts[0], pts[1], pts[2]); + pts += 3; + } break; + case PathCommand::Close: { + this->strokeClose(); + + validStrokeCap = false; + } break; + default: + break; + } + } + if (validStrokeCap) strokeCap(); +} + + +void WgStroker::strokeCap() +{ + if (mStrokeCap == StrokeCap::Butt) return; + + if (mStrokeCap == StrokeCap::Square) { + if (mStrokeState.firstPt == mStrokeState.prevPt) strokeSquarePoint(mStrokeState.firstPt); + else { + strokeSquare(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y}); + strokeSquare(mStrokeState.prevPt, mStrokeState.prevPtDir); + } + } else if (mStrokeCap == StrokeCap::Round) { + if (mStrokeState.firstPt == mStrokeState.prevPt) strokeRoundPoint(mStrokeState.firstPt); + else { + strokeRound(mStrokeState.firstPt, {-mStrokeState.firstPtDir.x, -mStrokeState.firstPtDir.y}); + strokeRound(mStrokeState.prevPt, mStrokeState.prevPtDir); + } + } +} + + +void WgStroker::strokeLineTo(const Point& curr) +{ + auto dir = (curr - mStrokeState.prevPt); + normalize(dir); + + if (dir.x == 0.f && dir.y == 0.f) return; //same point + + auto normal = Point{-dir.y, dir.x}; + auto a = mStrokeState.prevPt + normal * strokeRadius(); + auto b = mStrokeState.prevPt - normal * strokeRadius(); + auto c = curr + normal * strokeRadius(); + auto d = curr - normal * strokeRadius(); + + auto ia = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a); + auto ib = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b); + auto ic = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c); + auto id = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d); + + /** + * a --------- c + * | | + * | | + * b-----------d + */ + + mBuffer->ibuffer.push(ia); + mBuffer->ibuffer.push(ib); + mBuffer->ibuffer.push(ic); + mBuffer->ibuffer.push(ib); + mBuffer->ibuffer.push(id); + mBuffer->ibuffer.push(ic); + + if (mStrokeState.prevPt == mStrokeState.firstPt) { + // first point after moveTo + mStrokeState.prevPt = curr; + mStrokeState.prevPtDir = dir; + mStrokeState.firstPtDir = dir; + } else { + this->strokeJoin(dir); + mStrokeState.prevPtDir = dir; + mStrokeState.prevPt = curr; + } + + if (ia == 0) { + mRightBottom.x = mLeftTop.x = curr.x; + mRightBottom.y = mLeftTop.y = curr.y; + } + + mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x))); + mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y))); + mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x))); + mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y))); +} + + +void WgStroker::strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end) +{ + Bezier curve {mStrokeState.prevPt, cnt1, cnt2, end}; + + Bezier relCurve {curve.start, curve.ctrl1, curve.ctrl2, curve.end}; + relCurve.start *= mMatrix; + relCurve.ctrl1 *= mMatrix; + relCurve.ctrl2 *= mMatrix; + relCurve.end *= mMatrix; + + auto count = relCurve.segments(); + auto step = 1.f / count; + + for (uint32_t i = 0; i <= count; i++) { + strokeLineTo(curve.at(step * i)); + } +} + + +void WgStroker::strokeClose() +{ + if (length(mStrokeState.prevPt - mStrokeState.firstPt) > 0.015625f) { + this->strokeLineTo(mStrokeState.firstPt); + } + + // join firstPt with prevPt + this->strokeJoin(mStrokeState.firstPtDir); +} + + +void WgStroker::strokeJoin(const Point& dir) +{ + auto orient = orientation(mStrokeState.prevPt - mStrokeState.prevPtDir, mStrokeState.prevPt, mStrokeState.prevPt + dir); + + if (orient == Orientation::Linear) { + if (mStrokeState.prevPtDir == dir) return; // check is same direction + if (mStrokeJoin != StrokeJoin::Round) return; // opposite direction + + auto normal = Point{-dir.y, dir.x}; + auto p1 = mStrokeState.prevPt + normal * strokeRadius(); + auto p2 = mStrokeState.prevPt - normal * strokeRadius(); + auto oc = mStrokeState.prevPt + dir * strokeRadius(); + + this->strokeRound(p1, oc, mStrokeState.prevPt); + this->strokeRound(oc, p2, mStrokeState.prevPt); + + } else { + auto normal = Point{-dir.y, dir.x}; + auto prevNormal = Point{-mStrokeState.prevPtDir.y, mStrokeState.prevPtDir.x}; + Point prevJoin, currJoin; + + if (orient == Orientation::CounterClockwise) { + prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius(); + currJoin = mStrokeState.prevPt + normal * strokeRadius(); + } else { + prevJoin = mStrokeState.prevPt - prevNormal * strokeRadius(); + currJoin = mStrokeState.prevPt - normal * strokeRadius(); + } + + if (mStrokeJoin == StrokeJoin::Miter) strokeMiter(prevJoin, currJoin, mStrokeState.prevPt); + else if (mStrokeJoin == StrokeJoin::Bevel) strokeBevel(prevJoin, currJoin, mStrokeState.prevPt); + else this->strokeRound(prevJoin, currJoin, mStrokeState.prevPt); + } +} + + +void WgStroker::strokeRound(const Point &prev, const Point& curr, const Point& center) +{ + if (orientation(prev, center, curr) == Orientation::Linear) return; + + mLeftTop.x = std::min(mLeftTop.x, std::min(center.x, std::min(prev.x, curr.x))); + mLeftTop.y = std::min(mLeftTop.y, std::min(center.y, std::min(prev.y, curr.y))); + mRightBottom.x = std::max(mRightBottom.x, std::max(center.x, std::max(prev.x, curr.x))); + mRightBottom.y = std::max(mRightBottom.y, std::max(center.y, std::max(prev.y, curr.y))); + + // Fixme: just use bezier curve to calculate step count + auto count = Bezier(prev, curr, strokeRadius()).segments(); + auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center); + auto pi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev); + auto step = 1.f / (count - 1); + auto dir = curr - prev; + + for (uint32_t i = 1; i < static_cast(count); i++) { + auto t = i * step; + auto p = prev + dir * t; + auto o_dir = p - center; + normalize(o_dir); + + auto out = center + o_dir * strokeRadius(); + auto oi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(out); + + mBuffer->ibuffer.push(c); + mBuffer->ibuffer.push(pi); + mBuffer->ibuffer.push(oi); + + pi = oi; + + mLeftTop.x = std::min(mLeftTop.x, out.x); + mLeftTop.y = std::min(mLeftTop.y, out.y); + mRightBottom.x = std::max(mRightBottom.x, out.x); + mRightBottom.y = std::max(mRightBottom.y, out.y); + } +} + + +void WgStroker::strokeRoundPoint(const Point &p) +{ + // Fixme: just use bezier curve to calculate step count + auto count = Bezier(p, p, strokeRadius()).segments() * 2; + auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(p); + auto step = 2 * MATH_PI / (count - 1); + + for (uint32_t i = 1; i <= static_cast(count); i++) { + float angle = i * step; + Point dir = {cos(angle), sin(angle)}; + Point out = p + dir * strokeRadius(); + auto oi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(out); + + if (oi > 1) { + mBuffer->ibuffer.push(c); + mBuffer->ibuffer.push(oi); + mBuffer->ibuffer.push(oi - 1); + } + } + + mLeftTop.x = std::min(mLeftTop.x, p.x - strokeRadius()); + mLeftTop.y = std::min(mLeftTop.y, p.y - strokeRadius()); + mRightBottom.x = std::max(mRightBottom.x, p.x + strokeRadius()); + mRightBottom.y = std::max(mRightBottom.y, p.y + strokeRadius()); +} + + +void WgStroker::strokeMiter(const Point& prev, const Point& curr, const Point& center) +{ + auto pp1 = prev - center; + auto pp2 = curr - center; + auto out = pp1 + pp2; + auto k = 2.f * strokeRadius() * strokeRadius() / (out.x * out.x + out.y * out.y); + auto pe = out * k; + + if (length(pe) >= mMiterLimit * strokeRadius()) { + this->strokeBevel(prev, curr, center); + return; + } + + auto join = center + pe; + auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center); + auto cp1 = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev); + auto cp2 = mBuffer->vbuffer.count; mBuffer->vbuffer.push(curr); + auto e = mBuffer->vbuffer.count; mBuffer->vbuffer.push(join); + + mBuffer->ibuffer.push(c); + mBuffer->ibuffer.push(cp1); + mBuffer->ibuffer.push(e); + + mBuffer->ibuffer.push(e); + mBuffer->ibuffer.push(cp2); + mBuffer->ibuffer.push(c); + + mLeftTop.x = std::min(mLeftTop.x, join.x); + mLeftTop.y = std::min(mLeftTop.y, join.y); + + mRightBottom.x = std::max(mRightBottom.x, join.x); + mRightBottom.y = std::max(mRightBottom.y, join.y); +} + + +void WgStroker::strokeBevel(const Point& prev, const Point& curr, const Point& center) +{ + auto a = mBuffer->vbuffer.count; mBuffer->vbuffer.push(prev); + auto b = mBuffer->vbuffer.count; mBuffer->vbuffer.push(curr); + auto c = mBuffer->vbuffer.count; mBuffer->vbuffer.push(center); + + mBuffer->ibuffer.push(a); + mBuffer->ibuffer.push(b); + mBuffer->ibuffer.push(c); +} + + +void WgStroker::strokeSquare(const Point& p, const Point& outDir) +{ + auto normal = Point{-outDir.y, outDir.x}; + + auto a = p + normal * strokeRadius(); + auto b = p - normal * strokeRadius(); + auto c = a + outDir * strokeRadius(); + auto d = b + outDir * strokeRadius(); + + auto ai = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a); + auto bi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b); + auto ci = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c); + auto di = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d); + + mBuffer->ibuffer.push(ai); + mBuffer->ibuffer.push(bi); + mBuffer->ibuffer.push(ci); + + mBuffer->ibuffer.push(ci); + mBuffer->ibuffer.push(bi); + mBuffer->ibuffer.push(di); + + mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x))); + mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y))); + mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x))); + mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y))); +} + + +void WgStroker::strokeSquarePoint(const Point& p) +{ + auto offsetX = Point{strokeRadius(), 0.0f}; + auto offsetY = Point{0.0f, strokeRadius()}; + + auto a = p + offsetX + offsetY; + auto b = p - offsetX + offsetY; + auto c = p - offsetX - offsetY; + auto d = p + offsetX - offsetY; + + auto ai = mBuffer->vbuffer.count; mBuffer->vbuffer.push(a); + auto bi = mBuffer->vbuffer.count; mBuffer->vbuffer.push(b); + auto ci = mBuffer->vbuffer.count; mBuffer->vbuffer.push(c); + auto di = mBuffer->vbuffer.count; mBuffer->vbuffer.push(d); + + mBuffer->ibuffer.push(ai); + mBuffer->ibuffer.push(bi); + mBuffer->ibuffer.push(ci); + + mBuffer->ibuffer.push(ci); + mBuffer->ibuffer.push(di); + mBuffer->ibuffer.push(ai); + + mLeftTop.x = std::min(mLeftTop.x, std::min(std::min(a.x, b.x), std::min(c.x, d.x))); + mLeftTop.y = std::min(mLeftTop.y, std::min(std::min(a.y, b.y), std::min(c.y, d.y))); + mRightBottom.x = std::max(mRightBottom.x, std::max(std::max(a.x, b.x), std::max(c.x, d.x))); + mRightBottom.y = std::max(mRightBottom.y, std::max(std::max(a.y, b.y), std::max(c.y, d.y))); +} + + +void WgStroker::strokeRound(const Point& p, const Point& outDir) +{ + auto normal = Point{-outDir.y, outDir.x}; + auto a = p + normal * strokeRadius(); + auto b = p - normal * strokeRadius(); + auto c = p + outDir * strokeRadius(); + + strokeRound(a, c, p); + strokeRound(c, b, p); +} + + +WgBWTessellator::WgBWTessellator(WgMeshData* buffer): mBuffer(buffer) +{ +} + + +void WgBWTessellator::tessellate(const RenderPath& path, const Matrix& matrix) +{ + if (path.pts.count <= 2) return; + + auto cmds = path.cmds.data; + auto cmdCnt = path.cmds.count; + auto pts = path.pts.data; + auto ptsCnt = path.pts.count; + + uint32_t firstIndex = 0; + uint32_t prevIndex = 0; + + mBuffer->vbuffer.reserve(ptsCnt * 2); + mBuffer->ibuffer.reserve((ptsCnt - 2) * 3); + + for (uint32_t i = 0; i < cmdCnt; i++) { + switch(cmds[i]) { + case PathCommand::MoveTo: { + firstIndex = pushVertex(pts->x, pts->y); + prevIndex = 0; + pts++; + } break; + case PathCommand::LineTo: { + if (prevIndex == 0) { + prevIndex = pushVertex(pts->x, pts->y); + pts++; + } else { + auto currIndex = pushVertex(pts->x, pts->y); + pushTriangle(firstIndex, prevIndex, currIndex); + prevIndex = currIndex; + pts++; + } + } break; + case PathCommand::CubicTo: { + Bezier curve{pts[-1], pts[0], pts[1], pts[2]}; + Bezier relCurve {pts[-1], pts[0], pts[1], pts[2]}; + relCurve.start *= matrix; + relCurve.ctrl1 *= matrix; + relCurve.ctrl2 *= matrix; + relCurve.end *= matrix; + + auto stepCount = relCurve.segments(); + if (stepCount <= 1) stepCount = 2; + + float step = 1.f / stepCount; + + for (uint32_t s = 1; s <= static_cast(stepCount); s++) { + auto pt = curve.at(step * s); + auto currIndex = pushVertex(pt.x, pt.y); + + if (prevIndex == 0) { + prevIndex = currIndex; + continue; + } + + pushTriangle(firstIndex, prevIndex, currIndex); + prevIndex = currIndex; + } + + pts += 3; + } break; + case PathCommand::Close: + default: + break; + } + } +} + + +RenderRegion WgBWTessellator::bounds() const +{ + return {{int32_t(floor(bbox.min.x)), int32_t(floor(bbox.min.y))}, {int32_t(ceil(bbox.max.x)), int32_t(ceil(bbox.max.y))}}; +} + + +BBox WgBWTessellator::getBBox() const +{ + return bbox; +} + + +uint32_t WgBWTessellator::pushVertex(float x, float y) +{ + auto index = mBuffer->vbuffer.count; + mBuffer->vbuffer.push({x, y}); + if (index == 0) bbox.max = bbox.min = {x, y}; + else bbox = {{std::min(bbox.min.x, x), std::min(bbox.min.y, y)}, {std::max(bbox.max.x, x), std::max(bbox.max.y, y)}}; + return index; +} + + +void WgBWTessellator::pushTriangle(uint32_t a, uint32_t b, uint32_t c) +{ + mBuffer->ibuffer.push(a); + mBuffer->ibuffer.push(b); + mBuffer->ibuffer.push(c); +} diff --git a/src/renderer/wg_engine/tvgWgTessellator.h b/src/renderer/wg_engine/tvgWgTessellator.h new file mode 100644 index 00000000..545157af --- /dev/null +++ b/src/renderer/wg_engine/tvgWgTessellator.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_WG_TESSELLATOR_H_ +#define _TVG_WG_TESSELLATOR_H_ + +#include "tvgRender.h" +#include "tvgWgGeometry.h" + +#define MIN_WG_STROKE_WIDTH 1.0f + +class WgStroker +{ + struct State + { + Point firstPt; + Point firstPtDir; + Point prevPt; + Point prevPtDir; + }; +public: + WgStroker(WgMeshData* buffer, const Matrix& matrix); + void stroke(const RenderShape *rshape); + RenderRegion bounds() const; + BBox getBBox() const; +private: + void doStroke(const RenderPath& path); + + float strokeRadius() const + { + return mStrokeWidth * 0.5f; + } + + void strokeCap(); + void strokeLineTo(const Point& curr); + void strokeCubicTo(const Point& cnt1, const Point& cnt2, const Point& end); + void strokeClose(); + void strokeJoin(const Point& dir); + void strokeRound(const Point& prev, const Point& curr, const Point& center); + void strokeMiter(const Point& prev, const Point& curr, const Point& center); + void strokeBevel(const Point& prev, const Point& curr, const Point& center); + void strokeSquare(const Point& p, const Point& outDir); + void strokeSquarePoint(const Point& p); + void strokeRound(const Point& p, const Point& outDir); + void strokeRoundPoint(const Point& p); + + WgMeshData* mBuffer; + Matrix mMatrix; + float mStrokeWidth = MIN_WG_STROKE_WIDTH; + float mMiterLimit = 4.f; + StrokeCap mStrokeCap = StrokeCap::Square; + StrokeJoin mStrokeJoin = StrokeJoin::Bevel; + State mStrokeState = {}; + Point mLeftTop = {0.0f, 0.0f}; + Point mRightBottom = {0.0f, 0.0f}; +}; + +class WgBWTessellator +{ +public: + WgBWTessellator(WgMeshData* buffer); + void tessellate(const RenderPath& path, const Matrix& matrix); + RenderRegion bounds() const; + BBox getBBox() const; +private: + uint32_t pushVertex(float x, float y); + void pushTriangle(uint32_t a, uint32_t b, uint32_t c); + + WgMeshData* mBuffer; + BBox bbox = {}; +}; + +#endif /* _TVG_WG_TESSELLATOR_H_ */ \ No newline at end of file