wg_engine: Blending optimization

[issues 1479: lottie](#1479)

To optimize bled operations hardware pipeline blend stage are used for some blend methods:
	BlendMethod::SrcOver
    BlendMethod::Normal
    BlendMethod::Add
    BlendMethod::Multiply
    BlendMethod::Darken
    BlendMethod::Lighten

Other types compute shaders used
This commit is contained in:
Sergii Liebodkin 2024-04-03 00:19:34 +03:00 committed by Hermet Park
parent 622b25a3b4
commit b0280150db
8 changed files with 302 additions and 230 deletions

View file

@ -494,7 +494,7 @@ void WgPipeline::destroyShaderModule(WGPUShaderModule& shaderModule)
// render pipeline
//*****************************************************************************
void WgRenderPipeline::allocate(WGPUDevice device,
void WgRenderPipeline::allocate(WGPUDevice device, WgPipelineBlendType blendType,
WGPUVertexBufferLayout vertexBufferLayouts[], uint32_t attribsCount,
WGPUBindGroupLayout bindGroupLayouts[], uint32_t bindGroupsCount,
WGPUCompareFunction stencilCompareFunction, WGPUStencilOperation stencilOperation,
@ -506,7 +506,7 @@ void WgRenderPipeline::allocate(WGPUDevice device,
mPipelineLayout = createPipelineLayout(device, bindGroupLayouts, bindGroupsCount);
assert(mPipelineLayout);
mRenderPipeline = createRenderPipeline(device,
mRenderPipeline = createRenderPipeline(device, blendType,
vertexBufferLayouts, attribsCount,
stencilCompareFunction, stencilOperation,
mPipelineLayout, mShaderModule, pipelineLabel);
@ -527,14 +527,42 @@ void WgRenderPipeline::set(WGPURenderPassEncoder renderPassEncoder)
};
WGPUBlendState WgRenderPipeline::makeBlendState()
WGPUBlendState WgRenderPipeline::makeBlendState(WgPipelineBlendType blendType)
{
WGPUBlendState blendState{};
blendState.color.operation = WGPUBlendOperation_Add;
blendState.color.srcFactor = WGPUBlendFactor_One;
blendState.color.dstFactor = WGPUBlendFactor_Zero;
// src
if (blendType == WgPipelineBlendType::Src) {
blendState.color.operation = WGPUBlendOperation_Add;
blendState.color.srcFactor = WGPUBlendFactor_One;
blendState.color.dstFactor = WGPUBlendFactor_Zero;
} else // normal
if (blendType == WgPipelineBlendType::Normal) {
blendState.color.operation = WGPUBlendOperation_Add;
blendState.color.srcFactor = WGPUBlendFactor_SrcAlpha;
blendState.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha;
} else // add
if (blendType == WgPipelineBlendType::Add) {
blendState.color.operation = WGPUBlendOperation_Add;
blendState.color.srcFactor = WGPUBlendFactor_One;
blendState.color.dstFactor = WGPUBlendFactor_One;
} else // mult
if (blendType == WgPipelineBlendType::Mult) {
blendState.color.operation = WGPUBlendOperation_Add;
blendState.color.srcFactor = WGPUBlendFactor_Dst;
blendState.color.dstFactor = WGPUBlendFactor_Zero;
} else // min
if (blendType == WgPipelineBlendType::Min) {
blendState.color.operation = WGPUBlendOperation_Min;
blendState.color.srcFactor = WGPUBlendFactor_One;
blendState.color.dstFactor = WGPUBlendFactor_One;
} else // max
if (blendType == WgPipelineBlendType::Max) {
blendState.color.operation = WGPUBlendOperation_Max;
blendState.color.srcFactor = WGPUBlendFactor_One;
blendState.color.dstFactor = WGPUBlendFactor_One;
}
blendState.alpha.operation = WGPUBlendOperation_Add;
blendState.alpha.srcFactor = WGPUBlendFactor_One;
blendState.alpha.srcFactor = WGPUBlendFactor_SrcAlpha;
blendState.alpha.dstFactor = WGPUBlendFactor_Zero;
return blendState;
}
@ -638,13 +666,13 @@ WGPUFragmentState WgRenderPipeline::makeFragmentState(WGPUShaderModule shaderMod
}
WGPURenderPipeline WgRenderPipeline::createRenderPipeline(WGPUDevice device,
WGPURenderPipeline WgRenderPipeline::createRenderPipeline(WGPUDevice device, WgPipelineBlendType blendType,
WGPUVertexBufferLayout vertexBufferLayouts[], uint32_t attribsCount,
WGPUCompareFunction stencilCompareFunction, WGPUStencilOperation stencilOperation,
WGPUPipelineLayout pipelineLayout, WGPUShaderModule shaderModule,
const char* pipelineName)
{
WGPUBlendState blendState = makeBlendState();
WGPUBlendState blendState = makeBlendState(blendType);
WGPUColorTargetState colorTargetStates[] = {
makeColorTargetState(&blendState)
};

View file

@ -28,6 +28,15 @@
#include "tvgCommon.h"
#include "tvgRender.h"
enum class WgPipelineBlendType {
Src = 0, // S
Normal, // (Sa * S) + (255 - Sa) * D
Add, // (S + D)
Mult, // (S * D)
Min, // min(S, D)
Max // max(S, D)
};
struct WgPipelines;
struct WgContext {
@ -107,7 +116,7 @@ struct WgRenderPipeline: public WgPipeline
{
protected:
WGPURenderPipeline mRenderPipeline{};
void allocate(WGPUDevice device,
void allocate(WGPUDevice device, WgPipelineBlendType blendType,
WGPUVertexBufferLayout vertexBufferLayouts[], uint32_t attribsCount,
WGPUBindGroupLayout bindGroupLayouts[], uint32_t bindGroupsCount,
WGPUCompareFunction stencilCompareFunction, WGPUStencilOperation stencilOperation,
@ -116,7 +125,7 @@ public:
void release() override;
void set(WGPURenderPassEncoder renderPassEncoder);
static WGPUBlendState makeBlendState();
static WGPUBlendState makeBlendState(WgPipelineBlendType blendType);
static WGPUColorTargetState makeColorTargetState(const WGPUBlendState* blendState);
static WGPUVertexBufferLayout makeVertexBufferLayout(const WGPUVertexAttribute* vertexAttributes, uint32_t count, uint64_t stride);
static WGPUVertexState makeVertexState(WGPUShaderModule shaderModule, const WGPUVertexBufferLayout* buffers, uint32_t count);
@ -125,7 +134,7 @@ public:
static WGPUMultisampleState makeMultisampleState();
static WGPUFragmentState makeFragmentState(WGPUShaderModule shaderModule, WGPUColorTargetState* targets, uint32_t size);
static WGPURenderPipeline createRenderPipeline(WGPUDevice device,
static WGPURenderPipeline createRenderPipeline(WGPUDevice device, WgPipelineBlendType blendType,
WGPUVertexBufferLayout vertexBufferLayouts[], uint32_t attribsCount,
WGPUCompareFunction stencilCompareFunction, WGPUStencilOperation stencilOperation,
WGPUPipelineLayout pipelineLayout, WGPUShaderModule shaderModule,

View file

@ -53,7 +53,7 @@ void WgPipelineFillShape::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline fill shape";
// allocate all pipeline handles
allocate(device,
allocate(device, WgPipelineBlendType::Src,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -85,7 +85,7 @@ void WgPipelineFillStroke::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline fill stroke";
// allocate all pipeline handles
allocate(device,
allocate(device, WgPipelineBlendType::Src,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -93,7 +93,7 @@ void WgPipelineFillStroke::initialize(WGPUDevice device)
}
void WgPipelineSolid::initialize(WGPUDevice device)
void WgPipelineSolid::initialize(WGPUDevice device, WgPipelineBlendType blendType)
{
// vertex and buffers settings
WGPUVertexAttribute vertexAttributesPos = { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 };
@ -118,7 +118,7 @@ void WgPipelineSolid::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline solid color";
// allocate all pipeline handles
allocate(device,
allocate(device, blendType,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -126,7 +126,7 @@ void WgPipelineSolid::initialize(WGPUDevice device)
}
void WgPipelineLinear::initialize(WGPUDevice device)
void WgPipelineLinear::initialize(WGPUDevice device, WgPipelineBlendType blendType)
{
// vertex and buffers settings
WGPUVertexAttribute vertexAttributesPos = { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 };
@ -151,7 +151,7 @@ void WgPipelineLinear::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline linear gradient";
// allocate all pipeline handles
allocate(device,
allocate(device, blendType,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -159,7 +159,7 @@ void WgPipelineLinear::initialize(WGPUDevice device)
}
void WgPipelineRadial::initialize(WGPUDevice device)
void WgPipelineRadial::initialize(WGPUDevice device, WgPipelineBlendType blendType)
{
// vertex and buffers settings
WGPUVertexAttribute vertexAttributesPos = { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 };
@ -184,7 +184,7 @@ void WgPipelineRadial::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline radial gradient";
// allocate all pipeline handles
allocate(device,
allocate(device, blendType,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -192,7 +192,7 @@ void WgPipelineRadial::initialize(WGPUDevice device)
}
void WgPipelineImage::initialize(WGPUDevice device)
void WgPipelineImage::initialize(WGPUDevice device, WgPipelineBlendType blendType)
{
// vertex and buffers settings
WGPUVertexAttribute vertexAttributesPos = { WGPUVertexFormat_Float32x2, sizeof(float) * 0, 0 };
@ -219,7 +219,7 @@ void WgPipelineImage::initialize(WGPUDevice device)
auto pipelineLabel = "The render pipeline image";
// allocate all pipeline handles
allocate(device,
allocate(device, blendType,
vertexBufferLayouts, ARRAY_ELEMENTS_COUNT(vertexBufferLayouts),
bindGroupLayouts, ARRAY_ELEMENTS_COUNT(bindGroupLayouts),
stencilFuncion, stencilOperation,
@ -320,10 +320,12 @@ void WgPipelines::initialize(WgContext& context)
// fill pipelines
fillShape.initialize(context.device);
fillStroke.initialize(context.device);
solid.initialize(context.device);
linear.initialize(context.device);
radial.initialize(context.device);
image.initialize(context.device);
for (uint8_t type = (uint8_t)WgPipelineBlendType::Src; type <= (uint8_t)WgPipelineBlendType::Max; type++) {
solid[type].initialize(context.device, (WgPipelineBlendType)type);
linear[type].initialize(context.device, (WgPipelineBlendType)type);
radial[type].initialize(context.device, (WgPipelineBlendType)type);
image[type].initialize(context.device, (WgPipelineBlendType)type);
}
// compute pipelines
computeClear.initialize(context.device);
computeBlend.initialize(context.device);
@ -352,10 +354,41 @@ void WgPipelines::release()
computeBlend.release();
computeClear.release();
// fill pipelines
image.release();
radial.release();
linear.release();
solid.release();
for (uint8_t type = (uint8_t)WgPipelineBlendType::Src; type <= (uint8_t)WgPipelineBlendType::Max; type++) {
image[type].release();
radial[type].release();
linear[type].release();
solid[type].release();
}
fillStroke.release();
fillShape.release();
}
bool WgPipelines::isBlendMethodSupportsHW(BlendMethod blendMethod)
{
switch (blendMethod) {
case BlendMethod::SrcOver:
case BlendMethod::Normal:
case BlendMethod::Add:
case BlendMethod::Multiply:
case BlendMethod::Darken:
case BlendMethod::Lighten:
return true;
default: return false;
};
}
WgPipelineBlendType WgPipelines::blendMethodToBlendType(BlendMethod blendMethod)
{
switch (blendMethod) {
case BlendMethod::SrcOver: return WgPipelineBlendType::Src;
case BlendMethod::Normal: return WgPipelineBlendType::Normal;
case BlendMethod::Add: return WgPipelineBlendType::Add;
case BlendMethod::Multiply: return WgPipelineBlendType::Mult;
case BlendMethod::Darken: return WgPipelineBlendType::Min;
case BlendMethod::Lighten: return WgPipelineBlendType::Max;
default: return WgPipelineBlendType::Src;
};
}

View file

@ -53,7 +53,8 @@ struct WgPipelineFillStroke: public WgRenderPipeline
struct WgPipelineSolid: public WgRenderPipeline
{
void initialize(WGPUDevice device) override;
void initialize(WGPUDevice device) override {}
void initialize(WGPUDevice device, WgPipelineBlendType blendType);
void use(WGPURenderPassEncoder encoder, WgBindGroupCanvas& groupCanvas,WgBindGroupPaint& groupPaint, WgBindGroupSolidColor& groupSolid)
{
set(encoder);
@ -65,7 +66,8 @@ struct WgPipelineSolid: public WgRenderPipeline
struct WgPipelineLinear: public WgRenderPipeline
{
void initialize(WGPUDevice device) override;
void initialize(WGPUDevice device) override {}
void initialize(WGPUDevice device, WgPipelineBlendType blendType);
void use(WGPURenderPassEncoder encoder, WgBindGroupCanvas& groupCanvas, WgBindGroupPaint& groupPaint, WgBindGroupLinearGradient& groupLinear)
{
set(encoder);
@ -77,7 +79,8 @@ struct WgPipelineLinear: public WgRenderPipeline
struct WgPipelineRadial: public WgRenderPipeline
{
void initialize(WGPUDevice device) override;
void initialize(WGPUDevice device) override {}
void initialize(WGPUDevice device, WgPipelineBlendType blendType);
void use(WGPURenderPassEncoder encoder, WgBindGroupCanvas& groupCanvas, WgBindGroupPaint& groupPaint, WgBindGroupRadialGradient& groupRadial)
{
set(encoder);
@ -89,7 +92,8 @@ struct WgPipelineRadial: public WgRenderPipeline
struct WgPipelineImage: public WgRenderPipeline
{
void initialize(WGPUDevice device) override;
void initialize(WGPUDevice device) override {}
void initialize(WGPUDevice device, WgPipelineBlendType blendType);
void use(WGPURenderPassEncoder encoder, WgBindGroupCanvas& groupCanvas, WgBindGroupPaint& groupPaint, WgBindGroupPicture& groupPicture)
{
set(encoder);
@ -161,10 +165,11 @@ struct WgPipelines
// render pipelines
WgPipelineFillShape fillShape;
WgPipelineFillStroke fillStroke;
WgPipelineSolid solid;
WgPipelineLinear linear;
WgPipelineRadial radial;
WgPipelineImage image;
// fill pipelines
WgPipelineSolid solid[6];
WgPipelineLinear linear[6];
WgPipelineRadial radial[6];
WgPipelineImage image[6];
// compute pipelines
WgPipelineClear computeClear;
WgPipelineBlend computeBlend;
@ -173,6 +178,9 @@ struct WgPipelines
void initialize(WgContext& context);
void release();
static bool isBlendMethodSupportsHW(BlendMethod blendMethod);
static WgPipelineBlendType blendMethodToBlendType(BlendMethod blendMethod);
};
#endif // _TVG_WG_PIPELINES_H_

View file

@ -23,21 +23,30 @@
#include "tvgWgRenderTarget.h"
//*****************************************************************************
// render target
// render storage
//*****************************************************************************
void WgRenderTarget::initialize(WgContext& context, uint32_t w, uint32_t h, uint32_t samples)
{
void WgRenderStorage::initialize(WgContext& context, uint32_t w, uint32_t h, uint32_t samples)
{
release(context);
// store target storage size
width = w * samples;
height = h * samples;
workgroupsCountX = (width + WG_COMPUTE_WORKGROUP_SIZE_X - 1) / WG_COMPUTE_WORKGROUP_SIZE_X; // workgroup size x == 8
workgroupsCountY = (height + WG_COMPUTE_WORKGROUP_SIZE_Y - 1) / WG_COMPUTE_WORKGROUP_SIZE_Y; // workgroup size y == 8
// create color and stencil textures
texColor = context.createTexture2d(
WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_StorageBinding,
WGPUTextureFormat_RGBA8Unorm,
w * samples, h * samples, "The target texture color");
WGPUTextureUsage_CopySrc |
WGPUTextureUsage_CopyDst |
WGPUTextureUsage_TextureBinding |
WGPUTextureUsage_StorageBinding |
WGPUTextureUsage_RenderAttachment,
WGPUTextureFormat_RGBA8Unorm,
width, height, "The target texture color");
texStencil = context.createTexture2d(
WGPUTextureUsage_RenderAttachment,
WGPUTextureFormat_Stencil8,
w * samples, h * samples, "The target texture stencil");
WGPUTextureFormat_Stencil8,
width, height, "The target texture stencil");
assert(texColor);
assert(texStencil);
texViewColor = context.createTextureView2d(texColor, "The target texture view color");
@ -50,96 +59,150 @@ void WgRenderTarget::initialize(WgContext& context, uint32_t w, uint32_t h, uint
WgShaderTypeMat4x4f viewMat(w, h);
mBindGroupCanvas.initialize(context.device, context.queue, viewMat);
mPipelines = context.pipelines;
}
}
void WgRenderTarget::release(WgContext& context)
void WgRenderStorage::release(WgContext& context)
{
mRenderPassEncoder = nullptr;
mBindGroupCanvas.release();
bindGroupTexStorage.release();
context.releaseTextureView(texViewStencil);
context.releaseTextureView(texViewColor);
context.releaseTexture(texStencil);
context.releaseTexture(texColor);
workgroupsCountX = 0;
workgroupsCountY = 0;
height = 0;
width = 0;
}
void WgRenderTarget::renderShape(WGPUCommandEncoder commandEncoder, WgRenderDataShape* renderData)
void WgRenderStorage::renderShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
{
assert(renderData);
assert(commandEncoder);
WGPURenderPassEncoder renderPassEncoder = beginRenderPass(commandEncoder);
assert(mRenderPassEncoder);
if (renderData->strokeFirst)
drawStroke(renderPassEncoder, renderData);
drawShape(renderPassEncoder, renderData);
drawStroke(renderData, blendType);
drawShape(renderData, blendType);
if (!renderData->strokeFirst)
drawStroke(renderPassEncoder, renderData);
endRenderPass(renderPassEncoder);
drawStroke(renderData, blendType);
}
void WgRenderTarget::renderPicture(WGPUCommandEncoder commandEncoder, WgRenderDataPicture* renderData)
void WgRenderStorage::renderPicture(WgRenderDataPicture* renderData, WgPipelineBlendType blendType)
{
assert(renderData);
assert(commandEncoder);
WGPURenderPassEncoder renderPassEncoder = beginRenderPass(commandEncoder);
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
mPipelines->image.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, renderData->bindGroupPicture);
renderData->meshData.drawImage(renderPassEncoder);
endRenderPass(renderPassEncoder);
assert(mRenderPassEncoder);
uint8_t blend = (uint8_t)blendType;
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
mPipelines->image[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, renderData->bindGroupPicture);
renderData->meshData.drawImage(mRenderPassEncoder);
}
void WgRenderTarget::drawShape(WGPURenderPassEncoder renderPassEncoder, WgRenderDataShape* renderData)
void WgRenderStorage::drawShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
{
assert(renderData);
assert(renderPassEncoder);
assert(mRenderPassEncoder);
// draw shape geometry
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
uint8_t blend = (uint8_t)blendType;
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
for (uint32_t i = 0; i < renderData->meshGroupShapes.meshes.count; i++) {
// draw to stencil (first pass)
mPipelines->fillShape.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
renderData->meshGroupShapes.meshes[i]->draw(renderPassEncoder);
mPipelines->fillShape.use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
renderData->meshGroupShapes.meshes[i]->draw(mRenderPassEncoder);
// fill shape (second pass)
WgRenderSettings& settings = renderData->renderSettingsShape;
if (settings.fillType == WgRenderSettingsType::Solid)
mPipelines->solid.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupSolid);
mPipelines->solid[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupSolid);
else if (settings.fillType == WgRenderSettingsType::Linear)
mPipelines->linear.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
mPipelines->linear[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
else if (settings.fillType == WgRenderSettingsType::Radial)
mPipelines->radial.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
renderData->meshBBoxShapes.draw(renderPassEncoder);
mPipelines->radial[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
renderData->meshBBoxShapes.draw(mRenderPassEncoder);
}
}
void WgRenderTarget::drawStroke(WGPURenderPassEncoder renderPassEncoder, WgRenderDataShape* renderData)
void WgRenderStorage::drawStroke(WgRenderDataShape* renderData, WgPipelineBlendType blendType)
{
assert(renderData);
assert(renderPassEncoder);
assert(mRenderPassEncoder);
// draw stroke geometry
uint8_t blend = (uint8_t)blendType;
if (renderData->meshGroupStrokes.meshes.count > 0) {
// draw strokes to stencil (first pass)
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 255);
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 255);
for (uint32_t i = 0; i < renderData->meshGroupStrokes.meshes.count; i++) {
mPipelines->fillStroke.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
renderData->meshGroupStrokes.meshes[i]->draw(renderPassEncoder);
mPipelines->fillStroke.use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint);
renderData->meshGroupStrokes.meshes[i]->draw(mRenderPassEncoder);
}
// fill shape (second pass)
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0);
wgpuRenderPassEncoderSetStencilReference(mRenderPassEncoder, 0);
WgRenderSettings& settings = renderData->renderSettingsStroke;
if (settings.fillType == WgRenderSettingsType::Solid)
mPipelines->solid.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupSolid);
mPipelines->solid[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupSolid);
else if (settings.fillType == WgRenderSettingsType::Linear)
mPipelines->linear.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
mPipelines->linear[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupLinear);
else if (settings.fillType == WgRenderSettingsType::Radial)
mPipelines->radial.use(renderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
renderData->meshBBoxStrokes.draw(renderPassEncoder);
mPipelines->radial[blend].use(mRenderPassEncoder, mBindGroupCanvas, renderData->bindGroupPaint, settings.bindGroupRadial);
renderData->meshBBoxStrokes.draw(mRenderPassEncoder);
}
}
WGPURenderPassEncoder WgRenderTarget::beginRenderPass(WGPUCommandEncoder commandEncoder)
void WgRenderStorage::clear(WGPUCommandEncoder commandEncoder)
{
assert(commandEncoder);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeClear.use(computePassEncoder, bindGroupTexStorage);
dispatchWorkgroups(computePassEncoder);
endComputePass(computePassEncoder);
}
void WgRenderStorage::blend(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc, WgBindGroupBlendMethod* blendMethod)
{
assert(commandEncoder);
assert(targetSrc);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeBlend.use(computePassEncoder, targetSrc->bindGroupTexStorage, bindGroupTexStorage, *blendMethod);
dispatchWorkgroups(computePassEncoder);
endComputePass(computePassEncoder);
};
void WgRenderStorage::compose(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetMsk, WgBindGroupCompositeMethod* composeMethod, WgBindGroupOpacity* opacity)
{
assert(commandEncoder);
assert(targetMsk);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeCompose.use(computePassEncoder, bindGroupTexStorage, targetMsk->bindGroupTexStorage, *composeMethod, *opacity);
dispatchWorkgroups(computePassEncoder);
endComputePass(computePassEncoder);
};
void WgRenderStorage::antialias(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc)
{
assert(commandEncoder);
assert(targetSrc);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeAntiAliasing.use(computePassEncoder, targetSrc->bindGroupTexStorage, bindGroupTexStorage);
dispatchWorkgroups(computePassEncoder);
endComputePass(computePassEncoder);
}
void WgRenderStorage::dispatchWorkgroups(WGPUComputePassEncoder computePassEncoder)
{
assert(computePassEncoder);
wgpuComputePassEncoderDispatchWorkgroups(computePassEncoder, workgroupsCountX, workgroupsCountY, 1);
}
void WgRenderStorage::beginRenderPass(WGPUCommandEncoder commandEncoder, bool clear)
{
assert(commandEncoder);
// render pass depth stencil attachment
@ -157,7 +220,7 @@ WGPURenderPassEncoder WgRenderTarget::beginRenderPass(WGPUCommandEncoder command
WGPURenderPassColorAttachment colorAttachment{};
colorAttachment.view = texViewColor;
colorAttachment.resolveTarget = nullptr;
colorAttachment.loadOp = WGPULoadOp_Clear;
colorAttachment.loadOp = clear ? WGPULoadOp_Clear : WGPULoadOp_Load;
colorAttachment.clearValue = { 0, 0, 0, 0 };
colorAttachment.storeOp = WGPUStoreOp_Store;
// render pass descriptor
@ -171,111 +234,16 @@ WGPURenderPassEncoder WgRenderTarget::beginRenderPass(WGPUCommandEncoder command
renderPassDesc.occlusionQuerySet = nullptr;
renderPassDesc.timestampWrites = nullptr;
// begin render pass
return wgpuCommandEncoderBeginRenderPass(commandEncoder, &renderPassDesc);
mRenderPassEncoder = wgpuCommandEncoderBeginRenderPass(commandEncoder, &renderPassDesc);
}
void WgRenderTarget::endRenderPass(WGPURenderPassEncoder renderPassEncoder)
void WgRenderStorage::endRenderPass()
{
assert(renderPassEncoder);
wgpuRenderPassEncoderEnd(renderPassEncoder);
wgpuRenderPassEncoderRelease(renderPassEncoder);
}
//*****************************************************************************
// render storage
//*****************************************************************************
void WgRenderStorage::initialize(WgContext& context, uint32_t w, uint32_t h, uint32_t samples)
{
release(context);
// store target storage size
width = w * samples;
height = h * samples;
workgroupsCountX = (width + WG_COMPUTE_WORKGROUP_SIZE_X - 1) / WG_COMPUTE_WORKGROUP_SIZE_X; // workgroup size x == 8
workgroupsCountY = (height + WG_COMPUTE_WORKGROUP_SIZE_Y - 1) / WG_COMPUTE_WORKGROUP_SIZE_Y; // workgroup size y == 8
// create color and stencil textures
texStorage = context.createTexture2d(
WGPUTextureUsage_StorageBinding | WGPUTextureUsage_CopySrc | WGPUTextureUsage_CopyDst,
WGPUTextureFormat_RGBA8Unorm,
width, height, "The target texture storage color");
assert(texStorage);
texViewStorage = context.createTextureView2d(texStorage, "The target texture storage view color");
assert(texViewStorage);
// initialize bind group for blitting
bindGroupTexStorage.initialize(context.device, context.queue, texViewStorage);
mPipelines = context.pipelines;
}
void WgRenderStorage::release(WgContext& context)
{
bindGroupTexStorage.release();
context.releaseTextureView(texViewStorage);
context.releaseTexture(texStorage);
workgroupsCountX = 0;
workgroupsCountY = 0;
height = 0;
width = 0;
}
void WgRenderStorage::clear(WGPUCommandEncoder commandEncoder)
{
assert(commandEncoder);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeClear.use(computePassEncoder, bindGroupTexStorage);
dispatchWorkgroups(computePassEncoder);
endRenderPass(computePassEncoder);
}
void WgRenderStorage::blend(WGPUCommandEncoder commandEncoder, WgRenderTarget* targetSrc, WgBindGroupBlendMethod* blendMethod)
{
assert(commandEncoder);
assert(targetSrc);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeBlend.use(computePassEncoder, targetSrc->bindGroupTexStorage, bindGroupTexStorage, *blendMethod);
dispatchWorkgroups(computePassEncoder);
endRenderPass(computePassEncoder);
};
void WgRenderStorage::blend(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc, WgBindGroupBlendMethod* blendMethod)
{
assert(commandEncoder);
assert(targetSrc);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeBlend.use(computePassEncoder, targetSrc->bindGroupTexStorage, bindGroupTexStorage, *blendMethod);
dispatchWorkgroups(computePassEncoder);
endRenderPass(computePassEncoder);
};
void WgRenderStorage::compose(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetMsk, WgBindGroupCompositeMethod* composeMethod, WgBindGroupOpacity* opacity)
{
assert(commandEncoder);
assert(targetMsk);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeCompose.use(computePassEncoder, bindGroupTexStorage, targetMsk->bindGroupTexStorage, *composeMethod, *opacity);
dispatchWorkgroups(computePassEncoder);
endRenderPass(computePassEncoder);
};
void WgRenderStorage::antialias(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc)
{
assert(commandEncoder);
assert(targetSrc);
WGPUComputePassEncoder computePassEncoder = beginComputePass(commandEncoder);
mPipelines->computeAntiAliasing.use(computePassEncoder, targetSrc->bindGroupTexStorage, bindGroupTexStorage);
dispatchWorkgroups(computePassEncoder);
endRenderPass(computePassEncoder);
}
void WgRenderStorage::dispatchWorkgroups(WGPUComputePassEncoder computePassEncoder)
{
assert(computePassEncoder);
wgpuComputePassEncoderDispatchWorkgroups(computePassEncoder, workgroupsCountX, workgroupsCountY, 1);
assert(mRenderPassEncoder);
wgpuRenderPassEncoderEnd(mRenderPassEncoder);
wgpuRenderPassEncoderRelease(mRenderPassEncoder);
mRenderPassEncoder = nullptr;
}
@ -290,7 +258,7 @@ WGPUComputePassEncoder WgRenderStorage::beginComputePass(WGPUCommandEncoder comm
};
void WgRenderStorage::endRenderPass(WGPUComputePassEncoder computePassEncoder)
void WgRenderStorage::endComputePass(WGPUComputePassEncoder computePassEncoder)
{
assert(computePassEncoder);
wgpuComputePassEncoderEnd(computePassEncoder);

View file

@ -25,10 +25,11 @@
#include "tvgWgRenderData.h"
class WgRenderTarget {
class WgRenderStorage {
private:
// canvas info
// texture buffers
WgBindGroupCanvas mBindGroupCanvas;
WGPURenderPassEncoder mRenderPassEncoder{};
WgPipelines* mPipelines{}; // external handle
public:
WGPUTexture texColor{};
@ -36,29 +37,6 @@ public:
WGPUTextureView texViewColor{};
WGPUTextureView texViewStencil{};
WgBindGroupTextureStorage bindGroupTexStorage;
public:
void initialize(WgContext& context, uint32_t w, uint32_t h, uint32_t samples = 1);
void release(WgContext& context);
void renderShape(WGPUCommandEncoder commandEncoder, WgRenderDataShape* renderData);
void renderPicture(WGPUCommandEncoder commandEncoder, WgRenderDataPicture* renderData);
private:
void drawShape(WGPURenderPassEncoder renderPassEncoder, WgRenderDataShape* renderData);
void drawStroke(WGPURenderPassEncoder renderPassEncoder, WgRenderDataShape* renderData);
WGPURenderPassEncoder beginRenderPass(WGPUCommandEncoder commandEncoder);
void endRenderPass(WGPURenderPassEncoder renderPassEncoder);
};
class WgRenderStorage {
private:
// texture buffers
WgPipelines* mPipelines{}; // external handle
public:
WGPUTexture texStorage{};
WGPUTextureView texViewStorage{};
WgBindGroupTextureStorage bindGroupTexStorage;
uint32_t width{};
uint32_t height{};
uint32_t workgroupsCountX{};
@ -67,16 +45,24 @@ public:
void initialize(WgContext& context, uint32_t w, uint32_t h, uint32_t samples = 1);
void release(WgContext& context);
void beginRenderPass(WGPUCommandEncoder commandEncoder, bool clear);
void endRenderPass();
void renderShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
void renderPicture(WgRenderDataPicture* renderData, WgPipelineBlendType blendType);
void clear(WGPUCommandEncoder commandEncoder);
void blend(WGPUCommandEncoder commandEncoder, WgRenderTarget* targetSrc, WgBindGroupBlendMethod* blendMethod);
void blend(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc, WgBindGroupBlendMethod* blendMethod);
void compose(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetMsk, WgBindGroupCompositeMethod* composeMethod, WgBindGroupOpacity* opacity);
void antialias(WGPUCommandEncoder commandEncoder, WgRenderStorage* targetSrc);
private:
void drawShape(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
void drawStroke(WgRenderDataShape* renderData, WgPipelineBlendType blendType);
void dispatchWorkgroups(WGPUComputePassEncoder computePassEncoder);
WGPUComputePassEncoder beginComputePass(WGPUCommandEncoder commandEncoder);
void endRenderPass(WGPUComputePassEncoder computePassEncoder);
void endComputePass(WGPUComputePassEncoder computePassEncoder);
};

View file

@ -133,43 +133,74 @@ RenderData WgRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
return renderDataPicture;
}
bool WgRenderer::preRender()
{
WGPUCommandEncoderDescriptor commandEncoderDesc{};
commandEncoderDesc.nextInChain = nullptr;
commandEncoderDesc.label = "The command encoder";
mCommandEncoder = wgpuDeviceCreateCommandEncoder(mContext.device, &commandEncoderDesc);
mRenderStorageRoot.clear(mCommandEncoder);
mRenderStorageStack.push(&mRenderStorageRoot);
//mRenderStorageRoot.clear(mCommandEncoder);
mRenderStorageRoot.beginRenderPass(mCommandEncoder, true);
return true;
}
bool WgRenderer::renderShape(RenderData data)
{
// render shape to render target
mRenderTarget.renderShape(mCommandEncoder, (WgRenderDataShape *)data);
// blend shape with current render storage
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
mRenderStorageStack.last()->blend(mCommandEncoder, &mRenderTarget, blendMethod);
// get current render storage
WgPipelineBlendType blendType = WgPipelines::blendMethodToBlendType(mBlendMethod);
WgRenderStorage* renderStorage = mRenderStorageStack.last();
assert(renderStorage);
// use hardware blend
if (WgPipelines::isBlendMethodSupportsHW(mBlendMethod))
renderStorage->renderShape((WgRenderDataShape *)data, blendType);
else { // use custom blend
// terminate current render pass
renderStorage->endRenderPass();
// render image to render target
mRenderTarget.beginRenderPass(mCommandEncoder, true);
mRenderTarget.renderShape((WgRenderDataShape *)data, blendType);
mRenderTarget.endRenderPass();
// blend shape with current render storage
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
renderStorage->blend(mCommandEncoder, &mRenderTarget, blendMethod);
// restore current render pass
renderStorage->beginRenderPass(mCommandEncoder, false);
}
return true;
}
bool WgRenderer::renderImage(RenderData data)
{
// render image to render target
mRenderTarget.renderPicture(mCommandEncoder, (WgRenderDataPicture *)data);
// blend image with current render storage
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
mRenderStorageStack.last()->blend(mCommandEncoder, &mRenderTarget, blendMethod);
// get current render storage
WgPipelineBlendType blendType = WgPipelines::blendMethodToBlendType(mBlendMethod);
WgRenderStorage* renderStorage = mRenderStorageStack.last();
assert(renderStorage);
// use hardware blend
if (WgPipelines::isBlendMethodSupportsHW(mBlendMethod))
renderStorage->renderPicture((WgRenderDataPicture *)data, blendType);
else { // use custom blend
// terminate current render pass
renderStorage->endRenderPass();
// render image to render target
mRenderTarget.beginRenderPass(mCommandEncoder, true);
mRenderTarget.renderPicture((WgRenderDataPicture *)data, blendType);
mRenderTarget.endRenderPass();
// blend shape with current render storage
WgBindGroupBlendMethod* blendMethod = mBlendMethodPool.allocate(mContext, mBlendMethod);
renderStorage->blend(mCommandEncoder, &mRenderTarget, blendMethod);
// restore current render pass
renderStorage->beginRenderPass(mCommandEncoder, false);
}
return true;
}
bool WgRenderer::postRender()
{
mRenderStorageRoot.endRenderPass();
mRenderStorageStack.pop();
mContext.executeCommandEncoder(mCommandEncoder);
wgpuCommandEncoderRelease(mCommandEncoder);
@ -233,7 +264,7 @@ bool WgRenderer::sync()
mRenderStorageScreen.antialias(commandEncoder, &mRenderStorageRoot);
WGPUImageCopyTexture source{};
source.texture = mRenderStorageScreen.texStorage;
source.texture = mRenderStorageScreen.texColor;
WGPUImageCopyTexture dest{};
dest.texture = backBuffer.texture;
WGPUExtent3D copySize{};
@ -313,15 +344,17 @@ Compositor* WgRenderer::target(TVG_UNUSED const RenderRegion& region, TVG_UNUSED
bool WgRenderer::beginComposite(TVG_UNUSED Compositor* cmp, TVG_UNUSED CompositeMethod method, TVG_UNUSED uint8_t opacity)
{
// save current composition settings
// save current composition settings
cmp->method = method;
cmp->opacity = opacity;
// end current render pass
mRenderStorageStack.last()->endRenderPass();
// allocate new render storage and push it to top of render tree
WgRenderStorage* renderStorage = mRenderStoragePool.allocate(mContext, mTargetSurface.w * WG_SSAA_SAMPLES, mTargetSurface.h * WG_SSAA_SAMPLES);
renderStorage->clear(mCommandEncoder);
mRenderStorageStack.push(renderStorage);
// begin last render pass
mRenderStorageStack.last()->beginRenderPass(mCommandEncoder, true);
return true;
}
@ -329,6 +362,8 @@ bool WgRenderer::beginComposite(TVG_UNUSED Compositor* cmp, TVG_UNUSED Composite
bool WgRenderer::endComposite(TVG_UNUSED Compositor* cmp)
{
if (cmp->method == CompositeMethod::None) {
// end current render pass
mRenderStorageStack.last()->endRenderPass();
// get two last render targets
WgRenderStorage* renderStorageSrc = mRenderStorageStack.last();
mRenderStorageStack.pop();
@ -339,7 +374,11 @@ bool WgRenderer::endComposite(TVG_UNUSED Compositor* cmp)
// back render targets to the pool
mRenderStoragePool.free(mContext, renderStorageSrc);
// begin last render pass
mRenderStorageStack.last()->beginRenderPass(mCommandEncoder, false);
} else {
// end current render pass
mRenderStorageStack.last()->endRenderPass();
// get two last render targets
WgRenderStorage* renderStorageSrc = mRenderStorageStack.last();
mRenderStorageStack.pop();
@ -358,6 +397,8 @@ bool WgRenderer::endComposite(TVG_UNUSED Compositor* cmp)
// back render targets to the pool
mRenderStoragePool.free(mContext, renderStorageSrc);
mRenderStoragePool.free(mContext, renderStorageMsk);
// begin last render pass
mRenderStorageStack.last()->beginRenderPass(mCommandEncoder, false);
}
// delete current compositor

View file

@ -62,7 +62,7 @@ public:
private:
// render handles
WGPUCommandEncoder mCommandEncoder{};
WgRenderTarget mRenderTarget;
WgRenderStorage mRenderTarget;
WgRenderStorage mRenderStorageRoot;
WgRenderStorage mRenderStorageScreen;
WgRenderStoragePool mRenderStoragePool;
@ -76,7 +76,6 @@ private:
// native window handles
WGPUSurface mSurface{};
private:
WgContext mContext;
WgPipelines mPipelines;
Surface mTargetSurface;