mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
623 lines
31 KiB
C++
Executable file
623 lines
31 KiB
C++
Executable file
/*
|
|
* Copyright (c) 2024 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 "tvgWgCompositor.h"
|
|
#include "tvgWgShaderTypes.h"
|
|
|
|
void WgCompositor::initialize(WgContext& context, uint32_t width, uint32_t height)
|
|
{
|
|
// pipelines (external handle, do not release)
|
|
pipelines = context.pipelines;
|
|
// store render target dimensions
|
|
this->width = width;
|
|
this->height = height;
|
|
// allocate global stencil buffer handles
|
|
texStencil = context.createTexStencil(width, height, WGPUTextureFormat_Stencil8);
|
|
texViewStencil = context.createTextureView(texStencil);
|
|
// allocate global view matrix handles
|
|
WgShaderTypeMat4x4f viewMat(width, height);
|
|
context.allocateBufferUniform(bufferViewMat, &viewMat, sizeof(viewMat));
|
|
bindGroupViewMat = pipelines->layouts.createBindGroupBuffer1Un(bufferViewMat);
|
|
// initialize opacity pool
|
|
for (uint32_t i = 0; i < 256; i++) {
|
|
float opacity = i / 255.0f;
|
|
context.allocateBufferUniform(bufferOpacities[i], &opacity, sizeof(float));
|
|
bindGroupOpacities[i] = pipelines->layouts.createBindGroupBuffer1Un(bufferOpacities[i]);
|
|
}
|
|
// initialize intermediate render storages
|
|
storageClipPath.initialize(context, width, height);
|
|
storageInterm.initialize(context, width, height);
|
|
storageDstCopy.initialize(context, width, height);
|
|
// composition and blend geometries
|
|
meshData.blitBox(context);
|
|
}
|
|
|
|
|
|
void WgCompositor::release(WgContext& context)
|
|
{
|
|
// composition and blend geometries
|
|
meshData.release(context);
|
|
// release intermediate render storages
|
|
storageInterm.release(context);
|
|
storageDstCopy.release(context);
|
|
storageClipPath.release(context);
|
|
// release opacity pool
|
|
for (uint32_t i = 0; i < 256; i++) {
|
|
context.pipelines->layouts.releaseBindGroup(bindGroupOpacities[i]);
|
|
context.releaseBuffer(bufferOpacities[i]);
|
|
}
|
|
// release global view matrix handles
|
|
context.pipelines->layouts.releaseBindGroup(bindGroupViewMat);
|
|
context.releaseBuffer(bufferViewMat);
|
|
// release global stencil buffer handles
|
|
context.releaseTextureView(texViewStencil);
|
|
context.releaseTexture(texStencil);
|
|
height = 0;
|
|
width = 0;
|
|
pipelines = nullptr;
|
|
}
|
|
|
|
|
|
RenderRegion WgCompositor::shrinkRenderRegion(RenderRegion& rect)
|
|
{
|
|
// cut viewport to screen dimensions
|
|
int32_t xmin = std::max(0, std::min((int32_t)width, rect.x));
|
|
int32_t ymin = std::max(0, std::min((int32_t)height, rect.y));
|
|
int32_t xmax = std::max(0, std::min((int32_t)width, rect.x + rect.w));
|
|
int32_t ymax = std::max(0, std::min((int32_t)height, rect.y + rect.h));
|
|
return { xmin, ymin, xmax - xmin, ymax - ymin };
|
|
}
|
|
|
|
|
|
void WgCompositor::beginRenderPass(WGPUCommandEncoder commandEncoder, WgRenderStorage* target, bool clear)
|
|
{
|
|
assert(commandEncoder);
|
|
assert(target);
|
|
this->currentTarget = target;
|
|
this->commandEncoder = commandEncoder;
|
|
WGPURenderPassDepthStencilAttachment depthStencilAttachment{ .view = texViewStencil, .stencilLoadOp = WGPULoadOp_Clear, .stencilStoreOp = WGPUStoreOp_Discard };
|
|
WGPURenderPassColorAttachment colorAttachment{};
|
|
colorAttachment.view = target->texView,
|
|
colorAttachment.loadOp = clear ? WGPULoadOp_Clear : WGPULoadOp_Load,
|
|
colorAttachment.storeOp = WGPUStoreOp_Store;
|
|
#ifdef __EMSCRIPTEN__
|
|
colorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
|
#endif
|
|
WGPURenderPassDescriptor renderPassDesc{ .colorAttachmentCount = 1, .colorAttachments = &colorAttachment, .depthStencilAttachment = &depthStencilAttachment };
|
|
renderPassEncoder = wgpuCommandEncoderBeginRenderPass(commandEncoder, &renderPassDesc);
|
|
assert(renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::endRenderPass()
|
|
{
|
|
assert(renderPassEncoder);
|
|
wgpuRenderPassEncoderEnd(renderPassEncoder);
|
|
wgpuRenderPassEncoderRelease(renderPassEncoder);
|
|
this->renderPassEncoder = nullptr;
|
|
this->currentTarget = nullptr;
|
|
}
|
|
|
|
|
|
void WgCompositor::renderShape(WgContext& context, WgRenderDataShape* renderData, BlendMethod blendMethod)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
// apply clip path if neccessary
|
|
if (renderData->clips.count != 0) {
|
|
renderClipPath(context, renderData, &storageClipPath);
|
|
if (renderData->strokeFirst) {
|
|
clipStrokes(context, renderData, &storageClipPath);
|
|
clipShape(context, renderData, &storageClipPath);
|
|
} else {
|
|
clipShape(context, renderData, &storageClipPath);
|
|
clipStrokes(context, renderData, &storageClipPath);
|
|
}
|
|
// use custom blending
|
|
} else if (blendMethod != BlendMethod::Normal) {
|
|
if (renderData->strokeFirst) {
|
|
blendStrokes(context, renderData, blendMethod);
|
|
blendShape(context, renderData, blendMethod);
|
|
} else {
|
|
blendShape(context, renderData, blendMethod);
|
|
blendStrokes(context, renderData, blendMethod);
|
|
}
|
|
// use direct hardware blending
|
|
} else {
|
|
if (renderData->strokeFirst) {
|
|
drawStrokes(context, renderData);
|
|
drawShape(context, renderData);
|
|
} else {
|
|
drawShape(context, renderData);
|
|
drawStrokes(context, renderData);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void WgCompositor::renderImage(WgContext& context, WgRenderDataPicture* renderData, BlendMethod blendMethod)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
// apply clip path if neccessary
|
|
if (renderData->clips.count != 0) {
|
|
renderClipPath(context, renderData, &storageClipPath);
|
|
clipImage(context, renderData, &storageClipPath);
|
|
// use custom blending
|
|
} else if (blendMethod != BlendMethod::Normal)
|
|
blendImage(context, renderData, blendMethod);
|
|
// use direct hardware blending
|
|
else drawImage(context, renderData);
|
|
}
|
|
|
|
|
|
void WgCompositor::renderScene(WgContext& context, WgRenderStorage* scene, WgCompose* compose)
|
|
{
|
|
assert(scene);
|
|
assert(compose);
|
|
assert(renderPassEncoder);
|
|
// use custom blending
|
|
if (compose->blend != BlendMethod::Normal)
|
|
blendScene(context, scene, compose);
|
|
// use direct hardware blending
|
|
else drawScene(context, scene, compose);
|
|
}
|
|
|
|
|
|
void WgCompositor::composeScene(WgContext& context, WgRenderStorage* src, WgRenderStorage* mask, WgCompose* cmp)
|
|
{
|
|
assert(cmp);
|
|
assert(src);
|
|
assert(mask);
|
|
assert(renderPassEncoder);
|
|
RenderRegion rect = shrinkRenderRegion(cmp->aabb);
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, rect.x, rect.y, rect.w, rect.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, src->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, mask->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->scene_compose[(uint32_t)cmp->method]);
|
|
meshData.drawImage(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::blit(WgContext& context, WGPUCommandEncoder encoder, WgRenderStorage* src, WGPUTextureView dstView) {
|
|
WGPURenderPassDepthStencilAttachment depthStencilAttachment{ .view = texViewStencil, .stencilLoadOp = WGPULoadOp_Load, .stencilStoreOp = WGPUStoreOp_Discard };
|
|
WGPURenderPassColorAttachment colorAttachment { .view = dstView, .loadOp = WGPULoadOp_Load, .storeOp = WGPUStoreOp_Store };
|
|
#ifdef __EMSCRIPTEN__
|
|
colorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
|
#endif
|
|
WGPURenderPassDescriptor renderPassDesc{ .colorAttachmentCount = 1, .colorAttachments = &colorAttachment, .depthStencilAttachment = &depthStencilAttachment };
|
|
WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDesc);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPass, 0, src->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPass, pipelines->blit);
|
|
meshData.drawImage(context, renderPass);
|
|
wgpuRenderPassEncoderEnd(renderPass);
|
|
wgpuRenderPassEncoderRelease(renderPass);
|
|
}
|
|
|
|
|
|
void WgCompositor::drawShape(WgContext& context, WgRenderDataShape* renderData)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
|
if (renderData->renderSettingsShape.skip) return;
|
|
if (renderData->meshGroupShapes.meshes.count == 0) return;
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
// setup stencil rules
|
|
WGPURenderPipeline stencilPipeline = (renderData->fillRule == FillRule::Winding) ? pipelines->winding : pipelines->evenodd;
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
|
// draw to stencil (first pass)
|
|
for (uint32_t i = 0; i < renderData->meshGroupShapes.meshes.count; i++)
|
|
renderData->meshGroupShapes.meshes[i]->drawFan(context, renderPassEncoder);
|
|
// setup fill rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
WgRenderSettings& settings = renderData->renderSettingsShape;
|
|
if (settings.fillType == WgRenderSettingsType::Solid) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupSolid, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->solid);
|
|
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->linear);
|
|
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->radial);
|
|
}
|
|
// draw to color (second pass)
|
|
renderData->meshDataBBox.drawFan(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::blendShape(WgContext& context, WgRenderDataShape* renderData, BlendMethod blendMethod)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
|
if (renderData->renderSettingsShape.skip) return;
|
|
if (renderData->meshGroupShapes.meshes.count == 0) return;
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
// copy current render target data to dst storage
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
const WGPUImageCopyTexture texSrc { .texture = target->texture };
|
|
const WGPUImageCopyTexture texDst { .texture = storageDstCopy.texture };
|
|
const WGPUExtent3D copySize { .width = width, .height = height, .depthOrArrayLayers = 1 };
|
|
wgpuCommandEncoderCopyTextureToTexture(commandEncoder, &texSrc, &texDst, ©Size);
|
|
beginRenderPass(commandEncoder, target, false);
|
|
// render shape with blend settings
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
// setup stencil rules
|
|
WGPURenderPipeline stencilPipeline = (renderData->fillRule == FillRule::Winding) ? pipelines->winding : pipelines->evenodd;
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
|
// draw to stencil (first pass)
|
|
for (uint32_t i = 0; i < renderData->meshGroupShapes.meshes.count; i++)
|
|
renderData->meshGroupShapes.meshes[i]->drawFan(context, renderPassEncoder);
|
|
// setup fill rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, storageDstCopy.bindGroupTexure, 0, nullptr);
|
|
uint32_t blendMethodInd = (uint32_t)blendMethod;
|
|
WgRenderSettings& settings = renderData->renderSettingsShape;
|
|
if (settings.fillType == WgRenderSettingsType::Solid) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupSolid, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->solid_blend[blendMethodInd]);
|
|
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->linear_blend[blendMethodInd]);
|
|
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->radial_blend[blendMethodInd]);
|
|
}
|
|
// draw to color (second pass)
|
|
renderData->meshDataBBox.drawFan(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::clipShape(WgContext& context, WgRenderDataShape* renderData, WgRenderStorage* mask)
|
|
{
|
|
assert(mask);
|
|
assert(renderData);
|
|
assert(commandEncoder);
|
|
assert(currentTarget);
|
|
// skip shape composing if shape do not exist
|
|
if (renderData->renderSettingsShape.skip) return;
|
|
if (renderData->meshGroupShapes.meshes.count == 0) return;
|
|
// store current render pass
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
// render into intermediate buffer
|
|
beginRenderPass(commandEncoder, &storageInterm, true);
|
|
drawShape(context, renderData);
|
|
endRenderPass();
|
|
// restore current render pass
|
|
beginRenderPass(commandEncoder, target, false);
|
|
RenderRegion rect = shrinkRenderRegion(renderData->aabb);
|
|
clipRegion(context, &storageInterm, mask, rect);
|
|
}
|
|
|
|
|
|
void WgCompositor::drawStrokes(WgContext& context, WgRenderDataShape* renderData)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
|
if (renderData->renderSettingsStroke.skip) return;
|
|
if (renderData->meshGroupStrokes.meshes.count == 0) return;
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
// draw strokes to stencil (first pass)
|
|
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
|
// setup stencil rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->direct);
|
|
// draw to stencil (first pass)
|
|
renderData->meshGroupStrokes.meshes[i]->draw(context, renderPassEncoder);
|
|
// setup fill rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
|
if (settings.fillType == WgRenderSettingsType::Solid) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupSolid, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->solid);
|
|
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->linear);
|
|
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->radial);
|
|
}
|
|
// draw to color (second pass)
|
|
renderData->meshGroupStrokesBBox.meshes[i]->drawFan(context, renderPassEncoder);
|
|
}
|
|
}
|
|
|
|
|
|
void WgCompositor::blendStrokes(WgContext& context, WgRenderDataShape* renderData, BlendMethod blendMethod)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
assert(renderData->meshGroupStrokes.meshes.count == renderData->meshGroupStrokesBBox.meshes.count);
|
|
if (renderData->renderSettingsStroke.skip) return;
|
|
if (renderData->meshGroupStrokes.meshes.count == 0) return;
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
// copy current render target data to dst storage
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
const WGPUImageCopyTexture texSrc { .texture = target->texture };
|
|
const WGPUImageCopyTexture texDst { .texture = storageDstCopy.texture };
|
|
const WGPUExtent3D copySize { .width = width, .height = height, .depthOrArrayLayers = 1 };
|
|
wgpuCommandEncoderCopyTextureToTexture(commandEncoder, &texSrc, &texDst, ©Size);
|
|
beginRenderPass(commandEncoder, target, false);
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
// draw strokes to stencil (first pass)
|
|
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
|
|
// setup stencil rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->direct);
|
|
// draw to stencil (first pass)
|
|
renderData->meshGroupStrokes.meshes[i]->draw(context, renderPassEncoder);
|
|
// setup fill rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, storageDstCopy.bindGroupTexure, 0, nullptr);
|
|
uint32_t blendMethodInd = (uint32_t)blendMethod;
|
|
WgRenderSettings& settings = renderData->renderSettingsStroke;
|
|
if (settings.fillType == WgRenderSettingsType::Solid) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupSolid, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->solid_blend[blendMethodInd]);
|
|
} else if (settings.fillType == WgRenderSettingsType::Linear) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->linear_blend[blendMethodInd]);
|
|
} else if (settings.fillType == WgRenderSettingsType::Radial) {
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, settings.bindGroupGradient, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->radial_blend[blendMethodInd]);
|
|
}
|
|
// draw to color (second pass)
|
|
renderData->meshGroupStrokesBBox.meshes[i]->drawFan(context, renderPassEncoder);
|
|
}
|
|
};
|
|
|
|
|
|
void WgCompositor::clipStrokes(WgContext& context, WgRenderDataShape* renderData, WgRenderStorage* mask)
|
|
{
|
|
assert(mask);
|
|
assert(renderData);
|
|
assert(commandEncoder);
|
|
assert(currentTarget);
|
|
// skip shape composing if strokes do not exist
|
|
if (renderData->renderSettingsStroke.skip) return;
|
|
if (renderData->meshGroupStrokes.meshes.count == 0) return;
|
|
// store current render pass
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
// render into intermediate buffer
|
|
beginRenderPass(commandEncoder, &storageInterm, true);
|
|
drawStrokes(context, renderData);
|
|
endRenderPass();
|
|
// restore current render pass
|
|
beginRenderPass(commandEncoder, target, false);
|
|
RenderRegion rect = shrinkRenderRegion(renderData->aabb);
|
|
clipRegion(context, &storageInterm, mask, rect);
|
|
}
|
|
|
|
|
|
void WgCompositor::drawImage(WgContext& context, WgRenderDataPicture* renderData)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, renderData->bindGroupPicture, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->image);
|
|
renderData->meshData.drawImage(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::blendImage(WgContext& context, WgRenderDataPicture* renderData, BlendMethod blendMethod)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
// copy current render target data to dst storage
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
const WGPUImageCopyTexture texSrc { .texture = target->texture };
|
|
const WGPUImageCopyTexture texDst { .texture = storageDstCopy.texture };
|
|
const WGPUExtent3D copySize { .width = width, .height = height, .depthOrArrayLayers = 1 };
|
|
wgpuCommandEncoderCopyTextureToTexture(commandEncoder, &texSrc, &texDst, ©Size);
|
|
beginRenderPass(commandEncoder, target, false);
|
|
// blend image
|
|
uint32_t blendMethodInd = (uint32_t)blendMethod;
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, renderData->bindGroupPicture, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 3, storageDstCopy.bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->image_blend[blendMethodInd]);
|
|
renderData->meshData.drawImage(context, renderPassEncoder);
|
|
};
|
|
|
|
|
|
void WgCompositor::clipImage(WgContext& context, WgRenderDataPicture* renderData, WgRenderStorage* mask)
|
|
{
|
|
assert(mask);
|
|
assert(renderData);
|
|
assert(commandEncoder);
|
|
assert(currentTarget);
|
|
// store current render pass
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
// render into intermediate buffer
|
|
beginRenderPass(commandEncoder, &storageInterm, true);
|
|
drawImage(context, renderData);
|
|
endRenderPass();
|
|
// restore current render pass
|
|
beginRenderPass(commandEncoder, target, false);
|
|
RenderRegion rect { 0, 0, (int32_t)width, (int32_t)height };
|
|
clipRegion(context, &storageInterm, mask, rect);
|
|
}
|
|
|
|
|
|
void WgCompositor::drawScene(WgContext& context, WgRenderStorage* scene, WgCompose* compose)
|
|
{
|
|
assert(scene);
|
|
assert(compose);
|
|
assert(currentTarget);
|
|
// draw scene
|
|
RenderRegion rect = shrinkRenderRegion(compose->aabb);
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, rect.x, rect.y, rect.w, rect.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, scene->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, bindGroupOpacities[compose->opacity], 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->scene);
|
|
meshData.drawImage(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::blendScene(WgContext& context, WgRenderStorage* scene, WgCompose* compose)
|
|
{
|
|
assert(scene);
|
|
assert(compose);
|
|
assert(currentTarget);
|
|
// copy current render target data to dst storage
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
const WGPUImageCopyTexture texSrc { .texture = target->texture };
|
|
const WGPUImageCopyTexture texDst { .texture = storageDstCopy.texture };
|
|
const WGPUExtent3D copySize { .width = width, .height = height, .depthOrArrayLayers = 1 };
|
|
wgpuCommandEncoderCopyTextureToTexture(commandEncoder, &texSrc, &texDst, ©Size);
|
|
beginRenderPass(commandEncoder, target, false);
|
|
// blend scene
|
|
uint32_t blendMethodInd = (uint32_t)compose->blend;
|
|
RenderRegion rect = shrinkRenderRegion(compose->aabb);
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, rect.x, rect.y, rect.w, rect.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, scene->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, storageDstCopy.bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 2, bindGroupOpacities[compose->opacity], 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->scene_blend[blendMethodInd]);
|
|
meshData.drawImage(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::drawClipPath(WgContext& context, WgRenderDataShape* renderData)
|
|
{
|
|
assert(renderData);
|
|
assert(renderPassEncoder);
|
|
assert(renderData->meshGroupShapes.meshes.count == renderData->meshGroupShapesBBox.meshes.count);
|
|
if ((renderData->viewport.w <= 0) || (renderData->viewport.h <= 0)) return;
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, renderData->viewport.x, renderData->viewport.y, renderData->viewport.w, renderData->viewport.h);
|
|
// setup stencil rules
|
|
WGPURenderPipeline stencilPipeline = (renderData->fillRule == FillRule::Winding) ? pipelines->winding : pipelines->evenodd;
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, stencilPipeline);
|
|
// draw to stencil (first pass)
|
|
for (uint32_t i = 0; i < renderData->meshGroupShapes.meshes.count; i++)
|
|
renderData->meshGroupShapes.meshes[i]->drawFan(context, renderPassEncoder);
|
|
// setup fill rules
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, bindGroupViewMat, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, renderData->bindGroupPaint, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->clip_path);
|
|
// draw to color (second pass)
|
|
renderData->meshDataBBox.drawFan(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::clipRegion(WgContext& context, WgRenderStorage* src, WgRenderStorage* mask, RenderRegion& rect)
|
|
{
|
|
wgpuRenderPassEncoderSetScissorRect(renderPassEncoder, rect.x, rect.y, rect.w, rect.h);
|
|
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 0, storageInterm.bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, 1, mask->bindGroupTexure, 0, nullptr);
|
|
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, pipelines->scene_clip);
|
|
meshData.drawImage(context, renderPassEncoder);
|
|
}
|
|
|
|
|
|
void WgCompositor::renderClipPath(WgContext& context, WgRenderDataPaint* renderData, WgRenderStorage* dst)
|
|
{
|
|
assert(renderData);
|
|
if (renderData->clips.count == 0) return;
|
|
// store current render pass
|
|
WgRenderStorage *target = currentTarget;
|
|
endRenderPass();
|
|
// render first clip path
|
|
beginRenderPass(commandEncoder, dst, true);
|
|
drawClipPath(context, (WgRenderDataShape*)(renderData->clips[0]));
|
|
endRenderPass();
|
|
// render amd merge clip paths
|
|
for (uint32_t i = 1 ; i < renderData->clips.count; i++) {
|
|
// render clip path
|
|
beginRenderPass(commandEncoder, &storageInterm, true);
|
|
drawClipPath(context, (WgRenderDataShape*)(renderData->clips[i]));
|
|
endRenderPass();
|
|
// merge masks
|
|
mergeMasks(commandEncoder, &storageInterm, dst);
|
|
}
|
|
// restore current render pass
|
|
beginRenderPass(commandEncoder, target, false);
|
|
}
|
|
|
|
|
|
void WgCompositor::mergeMasks(WGPUCommandEncoder encoder, WgRenderStorage* mask0, WgRenderStorage* mask1)
|
|
{
|
|
assert(mask0);
|
|
assert(mask1);
|
|
assert(!renderPassEncoder);
|
|
// copy dst storage to temporary read only storage
|
|
const WGPUImageCopyTexture texSrc { .texture = mask1->texture };
|
|
const WGPUImageCopyTexture texDst { .texture = storageDstCopy.texture };
|
|
const WGPUExtent3D copySize { .width = width, .height = height, .depthOrArrayLayers = 1 };
|
|
wgpuCommandEncoderCopyTextureToTexture(encoder, &texSrc, &texDst, ©Size);
|
|
// execute compose shader
|
|
const WGPUComputePassDescriptor computePassDescriptor{};
|
|
WGPUComputePassEncoder computePassEncoder = wgpuCommandEncoderBeginComputePass(encoder, &computePassDescriptor);
|
|
wgpuComputePassEncoderSetBindGroup(computePassEncoder, 0, mask0->bindGroupRead, 0, nullptr);
|
|
wgpuComputePassEncoderSetBindGroup(computePassEncoder, 1, storageDstCopy.bindGroupRead, 0, nullptr);
|
|
wgpuComputePassEncoderSetBindGroup(computePassEncoder, 2, mask1->bindGroupWrite, 0, nullptr);
|
|
wgpuComputePassEncoderSetPipeline(computePassEncoder, pipelines->merge_masks);
|
|
wgpuComputePassEncoderDispatchWorkgroups(computePassEncoder, (width + 7) / 8, (height + 7) / 8, 1);
|
|
wgpuComputePassEncoderEnd(computePassEncoder);
|
|
}
|