wg_engine: multicanvas support

Added multicanas support
Issue https://github.com/thorvg/thorvg/issues/2745
This commit is contained in:
Sergii Liebodkin 2024-11-21 14:52:42 +00:00 committed by Hermet Park
parent 57fcada8cc
commit 3805f26aff
14 changed files with 181 additions and 126 deletions

View file

@ -425,14 +425,14 @@ struct WgWindow : Window
virtual ~WgWindow()
{
wgpuSurfaceRelease(surface);
//wgpuSurfaceRelease(surface);
wgpuInstanceRelease(instance);
}
void resize() override
{
//Set the canvas target and draw on it.
verify(static_cast<tvg::WgCanvas*>(canvas)->target(instance, surface, width, height));
verify(static_cast<tvg::WgCanvas*>(canvas)->target(nullptr, instance, surface, width, height));
}
void refresh() override

View file

@ -1774,11 +1774,12 @@ public:
/**
* @brief Sets the drawing target for the rasterization.
*
* @param[in] instance WGPUInstance, context for all other wgpu objects.
* @param[in] surface WGPUSurface, handle to a presentable surface.
* @param[in] w The width of the surface.
* @param[in] h The height of the surface.
* @param[in] device WGPUDevice, a desired handle for the wgpu device. If it is @c nullptr, ThorVG will assign an appropriate device internally.
* @param[in] instance WGPUInstance, context for all other wgpu objects.
* @param[in] target Either WGPUSurface or WGPUTexture, serving as handles to a presentable surface or texture
* @param[in] w The width of the target.
* @param[in] h The height of the target.
* @param[in] type 0: surface, 1: texture are used as pesentable target
*
* @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced.
* @retval Result::NonSupport In case the wg engine is not supported.
@ -1788,7 +1789,7 @@ public:
* @see Canvas::viewport()
* @see Canvas::sync()
*/
Result target(void* instance, void* surface, uint32_t w, uint32_t h, void* device = nullptr) noexcept;
Result target(void* device, void* instance, void* target, uint32_t w, uint32_t h, int type = 0) noexcept;
/**
* @brief Creates a new WgCanvas object.

View file

@ -560,11 +560,12 @@ TVG_API Tvg_Canvas* tvg_wgcanvas_create(void);
/*!
* \brief Sets the drawing target for the rasterization.
*
* \param[in] instance WGPUInstance, context for all other wgpu objects.
* \param[in] surface WGPUSurface, handle to a presentable surface.
* \param[in] w The width of the surface.
* \param[in] h The height of the surface.
* \param[in] device WGPUDevice, a desired handle for the wgpu device. If it is @c nullptr, ThorVG will assign an appropriate device internally.
* @param[in] device WGPUDevice, a desired handle for the wgpu device. If it is @c nullptr, ThorVG will assign an appropriate device internally.
* @param[in] instance WGPUInstance, context for all other wgpu objects.
* @param[in] target Either WGPUSurface or WGPUTexture, serving as handles to a presentable surface or texture
* @param[in] w The width of the target.
* @param[in] h The height of the target.
* @param[in] type 0: surface, 1: texture are used as pesentable target
*
* \return Tvg_Result enumeration.
* \retval TVG_RESULT_INSUFFICIENT_CONDITION if the canvas is performing rendering. Please ensure the canvas is synced.
@ -572,7 +573,7 @@ TVG_API Tvg_Canvas* tvg_wgcanvas_create(void);
*
* \note Experimental API
*/
TVG_API Tvg_Result tvg_wgcanvas_set_target(Tvg_Canvas* canvas, void* instance, void* surface, uint32_t w, uint32_t h, void* device);
TVG_API Tvg_Result tvg_wgcanvas_set_target(Tvg_Canvas* canvas, void* device, void* instance, void* target, uint32_t w, uint32_t h, int type = 0);
/** \} */ // end defgroup ThorVGCapi_WgCanvas

View file

