From 25513b591a20decec12b73ac59e07c656c0c7969 Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Tue, 14 Nov 2023 17:01:06 +0200 Subject: [PATCH] wg_engine: introduced images drawing support [issues 1479: pictures](https://github.com/thorvg/thorvg/issues/1479) auto picture = tvg::Picture::gen(); picture->load("images/test.png"); picture->translate(0, 0); picture->size(100, 100); picture->opacity(255); canvas->push(std::move(picture)); --- src/renderer/wg_engine/tvgWgPipelineImage.cpp | 414 ++++++++++++++++++ src/renderer/wg_engine/tvgWgPipelineImage.h | 59 +++ src/renderer/wg_engine/tvgWgRenderData.cpp | 78 +++- src/renderer/wg_engine/tvgWgRenderData.h | 7 + src/renderer/wg_engine/tvgWgRenderer.cpp | 31 +- src/renderer/wg_engine/tvgWgRenderer.h | 1 + src/renderer/wg_engine/tvgWgShaderSrc.cpp | 48 ++ src/renderer/wg_engine/tvgWgShaderSrc.h | 3 + 8 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 src/renderer/wg_engine/tvgWgPipelineImage.cpp create mode 100644 src/renderer/wg_engine/tvgWgPipelineImage.h diff --git a/src/renderer/wg_engine/tvgWgPipelineImage.cpp b/src/renderer/wg_engine/tvgWgPipelineImage.cpp new file mode 100644 index 00000000..23564990 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineImage.cpp @@ -0,0 +1,414 @@ +/* + * 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 "tvgWgPipelineImage.h" +#include "tvgWgShaderSrc.h" + +//************************************************************************ +// WgPipelineDataImage +//************************************************************************ + +void WgPipelineDataImage::updateOpacity(const uint8_t opacity) { + uColorInfo.color[0] = 1.0f; // red + uColorInfo.color[1] = 1.0f; // green + uColorInfo.color[2] = 1.0f; // blue + uColorInfo.color[3] = opacity / 255.0f; // alpha +} + +//************************************************************************ +// WgPipelineBindGroupImage +//************************************************************************ + +void WgPipelineBindGroupImage::initialize(WGPUDevice device, WgPipelineImage& pipelineImage, Surface* surface) { + // buffer uniform uMatrix + WGPUBufferDescriptor bufferUniformDesc_uMatrix{}; + bufferUniformDesc_uMatrix.nextInChain = nullptr; + bufferUniformDesc_uMatrix.label = "Buffer uniform pipeline image 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_uColorInfo{}; + bufferUniformDesc_uColorInfo.nextInChain = nullptr; + bufferUniformDesc_uColorInfo.label = "Buffer uniform pipeline image uColorInfo"; + bufferUniformDesc_uColorInfo.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform; + bufferUniformDesc_uColorInfo.size = sizeof(WgPipelineImageColorInfo); + bufferUniformDesc_uColorInfo.mappedAtCreation = false; + uBufferColorInfo = wgpuDeviceCreateBuffer(device, &bufferUniformDesc_uColorInfo); + assert(uBufferColorInfo); + // sampler uniform uSamplerBase + WGPUSamplerDescriptor samplerDesc_uSamplerBase{}; + samplerDesc_uSamplerBase.nextInChain = nullptr; + samplerDesc_uSamplerBase.label = "Sampler uniform pipeline image uSamplerBase"; + samplerDesc_uSamplerBase.addressModeU = WGPUAddressMode_ClampToEdge; + samplerDesc_uSamplerBase.addressModeV = WGPUAddressMode_ClampToEdge; + samplerDesc_uSamplerBase.addressModeW = WGPUAddressMode_ClampToEdge; + samplerDesc_uSamplerBase.magFilter = WGPUFilterMode_Nearest; + samplerDesc_uSamplerBase.minFilter = WGPUFilterMode_Nearest; + samplerDesc_uSamplerBase.mipmapFilter = WGPUMipmapFilterMode_Nearest; + samplerDesc_uSamplerBase.lodMinClamp = 0.0f; + samplerDesc_uSamplerBase.lodMaxClamp = 32.0f; + samplerDesc_uSamplerBase.compare = WGPUCompareFunction_Undefined; + samplerDesc_uSamplerBase.maxAnisotropy = 1; + uSamplerBase = wgpuDeviceCreateSampler(device, &samplerDesc_uSamplerBase); + assert(uSamplerBase); + // webgpu texture data holder + WGPUTextureDescriptor textureDesc{}; + textureDesc.nextInChain = nullptr; + textureDesc.label = "Texture base pipeline image"; + textureDesc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst; + textureDesc.dimension = WGPUTextureDimension_2D; + textureDesc.size = { surface->w, surface->h, 1 }; + textureDesc.format = WGPUTextureFormat_RGBA8Unorm; + textureDesc.mipLevelCount = 1; + textureDesc.sampleCount = 1; + textureDesc.viewFormatCount = 0; + textureDesc.viewFormats = nullptr; + mTextureBase = wgpuDeviceCreateTexture(device, &textureDesc); + assert(mTextureBase); + // texture view uniform uTextureViewBase + WGPUTextureViewDescriptor textureViewDesc{}; + textureViewDesc.nextInChain = nullptr; + textureViewDesc.label = "The depth-stencil texture view"; + textureViewDesc.format = WGPUTextureFormat_RGBA8Unorm; + textureViewDesc.dimension = WGPUTextureViewDimension_2D; + textureViewDesc.baseMipLevel = 0; + textureViewDesc.mipLevelCount = 1; + textureViewDesc.baseArrayLayer = 0; + textureViewDesc.arrayLayerCount = 1; + textureViewDesc.aspect = WGPUTextureAspect_All; + uTextureViewBase = wgpuTextureCreateView(mTextureBase, &textureViewDesc); + assert(uTextureViewBase); + + // 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) uColorInfo + WGPUBindGroupEntry bindGroupEntry_uColorInfo{}; + bindGroupEntry_uColorInfo.nextInChain = nullptr; + bindGroupEntry_uColorInfo.binding = 1; + bindGroupEntry_uColorInfo.buffer = uBufferColorInfo; + bindGroupEntry_uColorInfo.offset = 0; + bindGroupEntry_uColorInfo.size = sizeof(WgPipelineImageColorInfo); + bindGroupEntry_uColorInfo.sampler = nullptr; + bindGroupEntry_uColorInfo.textureView = nullptr; + // bind group entry @binding(2) uSamplerBase + WGPUBindGroupEntry bindGroupEntry_uSamplerBase{}; + bindGroupEntry_uSamplerBase.nextInChain = nullptr; + bindGroupEntry_uSamplerBase.binding = 2; + bindGroupEntry_uSamplerBase.buffer = nullptr; + bindGroupEntry_uSamplerBase.offset = 0; + bindGroupEntry_uSamplerBase.size = 0; + bindGroupEntry_uSamplerBase.sampler = uSamplerBase; + bindGroupEntry_uSamplerBase.textureView = nullptr; + // bind group entry @binding(3) uTextureViewBase + WGPUBindGroupEntry bindGroupEntry_uTextureViewBase{}; + bindGroupEntry_uTextureViewBase.nextInChain = nullptr; + bindGroupEntry_uTextureViewBase.binding = 3; + bindGroupEntry_uTextureViewBase.buffer = nullptr; + bindGroupEntry_uTextureViewBase.offset = 0; + bindGroupEntry_uTextureViewBase.size = 0; + bindGroupEntry_uTextureViewBase.sampler = nullptr; + bindGroupEntry_uTextureViewBase.textureView = uTextureViewBase; + // bind group entries + WGPUBindGroupEntry bindGroupEntries[] { + bindGroupEntry_uMatrix, // @binding(0) uMatrix + bindGroupEntry_uColorInfo, // @binding(1) uColorInfo + bindGroupEntry_uSamplerBase, // @binding(2) uSamplerBase + bindGroupEntry_uTextureViewBase // @binding(3) uTextureViewBase + }; + // bind group descriptor + WGPUBindGroupDescriptor bindGroupDescPipeline{}; + bindGroupDescPipeline.nextInChain = nullptr; + bindGroupDescPipeline.label = "The binding group pipeline image"; + bindGroupDescPipeline.layout = pipelineImage.mBindGroupLayout; + bindGroupDescPipeline.entryCount = 4; + bindGroupDescPipeline.entries = bindGroupEntries; + mBindGroup = wgpuDeviceCreateBindGroup(device, &bindGroupDescPipeline); + assert(mBindGroup); +} + +void WgPipelineBindGroupImage::release() { + if (uTextureViewBase) { + wgpuTextureViewRelease(uTextureViewBase); + uTextureViewBase = nullptr; + } + if (mTextureBase) { + wgpuTextureDestroy(mTextureBase); + wgpuTextureRelease(mTextureBase); + mTextureBase = nullptr; + } + if (uSamplerBase) { + wgpuSamplerRelease(uSamplerBase); + uSamplerBase = nullptr; + } + if (uBufferColorInfo) { + wgpuBufferDestroy(uBufferColorInfo); + wgpuBufferRelease(uBufferColorInfo); + uBufferColorInfo = nullptr; + } + if (uBufferMatrix) { + wgpuBufferDestroy(uBufferMatrix); + wgpuBufferRelease(uBufferMatrix); + uBufferMatrix = nullptr; + } + if (mBindGroup) { + wgpuBindGroupRelease(mBindGroup); + mBindGroup = nullptr; + } +} + +void WgPipelineBindGroupImage::update(WGPUQueue queue, WgPipelineDataImage& pipelineDataImage, Surface* surface) { + wgpuQueueWriteBuffer(queue, uBufferMatrix, 0, &pipelineDataImage.uMatrix, sizeof(pipelineDataImage.uMatrix)); + wgpuQueueWriteBuffer(queue, uBufferColorInfo, 0, &pipelineDataImage.uColorInfo, sizeof(pipelineDataImage.uColorInfo)); + WGPUImageCopyTexture imageCopyTexture{}; + imageCopyTexture.nextInChain = nullptr; + imageCopyTexture.texture = mTextureBase; + imageCopyTexture.mipLevel = 0; + imageCopyTexture.origin = { 0, 0, 0 }; + imageCopyTexture.aspect = WGPUTextureAspect_All; + WGPUTextureDataLayout textureDataLayout{}; + textureDataLayout.nextInChain = nullptr; + textureDataLayout.offset = 0; + textureDataLayout.bytesPerRow = 4 * surface->w; + textureDataLayout.rowsPerImage = surface->h; + WGPUExtent3D writeSize{}; + writeSize.width = surface->w; + writeSize.height = surface->h; + writeSize.depthOrArrayLayers = 1; + wgpuQueueWriteTexture(queue, &imageCopyTexture, surface->data, 4 * surface->w * surface->h, &textureDataLayout, &writeSize); +} + +//************************************************************************ +// WgPipelineImage +//************************************************************************ + +void WgPipelineImage::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 descriptor @group(0) @binding(2) uSamplerBase + WGPUBindGroupLayoutEntry bindGroupLayoutEntry_uSamplerBase{}; + bindGroupLayoutEntry_uSamplerBase.nextInChain = nullptr; + bindGroupLayoutEntry_uSamplerBase.binding = 2; + bindGroupLayoutEntry_uSamplerBase.visibility = WGPUShaderStage_Fragment; + bindGroupLayoutEntry_uSamplerBase.sampler.nextInChain = nullptr; + bindGroupLayoutEntry_uSamplerBase.sampler.type = WGPUSamplerBindingType_Filtering; + // bind group layout descriptor @group(0) @binding(3) uTextureViewBase + WGPUBindGroupLayoutEntry bindGroupLayoutEntry_uTextureViewBase{}; + bindGroupLayoutEntry_uTextureViewBase.nextInChain = nullptr; + bindGroupLayoutEntry_uTextureViewBase.binding = 3; + bindGroupLayoutEntry_uTextureViewBase.visibility = WGPUShaderStage_Fragment; + bindGroupLayoutEntry_uTextureViewBase.texture.nextInChain = nullptr; + bindGroupLayoutEntry_uTextureViewBase.texture.sampleType = WGPUTextureSampleType_Float; + bindGroupLayoutEntry_uTextureViewBase.texture.viewDimension = WGPUTextureViewDimension_2D; + bindGroupLayoutEntry_uTextureViewBase.texture.multisampled = false; + // bind group layout entries @group(0) + WGPUBindGroupLayoutEntry bindGroupLayoutEntries[] { + bindGroupLayoutEntry_uMatrix, + bindGroupLayoutEntry_uColorInfo, + bindGroupLayoutEntry_uSamplerBase, + bindGroupLayoutEntry_uTextureViewBase + }; + // bind group layout descriptor scene @group(0) + WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc{}; + bindGroupLayoutDesc.nextInChain = nullptr; + bindGroupLayoutDesc.label = "Bind group layout pipeline image"; + bindGroupLayoutDesc.entryCount = 4; + 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 image"; + 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_Zero; + depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Zero; + depthStencilState.stencilFront.passOp = WGPUStencilOperation_Zero; + // depthStencilState.stencilBack + depthStencilState.stencilBack.compare = WGPUCompareFunction_Always; + 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_PipelineImage; + // shader module descriptor + WGPUShaderModuleDescriptor shaderModuleDesc{}; + shaderModuleDesc.nextInChain = &shaderModuleWGSLDesc.chain; + shaderModuleDesc.label = "The shader module pipeline image"; + shaderModuleDesc.hintCount = 0; + shaderModuleDesc.hints = nullptr; + mShaderModule = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc); + assert(mShaderModule); + + // vertex attributes + WGPUVertexAttribute vertexAttributesPos[] = { + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 } // position + }; + // vertex buffer layout position + WGPUVertexBufferLayout vertexBufferLayoutPos{}; + vertexBufferLayoutPos.arrayStride = sizeof(float) * 2; // position + vertexBufferLayoutPos.stepMode = WGPUVertexStepMode_Vertex; + vertexBufferLayoutPos.attributeCount = 1; // position + vertexBufferLayoutPos.attributes = vertexAttributesPos; + // vertex attributes + WGPUVertexAttribute vertexAttributesTex[] = { + { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 1 } // tex coords + }; + // vertex buffer layout tex coords + WGPUVertexBufferLayout vertexBufferLayoutTex{}; + vertexBufferLayoutTex.arrayStride = sizeof(float) * 2; // tex coords + vertexBufferLayoutTex.stepMode = WGPUVertexStepMode_Vertex; + vertexBufferLayoutTex.attributeCount = 1; // tex coords + vertexBufferLayoutTex.attributes = vertexAttributesTex; + // vertex attributes + WGPUVertexBufferLayout vertexBufferLayouts[] = { + vertexBufferLayoutPos, + vertexBufferLayoutTex + }; + + // 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 image"; + // 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 = 2; + renderPipelineDesc.vertex.buffers = vertexBufferLayouts; // 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 WgPipelineImage::release() { + wgpuRenderPipelineRelease(mRenderPipeline); + wgpuShaderModuleRelease(mShaderModule); + wgpuPipelineLayoutRelease(mPipelineLayout); + wgpuBindGroupLayoutRelease(mBindGroupLayout); +} diff --git a/src/renderer/wg_engine/tvgWgPipelineImage.h b/src/renderer/wg_engine/tvgWgPipelineImage.h new file mode 100644 index 00000000..ad527ce1 --- /dev/null +++ b/src/renderer/wg_engine/tvgWgPipelineImage.h @@ -0,0 +1,59 @@ +/* + * 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_IMAGE_H_ +#define _TVG_WG_PIPELINE_IMAGE_H_ + +#include "tvgWgPipelineBase.h" + +class WgPipelineImage; + +struct WgPipelineImageColorInfo { + float color[4]{}; +}; + +struct WgPipelineDataImage: WgPipelineData { + WgPipelineImageColorInfo uColorInfo{}; // @binding(1) + + void updateOpacity(const uint8_t opacity); +}; + +class WgPipelineBindGroupImage: public WgPipelineBindGroup { +private: + WGPUBuffer uBufferColorInfo{}; // @binding(1) + WGPUSampler uSamplerBase{}; // @binding(2) + WGPUTextureView uTextureViewBase{}; // @binding(3) + WGPUTexture mTextureBase{}; // gpu texture data +public: + void initialize(WGPUDevice device, WgPipelineImage& pipelineImage, Surface* surface); + void release(); + + void update(WGPUQueue mQueue, WgPipelineDataImage& pipelineDataImage, Surface* surface); +}; + +class WgPipelineImage: public WgPipelineBase { +public: + void initialize(WGPUDevice device) override; + void release() override; +}; + +#endif //_TVG_WG_PIPELINE_IMAGE_H_ diff --git a/src/renderer/wg_engine/tvgWgRenderData.cpp b/src/renderer/wg_engine/tvgWgRenderData.cpp index 1b451252..fff7a5ec 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.cpp +++ b/src/renderer/wg_engine/tvgWgRenderData.cpp @@ -32,6 +32,13 @@ void WgGeometryData::draw(WGPURenderPassEncoder renderPassEncoder) { wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, mIndexCount, 1, 0, 0, 0); } +void WgGeometryData::drawImage(WGPURenderPassEncoder renderPassEncoder) { + wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, mBufferVertex, 0, mVertexCount * sizeof(float) * 2); + wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 1, mBufferTexCoords, 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, @@ -67,6 +74,20 @@ void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, float* vertexDat mIndexCount = indexCount; } +void WgGeometryData::update(WGPUDevice device, WGPUQueue queue, float* vertexData, float* texCoordsData, size_t vertexCount, uint32_t* indexData, size_t indexCount) { + update(device, queue, vertexData, vertexCount, indexData, indexCount); + // buffer tex coords data create and write + WGPUBufferDescriptor bufferTexCoordsDesc{}; + bufferTexCoordsDesc.nextInChain = nullptr; + bufferTexCoordsDesc.label = "Buffer tex coords geometry data"; + bufferTexCoordsDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex; + bufferTexCoordsDesc.size = sizeof(float) * vertexCount * 2; // u, v + bufferTexCoordsDesc.mappedAtCreation = false; + mBufferTexCoords = wgpuDeviceCreateBuffer(device, &bufferTexCoordsDesc); + assert(mBufferTexCoords); + wgpuQueueWriteBuffer(queue, mBufferTexCoords, 0, texCoordsData, vertexCount * sizeof(float) * 2); +} + void WgGeometryData::release() { if (mBufferIndex) { wgpuBufferDestroy(mBufferIndex); @@ -74,6 +95,11 @@ void WgGeometryData::release() { mBufferIndex = nullptr; mVertexCount = 0; } + if (mBufferTexCoords) { + wgpuBufferDestroy(mBufferTexCoords); + wgpuBufferRelease(mBufferTexCoords); + mBufferTexCoords = nullptr; + } if (mBufferVertex) { wgpuBufferDestroy(mBufferVertex); wgpuBufferRelease(mBufferVertex); @@ -89,7 +115,6 @@ void WgGeometryData::release() { 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) { @@ -138,9 +163,14 @@ void WgRenderDataShape::release() { releaseRenderData(); mRenderSettingsShape.release(); mRenderSettingsStroke.release(); + mPipelineBindGroupImage.release(); } void WgRenderDataShape::releaseRenderData() { + for (uint32_t i = 0; i < mGeometryDataImage.count; i++) { + mGeometryDataImage[i]->release(); + delete mGeometryDataImage[i]; + } for (uint32_t i = 0; i < mGeometryDataStroke.count; i++) { mGeometryDataStroke[i]->release(); delete mGeometryDataStroke[i]; @@ -153,6 +183,52 @@ void WgRenderDataShape::releaseRenderData() { mGeometryDataShape.clear(); } +void WgRenderDataShape::tesselate(WGPUDevice device, WGPUQueue queue, Surface* surface, const RenderMesh* mesh) { + // create image geometry data + Array vertexList; + Array texCoordsList; + Array indexList; + + if (mesh && mesh->triangleCnt) { + vertexList.reserve(mesh->triangleCnt * 3); + texCoordsList.reserve(mesh->triangleCnt * 3); + indexList.reserve(mesh->triangleCnt * 3); + for (uint32_t i = 0; i < mesh->triangleCnt; i++) { + vertexList.push(mesh->triangles[i].vertex[0].pt); + vertexList.push(mesh->triangles[i].vertex[1].pt); + vertexList.push(mesh->triangles[i].vertex[2].pt); + texCoordsList.push(mesh->triangles[i].vertex[0].uv); + texCoordsList.push(mesh->triangles[i].vertex[1].uv); + texCoordsList.push(mesh->triangles[i].vertex[2].uv); + indexList.push(i*3 + 0); + indexList.push(i*3 + 1); + indexList.push(i*3 + 2); + } + } else { + vertexList.push({ 0.0f, 0.0f }); + vertexList.push({ (float)surface->w, 0.0f }); + vertexList.push({ (float)surface->w, (float)surface->h }); + vertexList.push({ 0.0f, (float)surface->h }); + texCoordsList.push({0.0f, 0.0f}); + texCoordsList.push({1.0f, 0.0f}); + texCoordsList.push({1.0f, 1.0f}); + texCoordsList.push({0.0f, 1.0f}); + indexList.push(0); + indexList.push(1); + indexList.push(2); + indexList.push(0); + indexList.push(2); + indexList.push(3); + } + + // create geometry data for image + WgGeometryData* geometryData = new WgGeometryData(); + geometryData->update(device, queue, + (float *)vertexList.data, (float *)texCoordsList.data, vertexList.count, + indexList.data, indexList.count); + mGeometryDataImage.push(geometryData); +} + void WgRenderDataShape::tesselate(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape) { Array outlines{}; decodePath(rshape, outlines); diff --git a/src/renderer/wg_engine/tvgWgRenderData.h b/src/renderer/wg_engine/tvgWgRenderData.h index 0066c4cd..6e3535a0 100644 --- a/src/renderer/wg_engine/tvgWgRenderData.h +++ b/src/renderer/wg_engine/tvgWgRenderData.h @@ -26,11 +26,13 @@ #include "tvgWgPipelineSolid.h" #include "tvgWgPipelineLinear.h" #include "tvgWgPipelineRadial.h" +#include "tvgWgPipelineImage.h" #include "tvgWgGeometry.h" class WgGeometryData { public: WGPUBuffer mBufferVertex{}; + WGPUBuffer mBufferTexCoords{}; WGPUBuffer mBufferIndex{}; size_t mVertexCount{}; size_t mIndexCount{}; @@ -40,8 +42,10 @@ public: void initialize(WGPUDevice device) {}; void draw(WGPURenderPassEncoder renderPassEncoder); + void drawImage(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 update(WGPUDevice device, WGPUQueue queue, float* vertexData, float* texCoordsData, size_t vertexCount, uint32_t* indexData, size_t indexCount); void release(); }; @@ -70,15 +74,18 @@ class WgRenderDataShape: public WgRenderData { public: Array mGeometryDataShape; Array mGeometryDataStroke; + Array mGeometryDataImage; WgRenderDataShapeSettings mRenderSettingsShape; WgRenderDataShapeSettings mRenderSettingsStroke; + WgPipelineBindGroupImage mPipelineBindGroupImage; public: WgRenderDataShape() {} void release() override; void releaseRenderData(); + void tesselate(WGPUDevice device, WGPUQueue queue, Surface* surface, const RenderMesh* mesh); void tesselate(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape); void stroke(WGPUDevice device, WGPUQueue queue, const RenderShape& rshape); private: diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index d5d68b7c..4f9b5a95 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -93,6 +93,8 @@ void WgRenderer::initialize() { // on device error function auto onDeviceError = [](WGPUErrorType type, char const* message, void* pUserData) { TVGERR("WG_RENDERER", "Uncaptured device error: %s", message); + // TODO: remove direct error message + std::cout << message << std::endl; }; // set device error handling wgpuDeviceSetUncapturedErrorCallback(mDevice, onDeviceError, nullptr); @@ -106,6 +108,7 @@ void WgRenderer::initialize() { mPipelineSolid.initialize(mDevice); mPipelineLinear.initialize(mDevice); mPipelineRadial.initialize(mDevice); + mPipelineImage.initialize(mDevice); mPipelineBindGroupEmpty.initialize(mDevice, mPipelineEmpty); mPipelineBindGroupStroke.initialize(mDevice, mPipelineStroke); mGeometryDataWindow.initialize(mDevice); @@ -122,6 +125,7 @@ void WgRenderer::release() { mGeometryDataWindow.release(); mPipelineBindGroupStroke.release(); mPipelineBindGroupEmpty.release(); + mPipelineImage.release(); mPipelineRadial.release(); mPipelineLinear.release(); mPipelineSolid.release(); @@ -175,7 +179,21 @@ RenderData WgRenderer::prepare(const Array& scene, RenderData data, } RenderData WgRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array& clips, uint8_t opacity, RenderUpdateFlag flags) { - return nullptr; + // get or create render data shape + auto renderDataShape = (WgRenderDataShape*)data; + if (!renderDataShape) { + renderDataShape = new WgRenderDataShape(); + renderDataShape->initialize(mDevice); + renderDataShape->mPipelineBindGroupImage.initialize(mDevice, mPipelineImage, surface); + } + if (flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Image | RenderUpdateFlag::Transform)) { + WgPipelineDataImage pipelineDataImage{}; + pipelineDataImage.updateMatrix(mViewMatrix, transform); + pipelineDataImage.updateOpacity(opacity); + renderDataShape->mPipelineBindGroupImage.update(mQueue, pipelineDataImage, surface); + renderDataShape->tesselate(mDevice, mQueue, surface, mesh); + } + return renderDataShape; } bool WgRenderer::preRender() { @@ -188,6 +206,7 @@ bool WgRenderer::renderShape(RenderData data) { } bool WgRenderer::renderImage(RenderData data) { + mRenderDatas.push(data); return true; } @@ -274,6 +293,7 @@ bool WgRenderer::sync() { for (size_t i = 0; i < mRenderDatas.count; i++) { WgRenderDataShape* renderData = (WgRenderDataShape*)(mRenderDatas[i]); + // draw shape geometry wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); for (uint32_t j = 0; j < renderData->mGeometryDataShape.count; j++) { // draw to stencil (first pass) @@ -287,6 +307,7 @@ bool WgRenderer::sync() { mGeometryDataWindow.draw(renderPassEncoder); } + // draw stroke geometry if (renderData->mRenderSettingsStroke.mPipelineBase) { // draw strokes to stencil (first pass) wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255); @@ -302,6 +323,14 @@ bool WgRenderer::sync() { renderData->mRenderSettingsStroke.mPipelineBindGroup->bind(renderPassEncoder, 0); mGeometryDataWindow.draw(renderPassEncoder); } + + // draw image geometry + wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0); + for (uint32_t j = 0; j < renderData->mGeometryDataImage.count; j++) { + mPipelineImage.set(renderPassEncoder); + renderData->mPipelineBindGroupImage.bind(renderPassEncoder, 0); + renderData->mGeometryDataImage[j]->drawImage(renderPassEncoder); + } } } // end render pass diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index f7130c2d..93cd880d 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -83,6 +83,7 @@ private: WgPipelineSolid mPipelineSolid; WgPipelineLinear mPipelineLinear; WgPipelineRadial mPipelineRadial; + WgPipelineImage mPipelineImage; WgGeometryData mGeometryDataWindow; WgPipelineBindGroupEmpty mPipelineBindGroupEmpty; WgPipelineBindGroupStroke mPipelineBindGroupStroke; diff --git a/src/renderer/wg_engine/tvgWgShaderSrc.cpp b/src/renderer/wg_engine/tvgWgShaderSrc.cpp index aa1e8139..128f88fe 100644 --- a/src/renderer/wg_engine/tvgWgShaderSrc.cpp +++ b/src/renderer/wg_engine/tvgWgShaderSrc.cpp @@ -266,3 +266,51 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f { return color; })"; + +const char* cShaderSource_PipelineImage = R"( +// vertex input +struct VertexInput { + @location(0) position: vec2f, + @location(1) texCoords: vec2f +}; + +// Matrix +struct Matrix { + transform: mat4x4f +}; + +// ColorInfo +struct ColorInfo { + color: vec4f +}; + +// vertex output +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) texCoords: vec2f, +}; + +// uMatrix +@group(0) @binding(0) var uMatrix: Matrix; +// uColorInfo +@group(0) @binding(1) var uColorInfo: ColorInfo; +// uSamplerBase +@group(0) @binding(2) var uSamplerBase: sampler; +// uTextureViewBase +@group(0) @binding(3) var uTextureViewBase: texture_2d; + + +@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.texCoords = in.texCoords; + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4f { + var color: vec4f = textureSample(uTextureViewBase, uSamplerBase, in.texCoords.xy); + return vec4f(color.rgb, color.a * uColorInfo.color.a); +})"; diff --git a/src/renderer/wg_engine/tvgWgShaderSrc.h b/src/renderer/wg_engine/tvgWgShaderSrc.h index 2707a4cd..00a21c80 100644 --- a/src/renderer/wg_engine/tvgWgShaderSrc.h +++ b/src/renderer/wg_engine/tvgWgShaderSrc.h @@ -37,4 +37,7 @@ extern const char* cShaderSource_PipelineLinear; // pipeline shader module radial extern const char* cShaderSource_PipelineRadial; +// pipeline shader module image +extern const char* cShaderSource_PipelineImage; + #endif // _TVG_WG_SHADER_SRC_H_