thorvg/src/renderer/wg_engine/tvgWgRenderData.cpp
Hermet Park 5332876fe8 common: spec out TexMap
Spec out this incomplete experimental feature,
this is a still promising one, we will reintroduce
this officially after 1.0 release

size: -2kb

issue: https://github.com/thorvg/thorvg/issues/1372
2024-08-20 11:14:54 +09:00

520 lines
19 KiB
C++
Executable file

/*
* Copyright (c) 2023 - 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 <algorithm>
#include "tvgWgRenderData.h"
#include "tvgWgShaderTypes.h"
//***********************************************************************
// WgMeshData
//***********************************************************************
void WgMeshData::draw(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, bufferIndex, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
}
void WgMeshData::drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, context.bufferIndexFan, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
}
void WgMeshData::drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder)
{
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 0, bufferPosition, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder, 1, bufferTexCoord, 0, vertexCount * sizeof(float) * 2);
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder, bufferIndex, WGPUIndexFormat_Uint32, 0, indexCount * sizeof(uint32_t));
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder, indexCount, 1, 0, 0, 0);
};
void WgMeshData::update(WgContext& context, const WgPolyline* polyline)
{
assert(polyline);
assert(polyline->pts.count > 2);
vertexCount = polyline->pts.count;
indexCount = (polyline->pts.count - 2) * 3;
context.allocateBufferVertex(bufferPosition, (float *)&polyline->pts[0], vertexCount * sizeof(float) * 2);
context.allocateBufferIndexFan(vertexCount);
}
void WgMeshData::update(WgContext& context, const WgGeometryData* geometryData)
{
assert(geometryData);
vertexCount = geometryData->positions.pts.count;
indexCount = geometryData->indexes.count;
// buffer position data create and write
if (geometryData->positions.pts.count > 0)
context.allocateBufferVertex(bufferPosition, (float *)&geometryData->positions.pts[0], vertexCount * sizeof(float) * 2);
// buffer tex coords data create and write
if (geometryData->texCoords.count > 0)
context.allocateBufferVertex(bufferTexCoord, (float *)&geometryData->texCoords[0], vertexCount * sizeof(float) * 2);
// buffer index data create and write
if (geometryData->indexes.count > 0)
context.allocateBufferIndex(bufferIndex, &geometryData->indexes[0], indexCount * sizeof(uint32_t));
};
void WgMeshData::update(WgContext& context, const WgPoint pmin, const WgPoint pmax)
{
vertexCount = 4;
indexCount = 6;
const float data[] = {
pmin.x, pmin.y, pmax.x, pmin.y,
pmax.x, pmax.y, pmin.x, pmax.y
};
context.allocateBufferVertex(bufferPosition, data, sizeof(data));
context.allocateBufferIndexFan(vertexCount);
}
void WgMeshData::release(WgContext& context)
{
context.releaseBuffer(bufferIndex);
context.releaseBuffer(bufferTexCoord);
context.releaseBuffer(bufferPosition);
};
//***********************************************************************
// WgMeshDataPool
//***********************************************************************
WgMeshData* WgMeshDataPool::allocate(WgContext& context)
{
WgMeshData* meshData{};
if (mPool.count > 0) {
meshData = mPool.last();
mPool.pop();
} else {
meshData = new WgMeshData();
mList.push(meshData);
}
return meshData;
}
void WgMeshDataPool::free(WgContext& context, WgMeshData* meshData)
{
mPool.push(meshData);
}
void WgMeshDataPool::release(WgContext& context)
{
for (uint32_t i = 0; i < mList.count; i++) {
mList[i]->release(context);
delete mList[i];
}
mPool.clear();
mList.clear();
}
WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
//***********************************************************************
// WgMeshDataGroup
//***********************************************************************
void WgMeshDataGroup::append(WgContext& context, const WgPolyline* polyline)
{
assert(polyline);
assert(polyline->pts.count >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, polyline);
}
void WgMeshDataGroup::append(WgContext& context, const WgGeometryData* geometryData)
{
assert(geometryData);
assert(geometryData->positions.pts.count >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, geometryData);
}
void WgMeshDataGroup::append(WgContext& context, const WgPoint pmin, const WgPoint pmax)
{
meshes.push(gMeshDataPool->allocate(context));
meshes.last()->update(context, pmin, pmax);
}
void WgMeshDataGroup::release(WgContext& context)
{
for (uint32_t i = 0; i < meshes.count; i++)
gMeshDataPool->free(context, meshes[i]);
meshes.clear();
};
//***********************************************************************
// WgImageData
//***********************************************************************
void WgImageData::update(WgContext& context, Surface* surface)
{
release(context);
assert(surface);
texture = context.createTexture(surface->w, surface->h, WGPUTextureFormat_RGBA8Unorm);
assert(texture);
textureView = context.createTextureView(texture);
assert(textureView);
// update texture data
WGPUImageCopyTexture imageCopyTexture{ .texture = texture };
WGPUTextureDataLayout textureDataLayout{ .bytesPerRow = 4 * surface->w, .rowsPerImage = surface->h };
WGPUExtent3D writeSize{ .width = surface->w, .height = surface->h, .depthOrArrayLayers = 1 };
wgpuQueueWriteTexture(context.queue, &imageCopyTexture, surface->data, 4 * surface->w * surface->h, &textureDataLayout, &writeSize);
wgpuQueueSubmit(context.queue, 0, nullptr);
};
void WgImageData::release(WgContext& context)
{
context.releaseTextureView(textureView);
context.releaseTexture(texture);
};
//***********************************************************************
// WgRenderSettings
//***********************************************************************
void WgRenderSettings::update(WgContext& context, const Fill* fill, const uint8_t* color, const RenderUpdateFlag flags)
{
// setup fill properties
if ((flags & (RenderUpdateFlag::Gradient)) && fill) {
rasterType = WgRenderRasterType::Gradient;
WgShaderTypeGradient gradient;
if (fill->type() == Type::LinearGradient) {
gradient.update((LinearGradient*)fill);
fillType = WgRenderSettingsType::Linear;
} else if (fill->type() == Type::RadialGradient) {
gradient.update((RadialGradient*)fill);
fillType = WgRenderSettingsType::Radial;
}
// update gpu assets
bool bufferGradientSettingsChanged = context.allocateBufferUniform(bufferGroupGradient, &gradient.settings, sizeof(gradient.settings));
bool textureGradientChanged = context.allocateTexture(texGradient, WG_TEXTURE_GRADIENT_SIZE, 1, WGPUTextureFormat_RGBA8Unorm, gradient.texData);
if (bufferGradientSettingsChanged || textureGradientChanged) {
// update texture view
context.releaseTextureView(texViewGradient);
texViewGradient = context.createTextureView(texGradient);
// get sampler by spread type
WGPUSampler sampler = context.samplerLinearClamp;
if (fill->spread() == FillSpread::Reflect) sampler = context.samplerLinearMirror;
if (fill->spread() == FillSpread::Repeat) sampler = context.samplerLinearRepeat;
// update bind group
context.pipelines->layouts.releaseBindGroup(bindGroupGradient);
bindGroupGradient = context.pipelines->layouts.createBindGroupTexSampledBuff1Un(
sampler, texViewGradient, bufferGroupGradient);
}
skip = false;
} else if ((flags & (RenderUpdateFlag::Color)) && !fill) {
rasterType = WgRenderRasterType::Solid;
WgShaderTypeSolidColor solidColor(color);
if (context.allocateBufferUniform(bufferGroupSolid, &solidColor, sizeof(solidColor))) {
context.pipelines->layouts.releaseBindGroup(bindGroupSolid);
bindGroupSolid = context.pipelines->layouts.createBindGroupBuffer1Un(bufferGroupSolid);
}
fillType = WgRenderSettingsType::Solid;
skip = (color[3] == 0);
}
};
void WgRenderSettings::release(WgContext& context)
{
context.pipelines->layouts.releaseBindGroup(bindGroupSolid);
context.pipelines->layouts.releaseBindGroup(bindGroupGradient);
context.releaseBuffer(bufferGroupSolid);
context.releaseBuffer(bufferGroupGradient);
context.releaseTexture(texGradient);
context.releaseTextureView(texViewGradient);
};
//***********************************************************************
// WgRenderDataPaint
//***********************************************************************
void WgRenderDataPaint::release(WgContext& context)
{
context.pipelines->layouts.releaseBindGroup(bindGroupPaint);
context.releaseBuffer(bufferModelMat);
context.releaseBuffer(bufferBlendSettings);
clips.clear();
};
void WgRenderDataPaint::update(WgContext& context, const tvg::Matrix& transform, tvg::ColorSpace cs, uint8_t opacity)
{
WgShaderTypeMat4x4f modelMat(transform);
WgShaderTypeBlendSettings blendSettings(cs, opacity);
bool bufferModelMatChanged = context.allocateBufferUniform(bufferModelMat, &modelMat, sizeof(modelMat));
bool bufferBlendSettingsChanged = context.allocateBufferUniform(bufferBlendSettings, &blendSettings, sizeof(blendSettings));
if (bufferModelMatChanged || bufferBlendSettingsChanged) {
context.pipelines->layouts.releaseBindGroup(bindGroupPaint);
bindGroupPaint = context.pipelines->layouts.createBindGroupBuffer2Un(bufferModelMat, bufferBlendSettings);
}
}
void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
this->clips.clear();
for (uint32_t i = 0; i < clips.count; i++)
if (clips[i])
this->clips.push((WgRenderDataPaint*)clips[i]);
}
//***********************************************************************
// WgRenderDataShape
//***********************************************************************
void WgRenderDataShape::updateBBox(WgPoint pmin, WgPoint pmax)
{
pMin.x = std::min(pMin.x, pmin.x);
pMin.y = std::min(pMin.y, pmin.y);
pMax.x = std::max(pMax.x, pmax.x);
pMax.y = std::max(pMax.y, pmax.y);
}
void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rshape, const Matrix& rt)
{
releaseMeshes(context);
strokeFirst = rshape.stroke ? rshape.stroke->strokeFirst : false;
Array<WgPolyline*> polylines{};
// decode path
size_t pntIndex = 0;
for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) {
PathCommand cmd = rshape.path.cmds[cmdIndex];
if (cmd == PathCommand::MoveTo) {
// proceed current polyline
polylines.push(new WgPolyline);
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::LineTo) {
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
pntIndex++;
} else if (cmd == PathCommand::Close) {
polylines.last()->close();
} else if (cmd == PathCommand::CubicTo) {
assert(polylines.last()->pts.count > 0);
WgPoint pt0 = polylines.last()->pts.last().trans(rt);
WgPoint pt1 = WgPoint(rshape.path.pts[pntIndex + 0]).trans(rt);
WgPoint pt2 = WgPoint(rshape.path.pts[pntIndex + 1]).trans(rt);
WgPoint pt3 = WgPoint(rshape.path.pts[pntIndex + 2]).trans(rt);
uint32_t nsegs = (uint32_t)(pt0.dist(pt1) + pt1.dist(pt2) + pt2.dist(pt3));
polylines.last()->appendCubic(
rshape.path.pts[pntIndex + 0],
rshape.path.pts[pntIndex + 1],
rshape.path.pts[pntIndex + 2],
nsegs / 2);
pntIndex += 3;
}
}
// proceed shapes
float totalLen{};
for (uint32_t i = 0; i < polylines.count; i++) {
totalLen += polylines[i]->len;
updateShapes(context, polylines[i]);
}
// proceed strokes
if (rshape.stroke) {
float trimBegin{};
float trimEnd{};
if (!rshape.stroke->strokeTrim(trimBegin, trimEnd)) { trimBegin = 0.0f; trimEnd = 1.0f; }
if (rshape.stroke->trim.simultaneous) {
for (uint32_t i = 0; i < polylines.count; i++)
updateStrokes(context, polylines[i], rshape.stroke, trimBegin, trimEnd);
} else {
if (trimBegin <= trimEnd) {
updateStrokesList(context, polylines, rshape.stroke, totalLen, trimBegin, trimEnd);
} else {
updateStrokesList(context, polylines, rshape.stroke, totalLen, 0.0f, trimEnd);
updateStrokesList(context, polylines, rshape.stroke, totalLen, trimBegin, 1.0f);
}
}
}
// delete polylines
for (uint32_t i = 0; i < polylines.count; i++)
delete polylines[i];
// update shapes bbox
meshDataBBox.update(context, pMin, pMax);
}
void WgRenderDataShape::updateShapes(WgContext& context, const WgPolyline* polyline)
{
assert(polyline);
// generate fill geometry
if (polyline->pts.count >= 3) {
WgPoint pmin{}, pmax{};
polyline->getBBox(pmin, pmax);
meshGroupShapes.append(context, polyline);
meshGroupShapesBBox.append(context, pmin, pmax);
updateBBox(pmin, pmax);
}
}
void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float totalLen, float trimBegin, float trimEnd)
{
float tp1 = totalLen * trimBegin; // trim point begin
float tp2 = totalLen * trimEnd; // trim point end
float pc = 0; // point current
for (uint32_t i = 0; i < polylines.count; i++) {
float pl = polylines[i]->len; // current polyline length
float trimBegin = ((pc <= tp1) && (pc + pl > tp1)) ? (tp1 - pc) / pl : 0.0f;
float trimEnd = ((pc <= tp2) && (pc + pl > tp2)) ? (tp2 - pc) / pl : 1.0f;
if ((pc + pl >= tp1) && (pc <= tp2))
updateStrokes(context, polylines[i], rstroke, trimBegin, trimEnd);
pc += pl;
// break if reached the tail
if (pc > tp2) break;
}
}
void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float trimBegin, float trimEnd)
{
assert(polyline);
// generate strokes geometry
if ((polyline->pts.count >= 1) && rstroke && (rstroke->width > 0.0f)) {
static WgGeometryData geometryData; geometryData.clear();
static WgPolyline trimmed;
// trim -> split -> stroke
if (trimBegin == trimEnd) return;
if ((rstroke->dashPattern) && ((trimBegin != 0.0f) || (trimEnd != 1.0f))) {
trimmed.clear();
if (trimBegin < trimEnd)
polyline->trim(&trimmed, trimBegin, trimEnd);
else {
polyline->trim(&trimmed, trimBegin, 1.0f);
polyline->trim(&trimmed, 0.0f, trimEnd);
}
geometryData.appendStrokeDashed(&trimmed, rstroke);
} else // trim -> stroke
if ((trimBegin != 0.0f) || (trimEnd != 1.0f)) {
trimmed.clear();
if (trimBegin < trimEnd)
polyline->trim(&trimmed, trimBegin, trimEnd);
else {
polyline->trim(&trimmed, trimBegin, 1.0f);
polyline->trim(&trimmed, 0.0f, trimEnd);
}
geometryData.appendStroke(&trimmed, rstroke);
} else // split -> stroke
if (rstroke->dashPattern) {
geometryData.appendStrokeDashed(polyline, rstroke);
} else { // stroke
geometryData.appendStroke(polyline, rstroke);
}
// append render meshes and bboxes
if(geometryData.positions.pts.count >= 3) {
WgPoint pmin{}, pmax{};
geometryData.positions.getBBox(pmin, pmax);
meshGroupStrokes.append(context, &geometryData);
meshGroupStrokesBBox.append(context, pmin, pmax);
}
}
}
void WgRenderDataShape::releaseMeshes(WgContext &context)
{
meshGroupStrokesBBox.release(context);
meshGroupStrokes.release(context);
meshGroupShapesBBox.release(context);
meshGroupShapes.release(context);
pMin = {FLT_MAX, FLT_MAX};
pMax = {0.0f, 0.0f};
clips.clear();
}
void WgRenderDataShape::release(WgContext& context)
{
releaseMeshes(context);
meshDataBBox.release(context);
renderSettingsStroke.release(context);
renderSettingsShape.release(context);
WgRenderDataPaint::release(context);
};
//***********************************************************************
// WgRenderDataShapePool
//***********************************************************************
WgRenderDataShape* WgRenderDataShapePool::allocate(WgContext& context)
{
WgRenderDataShape* dataShape{};
if (mPool.count > 0) {
dataShape = mPool.last();
mPool.pop();
} else {
dataShape = new WgRenderDataShape();
mList.push(dataShape);
}
return dataShape;
}
void WgRenderDataShapePool::free(WgContext& context, WgRenderDataShape* dataShape)
{
dataShape->meshGroupShapes.release(context);
dataShape->meshGroupShapesBBox.release(context);
dataShape->meshGroupStrokes.release(context);
dataShape->meshGroupStrokesBBox.release(context);
dataShape->clips.clear();
mPool.push(dataShape);
}
void WgRenderDataShapePool::release(WgContext& context)
{
for (uint32_t i = 0; i < mList.count; i++) {
mList[i]->release(context);
delete mList[i];
}
mPool.clear();
mList.clear();
}
//***********************************************************************
// WgRenderDataPicture
//***********************************************************************
void WgRenderDataPicture::release(WgContext& context)
{
imageData.release(context);
context.pipelines->layouts.releaseBindGroup(bindGroupPicture);
WgRenderDataPaint::release(context);
}