@ -109,10 +109,10 @@ TVG_API Tvg_Result tvg_glcanvas_set_target(Tvg_Canvas* canvas, int32_t id, uint3
}
TVG_API Tvg_Result tvg_wgcanvas_set_target(Tvg_Canvas* canvas, void* instance, void* surface, uint32_t w, uint32_t h, void* device)
TVG_API Tvg_Result tvg_wgcanvas_set_target(Tvg_Canvas* canvas, void* device, void* instance, void* target, uint32_t w, uint32_t h, int type)
{
if (!canvas) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<WgCanvas*>(canvas)->target(instance, surface, w, h, device);
return (Tvg_Result) reinterpret_cast<WgCanvas*>(canvas)->target(device, instance, target, w, h, type);
}

View file

@ -150,7 +150,7 @@ struct TvgWgEngine : TvgEngineMethod
void resize(Canvas* canvas, int w, int h) override
{
#ifdef THORVG_WG_RASTER_SUPPORT
static_cast<WgCanvas*>(canvas)->target(instance, surface, w, h, device);
static_cast<WgCanvas*>(canvas)->target(device, instance, surface, w, h);
#endif
}
};

View file

@ -52,25 +52,23 @@ WgCanvas::~WgCanvas()
{
#ifdef THORVG_WG_RASTER_SUPPORT
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
renderer->target(nullptr, 0, 0);
renderer->target(nullptr, nullptr, nullptr, 0, 0);
#endif
}
Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h, void* device) noexcept
Result WgCanvas::target(void* device, void* instance, void* target, uint32_t w, uint32_t h, int type) noexcept
{
#ifdef THORVG_WG_RASTER_SUPPORT
if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) {
return Result::InsufficientCondition;
}
if (!instance || !surface || (w == 0) || (h == 0)) return Result::InvalidArguments;
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h, (WGPUDevice)device)) return Result::Unknown;
if (!renderer->target((WGPUDevice)device, (WGPUInstance)instance, target, w, h, type)) return Result::Unknown;
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
renderer->viewport(Canvas::pImpl->vport);

View file

