From db1f171d2ac9aa60d8f11aebf16afc9f9fe8ee5a Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Fri, 20 Oct 2023 22:27:21 +0300 Subject: [PATCH] wg_engine: Added ability to draw multiple linear gradient filled shapes [issues 1479: LinearGradient](thorvg#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); // gradient color stops tvg::Fill::ColorStop colorStops[2]; colorStops[0] = {0, 0, 0, 0, 255}; colorStops[1] = {1, 255, 255, 255, 255}; // linear gradient auto fill = tvg::LinearGradient::gen(); fill->linear(0, 0, 400, 400); fill->colorStops(colorStops, 2); // prepare rectangle auto shape1 = tvg::Shape::gen(); shape1->appendRect(0, 0, 400, 400); //x, y, w, h shape1->fill(std::move(fill)); canvas_wg->push(std::move(shape1)); // gradient color stops tvg::Fill::ColorStop colorStops2[3]; colorStops2[0] = { 0, 255, 0, 0, 255 }; colorStops2[1] = { 0.5, 255, 255, 0, 255 }; colorStops2[2] = { 1, 255, 255, 255, 255 }; // linear gradient auto fill2 = tvg::LinearGradient::gen(); fill2->linear(400, 200, 400, 600); fill2->colorStops(colorStops2, 3); // prepare circle auto shape2 = tvg::Shape::gen(); shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH shape2->fill(std::move(fill2)); canvas_wg->push(std::move(shape2)); // gradient color stops tvg::Fill::ColorStop colorStops3[4]; colorStops3[0] = { 0, 0, 127, 0, 127 }; colorStops3[1] = { 0.25, 0, 170, 170, 170 }; colorStops3[2] = { 0.5, 200, 0, 200, 200 }; colorStops3[3] = { 1, 255, 255, 255, 255 }; // linear gradient auto fill3 = tvg::LinearGradient::gen(); fill3->linear(450, 600, 750, 600); fill3->colorStops(colorStops3, 4); // prepare ellipse auto shape3 = tvg::Shape::gen(); shape3->appendCircle(600, 600, 150, 100); //cx, cy, radiusW, radiusH shape3->fill(std::move(fill3)); canvas_wg->push(std::move(shape3)); 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(); --- src/renderer/wg_engine/meson.build | 1 + .../wg_engine/tvgWgPipelineLinear.cpp | 305 ++++++++++++++++++ src/renderer/wg_engine/tvgWgPipelineLinear.h | 61 ++++ src/renderer/wg_engine/tvgWgRenderData.h | 2 + src/renderer/wg_engine/tvgWgRenderer.cpp | 16 + src/renderer/wg_engine/tvgWgRenderer.h | 2 +- src/renderer/wg_engine/tvgWgShaderSrc.cpp | 86 ++++- src/renderer/wg_engine/tvgWgShaderSrc.h | 3 + 8 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 src/renderer/wg_engine/tvgWgPipelineLinear.cpp create mode 100644 src/renderer/wg_engine/tvgWgPipelineLinear.h diff --git a/src/renderer/wg_engine/meson.build b/src/renderer/wg_engine/meson.build index 9f3a15e4..d086e646 100644 --- a/src/renderer/wg_engine/meson.build +++ b/src/renderer/wg_engine/meson.build @@ -1,6 +1,7 @@ source_file = [ 'tvgWgPipelineBase.cpp', 'tvgWgPipelineEmpty.cpp', + 'tvgWgPipelineLinear.cpp', 'tvgWgPipelineSolid.cpp', 'tvgWgRenderData.cpp', 'tvgWgRenderer.cpp', diff --git a/src/renderer/wg_engine/tvgWgPipelineLinear.cpp b/src/renderer/wg_engine/tvgWgPipelineLinear.cpp new file mode 100644 index 00000000..8d2ad946 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineLinear.cpp @@ -0,0 +1,305 @@ +/* + * 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 "tvgWgPipelineLinear.h" +#include "tvgWgShaderSrc.h" + +//************************************************************************ +// WgPipelineBindGroupLinear +//************************************************************************ + +void WgPipelineDataLinear::updateGradient(LinearGradient* linearGradient) { + + const Fill::ColorStop* stops = nullptr; + auto stopCnt = linearGradient->colorStops(&stops); + + uGradientInfo.nStops[0] = stopCnt; + uGradientInfo.nStops[1] = 0.5f; + + for (uint32_t i = 0; i < stopCnt; ++i) { + uGradientInfo.stopPoints[i] = stops[i].offset; + uGradientInfo.stopColors[i * 4 + 0] = stops[i].r / 255.f; + uGradientInfo.stopColors[i * 4 + 1] = stops[i].g / 255.f; + uGradientInfo.stopColors[i * 4 + 2] = stops[i].b / 255.f; + uGradientInfo.stopColors[i * 4 + 3] = stops[i].a / 255.f; + } + + linearGradient->linear( + &uGradientInfo.startPos[0], + &uGradientInfo.startPos[1], + &uGradientInfo.endPos[0], + &uGradientInfo.endPos[1]); +} + +//************************************************************************ +// WgPipelineBindGroupLinear +//************************************************************************ + +void WgPipelineBindGroupLinear::initialize(WGPUDevice device, WgPipelineLinear& pipelinePipelineLinear) { + // buffer uniform uMatrix + WGPUBufferDescriptor bufferUniformDesc_uMatrix{}; + bufferUniformDesc_uMatrix.nextInChain = nullptr; + bufferUniformDesc_uMatrix.label = "Buffer uniform pipeline linear uMatrix"; + bufferUniformDesc_uMatrix.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; + bufferUniformDesc_uMatrix.size = sizeof(WgPipelineMatrix); + bufferUniformDesc_uMatrix.mappedAtCreation = false; + uBufferMatrix = wgpuDeviceCreateBuffer(device, &bufferUniformDesc_uMatrix); + assert(uBufferMatrix); + // buffer uniform uColorInfo + WGPUBufferDescriptor bufferUniformDesc_uGradientInfo{}; + bufferUniformDesc_uGradientInfo.nextInChain = nullptr; + bufferUniformDesc_uGradientInfo.label = "Buffer uniform pipeline linear uGradientInfo"; + bufferUniformDesc_uGradientInfo.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; + bufferUniformDesc_uGradientInfo.size = sizeof(WgPipelineLinearGradientInfo); + bufferUniformDesc_uGradientInfo.mappedAtCreation = false; + uBufferGradientInfo = wgpuDeviceCreateBuffer(device, &bufferUniformDesc_uGradientInfo); + assert(uBufferGradientInfo); + + // 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 entry @binding(1) uGradientInfo + WGPUBindGroupEntry bindGroupEntry_uGradientInfo{}; + bindGroupEntry_uGradientInfo.nextInChain = nullptr; + bindGroupEntry_uGradientInfo.binding = 1; + bindGroupEntry_uGradientInfo.buffer = uBufferGradientInfo; + bindGroupEntry_uGradientInfo.offset = 0; + bindGroupEntry_uGradientInfo.size = sizeof(WgPipelineLinearGradientInfo); + bindGroupEntry_uGradientInfo.sampler = nullptr; + bindGroupEntry_uGradientInfo.textureView = nullptr; + // bind group entries + WGPUBindGroupEntry bindGroupEntries[] { + bindGroupEntry_uMatrix, // @binding(0) uMatrix + bindGroupEntry_uGradientInfo // @binding(1) uGradientInfo + }; + // bind group descriptor + WGPUBindGroupDescriptor bindGroupDescPipeline{}; + bindGroupDescPipeline.nextInChain = nullptr; + bindGroupDescPipeline.label = "The binding group pipeline linear"; + bindGroupDescPipeline.layout = pipelinePipelineLinear.mBindGroupLayout; + bindGroupDescPipeline.entryCount = 2; + bindGroupDescPipeline.entries = bindGroupEntries; + mBindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDescPipeline); + assert(mBindGroup); +} + +void WgPipelineBindGroupLinear::release() { + if (uBufferGradientInfo) { + wgpuBufferDestroy(uBufferGradientInfo); + wgpuBufferRelease(uBufferGradientInfo); + uBufferGradientInfo = nullptr; + } + if (uBufferMatrix) { + wgpuBufferDestroy(uBufferMatrix); + wgpuBufferRelease(uBufferMatrix); + uBufferMatrix = nullptr; + } + if (mBindGroup) { + wgpuBindGroupRelease(mBindGroup); + mBindGroup = nullptr; + } +} + +void WgPipelineBindGroupLinear::update(WGPUQueue queue, WgPipelineDataLinear& pipelineDataLinear) { + wgpuQueueWriteBuffer(queue, uBufferMatrix, 0, &pipelineDataLinear.uMatrix, sizeof(pipelineDataLinear.uMatrix)); + wgpuQueueWriteBuffer(queue, uBufferGradientInfo, 0, &pipelineDataLinear.uGradientInfo, sizeof(pipelineDataLinear.uGradientInfo)); +} + +//************************************************************************ +// WgPipelineLinear +//************************************************************************ + +void WgPipelineLinear::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 descriptor @group(0) @binding(1) uColorInfo + WGPUBindGroupLayoutEntry bindGroupLayoutEntry_uColorInfo{}; + bindGroupLayoutEntry_uColorInfo.nextInChain = nullptr; + bindGroupLayoutEntry_uColorInfo.binding = 1; + bindGroupLayoutEntry_uColorInfo.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; + bindGroupLayoutEntry_uColorInfo.buffer.nextInChain = nullptr; + bindGroupLayoutEntry_uColorInfo.buffer.type = WGPUBufferBindingType_Uniform; + bindGroupLayoutEntry_uColorInfo.buffer.hasDynamicOffset = false; + bindGroupLayoutEntry_uColorInfo.buffer.minBindingSize = 0; + // bind group layout entries @group(0) + WGPUBindGroupLayoutEntry bindGroupLayoutEntries[] { + bindGroupLayoutEntry_uMatrix, + bindGroupLayoutEntry_uColorInfo + }; + // bind group layout descriptor scene @group(0) + WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc{}; + bindGroupLayoutDesc.nextInChain = nullptr; + bindGroupLayoutDesc.label = "Bind group layout pipeline linear"; + bindGroupLayoutDesc.entryCount = 2; + 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 linear"; + 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_NotEqual; + depthStencilState.stencilFront.failOp = WGPUStencilOperation_Zero; + depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Zero; + depthStencilState.stencilFront.passOp = WGPUStencilOperation_Zero; + // depthStencilState.stencilBack + depthStencilState.stencilBack.compare = WGPUCompareFunction_NotEqual; + depthStencilState.stencilBack.failOp = WGPUStencilOperation_Zero; + depthStencilState.stencilBack.depthFailOp = WGPUStencilOperation_Zero; + depthStencilState.stencilBack.passOp = WGPUStencilOperation_Zero; + // 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_PipelineLinear; + // shader module descriptor + WGPUShaderModuleDescriptor shaderModuleDesc{}; + shaderModuleDesc.nextInChain = &shaderModuleWGSLDesc.chain; + shaderModuleDesc.label = "The shader module pipeline linear"; + shaderModuleDesc.hintCount = 0; + shaderModuleDesc.hints = nullptr; + mShaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); + assert(mShaderModule); + + // vertex attributes + WGPUVertexAttribute vertexAttributes[] = { + { WGPUVertexFormat_Float32x3, sizeof(float) * 0, 0 }, // position + }; + // vertex buffer layout + WGPUVertexBufferLayout vertexBufferLayout{}; + vertexBufferLayout.arrayStride = sizeof(float) * 3; // 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 linear"; + // 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 WgPipelineLinear::release() { + wgpuRenderPipelineRelease(mRenderPipeline); + wgpuShaderModuleRelease(mShaderModule); + wgpuPipelineLayoutRelease(mPipelineLayout); + wgpuBindGroupLayoutRelease(mBindGroupLayout); +} diff --git a/src/renderer/wg_engine/tvgWgPipelineLinear.h b/src/renderer/wg_engine/tvgWgPipelineLinear.h new file mode 100644 index 00000000..6d964334 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineLinear.h @@ -0,0 +1,61 @@ +/* + * 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_LINEAR_H_ +#define _TVG_WG_PIPELINE_LINEAR_H_ + +#include "tvgWgPipelineBase.h" + +class WgPipelineLinear; + +#define MAX_LINEAR_GRADIENT_STOPS 4 +struct WgPipelineLinearGradientInfo { + alignas(16) float nStops[4]{}; + alignas(16) float startPos[2]{}; + alignas(8) float endPos[2]{}; + alignas(8) float stopPoints[MAX_LINEAR_GRADIENT_STOPS]{}; + alignas(16) float stopColors[4 * MAX_LINEAR_GRADIENT_STOPS]{}; +}; + +struct WgPipelineDataLinear: WgPipelineData { + WgPipelineLinearGradientInfo uGradientInfo{}; // @binding(1) + + void updateGradient(LinearGradient* linearGradient); +}; + +class WgPipelineBindGroupLinear: public WgPipelineBindGroup { +private: + WGPUBuffer uBufferGradientInfo{}; // @binding(1) +public: + void initialize(WGPUDevice device, WgPipelineLinear& pipelinePipelineLinear); + void release(); + + void update(WGPUQueue mQueue, WgPipelineDataLinear& pipelineDataLinear); +}; + +class WgPipelineLinear: public WgPipelineBase { +public: + void initialize(WGPUDevice device) override; + void release() override; +}; + +#endif //_TVG_WG_PIPELINE_LINEAR_H_ diff --git a/src/renderer/wg_engine/tvgWgRenderData.h b/src/renderer/wg_engine/tvgWgRenderData.h index 74afe9c1..c5e304f3 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.h +++ b/src/renderer/wg_engine/tvgWgRenderData.h @@ -21,6 +21,7 @@ */ #include "tvgWgPipelineSolid.h" +#include "tvgWgPipelineLinear.h" #ifndef _TVG_WG_RENDER_DATA_H_ #define _TVG_WG_RENDER_DATA_H_ @@ -53,6 +54,7 @@ public: Array mGeometryDataStroke; WgPipelineBindGroupSolid mPipelineBindGroupSolid{}; + WgPipelineBindGroupLinear mPipelineBindGroupLinear{}; WgPipelineBase* mPipelineBase{}; // external WgPipelineBindGroup* mPipelineBindGroup{}; // external diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index fed5459d..eb3543c8 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -103,6 +103,7 @@ void WgRenderer::initialize() { // create pipelines mPipelineEmpty.initialize(mDevice); mPipelineSolid.initialize(mDevice); + mPipelineLinear.initialize(mDevice); mPipelineBindGroupEmpty.initialize(mDevice, mPipelineEmpty); mGeometryDataPipeline.initialize(mDevice); } @@ -117,6 +118,7 @@ void WgRenderer::release() { if (mSurface) wgpuSurfaceRelease(mSurface); mGeometryDataPipeline.release(); mPipelineBindGroupEmpty.release(); + mPipelineLinear.release(); mPipelineSolid.release(); mPipelineEmpty.release(); if (mDevice) { @@ -134,11 +136,25 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const renderDataShape = new WgRenderDataShape(); renderDataShape->initialize(mDevice); renderDataShape->mPipelineBindGroupSolid.initialize(mDevice, mPipelineSolid); + renderDataShape->mPipelineBindGroupLinear.initialize(mDevice, mPipelineLinear); } 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 solid fill properties if ((flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Transform)) && (!rshape.fill)) { WgPipelineDataSolid pipelineDataSolid{}; diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index 6e0d59ae..9c9a6acb 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -24,7 +24,6 @@ #define _TVG_WG_RENDERER_H_ #include "tvgWgPipelineEmpty.h" -#include "tvgWgPipelineSolid.h" #include "tvgWgRenderData.h" class WgRenderer : public RenderMethod @@ -80,6 +79,7 @@ private: private: WgPipelineEmpty mPipelineEmpty; WgPipelineSolid mPipelineSolid; + WgPipelineLinear mPipelineLinear; WgGeometryData mGeometryDataPipeline; WgPipelineBindGroupEmpty mPipelineBindGroupEmpty; }; diff --git a/src/renderer/wg_engine/tvgWgShaderSrc.cpp b/src/renderer/wg_engine/tvgWgShaderSrc.cpp index a9e6139a..6a2b5e3d 100644 --- a/src/renderer/wg_engine/tvgWgShaderSrc.cpp +++ b/src/renderer/wg_engine/tvgWgShaderSrc.cpp @@ -99,4 +99,88 @@ fn vs_main(in: VertexInput) -> VertexOutput { @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f { return uColorInfo.color; -})"; \ No newline at end of file +})"; + +//************************************************************************ +// cShaderSource_PipelineRadial +//************************************************************************ +const char* cShaderSource_PipelineLinear = R"( +// vertex input +struct VertexInput { + @location(0) position: vec3f +}; + +// Matrix +struct Matrix { + transform: mat4x4f +}; + +// GradientInfo +const MAX_STOP_COUNT = 4; +struct GradientInfo { + nStops : vec4f, + gradStartPos : vec2f, + gradEndPos : vec2f, + stopPoints : vec4f, + stopColors : array +}; + +// vertex output +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) vScreenCoord: vec2f +}; + +// uMatrix +@group(0) @binding(0) var uMatrix: Matrix; +// uGradientInfo +@group(0) @binding(1) var uGradientInfo: GradientInfo; + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + // fill output + var out: VertexOutput; + out.position = uMatrix.transform * vec4f(in.position.xy, 0.0, 1.0); + out.vScreenCoord = in.position.xy; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4f { + let pos: vec2f = in.vScreenCoord; + let st: vec2f = uGradientInfo.gradStartPos; + let ed: vec2f = uGradientInfo.gradEndPos; + + let ba: vec2f = ed - st; + + // get interpolation factor + let t: f32 = clamp(dot(pos - st, ba) / dot(ba, ba), 0.0, 1.0); + + // get stops count + let last: i32 = i32(uGradientInfo.nStops[0]) - 1; + + // resulting color + var color = vec4(1.0); + + // closer than first stop + if (t <= uGradientInfo.stopPoints[0]) { + color = uGradientInfo.stopColors[0]; + } + + // further than last stop + if (t >= uGradientInfo.stopPoints[last]) { + color = uGradientInfo.stopColors[last]; + } + + // look in the middle + for (var i = 0i; i < last; i++) { + let strt = uGradientInfo.stopPoints[i]; + let stop = uGradientInfo.stopPoints[i+1]; + if ((t > strt) && (t < stop)) { + let step: f32 = (t - strt) / (stop - strt); + color = mix(uGradientInfo.stopColors[i], uGradientInfo.stopColors[i+1], step); + } + } + + return color; +})"; diff --git a/src/renderer/wg_engine/tvgWgShaderSrc.h b/src/renderer/wg_engine/tvgWgShaderSrc.h index eb70d3fc..e8db9d92 100644 --- a/src/renderer/wg_engine/tvgWgShaderSrc.h +++ b/src/renderer/wg_engine/tvgWgShaderSrc.h @@ -31,4 +31,7 @@ extern const char* cShaderSource_PipelineEmpty; // pipeline shader module solid extern const char* cShaderSource_PipelineSolid; +// pipeline shader module linear +extern const char* cShaderSource_PipelineLinear; + #endif