Added ability to draw solid strokes

[issues 1479: Shape](https://github.com/thorvg/thorvg/issues/1479)

In order to build you need third party libraries. Before you start please read this: [LearnWebGPU](https://eliemichel.github.io/LearnWebGPU/getting-started/hello-webgpu.html)

Usage example:

    // init glfw
    glfwInit();

    // create a windowed mode window and its opengl context
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    GLFWwindow* window = glfwCreateWindow(800, 800, "WebGPU base app", nullptr, nullptr);

    // get window size
    int width{}, height{};
    glfwGetWindowSize(window, &width, &height);

    // init engine webgpu
    tvg::Initializer::init(tvg::CanvasEngine::Wg, 0);

    // create wg canvas
    auto canvasWg = tvg::WgCanvas::gen();
    canvas_wg->target(glfwGetWin32Window(window), width, height);

    //Test for Stroke Dash for Arc, Circle, Rect
    auto shape = tvg::Shape::gen();
    shape->appendArc(70, 600, 160, 10, 30, true);
    shape->appendCircle(70, 700, 20, 60);
    shape->appendRect(130, 710, 100, 40);
    shape->strokeFill(255, 0, 0);
    shape->strokeWidth(5);
    shape->strokeJoin(tvg::StrokeJoin::Round);
    shape->strokeCap(tvg::StrokeCap::Round);
    if (canvas_wg->push(std::move(shape)) != tvg::Result::Success) return;

    while (!glfwWindowShouldClose(window)) {
        // webgpu
        canvas_wg->draw();
        canvas_wg->sync();

        // pull events
        glfwPollEvents();
    }

    // terminate engine and window
    tvg::Initializer::term(tvg::CanvasEngine::Wg);
    glfwDestroyWindow(window);
    glfwTerminate();
This commit is contained in:
Sergii Liebodkin 2023-11-01 12:02:56 +02:00 committed by Hermet Park
parent 2f8c920654
commit a3adbef2c2
15 changed files with 825 additions and 147 deletions

View file

@ -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'

View file

@ -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]);
}

View file

@ -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 <cassert>
#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<WgPoint> mVertexList;
Array<uint32_t> 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_

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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);
}

View file

@ -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_

View file

@ -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<WgVertexList*> outlines{};
decodePath(rshape, outlines);
Array<Array<float>*> 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<WgVertexList*> 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<WgVertexList*>& 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<float>);
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<uint32_t> indexBuffer;
for (size_t i = 0; i < outlines.count; i++) {
void WgRenderDataShape::strokeSublines(const RenderShape& rshape, Array<WgVertexList*>& 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);
// 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
);
}
}
mGeometryDataFill.push(geometryData);
// 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];
}

View file

@ -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<WgGeometryData*> mGeometryDataFill;
Array<WgGeometryData*> 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<WgGeometryData*> mGeometryDataShape;
Array<WgGeometryData*> 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<WgVertexList*>& outlines);
void strokeSublines(const RenderShape& rshape, Array<WgVertexList*>& outlines, WgVertexList& strokes);
};
#endif //_TVG_WG_RENDER_DATA_H_

View file

@ -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;
}

View file

@ -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_ */

View file

@ -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