@ -140,6 +140,12 @@ void WgBindGroupLayouts::releaseBindGroup(WGPUBindGroup& bindGroup)
bindGroup = nullptr;
}
void WgBindGroupLayouts::releaseBindGroupLayout(WGPUBindGroupLayout& bindGroupLayout)
{
if (bindGroupLayout) wgpuBindGroupLayoutRelease(bindGroupLayout);
bindGroupLayout = nullptr;
}
void WgBindGroupLayouts::initialize(WgContext& context)
{
@ -262,14 +268,15 @@ void WgBindGroupLayouts::initialize(WgContext& context)
void WgBindGroupLayouts::release(WgContext& context)
{
wgpuBindGroupLayoutRelease(layoutBuffer3Un);
wgpuBindGroupLayoutRelease(layoutBuffer2Un);
wgpuBindGroupLayoutRelease(layoutBuffer1Un);
wgpuBindGroupLayoutRelease(layoutTexStrorage3RO);
wgpuBindGroupLayoutRelease(layoutTexStrorage2RO);
wgpuBindGroupLayoutRelease(layoutTexStrorage1RO);
wgpuBindGroupLayoutRelease(layoutTexStrorage1WO);
wgpuBindGroupLayoutRelease(layoutTexSampledBuff1Un);
wgpuBindGroupLayoutRelease(layoutTexSampled);
releaseBindGroupLayout(layoutBuffer3Un);
releaseBindGroupLayout(layoutBuffer2Un);
releaseBindGroupLayout(layoutBuffer1Un);
releaseBindGroupLayout(layoutTexStrorage3RO);
releaseBindGroupLayout(layoutTexStrorage2RO);
releaseBindGroupLayout(layoutTexStrorage1RO);
releaseBindGroupLayout(layoutTexStrorage1WO);
releaseBindGroupLayout(layoutTexSampledBuff1Un);
releaseBindGroupLayout(layoutTexSampledBuff2Un);
releaseBindGroupLayout(layoutTexSampled);
device = nullptr;
}

View file

@ -51,6 +51,7 @@ public:
WGPUBindGroup createBindGroupBuffer2Un(WGPUBuffer buff0, WGPUBuffer buff1);
WGPUBindGroup createBindGroupBuffer3Un(WGPUBuffer buff0, WGPUBuffer buff1, WGPUBuffer buff2);
void releaseBindGroup(WGPUBindGroup& bindGroup);
void releaseBindGroupLayout(WGPUBindGroupLayout& bindGroupLayout);
public:
void initialize(WgContext& context);
void release(WgContext& context);

View file

@ -260,7 +260,7 @@ void WgContext::releaseBuffer(WGPUBuffer& buffer)
}
}
void WgContext::releaseQueue(WGPUQueue queue)
void WgContext::releaseQueue(WGPUQueue& queue)
{
if (queue) {
wgpuQueueRelease(queue);

View file

@ -63,7 +63,7 @@ struct WgContext {
void releaseTextureView(WGPUTextureView& textureView);
void releaseTexture(WGPUTexture& texture);
void releaseSampler(WGPUSampler& sampler);
void releaseQueue(WGPUQueue queue);
void releaseQueue(WGPUQueue& queue);
// create buffer objects (return true, if buffer handle was changed)
bool allocateBufferUniform(WGPUBuffer& buffer, const void* data, uint64_t size);

View file

@ -165,7 +165,8 @@ void WgMeshDataPool::release(WgContext& context)
mList.clear();
}
WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
WgMeshDataPool gMeshDataPoolInstance;
WgMeshDataPool* WgMeshDataPool::gMeshDataPool = &gMeshDataPoolInstance;
//***********************************************************************
// WgMeshDataGroup
@ -174,7 +175,7 @@ WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
void WgMeshDataGroup::append(WgContext& context, const WgVertexBuffer& vertexBuffer)
{
assert(vertexBuffer.vcount >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->update(context, vertexBuffer);
}
@ -182,14 +183,14 @@ void WgMeshDataGroup::append(WgContext& context, const WgVertexBuffer& vertexBuf
void WgMeshDataGroup::append(WgContext& context, const WgVertexBufferInd& vertexBufferInd)
{
assert(vertexBufferInd.vcount >= 3);
meshes.push(gMeshDataPool->allocate(context));
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->update(context, vertexBufferInd);
}
void WgMeshDataGroup::append(WgContext& context, const Point pmin, const Point pmax)
{
meshes.push(gMeshDataPool->allocate(context));
meshes.push(WgMeshDataPool::gMeshDataPool->allocate(context));
meshes.last()->bbox(context, pmin, pmax);
}
@ -197,7 +198,7 @@ void WgMeshDataGroup::append(WgContext& context, const Point pmin, const Point p
void WgMeshDataGroup::release(WgContext& context)
{
for (uint32_t i = 0; i < meshes.count; i++)
gMeshDataPool->free(context, meshes[i]);
WgMeshDataPool::gMeshDataPool->free(context, meshes[i]);
meshes.clear();
};
@ -298,8 +299,6 @@ void WgRenderSettings::release(WgContext& context)
// WgRenderDataPaint
//***********************************************************************
WgVertexBufferInd* WgRenderDataPaint::gStrokesGenerator = nullptr;
void WgRenderDataPaint::release(WgContext& context)
{
context.pipelines->layouts.releaseBindGroup(bindGroupPaint);
@ -455,7 +454,8 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
void WgRenderDataShape::proceedStrokes(WgContext context, const RenderStroke* rstroke, float tbeg, float tend, const WgVertexBuffer& buff)
{
assert(rstroke);
gStrokesGenerator->reset(buff.tscale);
static WgVertexBufferInd strokesGenerator;
strokesGenerator.reset(buff.tscale);
// trim -> dash -> stroke
if ((tbeg != 0.0f) || (tend != 1.0f)) {
if (tbeg == tend) return;
@ -464,17 +464,17 @@ void WgRenderDataShape::proceedStrokes(WgContext context, const RenderStroke* rs
trimed_buff.trim(buff, tbeg, tend);
trimed_buff.updateDistances();
// trim ->dash -> stroke
if (rstroke->dashPattern) gStrokesGenerator->appendStrokesDashed(trimed_buff, rstroke);
if (rstroke->dashPattern) strokesGenerator.appendStrokesDashed(trimed_buff, rstroke);
// trim -> stroke
else gStrokesGenerator->appendStrokes(trimed_buff, rstroke);
else strokesGenerator.appendStrokes(trimed_buff, rstroke);
} else
// dash -> stroke
if (rstroke->dashPattern) {
gStrokesGenerator->appendStrokesDashed(buff, rstroke);
strokesGenerator.appendStrokesDashed(buff, rstroke);
// stroke
} else
gStrokesGenerator->appendStrokes(buff, rstroke);
appendStroke(context, *gStrokesGenerator);
strokesGenerator.appendStrokes(buff, rstroke);
appendStroke(context, strokesGenerator);
}

View file

@ -50,14 +50,13 @@ private:
Array<WgMeshData*> mPool;
Array<WgMeshData*> mList;
public:
static WgMeshDataPool* gMeshDataPool;
WgMeshData* allocate(WgContext& context);
void free(WgContext& context, WgMeshData* meshData);
void release(WgContext& context);
};
struct WgMeshDataGroup {
static WgMeshDataPool* gMeshDataPool;
Array<WgMeshData*> meshes{};
void append(WgContext& context, const WgVertexBuffer& vertexBuffer);
@ -96,9 +95,6 @@ struct WgRenderSettings
struct WgRenderDataPaint
{
// global strokes generator. single instance
static WgVertexBufferInd* gStrokesGenerator;
WGPUBuffer bufferModelMat{};
WGPUBuffer bufferBlendSettings{};
WGPUBindGroup bindGroupPaint{};

View file

@ -30,47 +30,45 @@ WgRenderer::WgRenderer()
WgRenderer::~WgRenderer()
{
release();
mContext.release();
}
void WgRenderer::initialize()
{
mPipelines.initialize(mContext);
WgMeshDataGroup::gMeshDataPool = new WgMeshDataPool();
WgRenderDataShape::gStrokesGenerator = new WgVertexBufferInd();
}
void WgRenderer::release()
{
// check for general context availability
if (!mContext.queue) return;
// dispose stored objects
disposeObjects();
mStorageRoot.release(mContext);
mRenderStoragePool.release(mContext);
mRenderDataShapePool.release(mContext);
delete WgRenderDataShape::gStrokesGenerator;
WgMeshDataGroup::gMeshDataPool->release(mContext);
delete WgMeshDataGroup::gMeshDataPool;
// clear rendering tree stacks
mCompositorStack.clear();
mRenderStorageStack.clear();
mRenderStoragePool.release(mContext);
mRenderDataShapePool.release(mContext);
WgMeshDataPool::gMeshDataPool->release(mContext);
mStorageRoot.release(mContext);
// release context handles
mCompositor.release(mContext);
mPipelines.release(mContext);
if (gpuOwner) {
if (device) wgpuDeviceRelease(device);
device = nullptr;
if (adapter) wgpuAdapterRelease(adapter);
adapter = nullptr;
gpuOwner = false;
}
releaseSurfaceTexture();
mContext.release();
// release gpu handles
clearTargets();
releaseDevice();
}
void WgRenderer::disposeObjects()
{
if (mDisposeRenderDatas.count == 0) return;
for (auto p = mDisposeRenderDatas.begin(); p < mDisposeRenderDatas.end(); p++) {
auto renderData = (WgRenderDataPaint*)(*p);
for (uint32_t i = 0; i < mDisposeRenderDatas.count; i++) {
WgRenderDataPaint* renderData = (WgRenderDataPaint*)mDisposeRenderDatas[i];
if (renderData->type() == Type::Shape) {
mRenderDataShapePool.free(mContext, (WgRenderDataShape*)renderData);
} else {
@ -260,20 +258,26 @@ void WgRenderer::releaseSurfaceTexture()
bool WgRenderer::sync()
{
disposeObjects();
if (!surface) return false;
// if texture buffer used
WGPUTexture dstTexture = targetTexture;
if (surface) {
releaseSurfaceTexture();
wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture);
dstTexture = surfaceTexture.texture;
}
// there is no external dest buffer
if (!dstTexture) return false;
WGPUTextureView dstView = mContext.createTextureView(surfaceTexture.texture);
// get external dest buffer
WGPUTextureView dstTextureView = mContext.createTextureView(dstTexture);
// create command encoder
const WGPUCommandEncoderDescriptor commandEncoderDesc{};
WGPUCommandEncoder commandEncoder = wgpuDeviceCreateCommandEncoder(mContext.device, &commandEncoderDesc);
// show root offscreen buffer
mCompositor.blit(mContext, commandEncoder, &mStorageRoot, dstView);
mCompositor.blit(mContext, commandEncoder, &mStorageRoot, dstTextureView);
// release command encoder
const WGPUCommandBufferDescriptor commandBufferDesc{};
@ -281,20 +285,54 @@ bool WgRenderer::sync()
wgpuQueueSubmit(mContext.queue, 1, &commandsBuffer);
wgpuCommandBufferRelease(commandsBuffer);
wgpuCommandEncoderRelease(commandEncoder);
mContext.releaseTextureView(dstView);
// release dest buffer view
mContext.releaseTextureView(dstTextureView);
return true;
}
// target for native window handle
bool WgRenderer::target(WGPUInstance instance, WGPUSurface surface, uint32_t w, uint32_t h, WGPUDevice device)
// render target handle
bool WgRenderer::target(WGPUDevice device, WGPUInstance instance, void* target, uint32_t width, uint32_t height, int type)
{
gpuOwner = false;
// release existing handles
release();
// can not initialize renderer, give up
if (!instance || !target || !width || !height) return false;
// store or regest gpu device
this->device = device;
if (!this->device) {
if (!this->device)
reguestDevice(instance, (WGPUSurface)target);
// store target properties
mTargetSurface.stride = width;
mTargetSurface.w = width;
mTargetSurface.h = height;
// initialize rendering context
mContext.initialize(instance, this->device);
mPipelines.initialize(mContext);
// initialize render tree instances
mRenderStoragePool.initialize(mContext, width, height);
mStorageRoot.initialize(mContext, width, height);
mCompositor.initialize(mContext, width, height);
// configure surface (must be called after context creation)
if (type == 0) {
surface = (WGPUSurface)target;
surfaceConfigure(surface, mContext, width, height);
} else targetTexture = (WGPUTexture)target;
return true;
}
void WgRenderer::reguestDevice(WGPUInstance instance, WGPUSurface surface)
{
// request adapter
const WGPURequestAdapterOptions requestAdapterOptions { .nextInChain = nullptr, .compatibleSurface = surface, .powerPreference = WGPUPowerPreference_HighPerformance, .forceFallbackAdapter = false };
const WGPURequestAdapterOptions requestAdapterOptions { .compatibleSurface = surface, .powerPreference = WGPUPowerPreference_HighPerformance };
auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const * message, void * pUserData) { *((WGPUAdapter*)pUserData) = adapter; };
wgpuInstanceRequestAdapter(instance, &requestAdapterOptions, onAdapterRequestEnded, &this->adapter);
@ -303,46 +341,53 @@ bool WgRenderer::target(WGPUInstance instance, WGPUSurface surface, uint32_t w,
size_t featuresCount = wgpuAdapterEnumerateFeatures(this->adapter, featureNames);
// request device
const WGPUDeviceDescriptor deviceDesc { .nextInChain = nullptr, .label = "The device", .requiredFeatureCount = featuresCount, .requiredFeatures = featureNames };
const WGPUDeviceDescriptor deviceDesc { .label = "The owned device", .requiredFeatureCount = featuresCount, .requiredFeatures = featureNames };
auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, char const * message, void * pUserData) { *((WGPUDevice*)pUserData) = device; };
wgpuAdapterRequestDevice(this->adapter, &deviceDesc, onDeviceRequestEnded, &this->device);
gpuOwner = true;
}
mContext.initialize(instance, this->device);
initialize();
target(surface, w, h);
mRenderStoragePool.initialize(mContext, w, h);
mStorageRoot.initialize(mContext, w, h);
mCompositor.initialize(mContext, w, h);
return true;
void WgRenderer::releaseDevice()
{
if (!gpuOwner) return;
wgpuDeviceRelease(device);
wgpuDeviceDestroy(device);
wgpuAdapterRelease(adapter);
device = nullptr;
adapter = nullptr;
gpuOwner = false;
}
bool WgRenderer::target(WGPUSurface surface, uint32_t w, uint32_t h) {
void WgRenderer::clearTargets() {
releaseSurfaceTexture();
targetTexture = nullptr;
surface = nullptr;
}
bool WgRenderer::surfaceConfigure(WGPUSurface surface, WgContext& context, uint32_t width, uint32_t height)
{
// store target surface properties
this->surface = surface;
mTargetSurface.stride = w;
mTargetSurface.w = w;
mTargetSurface.h = h;
if (w == 0 || h == 0) return false;
if (!surface) return true;
if (width == 0 || height == 0 || !surface) return false;
// setup surface configuration
WGPUSurfaceConfiguration surfaceConfiguration {
.device = mContext.device,
.format = mContext.preferredFormat,
.device = context.device,
.format = context.preferredFormat,
.usage = WGPUTextureUsage_RenderAttachment,
#ifdef __EMSCRIPTEN__
.alphaMode = WGPUCompositeAlphaMode_Premultiplied,
#endif
.width = w, .height = h,
#ifdef __EMSCRIPTEN__
.width = width,
.height = height,
#ifndef __EMSCRIPTEN__
.presentMode = WGPUPresentMode_Fifo,
#else
.presentMode = WGPUPresentMode_Immediate
#endif
};
wgpuSurfaceConfigure(surface, &surfaceConfiguration);
return true;
}
@ -369,7 +414,7 @@ bool WgRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_
mRenderStorageStack.push(storage);
// begin newly added render pass
WGPUColor color{};
if ((method == MaskMethod::None) && (opacity != 255)) color = {1.0f, 1.0f, 1.0f, 0.0f};
if ((method == MaskMethod::None) && (opacity != 255)) color = { 1.0, 1.0, 1.0, 0.0 };
mCompositor.beginRenderPass(mCommandEncoder, mRenderStorageStack.last(), true, color);
return true;
}

View file

@ -45,8 +45,7 @@ public:
bool clear() override;
bool sync() override;
bool target(WGPUInstance instance, WGPUSurface surface, uint32_t w, uint32_t h, WGPUDevice device);
bool target(WGPUSurface surface, uint32_t w, uint32_t h);
bool target(WGPUDevice device, WGPUInstance instance, void* target, uint32_t width, uint32_t height, int type = 0);
RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override;
bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override;
@ -59,8 +58,6 @@ public:
static bool init(uint32_t threads);
static bool term();
WGPUSurface surface{}; // external handle
private:
WgRenderer();
~WgRenderer();
@ -69,28 +66,37 @@ private:
void disposeObjects();
void releaseSurfaceTexture();
WGPUSurfaceTexture surfaceTexture{};
void reguestDevice(WGPUInstance instance, WGPUSurface surface);
void releaseDevice();
void clearTargets();
bool surfaceConfigure(WGPUSurface surface, WgContext& context, uint32_t width, uint32_t height);
WGPUCommandEncoder mCommandEncoder{};
WgRenderDataShapePool mRenderDataShapePool;
// render tree stacks
// render tree stacks and pools
WgRenderStorage mStorageRoot;
Array<WgCompose*> mCompositorStack;
Array<WgRenderStorage*> mRenderStorageStack;
WgRenderStoragePool mRenderStoragePool;
WgRenderDataShapePool mRenderDataShapePool;
// rendering context
WgContext mContext;
WgPipelines mPipelines;
WgCompositor mCompositor;
// rendering states
RenderSurface mTargetSurface;
BlendMethod mBlendMethod{};
RenderRegion mViewport{};
// disposable data list
Array<RenderData> mDisposeRenderDatas{};
Key mDisposeKey{};
// gpu handles
WGPUCommandEncoder mCommandEncoder{};
WGPUTexture targetTexture{}; // external handle
WGPUSurfaceTexture surfaceTexture{};
WGPUSurface surface{}; // external handle
WGPUAdapter adapter{};
WGPUDevice device{};
bool gpuOwner{};