diff --git a/src/renderer/wg_engine/meson.build b/src/renderer/wg_engine/meson.build index 5405b559..451edfaf 100644 --- a/src/renderer/wg_engine/meson.build +++ b/src/renderer/wg_engine/meson.build @@ -1,9 +1,11 @@ source_file = [ + 'tvgWgGeometry.cpp', 'tvgWgPipelineBase.cpp', 'tvgWgPipelineEmpty.cpp', 'tvgWgPipelineLinear.cpp', 'tvgWgPipelineSolid.cpp', 'tvgWgPipelineRadial.cpp', + 'tvgWgPipelineStroke.cpp', 'tvgWgRenderData.cpp', 'tvgWgRenderer.cpp', 'tvgWgShaderSrc.cpp' diff --git a/src/renderer/wg_engine/tvgWgGeometry.cpp b/src/renderer/wg_engine/tvgWgGeometry.cpp new file mode 100644 index 00000000..ecda95a6 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgGeometry.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 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 "tvgWgGeometry.h" + +void WgVertexList::computeTriFansIndexes() { + assert(mVertexList.count > 2); + mIndexList.reserve((mVertexList.count - 2) * 3); + for (size_t i = 0; i < mVertexList.count - 2; i++) { + mIndexList.push(0); + mIndexList.push(i + 1); + mIndexList.push(i + 2); + } +} + +void WgVertexList::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3) { + WgPoint p0 = mVertexList.count > 0 ? mVertexList.last() : WgPoint(0.0f, 0.0f); + const size_t segs = 16; + for (size_t i = 1; i <= segs; i++) { + float t = i / (float)segs; + // get cubic spline interpolation coefficients + float t0 = 1 * (1.0f - t) * (1.0f - t) * (1.0f - t); + float t1 = 3 * (1.0f - t) * (1.0f - t) * t; + float t2 = 3 * (1.0f - t) * t * t; + float t3 = 1 * t * t * t; + mVertexList.push(p0 * t0 + p1 * t1 + p2 * t2 + p3 * t3); + } +} + +void WgVertexList::appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3) { + uint32_t index = mVertexList.count; + mVertexList.push(p0); // +0 + mVertexList.push(p1); // +1 + mVertexList.push(p2); // +2 + mVertexList.push(p3); // +3 + mIndexList.push(index + 0); + mIndexList.push(index + 1); + mIndexList.push(index + 2); + mIndexList.push(index + 1); + mIndexList.push(index + 3); + mIndexList.push(index + 2); +} + +// TODO: optimize vertex and index count +void WgVertexList::appendCircle(WgPoint center, float radius) { + uint32_t index = mVertexList.count; + uint32_t nSegments = 32; + for (uint32_t i = 0; i < nSegments; i++) { + float angle0 = (float)(i + 0) / nSegments * 3.141593f * 2.0f; + float angle1 = (float)(i + 1) / nSegments * 3.141593f * 2.0f; + WgPoint p0 = center + WgPoint(sin(angle0) * radius, cos(angle0) * radius); + WgPoint p1 = center + WgPoint(sin(angle1) * radius, cos(angle1) * radius); + mVertexList.push(center); // +0 + mVertexList.push(p0); // +1 + mVertexList.push(p1); // +2 + mIndexList.push(index + 0); + mIndexList.push(index + 1); + mIndexList.push(index + 2); + index += 3; + } +} + +void WgVertexList::close() { + if (mVertexList.count > 1) + mVertexList.push(mVertexList[0]); +} \ No newline at end of file diff --git a/src/renderer/wg_engine/tvgWgGeometry.h b/src/renderer/wg_engine/tvgWgGeometry.h new file mode 100644 index 00000000..402a607f --- /dev/null +++ b/src/renderer/wg_engine/tvgWgGeometry.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 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_GEOMETRY_H_ +#define _TVG_WG_GEOMETRY_H_ + +#include +#include "tvgMath.h" +#include "tvgCommon.h" +#include "tvgArray.h" + +class WgPoint +{ +public: + float x; + float y; + +public: + WgPoint() {} + WgPoint(float x, float y): x(x), y(y) {} + WgPoint(const Point& p): x(p.x), y(p.y) {} + + WgPoint operator + (const WgPoint& p) const { return { x + p.x, y + p.y }; } + WgPoint operator - (const WgPoint& p) const { return { x - p.x, y - p.y }; } + WgPoint operator * (const WgPoint& p) const { return { x * p.x, y * p.y }; } + WgPoint operator / (const WgPoint& p) const { return { x / p.x, y / p.y }; } + + WgPoint operator + (const float c) const { return { x + c, y + c }; } + WgPoint operator - (const float c) const { return { x - c, y - c }; } + WgPoint operator * (const float c) const { return { x * c, y * c }; } + WgPoint operator / (const float c) const { return { x / c, y / c }; } + + WgPoint negative() const { return {-x, -y}; } + void negate() { x = -x; y = -y; } + float length() const { return sqrt(x*x + y*y); } + + float dot(const WgPoint& p) const { return x * p.x + y * p.y; } + float dist(const WgPoint& p) const { + return sqrt( + (p.x - x)*(p.x - x) + + (p.y - y)*(p.y - y) + ); + } + + void normalize() { + float rlen = 1.0f / length(); + x *= rlen; + y *= rlen; + } + + WgPoint normal() const { + float rlen = 1.0f / length(); + return { x * rlen, y * rlen }; + } +}; + +class WgVertexList { +public: + Array mVertexList; + Array mIndexList; + + void computeTriFansIndexes(); + void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3); + void appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3); + void appendCircle(WgPoint center, float radius); + void close(); +}; + +#endif // _TVG_WG_GEOMETRY_H_ diff --git a/src/renderer/wg_engine/tvgWgPipelineEmpty.cpp b/src/renderer/wg_engine/tvgWgPipelineEmpty.cpp index 81703352..61d07f7c 100644 --- a/src/renderer/wg_engine/tvgWgPipelineEmpty.cpp +++ b/src/renderer/wg_engine/tvgWgPipelineEmpty.cpp @@ -161,11 +161,11 @@ void WgPipelineEmpty::initialize(WGPUDevice device) { // vertex attributes WGPUVertexAttribute vertexAttributes[] = { - { WGPUVertexFormat_Float32x3, sizeof(float) * 0, 0 }, // position + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 }, // position }; // vertex buffer layout WGPUVertexBufferLayout vertexBufferLayout{}; - vertexBufferLayout.arrayStride = sizeof(float) * 3; // position + vertexBufferLayout.arrayStride = sizeof(float) * 2; // position vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = 1; // position vertexBufferLayout.attributes = vertexAttributes; diff --git a/src/renderer/wg_engine/tvgWgPipelineLinear.cpp b/src/renderer/wg_engine/tvgWgPipelineLinear.cpp index 8d2ad946..67db9c36 100644 --- a/src/renderer/wg_engine/tvgWgPipelineLinear.cpp +++ b/src/renderer/wg_engine/tvgWgPipelineLinear.cpp @@ -223,11 +223,11 @@ void WgPipelineLinear::initialize(WGPUDevice device) { // vertex attributes WGPUVertexAttribute vertexAttributes[] = { - { WGPUVertexFormat_Float32x3, sizeof(float) * 0, 0 }, // position + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 }, // position }; // vertex buffer layout WGPUVertexBufferLayout vertexBufferLayout{}; - vertexBufferLayout.arrayStride = sizeof(float) * 3; // position + vertexBufferLayout.arrayStride = sizeof(float) * 2; // position vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = 1; // position vertexBufferLayout.attributes = vertexAttributes; diff --git a/src/renderer/wg_engine/tvgWgPipelineRadial.cpp b/src/renderer/wg_engine/tvgWgPipelineRadial.cpp index 64b15231..7fc8ccef 100644 --- a/src/renderer/wg_engine/tvgWgPipelineRadial.cpp +++ b/src/renderer/wg_engine/tvgWgPipelineRadial.cpp @@ -221,11 +221,11 @@ void WgPipelineRadial::initialize(WGPUDevice device) { // vertex attributes WGPUVertexAttribute vertexAttributes[] = { - { WGPUVertexFormat_Float32x3, sizeof(float) * 0, 0 }, // position + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 }, // position }; // vertex buffer layout WGPUVertexBufferLayout vertexBufferLayout{}; - vertexBufferLayout.arrayStride = sizeof(float) * 3; // position + vertexBufferLayout.arrayStride = sizeof(float) * 2; // position vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = 1; // position vertexBufferLayout.attributes = vertexAttributes; diff --git a/src/renderer/wg_engine/tvgWgPipelineSolid.cpp b/src/renderer/wg_engine/tvgWgPipelineSolid.cpp index cf0d21ee..bfa4e447 100644 --- a/src/renderer/wg_engine/tvgWgPipelineSolid.cpp +++ b/src/renderer/wg_engine/tvgWgPipelineSolid.cpp @@ -27,11 +27,11 @@ // WgPipelineDataSolid //************************************************************************ -void WgPipelineDataSolid::updateColor(const RenderShape& renderShape) { - uColorInfo.color[0] = renderShape.color[0] / 255.0f; // red - uColorInfo.color[1] = renderShape.color[1] / 255.0f; // green - uColorInfo.color[2] = renderShape.color[2] / 255.0f; // blue - uColorInfo.color[3] = renderShape.color[3] / 255.0f; // alpha +void WgPipelineDataSolid::updateColor(const uint8_t* color) { + uColorInfo.color[0] = color[0] / 255.0f; // red + uColorInfo.color[1] = color[1] / 255.0f; // green + uColorInfo.color[2] = color[2] / 255.0f; // blue + uColorInfo.color[3] = color[3] / 255.0f; // alpha } //************************************************************************ @@ -207,11 +207,11 @@ void WgPipelineSolid::initialize(WGPUDevice device) { // vertex attributes WGPUVertexAttribute vertexAttributes[] = { - { WGPUVertexFormat_Float32x3, sizeof(float) * 0, 0 }, // position + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 }, // position }; // vertex buffer layout WGPUVertexBufferLayout vertexBufferLayout{}; - vertexBufferLayout.arrayStride = sizeof(float) * 3; // position + vertexBufferLayout.arrayStride = sizeof(float) * 2; // position vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; vertexBufferLayout.attributeCount = 1; // position vertexBufferLayout.attributes = vertexAttributes; diff --git a/src/renderer/wg_engine/tvgWgPipelineSolid.h b/src/renderer/wg_engine/tvgWgPipelineSolid.h index 4e6bbecf..4fffcc6b 100644 --- a/src/renderer/wg_engine/tvgWgPipelineSolid.h +++ b/src/renderer/wg_engine/tvgWgPipelineSolid.h @@ -34,7 +34,7 @@ struct WgPipelineSolidColorInfo { struct WgPipelineDataSolid: WgPipelineData { WgPipelineSolidColorInfo uColorInfo{}; // @binding(1) - void updateColor(const RenderShape& renderShape); + void updateColor(const uint8_t* color); }; class WgPipelineBindGroupSolid: public WgPipelineBindGroup { diff --git a/src/renderer/wg_engine/tvgWgPipelineStroke.cpp b/src/renderer/wg_engine/tvgWgPipelineStroke.cpp new file mode 100644 index 00000000..bf75cb4d --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineStroke.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2023 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 "tvgWgPipelineStroke.h" +#include "tvgWgShaderSrc.h" + +//************************************************************************ +// WgPipelineBindGroupStroke +//************************************************************************ + +void WgPipelineBindGroupStroke::initialize(WGPUDevice device, WgPipelineStroke& pipelinePipelineStroke) { + // buffer uniform uMatrix + WGPUBufferDescriptor bufferUniformDesc_uMatrix{}; + bufferUniformDesc_uMatrix.nextInChain = nullptr; + bufferUniformDesc_uMatrix.label = "Buffer uniform pipeline Stroke uMatrix"; + bufferUniformDesc_uMatrix.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; + bufferUniformDesc_uMatrix.size = sizeof(WgPipelineMatrix); + bufferUniformDesc_uMatrix.mappedAtCreation = false; + uBufferMatrix = wgpuDeviceCreateBuffer(device, &bufferUniformDesc_uMatrix); + assert(uBufferMatrix); + + // bind group entry @binding(0) uMatrix + WGPUBindGroupEntry bindGroupEntry_uMatrix{}; + bindGroupEntry_uMatrix.nextInChain = nullptr; + bindGroupEntry_uMatrix.binding = 0; + bindGroupEntry_uMatrix.buffer = uBufferMatrix; + bindGroupEntry_uMatrix.offset = 0; + bindGroupEntry_uMatrix.size = sizeof(WgPipelineMatrix); + bindGroupEntry_uMatrix.sampler = nullptr; + bindGroupEntry_uMatrix.textureView = nullptr; + // bind group entries + WGPUBindGroupEntry bindGroupEntries[] { + bindGroupEntry_uMatrix // @binding(0) uMatrix + }; + // bind group descriptor + WGPUBindGroupDescriptor bindGroupDescPipeline{}; + bindGroupDescPipeline.nextInChain = nullptr; + bindGroupDescPipeline.label = "The binding group pipeline stroke"; + bindGroupDescPipeline.layout = pipelinePipelineStroke.mBindGroupLayout; + bindGroupDescPipeline.entryCount = 1; + bindGroupDescPipeline.entries = bindGroupEntries; + mBindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDescPipeline); + assert(mBindGroup); +} + +void WgPipelineBindGroupStroke::release() { + if (uBufferMatrix) { + wgpuBufferDestroy(uBufferMatrix); + wgpuBufferRelease(uBufferMatrix); + uBufferMatrix = nullptr; + } + if (mBindGroup) { + wgpuBindGroupRelease(mBindGroup); + mBindGroup = nullptr; + } +} + +void WgPipelineBindGroupStroke::update(WGPUQueue queue, WgPipelineDataStroke& pipelineDataStroke) { + wgpuQueueWriteBuffer(queue, uBufferMatrix, 0, &pipelineDataStroke.uMatrix, sizeof(pipelineDataStroke.uMatrix)); +} + +//************************************************************************ +// WgPipelineStroke +//************************************************************************ + +void WgPipelineStroke::initialize(WGPUDevice device) { + // bind group layout group 0 + // bind group layout descriptor @group(0) @binding(0) uMatrix + WGPUBindGroupLayoutEntry bindGroupLayoutEntry_uMatrix{}; + bindGroupLayoutEntry_uMatrix.nextInChain = nullptr; + bindGroupLayoutEntry_uMatrix.binding = 0; + bindGroupLayoutEntry_uMatrix.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; + bindGroupLayoutEntry_uMatrix.buffer.nextInChain = nullptr; + bindGroupLayoutEntry_uMatrix.buffer.type = WGPUBufferBindingType_Uniform; + bindGroupLayoutEntry_uMatrix.buffer.hasDynamicOffset = false; + bindGroupLayoutEntry_uMatrix.buffer.minBindingSize = 0; + // bind group layout entries @group(0) + WGPUBindGroupLayoutEntry bindGroupLayoutEntries[] { + bindGroupLayoutEntry_uMatrix + }; + // bind group layout descriptor scene @group(0) + WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc{}; + bindGroupLayoutDesc.nextInChain = nullptr; + bindGroupLayoutDesc.label = "Bind group layout pipeline stroke"; + bindGroupLayoutDesc.entryCount = 1; + bindGroupLayoutDesc.entries = bindGroupLayoutEntries; // @binding + mBindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bindGroupLayoutDesc); + assert(mBindGroupLayout); + + // pipeline layout + + // bind group layout descriptors + WGPUBindGroupLayout mBindGroupLayouts[] { + mBindGroupLayout + }; + // pipeline layout descriptor + WGPUPipelineLayoutDescriptor pipelineLayoutDesc{}; + pipelineLayoutDesc.nextInChain = nullptr; + pipelineLayoutDesc.label = "Pipeline pipeline layout stroke"; + pipelineLayoutDesc.bindGroupLayoutCount = 1; + pipelineLayoutDesc.bindGroupLayouts = mBindGroupLayouts; + mPipelineLayout = wgpuDeviceCreatePipelineLayout(device, &pipelineLayoutDesc); + assert(mPipelineLayout); + + // depth stencil state + WGPUDepthStencilState depthStencilState{}; + depthStencilState.nextInChain = nullptr; + depthStencilState.format = WGPUTextureFormat_Stencil8; + depthStencilState.depthWriteEnabled = false; + depthStencilState.depthCompare = WGPUCompareFunction_Always; + // depthStencilState.stencilFront + depthStencilState.stencilFront.compare = WGPUCompareFunction_Always; + depthStencilState.stencilFront.failOp = WGPUStencilOperation_Replace; + depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Replace; + depthStencilState.stencilFront.passOp = WGPUStencilOperation_Replace; + // depthStencilState.stencilBack + depthStencilState.stencilBack.compare = WGPUCompareFunction_Always; + depthStencilState.stencilBack.failOp = WGPUStencilOperation_Replace; + depthStencilState.stencilBack.depthFailOp = WGPUStencilOperation_Replace; + depthStencilState.stencilBack.passOp = WGPUStencilOperation_Replace; + // stencil mask + depthStencilState.stencilReadMask = 0xFFFFFFFF; + depthStencilState.stencilWriteMask = 0xFFFFFFFF; + // depth bias + depthStencilState.depthBias = 0; + depthStencilState.depthBiasSlopeScale = 0.0f; + depthStencilState.depthBiasClamp = 0.0f; + + // shader module wgsl descriptor + WGPUShaderModuleWGSLDescriptor shaderModuleWGSLDesc{}; + shaderModuleWGSLDesc.chain.next = nullptr; + shaderModuleWGSLDesc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; + shaderModuleWGSLDesc.code = cShaderSource_PipelineEmpty; + // shader module descriptor + WGPUShaderModuleDescriptor shaderModuleDesc{}; + shaderModuleDesc.nextInChain = &shaderModuleWGSLDesc.chain; + shaderModuleDesc.label = "The shader module pipeline Stroke"; + shaderModuleDesc.hintCount = 0; + shaderModuleDesc.hints = nullptr; + mShaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); + assert(mShaderModule); + + // vertex attributes + WGPUVertexAttribute vertexAttributes[] = { + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 }, // position + }; + // vertex buffer layout + WGPUVertexBufferLayout vertexBufferLayout{}; + vertexBufferLayout.arrayStride = sizeof(float) * 2; // position + vertexBufferLayout.stepMode = WGPUVertexStepMode_Vertex; + vertexBufferLayout.attributeCount = 1; // position + vertexBufferLayout.attributes = vertexAttributes; + + // blend state + WGPUBlendState blendState{}; + // blendState.color + blendState.color.operation = WGPUBlendOperation_Add; + blendState.color.srcFactor = WGPUBlendFactor_SrcAlpha; + blendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + // blendState.alpha + blendState.alpha.operation = WGPUBlendOperation_Add; + blendState.alpha.srcFactor = WGPUBlendFactor_Zero; + blendState.alpha.dstFactor = WGPUBlendFactor_One; + + // color target state (WGPUTextureFormat_BGRA8UnormSrgb) + WGPUColorTargetState colorTargetState0{}; + colorTargetState0.nextInChain = nullptr; + colorTargetState0.format = WGPUTextureFormat_BGRA8Unorm; + //colorTargetState0.format = WGPUTextureFormat_BGRA8UnormSrgb; + colorTargetState0.blend = &blendState; + colorTargetState0.writeMask = WGPUColorWriteMask_All; + // color target states + WGPUColorTargetState colorTargetStates[] = { + colorTargetState0 + }; + // fragmanet state + WGPUFragmentState fragmentState{}; + fragmentState.nextInChain = nullptr; + fragmentState.module = mShaderModule; + fragmentState.entryPoint = "fs_main"; + fragmentState.constantCount = 0; + fragmentState.constants = nullptr; + fragmentState.targetCount = 1; + fragmentState.targets = colorTargetStates; // render target index + + // render pipeline descriptor + WGPURenderPipelineDescriptor renderPipelineDesc{}; + renderPipelineDesc.nextInChain = nullptr; + renderPipelineDesc.label = "Render pipeline pipeline stroke"; + // renderPipelineDesc.layout + renderPipelineDesc.layout = mPipelineLayout; + // renderPipelineDesc.vertex + renderPipelineDesc.vertex.nextInChain = nullptr; + renderPipelineDesc.vertex.module = mShaderModule; + renderPipelineDesc.vertex.entryPoint = "vs_main"; + renderPipelineDesc.vertex.constantCount = 0; + renderPipelineDesc.vertex.constants = nullptr; + renderPipelineDesc.vertex.bufferCount = 1; + renderPipelineDesc.vertex.buffers = &vertexBufferLayout; // buffer index + // renderPipelineDesc.primitive + renderPipelineDesc.primitive.nextInChain = nullptr; + renderPipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + renderPipelineDesc.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; + renderPipelineDesc.primitive.frontFace = WGPUFrontFace_CCW; + renderPipelineDesc.primitive.cullMode = WGPUCullMode_None; + // renderPipelineDesc.depthStencil + renderPipelineDesc.depthStencil = &depthStencilState; + // renderPipelineDesc.multisample + renderPipelineDesc.multisample.nextInChain = nullptr; + renderPipelineDesc.multisample.count = 1; + renderPipelineDesc.multisample.mask = 0xFFFFFFFF; + renderPipelineDesc.multisample.alphaToCoverageEnabled = false; + // renderPipelineDesc.fragment + renderPipelineDesc.fragment = &fragmentState; + mRenderPipeline = wgpuDeviceCreateRenderPipeline(device, &renderPipelineDesc); + assert(mRenderPipeline); +} + +void WgPipelineStroke::release() { + wgpuRenderPipelineRelease(mRenderPipeline); + wgpuShaderModuleRelease(mShaderModule); + wgpuPipelineLayoutRelease(mPipelineLayout); + wgpuBindGroupLayoutRelease(mBindGroupLayout); +} diff --git a/src/renderer/wg_engine/tvgWgPipelineStroke.h b/src/renderer/wg_engine/tvgWgPipelineStroke.h new file mode 100644 index 00000000..83e90f90 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineStroke.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 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_PIPELINE_STROKE_H_ +#define _TVG_WG_PIPELINE_STROKE_H_ + +#include "tvgWgPipelineBase.h" + +class WgPipelineStroke; + +struct WgPipelineDataStroke: WgPipelineData {}; + +class WgPipelineBindGroupStroke: public WgPipelineBindGroup { +public: + void initialize(WGPUDevice device, WgPipelineStroke& pipelinePipelineStroke); + void release(); + + void update(WGPUQueue mQueue, WgPipelineDataStroke& pipelineDataSolid); +}; + +class WgPipelineStroke: public WgPipelineBase { +public: + void initialize(WGPUDevice device) override; + void release() override; +}; + +#endif // _TVG_WG_PIPELINE_STROKE_H_ diff --git a/src/renderer/wg_engine/tvgWgRenderData.cpp b/src/renderer/wg_engine/tvgWgRenderData.cpp index dc248e3b..0aa34158 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.cpp +++ b/src/renderer/wg_engine/tvgWgRenderData.cpp @@ -27,11 +27,19 @@ //*********************************************************************** void WgGeometryData::draw(WGPURenderPassEncoder renderPassEncoder) { - wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, mBufferVertex, 0, mVertexCount * sizeof(float) * 3); + wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, mBufferVertex, 0, mVertexCount * sizeof(float) * 2); wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, mBufferIndex, WGPUIndexFormat_Uint32, 0, mIndexCount * sizeof(uint32_t)); wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, mIndexCount, 1, 0, 0, 0); } +void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, WgVertexList* vertexList) { + update(device, queue, + (float *)vertexList->mVertexList.data, + vertexList->mVertexList.count, + vertexList->mIndexList.data, + vertexList->mIndexList.count); +} + void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, float* vertexData, size_t vertexCount, uint32_t* indexData, size_t indexCount) { release(); @@ -40,11 +48,11 @@ void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, float* vertexDat bufferVertexDesc.nextInChain = nullptr; bufferVertexDesc.label = "Buffer vertex geometry data"; bufferVertexDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex; - bufferVertexDesc.size = sizeof(float) * vertexCount * 3; // x, y, z + bufferVertexDesc.size = sizeof(float) * vertexCount * 2; // x, y bufferVertexDesc.mappedAtCreation = false; mBufferVertex = wgpuDeviceCreateBuffer(device, &bufferVertexDesc); assert(mBufferVertex); - wgpuQueueWriteBuffer(queue, mBufferVertex, 0, vertexData, sizeof(float) * vertexCount * 3); + wgpuQueueWriteBuffer(queue, mBufferVertex, 0, vertexData, vertexCount * sizeof(float) * 2); mVertexCount = vertexCount; // buffer index data create and write WGPUBufferDescriptor bufferIndexDesc{}; @@ -55,7 +63,7 @@ void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, float* vertexDat bufferIndexDesc.mappedAtCreation = false; mBufferIndex = wgpuDeviceCreateBuffer(device, &bufferIndexDesc); assert(mBufferIndex); - wgpuQueueWriteBuffer(queue, mBufferIndex, 0, indexData, sizeof(uint32_t) * indexCount); + wgpuQueueWriteBuffer(queue, mBufferIndex, 0, indexData, indexCount * sizeof(uint32_t)); mIndexCount = indexCount; } @@ -75,104 +83,280 @@ void WgGeometryData::release() { } //*********************************************************************** -// WgRenderDataShape +// WgRenderDataShapeSettings //*********************************************************************** -void WgRenderDataShape::release() { - releaseRenderData(); +void WgRenderDataShapeSettings::update(WGPUQueue queue, const Fill* fill, const RenderUpdateFlag flags, + const RenderTransform* transform, const float* viewMatrix, const uint8_t* color, + WgPipelineLinear& linear, WgPipelineRadial& radial, WgPipelineSolid& solid) + +{ + // setup fill properties + if ((flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) && fill) { + // setup linear fill properties + if (fill->identifier() == TVG_CLASS_ID_LINEAR) { + WgPipelineDataLinear brushDataLinear{}; + brushDataLinear.updateMatrix(viewMatrix, transform); + brushDataLinear.updateGradient((LinearGradient*)fill); + mPipelineBindGroupLinear.update(queue, brushDataLinear); + + mPipelineBindGroup = &mPipelineBindGroupLinear; + mPipelineBase = &linear; + } // setup radial fill properties + else if (fill->identifier() == TVG_CLASS_ID_RADIAL) { + WgPipelineDataRadial brushDataRadial{}; + brushDataRadial.updateMatrix(viewMatrix, transform); + brushDataRadial.updateGradient((RadialGradient*)fill); + mPipelineBindGroupRadial.update(queue, brushDataRadial); + + mPipelineBindGroup = &mPipelineBindGroupRadial; + mPipelineBase = &radial; + } + } // setup solid fill properties + else if ((flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Transform)) && !fill) { + WgPipelineDataSolid pipelineDataSolid{}; + pipelineDataSolid.updateMatrix(viewMatrix, transform); + pipelineDataSolid.updateColor(color); + mPipelineBindGroupSolid.update(queue, pipelineDataSolid); + + mPipelineBindGroup = &mPipelineBindGroupSolid; + mPipelineBase = &solid; + } +} + +void WgRenderDataShapeSettings::release() { mPipelineBindGroupSolid.release(); mPipelineBindGroupLinear.release(); mPipelineBindGroupRadial.release(); } +//*********************************************************************** +// WgRenderDataShape +//*********************************************************************** + +void WgRenderDataShape::release() { + releaseRenderData(); + mRenderSettingsShape.release(); + mRenderSettingsStroke.release(); +} + void WgRenderDataShape::releaseRenderData() { - for (uint32_t i = 0; i < mGeometryDataFill.count; i++) { - mGeometryDataFill[i]->release(); - delete mGeometryDataFill[i]; + for (uint32_t i = 0; i < mGeometryDataStroke.count; i++) { + mGeometryDataStroke[i]->release(); + delete mGeometryDataStroke[i]; } - mGeometryDataFill.clear(); + mGeometryDataStroke.clear(); + for (uint32_t i = 0; i < mGeometryDataShape.count; i++) { + mGeometryDataShape[i]->release(); + delete mGeometryDataShape[i]; + } + mGeometryDataShape.clear(); } void WgRenderDataShape::tesselate(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape) { - releaseRenderData(); - - Array*> outlines; - + Array outlines{}; + decodePath(rshape, outlines); + + // create geometry data for fill for each outline + for (uint32_t i = 0; i < outlines.count; i++) { + auto outline = outlines[i]; + // append shape if it can create at least one triangle + if (outline->mVertexList.count > 2) { + outline->computeTriFansIndexes(); + + // create geometry data for fill using triangle fan indexes + WgGeometryData* geometryData = new WgGeometryData(); + geometryData->update(device, queue, outline); + mGeometryDataShape.push(geometryData); + } + } + + for (uint32_t i = 0; i < outlines.count; i++) + delete outlines[i]; +} + +// TODO: separate to entity +void WgRenderDataShape::stroke(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape) { + if (!rshape.stroke) return; + + // TODO: chnage to shared_ptrs + Array outlines{}; + decodePath(rshape, outlines); + + WgVertexList strokes; + strokeSublines(rshape, outlines, strokes); + + // append shape if it can create at least one triangle + // TODO: create single geometry data for strokes pere shape + if (strokes.mIndexList.count > 2) { + WgGeometryData* geometryData = new WgGeometryData(); + geometryData->initialize(device); + geometryData->update(device, queue, &strokes); + mGeometryDataStroke.push(geometryData); + } + + for (uint32_t i = 0; i < outlines.count; i++) + delete outlines[i]; +} + +void WgRenderDataShape::decodePath(const RenderShape& rshape, Array& outlines) { size_t pntIndex = 0; for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) { PathCommand cmd = rshape.path.cmds[cmdIndex]; if (cmd == PathCommand::MoveTo) { - outlines.push(new Array); - + outlines.push(new WgVertexList); auto outline = outlines.last(); - outline->push(rshape.path.pts[pntIndex].x); - outline->push(rshape.path.pts[pntIndex].y); - outline->push(0.0f); - + outline->mVertexList.push(rshape.path.pts[pntIndex]); pntIndex++; } else if (cmd == PathCommand::LineTo) { auto outline = outlines.last(); - outline->push(rshape.path.pts[pntIndex].x); - outline->push(rshape.path.pts[pntIndex].y); - outline->push(0.0f); - + if (outline) + outline->mVertexList.push(rshape.path.pts[pntIndex]); pntIndex++; } else if (cmd == PathCommand::Close) { auto outline = outlines.last(); - if ((outline) && (outline->count > 1)) { - outline->push(outline->data[0]); - outline->push(outline->data[1]); - outline->push(0.0f); - } + if ((outline) && (outline->mVertexList.count > 0)) + outline->mVertexList.push(outline->mVertexList[0]); } else if (cmd == PathCommand::CubicTo) { auto outline = outlines.last(); - - Point p0 = { outline->data[outline->count - 3], outline->data[outline->count - 2] }; - Point p1 = { rshape.path.pts[pntIndex + 0].x, rshape.path.pts[pntIndex + 0].y }; - Point p2 = { rshape.path.pts[pntIndex + 1].x, rshape.path.pts[pntIndex + 1].y }; - Point p3 = { rshape.path.pts[pntIndex + 2].x, rshape.path.pts[pntIndex + 2].y }; - - const size_t segs = 16; - for (size_t i = 1; i <= segs; i++) { - float t = i / (float)segs; - // get cubic spline interpolation coefficients - float t0 = 1 * (1.0f - t) * (1.0f - t) * (1.0f - t); - float t1 = 3 * (1.0f - t) * (1.0f - t) * t; - float t2 = 3 * (1.0f - t) * t * t; - float t3 = 1 * t * t * t; - - outline->push(p0.x * t0 + p1.x * t1 + p2.x * t2 + p3.x * t3); - outline->push(p0.y * t0 + p1.y * t1 + p2.y * t2 + p3.y * t3); - outline->push(0.0f); - } - + if ((outline) && (outline->mVertexList.count > 0)) + outline->appendCubic( + rshape.path.pts[pntIndex + 0], + rshape.path.pts[pntIndex + 1], + rshape.path.pts[pntIndex + 2] + ); pntIndex += 3; } } +} - // create render index buffers to emulate triangle fan polygon - Array indexBuffer; - for (size_t i = 0; i < outlines.count; i++) { +void WgRenderDataShape::strokeSublines(const RenderShape& rshape, Array& outlines, WgVertexList& strokes) { + float wdt = rshape.stroke->width / 2; + for (uint32_t i = 0; i < outlines.count; i++) { auto outline = outlines[i]; - size_t vertexCount = outline->count / 3; - assert(vertexCount > 2); - - indexBuffer.clear(); - indexBuffer.reserve((vertexCount - 2) * 3); - for (size_t j = 0; j < vertexCount - 2; j++) { - indexBuffer.push(0); - indexBuffer.push(j + 1); - indexBuffer.push(j + 2); + // single point sub-path + if (outline->mVertexList.count == 1) { + if (rshape.stroke->cap == StrokeCap::Round) { + strokes.appendCircle(outline->mVertexList[0], wdt); + } else if (rshape.stroke->cap == StrokeCap::Butt) { + // for zero length sub-paths no stroke is rendered + } else if (rshape.stroke->cap == StrokeCap::Square) { + strokes.appendRect( + outline->mVertexList[0] + WgPoint(+wdt, +wdt), + outline->mVertexList[0] + WgPoint(+wdt, -wdt), + outline->mVertexList[0] + WgPoint(-wdt, +wdt), + outline->mVertexList[0] + WgPoint(-wdt, -wdt) + ); + } } - - WgGeometryData* geometryData = new WgGeometryData(); - geometryData->initialize(device); - geometryData->update(device, queue, outline->data, vertexCount, indexBuffer.data, indexBuffer.count); - - mGeometryDataFill.push(geometryData); + // single line sub-path + if (outline->mVertexList.count == 2) { + WgPoint v0 = outline->mVertexList[0]; + WgPoint v1 = outline->mVertexList[1]; + WgPoint dir0 = (v1 - v0).normal(); + WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x }; + if (rshape.stroke->cap == StrokeCap::Round) { + strokes.appendRect( + v0 - nrm0 * wdt, v0 + nrm0 * wdt, + v1 - nrm0 * wdt, v1 + nrm0 * wdt); + strokes.appendCircle(outline->mVertexList[0], wdt); + strokes.appendCircle(outline->mVertexList[1], wdt); + } else if (rshape.stroke->cap == StrokeCap::Butt) { + strokes.appendRect( + v0 - nrm0 * wdt, v0 + nrm0 * wdt, + v1 - nrm0 * wdt, v1 + nrm0 * wdt + ); + } else if (rshape.stroke->cap == StrokeCap::Square) { + strokes.appendRect( + v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt, + v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt + ); + } + } + + // multi-lined sub-path + if (outline->mVertexList.count > 2) { + // append first cap + WgPoint v0 = outline->mVertexList[0]; + WgPoint v1 = outline->mVertexList[1]; + WgPoint dir0 = (v1 - v0).normal(); + WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x }; + if (rshape.stroke->cap == StrokeCap::Round) { + strokes.appendCircle(v0, wdt); + } else if (rshape.stroke->cap == StrokeCap::Butt) { + // no cap needed + } else if (rshape.stroke->cap == StrokeCap::Square) { + strokes.appendRect( + v0 - nrm0 * wdt - dir0 * wdt, + v0 + nrm0 * wdt - dir0 * wdt, + v0 - nrm0 * wdt, + v0 + nrm0 * wdt + ); + } + + // append last cap + v0 = outline->mVertexList[outline->mVertexList.count - 2]; + v1 = outline->mVertexList[outline->mVertexList.count - 1]; + dir0 = (v1 - v0).normal(); + nrm0 = WgPoint{ -dir0.y, +dir0.x }; + if (rshape.stroke->cap == StrokeCap::Round) { + strokes.appendCircle(v1, wdt); + } else if (rshape.stroke->cap == StrokeCap::Butt) { + // no cap needed + } else if (rshape.stroke->cap == StrokeCap::Square) { + strokes.appendRect( + v1 - nrm0 * wdt, + v1 + nrm0 * wdt, + v1 - nrm0 * wdt + dir0 * wdt, + v1 + nrm0 * wdt + dir0 * wdt + ); + } + + // append sub-lines + for (uint32_t j = 0; j < outline->mVertexList.count - 1; j++) { + WgPoint v0 = outline->mVertexList[j + 0]; + WgPoint v1 = outline->mVertexList[j + 1]; + WgPoint dir = (v1 - v0).normal(); + WgPoint nrm { -dir.y, +dir.x }; + strokes.appendRect( + v0 - nrm * wdt, + v0 + nrm * wdt, + v1 - nrm * wdt, + v1 + nrm * wdt + ); + } + + // append joints (TODO: separate by joint types) + for (uint32_t j = 1; j < outline->mVertexList.count - 1; j++) { + WgPoint v0 = outline->mVertexList[j - 1]; + WgPoint v1 = outline->mVertexList[j + 0]; + WgPoint v2 = outline->mVertexList[j + 1]; + WgPoint dir0 = (v1 - v0).normal(); + WgPoint dir1 = (v1 - v2).normal(); + WgPoint nrm0 { -dir0.y, +dir0.x }; + WgPoint nrm1 { +dir1.y, -dir1.x }; + if (rshape.stroke->join == StrokeJoin::Round) { + strokes.appendCircle(v1, wdt); + } else if (rshape.stroke->join == StrokeJoin::Bevel) { + strokes.appendRect( + v1 - nrm0 * wdt, v1 - nrm1 * wdt, + v1 + nrm1 * wdt, v1 + nrm0 * wdt + ); + } else if (rshape.stroke->join == StrokeJoin::Miter) { + WgPoint nrm = (dir0 + dir1).normal(); + float cosine = nrm.dot(nrm0); + if ((cosine != 0.0f) && (abs(wdt / cosine) <= rshape.stroke->miterlimit * 2)) { + strokes.appendRect(v1 + nrm * (wdt / cosine), v1 + nrm0 * wdt, v1 + nrm1 * wdt, v1); + strokes.appendRect(v1 - nrm * (wdt / cosine), v1 - nrm0 * wdt, v1 - nrm1 * wdt, v1); + } else { + strokes.appendRect( + v1 - nrm0 * wdt, v1 - nrm1 * wdt, + v1 + nrm1 * wdt, v1 + nrm0 * wdt); + } + } + } + } } - for (size_t i = 0; i < outlines.count; i++) - delete outlines[i]; -} +} \ No newline at end of file diff --git a/src/renderer/wg_engine/tvgWgRenderData.h b/src/renderer/wg_engine/tvgWgRenderData.h index 80250dcf..2c125246 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.h +++ b/src/renderer/wg_engine/tvgWgRenderData.h @@ -20,12 +20,13 @@ * SOFTWARE. */ +#ifndef _TVG_WG_RENDER_DATA_H_ +#define _TVG_WG_RENDER_DATA_H_ + #include "tvgWgPipelineSolid.h" #include "tvgWgPipelineLinear.h" #include "tvgWgPipelineRadial.h" - -#ifndef _TVG_WG_RENDER_DATA_H_ -#define _TVG_WG_RENDER_DATA_H_ +#include "tvgWgGeometry.h" class WgGeometryData { public: @@ -39,6 +40,7 @@ public: void initialize(WGPUDevice device) {}; void draw(WGPURenderPassEncoder renderPassEncoder); + void update(WGPUDevice device, WGPUQueue queue, WgVertexList* vertexList); void update(WGPUDevice device, WGPUQueue queue, float* vertexData, size_t vertexCount, uint32_t* indexData, size_t indexCount); void release(); }; @@ -49,17 +51,28 @@ public: virtual void release() = 0; }; -class WgRenderDataShape: public WgRenderData { -public: - Array mGeometryDataFill; - Array mGeometryDataStroke; - +struct WgRenderDataShapeSettings { WgPipelineBindGroupSolid mPipelineBindGroupSolid{}; WgPipelineBindGroupLinear mPipelineBindGroupLinear{}; WgPipelineBindGroupRadial mPipelineBindGroupRadial{}; WgPipelineBase* mPipelineBase{}; // external WgPipelineBindGroup* mPipelineBindGroup{}; // external + + // update render shape settings defined by flags and fill settings + void update(WGPUQueue queue, const Fill* fill, const RenderUpdateFlag flags, + const RenderTransform* transform, const float* viewMatrix, const uint8_t* color, + WgPipelineLinear& linear, WgPipelineRadial& radial, WgPipelineSolid& solid); + void release(); +}; + +class WgRenderDataShape: public WgRenderData { +public: + Array mGeometryDataShape; + Array mGeometryDataStroke; + + WgRenderDataShapeSettings mRenderSettingsShape; + WgRenderDataShapeSettings mRenderSettingsStroke; public: WgRenderDataShape() {} @@ -67,6 +80,10 @@ public: void releaseRenderData(); void tesselate(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape); + void stroke(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape); +private: + void decodePath(const RenderShape& rshape, Array& outlines); + void strokeSublines(const RenderShape& rshape, Array& outlines, WgVertexList& strokes); }; #endif //_TVG_WG_RENDER_DATA_H_ diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index 49fe8c32..d5d68b7c 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -102,11 +102,13 @@ void WgRenderer::initialize() { // create pipelines mPipelineEmpty.initialize(mDevice); + mPipelineStroke.initialize(mDevice); mPipelineSolid.initialize(mDevice); mPipelineLinear.initialize(mDevice); mPipelineRadial.initialize(mDevice); mPipelineBindGroupEmpty.initialize(mDevice, mPipelineEmpty); - mGeometryDataPipeline.initialize(mDevice); + mPipelineBindGroupStroke.initialize(mDevice, mPipelineStroke); + mGeometryDataWindow.initialize(mDevice); } void WgRenderer::release() { @@ -117,11 +119,13 @@ void WgRenderer::release() { if (mStencilTexView) wgpuTextureViewRelease(mStencilTexView); if (mSwapChain) wgpuSwapChainRelease(mSwapChain); if (mSurface) wgpuSurfaceRelease(mSurface); - mGeometryDataPipeline.release(); + mGeometryDataWindow.release(); + mPipelineBindGroupStroke.release(); mPipelineBindGroupEmpty.release(); mPipelineRadial.release(); mPipelineLinear.release(); mPipelineSolid.release(); + mPipelineStroke.release(); mPipelineEmpty.release(); if (mDevice) { wgpuDeviceDestroy(mDevice); @@ -137,44 +141,31 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const if (!renderDataShape) { renderDataShape = new WgRenderDataShape(); renderDataShape->initialize(mDevice); - renderDataShape->mPipelineBindGroupSolid.initialize(mDevice, mPipelineSolid); - renderDataShape->mPipelineBindGroupLinear.initialize(mDevice, mPipelineLinear); - renderDataShape->mPipelineBindGroupRadial.initialize(mDevice, mPipelineRadial); + renderDataShape->mRenderSettingsShape.mPipelineBindGroupSolid.initialize(mDevice, mPipelineSolid); + renderDataShape->mRenderSettingsShape.mPipelineBindGroupLinear.initialize(mDevice, mPipelineLinear); + renderDataShape->mRenderSettingsShape.mPipelineBindGroupRadial.initialize(mDevice, mPipelineRadial); + renderDataShape->mRenderSettingsStroke.mPipelineBindGroupSolid.initialize(mDevice, mPipelineSolid); + renderDataShape->mRenderSettingsStroke.mPipelineBindGroupLinear.initialize(mDevice, mPipelineLinear); + renderDataShape->mRenderSettingsStroke.mPipelineBindGroupRadial.initialize(mDevice, mPipelineRadial); } + if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke)) + renderDataShape->releaseRenderData(); + if (flags & RenderUpdateFlag::Path) renderDataShape->tesselate(mDevice, mQueue, rshape); - // setup fill properties - if ((flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) && (rshape.fill)) { - // setup linear fill properties - if (rshape.fill->identifier() == TVG_CLASS_ID_LINEAR) { - WgPipelineDataLinear brushDataLinear{}; - brushDataLinear.updateMatrix(mViewMatrix, transform); - brushDataLinear.updateGradient((LinearGradient*)rshape.fill); - renderDataShape->mPipelineBindGroupLinear.update(mQueue, brushDataLinear); - renderDataShape->mPipelineBindGroup = &renderDataShape->mPipelineBindGroupLinear; - renderDataShape->mPipelineBase = &mPipelineLinear; - } // setup radial fill properties - else if (rshape.fill->identifier() == TVG_CLASS_ID_RADIAL) { - WgPipelineDataRadial brushDataRadial{}; - brushDataRadial.updateMatrix(mViewMatrix, transform); - brushDataRadial.updateGradient((RadialGradient*)rshape.fill); - renderDataShape->mPipelineBindGroupRadial.update(mQueue, brushDataRadial); - renderDataShape->mPipelineBindGroup = &renderDataShape->mPipelineBindGroupRadial; - renderDataShape->mPipelineBase = &mPipelineRadial; - } - } + if (flags & RenderUpdateFlag::Stroke) + renderDataShape->stroke(mDevice, mQueue, rshape); - // setup solid fill properties - if ((flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Transform)) && (!rshape.fill)) { - WgPipelineDataSolid pipelineDataSolid{}; - pipelineDataSolid.updateMatrix(mViewMatrix, transform); - pipelineDataSolid.updateColor(rshape); - renderDataShape->mPipelineBindGroupSolid.update(mQueue, pipelineDataSolid); - renderDataShape->mPipelineBindGroup = &renderDataShape->mPipelineBindGroupSolid; - renderDataShape->mPipelineBase = &mPipelineSolid; - } + // setup shape fill properties + renderDataShape->mRenderSettingsShape.update(mQueue, rshape.fill, flags, transform, mViewMatrix, rshape.color, + mPipelineLinear, mPipelineRadial, mPipelineSolid); + + // setup stroke fill properties + if (rshape.stroke) + renderDataShape->mRenderSettingsStroke.update(mQueue, rshape.stroke->fill, flags, transform, mViewMatrix, rshape.stroke->color, + mPipelineLinear, mPipelineRadial, mPipelineSolid); return renderDataShape; } @@ -283,16 +274,33 @@ bool WgRenderer::sync() { for (size_t i = 0; i < mRenderDatas.count; i++) { WgRenderDataShape* renderData = (WgRenderDataShape*)(mRenderDatas[i]); - for (uint32_t j = 0; j < renderData->mGeometryDataFill.count; j++) { + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + for (uint32_t j = 0; j < renderData->mGeometryDataShape.count; j++) { // draw to stencil (first pass) mPipelineEmpty.set(renderPassEncoder); mPipelineBindGroupEmpty.bind(renderPassEncoder, 0); - renderData->mGeometryDataFill[j]->draw(renderPassEncoder); + renderData->mGeometryDataShape[j]->draw(renderPassEncoder); - // fill shape (secind pass) - renderData->mPipelineBase->set(renderPassEncoder); - renderData->mPipelineBindGroup->bind(renderPassEncoder, 0); - mGeometryDataPipeline.draw(renderPassEncoder); + // fill shape (second pass) + renderData->mRenderSettingsShape.mPipelineBase->set(renderPassEncoder); + renderData->mRenderSettingsShape.mPipelineBindGroup->bind(renderPassEncoder, 0); + mGeometryDataWindow.draw(renderPassEncoder); + } + + if (renderData->mRenderSettingsStroke.mPipelineBase) { + // draw strokes to stencil (first pass) + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); + for (uint32_t j = 0; j < renderData->mGeometryDataStroke.count; j++) { + mPipelineStroke.set(renderPassEncoder); + mPipelineBindGroupStroke.bind(renderPassEncoder, 0); + renderData->mGeometryDataStroke[j]->draw(renderPassEncoder); + } + + // fill strokes (second pass) + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + renderData->mRenderSettingsStroke.mPipelineBase->set(renderPassEncoder); + renderData->mRenderSettingsStroke.mPipelineBindGroup->bind(renderPassEncoder, 0); + mGeometryDataWindow.draw(renderPassEncoder); } } } @@ -402,19 +410,21 @@ bool WgRenderer::target(void* window, uint32_t w, uint32_t h) { assert(mStencilTexView); // update pipeline geometry data - static float vertexData[] = { - 0.0f, 0.0f, 0.0f, - (float)w, 0.0f, 0.0f, - (float)w, (float)h, 0.0f, - 1.0f, (float)h, 0.0f - }; - static uint32_t indexData[] = { 0, 1, 2, 0, 2, 3 }; - // update render data pipeline empty - mGeometryDataPipeline.update(mDevice, mQueue, vertexData, 4, indexData, 6); + WgVertexList wnd; + wnd.appendRect( + WgPoint(0.0f, 0.0f), WgPoint(w, 0.0f), + WgPoint(0.0f, h), WgPoint(w, h) + ); + // update render data pipeline empty and stroke + mGeometryDataWindow.update(mDevice, mQueue, &wnd); // update bind group pipeline empty WgPipelineDataEmpty pipelineDataEmpty{}; pipelineDataEmpty.updateMatrix(mViewMatrix, nullptr); mPipelineBindGroupEmpty.update(mQueue, pipelineDataEmpty); + // update bind group pipeline stroke + WgPipelineDataStroke pipelineDataStroke{}; + pipelineDataStroke.updateMatrix(mViewMatrix, nullptr); + mPipelineBindGroupStroke.update(mQueue, pipelineDataStroke); return true; } diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index 1461591c..f7130c2d 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -24,6 +24,7 @@ #define _TVG_WG_RENDERER_H_ #include "tvgWgPipelineEmpty.h" +#include "tvgWgPipelineStroke.h" #include "tvgWgRenderData.h" class WgRenderer : public RenderMethod @@ -78,11 +79,13 @@ private: WGPUTextureView mStencilTexView{}; private: WgPipelineEmpty mPipelineEmpty; + WgPipelineStroke mPipelineStroke; WgPipelineSolid mPipelineSolid; WgPipelineLinear mPipelineLinear; WgPipelineRadial mPipelineRadial; - WgGeometryData mGeometryDataPipeline; + WgGeometryData mGeometryDataWindow; WgPipelineBindGroupEmpty mPipelineBindGroupEmpty; + WgPipelineBindGroupStroke mPipelineBindGroupStroke; }; #endif /* _TVG_WG_RENDERER_H_ */ diff --git a/src/renderer/wg_engine/tvgWgShaderSrc.cpp b/src/renderer/wg_engine/tvgWgShaderSrc.cpp index f26d383f..aa1e8139 100644 --- a/src/renderer/wg_engine/tvgWgShaderSrc.cpp +++ b/src/renderer/wg_engine/tvgWgShaderSrc.cpp @@ -30,7 +30,7 @@ const char* cShaderSource_PipelineEmpty = R"( // vertex input struct VertexInput { - @location(0) position: vec3f + @location(0) position: vec2f }; // Matrix @@ -66,7 +66,7 @@ fn fs_main(in: VertexOutput) -> void { const char* cShaderSource_PipelineSolid = R"( // vertex input struct VertexInput { - @location(0) position: vec3f + @location(0) position: vec2f }; // Matrix @@ -110,7 +110,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f { const char* cShaderSource_PipelineLinear = R"( // vertex input struct VertexInput { - @location(0) position: vec3f + @location(0) position: vec2f }; // Matrix @@ -195,7 +195,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f { const char* cShaderSource_PipelineRadial = R"( // vertex input struct VertexInput { - @location(0) position: vec3f + @location(0) position: vec2f }; // Matrix