| #ifdef RIVE_WASM_MODULE | |
| #if defined(RIVE_CANVAS) && defined(RIVE_ORE) | |
| // Module-side ore objects over rive_gpu_v1 handles, the same shape | |
| // render_proxy.cpp gives the 2D surface. The binding files in lua_gpu.cpp | |
| // compile in untouched and talk to this context; factories realize against | |
| // imports namespace slice by namespace slice, the rest report through | |
| // lastError like any backend construction failure. | |
| #include "rive/renderer/ore/ore_types.hpp" | |
| #include "rive/renderer/ore/ore_buffer.hpp" | |
| #include "rive/renderer/ore/ore_texture.hpp" | |
| #include "rive/renderer/ore/ore_sampler.hpp" | |
| #include "rive/renderer/ore/ore_shader_module.hpp" | |
| #include "rive/renderer/ore/ore_bind_group_layout.hpp" | |
| #include "rive/renderer/ore/ore_bind_group.hpp" | |
| #include "rive/renderer/ore/ore_pipeline.hpp" | |
| #include "rive/renderer/ore/ore_render_pass.hpp" | |
| #include "rive/renderer/render_canvas.hpp" | |
| #include "rive/renderer/render_target.hpp" | |
| #include "rive/wasm/module_render.hpp" | |
| #include "rive/renderer/ore/ore_context.hpp" | |
| #include "rive/wasm/rive_bindings_v1.h" | |
| #include <algorithm> | |
| #include <cstring> | |
| #include <vector> | |
| namespace rive | |
| { | |
| namespace | |
| { | |
| class ModuleOreBuffer : public ore::Buffer | |
| { | |
| public: | |
| ModuleOreBuffer(uint32_t handle, uint32_t size, ore::BufferUsage usage) : | |
| ore::Buffer(size, usage), m_handle(handle) | |
| {} | |
| ~ModuleOreBuffer() override { rive_gpu_buffer_release(m_handle); } | |
| uint32_t handle() const { return m_handle; } | |
| void update(const void* data, uint32_t size, uint32_t offset) override | |
| { | |
| rive_gpu_buffer_update(m_handle, | |
| offset, | |
| static_cast<const uint8_t*>(data), | |
| size); | |
| } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreTexture : public ore::Texture | |
| { | |
| public: | |
| ModuleOreTexture(uint32_t handle, const ore::TextureDesc& desc) : | |
| ore::Texture(desc), m_handle(handle) | |
| {} | |
| // Handle 0 is the canvas metadata wrapper, which owns nothing host side. | |
| ~ModuleOreTexture() override | |
| { | |
| if (m_handle != 0) | |
| { | |
| rive_gpu_texture_release(m_handle); | |
| } | |
| } | |
| uint32_t handle() const { return m_handle; } | |
| void upload(const ore::TextureDataDesc& data) override | |
| { | |
| rive_gpu_texture_upload_v1 region; | |
| region.bytesPerRow = data.bytesPerRow; | |
| region.rowsPerImage = data.rowsPerImage; | |
| region.mipLevel = data.mipLevel; | |
| region.layer = data.layer; | |
| region.x = data.x; | |
| region.y = data.y; | |
| region.z = data.z; | |
| region.width = data.width; | |
| region.height = data.height; | |
| region.depth = data.depth; | |
| uint32_t byteCount = | |
| data.bytesPerRow * data.rowsPerImage * std::max(1u, data.depth); | |
| rive_gpu_texture_upload(m_handle, | |
| ®ion, | |
| sizeof(region), | |
| static_cast<const uint8_t*>(data.data), | |
| byteCount); | |
| } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreSampler : public ore::Sampler | |
| { | |
| public: | |
| ModuleOreSampler(uint32_t handle) : m_handle(handle) {} | |
| ~ModuleOreSampler() override { rive_gpu_sampler_release(m_handle); } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreTextureView : public ore::TextureView | |
| { | |
| public: | |
| ModuleOreTextureView(uint32_t handle, | |
| rcp<ore::Texture> texture, | |
| const ore::TextureViewDesc& desc) : | |
| ore::TextureView(std::move(texture), desc), m_handle(handle) | |
| {} | |
| ~ModuleOreTextureView() override | |
| { | |
| rive_gpu_texture_view_release(m_handle); | |
| } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreShaderModule : public ore::ShaderModule | |
| { | |
| public: | |
| ModuleOreShaderModule(uint32_t handle, const ore::ShaderModuleDesc& desc) : | |
| m_handle(handle) | |
| { | |
| // Parses the binding-map sidecar into m_bindingMap so module-side | |
| // layout derivation walks the same data every backend does. | |
| applyBindingMapFromDesc(desc); | |
| } | |
| ~ModuleOreShaderModule() override | |
| { | |
| rive_gpu_shader_module_release(m_handle); | |
| } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreBindGroupLayout : public ore::BindGroupLayout | |
| { | |
| public: | |
| ModuleOreBindGroupLayout(uint32_t handle, | |
| const ore::BindGroupLayoutDesc& desc) : | |
| m_handle(handle) | |
| { | |
| m_groupIndex = desc.groupIndex; | |
| m_entries.assign(desc.entries, desc.entries + desc.entryCount); | |
| } | |
| ~ModuleOreBindGroupLayout() override | |
| { | |
| rive_gpu_bind_group_layout_release(m_handle); | |
| } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreBindGroup : public ore::BindGroup | |
| { | |
| public: | |
| ModuleOreBindGroup(uint32_t handle, const ore::BindGroupDesc& desc) : | |
| m_handle(handle) | |
| { | |
| m_layoutRef = ref_rcp(desc.layout); | |
| for (uint32_t i = 0; i < desc.uboCount; i++) | |
| { | |
| m_retainedBuffers.push_back(ref_rcp(desc.ubos[i].buffer)); | |
| if (m_layoutRef->hasDynamicOffset(desc.ubos[i].slot)) | |
| { | |
| m_dynamicOffsetCount++; | |
| } | |
| } | |
| for (uint32_t i = 0; i < desc.textureCount; i++) | |
| { | |
| m_retainedViews.push_back(ref_rcp(desc.textures[i].view)); | |
| } | |
| for (uint32_t i = 0; i < desc.samplerCount; i++) | |
| { | |
| m_retainedSamplers.push_back(ref_rcp(desc.samplers[i].sampler)); | |
| } | |
| } | |
| ~ModuleOreBindGroup() override { rive_gpu_bind_group_release(m_handle); } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOrePipeline : public ore::Pipeline | |
| { | |
| public: | |
| ModuleOrePipeline(uint32_t handle, const ore::PipelineDesc& desc) : | |
| ore::Pipeline(desc), | |
| m_handle(handle), | |
| m_vertexEntry(desc.vertexEntryPoint), | |
| m_fragmentEntry(desc.fragmentEntryPoint) | |
| { | |
| // The base copies the caller's entry point pointers; repoint them at | |
| // owned storage so desc() stays valid past construction. | |
| m_desc.vertexEntryPoint = m_vertexEntry.c_str(); | |
| m_desc.fragmentEntryPoint = m_fragmentEntry.c_str(); | |
| } | |
| ~ModuleOrePipeline() override { rive_gpu_pipeline_release(m_handle); } | |
| uint32_t handle() const { return m_handle; } | |
| private: | |
| uint32_t m_handle; | |
| std::string m_vertexEntry; | |
| std::string m_fragmentEntry; | |
| }; | |
| // Mirrors RenderPassRecording: the base validators run module side so | |
| // lastError parity holds, then every command forwards over the wire. | |
| class ModuleOreRenderPass : public ore::RenderPass | |
| { | |
| public: | |
| ModuleOreRenderPass(ore::Context* context, | |
| uint32_t handle, | |
| const ore::RenderPassDesc& desc) : | |
| ore::RenderPass(context), m_handle(handle) | |
| { | |
| populateAttachmentMetadata(desc); | |
| } | |
| ~ModuleOreRenderPass() override { rive_gpu_pass_release(m_handle); } | |
| uint32_t handle() const { return m_handle; } | |
| void setPipeline(ore::Pipeline* pipeline) override | |
| { | |
| if (!checkPipelineCompat(pipeline)) | |
| { | |
| return; | |
| } | |
| rive_gpu_pass_set_pipeline( | |
| m_handle, | |
| static_cast<ModuleOrePipeline*>(pipeline)->handle()); | |
| } | |
| void setVertexBuffer(uint32_t slot, | |
| ore::Buffer* buffer, | |
| uint32_t offset) override | |
| { | |
| rive_gpu_pass_set_vertex_buffer( | |
| m_handle, | |
| slot, | |
| static_cast<ModuleOreBuffer*>(buffer)->handle(), | |
| offset); | |
| } | |
| void setIndexBuffer(ore::Buffer* buffer, | |
| ore::IndexFormat format, | |
| uint32_t offset) override | |
| { | |
| rive_gpu_pass_set_index_buffer( | |
| m_handle, | |
| static_cast<ModuleOreBuffer*>(buffer)->handle(), | |
| (uint32_t)format, | |
| offset); | |
| } | |
| void setBindGroup(uint32_t groupIndex, | |
| ore::BindGroup* bg, | |
| const uint32_t* dynamicOffsets, | |
| uint32_t dynamicOffsetCount) override | |
| { | |
| if (groupIndex < ore::kMaxBindGroups) | |
| { | |
| m_boundGroups[groupIndex] = ref_rcp(bg); | |
| } | |
| rive_gpu_pass_set_bind_group( | |
| m_handle, | |
| groupIndex, | |
| static_cast<ModuleOreBindGroup*>(bg)->handle(), | |
| dynamicOffsets, | |
| dynamicOffsetCount * (uint32_t)sizeof(uint32_t)); | |
| } | |
| void setViewport(float x, | |
| float y, | |
| float width, | |
| float height, | |
| float minDepth, | |
| float maxDepth) override | |
| { | |
| rive_gpu_pass_set_viewport(m_handle, | |
| x, | |
| y, | |
| width, | |
| height, | |
| minDepth, | |
| maxDepth); | |
| } | |
| void setScissorRect(uint32_t x, | |
| uint32_t y, | |
| uint32_t width, | |
| uint32_t height) override | |
| { | |
| rive_gpu_pass_set_scissor(m_handle, x, y, width, height); | |
| } | |
| void setStencilReference(uint32_t ref) override | |
| { | |
| rive_gpu_pass_set_stencil_reference(m_handle, ref); | |
| } | |
| void setBlendColor(float r, float g, float b, float a) override | |
| { | |
| rive_gpu_pass_set_blend_color(m_handle, r, g, b, a); | |
| } | |
| void draw(uint32_t vertexCount, | |
| uint32_t instanceCount, | |
| uint32_t firstVertex, | |
| uint32_t firstInstance) override | |
| { | |
| rive_gpu_pass_draw(m_handle, | |
| vertexCount, | |
| instanceCount, | |
| firstVertex, | |
| firstInstance); | |
| } | |
| void drawIndexed(uint32_t indexCount, | |
| uint32_t instanceCount, | |
| uint32_t firstIndex, | |
| int32_t baseVertex, | |
| uint32_t firstInstance) override | |
| { | |
| rive_gpu_pass_draw_indexed(m_handle, | |
| indexCount, | |
| instanceCount, | |
| firstIndex, | |
| baseVertex, | |
| firstInstance); | |
| } | |
| void finish() override | |
| { | |
| if (m_finished) | |
| { | |
| return; | |
| } | |
| rive_gpu_pass_finish(m_handle); | |
| m_finished = true; | |
| for (uint32_t i = 0; i < ore::kMaxBindGroups; ++i) | |
| { | |
| m_boundGroups[i] = nullptr; | |
| } | |
| } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| class ModuleOreContext : public ore::Context | |
| { | |
| public: | |
| ModuleOreContext() : ore::Context(nullptr) {} | |
| rcp<ore::Buffer> makeBuffer(const ore::BufferDesc& desc) override | |
| { | |
| uint32_t handle = | |
| rive_gpu_buffer_new((uint32_t)desc.usage, | |
| desc.size, | |
| desc.immutable ? 1 : 0, | |
| static_cast<const uint8_t*>(desc.data), | |
| desc.data != nullptr ? desc.size : 0); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected buffer"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreBuffer>(handle, desc.size, desc.usage); | |
| } | |
| rcp<ore::Texture> makeTexture(const ore::TextureDesc& desc) override | |
| { | |
| rive_gpu_texture_desc_v1 pod; | |
| pod.width = desc.width; | |
| pod.height = desc.height; | |
| pod.depthOrArrayLayers = desc.depthOrArrayLayers; | |
| pod.format = (uint32_t)desc.format; | |
| pod.textureType = (uint32_t)desc.type; | |
| pod.renderTarget = desc.renderTarget ? 1 : 0; | |
| pod.numMipmaps = desc.numMipmaps; | |
| pod.sampleCount = desc.sampleCount; | |
| uint32_t handle = rive_gpu_texture_new(&pod, sizeof(pod)); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected texture"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreTexture>(handle, desc); | |
| } | |
| rcp<ore::TextureView> makeTextureView( | |
| const ore::TextureViewDesc& desc) override | |
| { | |
| auto* texture = static_cast<ModuleOreTexture*>(desc.texture); | |
| if (texture == nullptr) | |
| { | |
| setLastError("texture view requires a texture"); | |
| return nullptr; | |
| } | |
| rive_gpu_texture_view_desc_v1 pod; | |
| pod.dimension = (uint32_t)desc.dimension; | |
| pod.aspect = (uint32_t)desc.aspect; | |
| pod.baseMipLevel = desc.baseMipLevel; | |
| pod.mipCount = desc.mipCount; | |
| pod.baseLayer = desc.baseLayer; | |
| pod.layerCount = desc.layerCount; | |
| uint32_t handle = | |
| rive_gpu_texture_view_new(texture->handle(), &pod, sizeof(pod)); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected texture view"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreTextureView>(handle, | |
| ref_rcp<ore::Texture>(texture), | |
| desc); | |
| } | |
| rcp<ore::Sampler> makeSampler(const ore::SamplerDesc& desc) override | |
| { | |
| rive_gpu_sampler_desc_v1 pod; | |
| pod.minFilter = (uint32_t)desc.minFilter; | |
| pod.magFilter = (uint32_t)desc.magFilter; | |
| pod.mipmapFilter = (uint32_t)desc.mipmapFilter; | |
| pod.wrapU = (uint32_t)desc.wrapU; | |
| pod.wrapV = (uint32_t)desc.wrapV; | |
| pod.wrapW = (uint32_t)desc.wrapW; | |
| pod.compare = (uint32_t)desc.compare; | |
| pod.minLod = desc.minLod; | |
| pod.maxLod = desc.maxLod; | |
| pod.maxAnisotropy = desc.maxAnisotropy; | |
| uint32_t handle = rive_gpu_sampler_new(&pod, sizeof(pod)); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected sampler"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreSampler>(handle); | |
| } | |
| rcp<ore::ShaderModule> makeShaderModule( | |
| const ore::ShaderModuleDesc& desc) override | |
| { | |
| rive_gpu_shader_module_desc_v1 pod; | |
| pod.language = (uint32_t)desc.language; | |
| pod.stage = (uint32_t)desc.stage; | |
| pod.codeSize = desc.codeSize; | |
| pod.hlslSourceSize = desc.hlslSourceSize; | |
| pod.hlslEntryPointSize = desc.hlslEntryPoint != nullptr | |
| ? (uint32_t)strlen(desc.hlslEntryPoint) | |
| : 0; | |
| pod.bindingMapSize = desc.bindingMapSize; | |
| pod.glFixupSize = desc.glFixupSize; | |
| pod.shaderAssetId = desc.shaderAssetId; | |
| std::vector<uint8_t> blob; | |
| blob.reserve(pod.codeSize + pod.hlslSourceSize + | |
| pod.hlslEntryPointSize + pod.bindingMapSize + | |
| pod.glFixupSize); | |
| auto append = [&blob](const void* data, uint32_t size) { | |
| if (size != 0) | |
| { | |
| const uint8_t* bytes = static_cast<const uint8_t*>(data); | |
| blob.insert(blob.end(), bytes, bytes + size); | |
| } | |
| }; | |
| append(desc.code, pod.codeSize); | |
| append(desc.hlslSource, pod.hlslSourceSize); | |
| append(desc.hlslEntryPoint, pod.hlslEntryPointSize); | |
| append(desc.bindingMapBytes, pod.bindingMapSize); | |
| append(desc.glFixupBytes, pod.glFixupSize); | |
| uint32_t handle = rive_gpu_shader_module_new(&pod, | |
| sizeof(pod), | |
| blob.data(), | |
| (uint32_t)blob.size()); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected shader module"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreShaderModule>(handle, desc); | |
| } | |
| rcp<ore::BindGroupLayout> makeBindGroupLayout( | |
| const ore::BindGroupLayoutDesc& desc) override | |
| { | |
| std::vector<rive_gpu_bind_group_layout_entry_v1> pods(desc.entryCount); | |
| for (uint32_t i = 0; i < desc.entryCount; i++) | |
| { | |
| auto& pod = pods[i]; | |
| const auto& entry = desc.entries[i]; | |
| pod.binding = entry.binding; | |
| pod.kind = (uint32_t)entry.kind; | |
| pod.visibility = entry.visibility.mask; | |
| pod.hasDynamicOffset = entry.hasDynamicOffset ? 1 : 0; | |
| pod.textureViewDim = (uint32_t)entry.textureViewDim; | |
| pod.textureSampleType = (uint32_t)entry.textureSampleType; | |
| pod.textureMultisampled = entry.textureMultisampled ? 1 : 0; | |
| pod.minBindingSize = entry.minBindingSize; | |
| pod.nativeSlotVS = entry.nativeSlotVS; | |
| pod.nativeSlotFS = entry.nativeSlotFS; | |
| pod.nativeSlotCS = entry.nativeSlotCS; | |
| } | |
| uint32_t handle = rive_gpu_bind_group_layout_new( | |
| desc.groupIndex, | |
| pods.data(), | |
| (uint32_t)(pods.size() * sizeof(pods[0]))); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected bind group layout"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreBindGroupLayout>(handle, desc); | |
| } | |
| rcp<ore::Pipeline> makePipeline(const ore::PipelineDesc& desc, | |
| std::string* outError) override | |
| { | |
| rive_gpu_pipeline_desc_v1 pod = {}; | |
| auto moduleHandle = [](ore::ShaderModule* module) -> uint32_t { | |
| return module != nullptr | |
| ? static_cast<ModuleOreShaderModule*>(module)->handle() | |
| : 0; | |
| }; | |
| pod.vertexModule = moduleHandle(desc.vertexModule); | |
| pod.fragmentModule = moduleHandle(desc.fragmentModule); | |
| pod.vertexEntrySize = desc.vertexEntryPoint != nullptr | |
| ? (uint32_t)strlen(desc.vertexEntryPoint) | |
| : 0; | |
| pod.fragmentEntrySize = desc.fragmentEntryPoint != nullptr | |
| ? (uint32_t)strlen(desc.fragmentEntryPoint) | |
| : 0; | |
| pod.colorCount = desc.colorCount; | |
| pod.vertexBufferCount = desc.vertexBufferCount; | |
| uint32_t attributeCount = 0; | |
| for (uint32_t i = 0; i < desc.vertexBufferCount; i++) | |
| { | |
| attributeCount += desc.vertexBuffers[i].attributeCount; | |
| } | |
| pod.attributeCount = attributeCount; | |
| pod.bindGroupLayoutCount = desc.bindGroupLayoutCount; | |
| pod.topology = (uint32_t)desc.topology; | |
| pod.indexFormat = (uint32_t)desc.indexFormat; | |
| pod.cullMode = (uint32_t)desc.cullMode; | |
| pod.winding = (uint32_t)desc.winding; | |
| pod.depthFormat = (uint32_t)desc.depthStencil.format; | |
| pod.depthCompare = (uint32_t)desc.depthStencil.depthCompare; | |
| pod.depthWriteEnabled = desc.depthStencil.depthWriteEnabled ? 1 : 0; | |
| pod.depthBias = (uint32_t)desc.depthStencil.depthBias; | |
| pod.depthBiasSlopeScale = desc.depthStencil.depthBiasSlopeScale; | |
| pod.depthBiasClamp = desc.depthStencil.depthBiasClamp; | |
| pod.stencilFrontCompare = (uint32_t)desc.stencilFront.compare; | |
| pod.stencilFrontFailOp = (uint32_t)desc.stencilFront.failOp; | |
| pod.stencilFrontDepthFailOp = (uint32_t)desc.stencilFront.depthFailOp; | |
| pod.stencilFrontPassOp = (uint32_t)desc.stencilFront.passOp; | |
| pod.stencilBackCompare = (uint32_t)desc.stencilBack.compare; | |
| pod.stencilBackFailOp = (uint32_t)desc.stencilBack.failOp; | |
| pod.stencilBackDepthFailOp = (uint32_t)desc.stencilBack.depthFailOp; | |
| pod.stencilBackPassOp = (uint32_t)desc.stencilBack.passOp; | |
| pod.stencilReadMask = desc.stencilReadMask; | |
| pod.stencilWriteMask = desc.stencilWriteMask; | |
| pod.sampleCount = desc.sampleCount; | |
| std::vector<uint8_t> blob; | |
| auto append = [&blob](const void* data, size_t size) { | |
| if (size != 0) | |
| { | |
| const uint8_t* bytes = static_cast<const uint8_t*>(data); | |
| blob.insert(blob.end(), bytes, bytes + size); | |
| } | |
| }; | |
| append(desc.vertexEntryPoint, pod.vertexEntrySize); | |
| append(desc.fragmentEntryPoint, pod.fragmentEntrySize); | |
| for (uint32_t i = 0; i < desc.colorCount; i++) | |
| { | |
| rive_gpu_color_target_v1 target; | |
| target.format = (uint32_t)desc.colorTargets[i].format; | |
| target.blendEnabled = desc.colorTargets[i].blendEnabled ? 1 : 0; | |
| target.srcColor = (uint32_t)desc.colorTargets[i].blend.srcColor; | |
| target.dstColor = (uint32_t)desc.colorTargets[i].blend.dstColor; | |
| target.colorOp = (uint32_t)desc.colorTargets[i].blend.colorOp; | |
| target.srcAlpha = (uint32_t)desc.colorTargets[i].blend.srcAlpha; | |
| target.dstAlpha = (uint32_t)desc.colorTargets[i].blend.dstAlpha; | |
| target.alphaOp = (uint32_t)desc.colorTargets[i].blend.alphaOp; | |
| target.writeMask = (uint32_t)desc.colorTargets[i].writeMask; | |
| append(&target, sizeof(target)); | |
| } | |
| for (uint32_t i = 0; i < desc.vertexBufferCount; i++) | |
| { | |
| rive_gpu_vertex_buffer_layout_v1 layout; | |
| layout.stride = desc.vertexBuffers[i].stride; | |
| layout.stepMode = (uint32_t)desc.vertexBuffers[i].stepMode; | |
| layout.attributeCount = desc.vertexBuffers[i].attributeCount; | |
| append(&layout, sizeof(layout)); | |
| } | |
| for (uint32_t i = 0; i < desc.vertexBufferCount; i++) | |
| { | |
| for (uint32_t a = 0; a < desc.vertexBuffers[i].attributeCount; a++) | |
| { | |
| rive_gpu_vertex_attribute_v1 attribute; | |
| attribute.format = | |
| (uint32_t)desc.vertexBuffers[i].attributes[a].format; | |
| attribute.offset = desc.vertexBuffers[i].attributes[a].offset; | |
| attribute.shaderSlot = | |
| desc.vertexBuffers[i].attributes[a].shaderSlot; | |
| append(&attribute, sizeof(attribute)); | |
| } | |
| } | |
| for (uint32_t i = 0; i < desc.bindGroupLayoutCount; i++) | |
| { | |
| uint32_t handle = desc.bindGroupLayouts[i] != nullptr | |
| ? static_cast<ModuleOreBindGroupLayout*>( | |
| desc.bindGroupLayouts[i]) | |
| ->handle() | |
| : 0; | |
| append(&handle, sizeof(handle)); | |
| } | |
| uint32_t handle = rive_gpu_pipeline_new(&pod, | |
| sizeof(pod), | |
| blob.data(), | |
| (uint32_t)blob.size()); | |
| if (handle == 0) | |
| { | |
| if (outError != nullptr) | |
| { | |
| *outError = "host rejected pipeline"; | |
| } | |
| setLastError("host rejected pipeline"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOrePipeline>(handle, desc); | |
| } | |
| rcp<ore::BindGroup> makeBindGroup(const ore::BindGroupDesc& desc) override | |
| { | |
| auto* layout = static_cast<ModuleOreBindGroupLayout*>(desc.layout); | |
| if (layout == nullptr) | |
| { | |
| setLastError("bind group requires a layout"); | |
| return nullptr; | |
| } | |
| std::vector<rive_gpu_bind_group_ubo_v1> ubos(desc.uboCount); | |
| for (uint32_t i = 0; i < desc.uboCount; i++) | |
| { | |
| ubos[i].slot = desc.ubos[i].slot; | |
| ubos[i].buffer = | |
| static_cast<ModuleOreBuffer*>(desc.ubos[i].buffer)->handle(); | |
| ubos[i].offset = desc.ubos[i].offset; | |
| ubos[i].size = desc.ubos[i].size; | |
| } | |
| std::vector<rive_gpu_bind_group_texture_v1> textures(desc.textureCount); | |
| for (uint32_t i = 0; i < desc.textureCount; i++) | |
| { | |
| textures[i].slot = desc.textures[i].slot; | |
| textures[i].view = | |
| static_cast<ModuleOreTextureView*>(desc.textures[i].view) | |
| ->handle(); | |
| } | |
| std::vector<rive_gpu_bind_group_sampler_v1> samplers(desc.samplerCount); | |
| for (uint32_t i = 0; i < desc.samplerCount; i++) | |
| { | |
| samplers[i].slot = desc.samplers[i].slot; | |
| samplers[i].sampler = | |
| static_cast<ModuleOreSampler*>(desc.samplers[i].sampler) | |
| ->handle(); | |
| } | |
| uint32_t handle = rive_gpu_bind_group_new( | |
| layout->handle(), | |
| ubos.data(), | |
| (uint32_t)(ubos.size() * sizeof(rive_gpu_bind_group_ubo_v1)), | |
| textures.data(), | |
| (uint32_t)(textures.size() * | |
| sizeof(rive_gpu_bind_group_texture_v1)), | |
| samplers.data(), | |
| (uint32_t)(samplers.size() * | |
| sizeof(rive_gpu_bind_group_sampler_v1))); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected bind group"); | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreBindGroup>(handle, desc); | |
| } | |
| std::unique_ptr<ore::RenderPass> beginRenderPass( | |
| const ore::RenderPassDesc& desc, | |
| std::string* outError) override | |
| { | |
| auto viewHandle = [](ore::TextureView* view) -> uint32_t { | |
| return view != nullptr | |
| ? static_cast<ModuleOreTextureView*>(view)->handle() | |
| : 0; | |
| }; | |
| rive_gpu_pass_desc_v1 pod = {}; | |
| pod.colorCount = desc.colorCount; | |
| pod.depthView = viewHandle(desc.depthStencil.view); | |
| pod.depthLoadOp = (uint32_t)desc.depthStencil.depthLoadOp; | |
| pod.depthStoreOp = (uint32_t)desc.depthStencil.depthStoreOp; | |
| pod.depthClearValue = desc.depthStencil.depthClearValue; | |
| pod.stencilLoadOp = (uint32_t)desc.depthStencil.stencilLoadOp; | |
| pod.stencilStoreOp = (uint32_t)desc.depthStencil.stencilStoreOp; | |
| pod.stencilClearValue = desc.depthStencil.stencilClearValue; | |
| rive_gpu_pass_color_attachment_v1 colors[4] = {}; | |
| for (uint32_t i = 0; i < desc.colorCount && i < 4; i++) | |
| { | |
| colors[i].view = viewHandle(desc.colorAttachments[i].view); | |
| colors[i].resolveTarget = | |
| viewHandle(desc.colorAttachments[i].resolveTarget); | |
| colors[i].loadOp = (uint32_t)desc.colorAttachments[i].loadOp; | |
| colors[i].storeOp = (uint32_t)desc.colorAttachments[i].storeOp; | |
| colors[i].clearR = desc.colorAttachments[i].clearColor.r; | |
| colors[i].clearG = desc.colorAttachments[i].clearColor.g; | |
| colors[i].clearB = desc.colorAttachments[i].clearColor.b; | |
| colors[i].clearA = desc.colorAttachments[i].clearColor.a; | |
| } | |
| uint32_t handle = | |
| rive_gpu_pass_begin(&pod, | |
| sizeof(pod), | |
| colors, | |
| desc.colorCount * (uint32_t)sizeof(colors[0])); | |
| if (handle == 0) | |
| { | |
| if (outError != nullptr) | |
| { | |
| *outError = "host rejected render pass"; | |
| } | |
| setLastError("host rejected render pass"); | |
| return nullptr; | |
| } | |
| return std::make_unique<ModuleOreRenderPass>(this, handle, desc); | |
| } | |
| rcp<ore::TextureView> wrapCanvasTexture(gpu::RenderCanvas*) override | |
| { | |
| return notPortedYet<ore::TextureView>("canvas wraps"); | |
| } | |
| rcp<ore::TextureView> wrapRiveTexture(gpu::Texture*, | |
| uint32_t, | |
| uint32_t) override | |
| { | |
| return notPortedYet<ore::TextureView>("image wraps"); | |
| } | |
| // Module images reach the host by handle; the host reruns the same | |
| // deferred-vs-canvas dispatch the Luau binding does. | |
| rcp<ore::TextureView> recordWrapCanvasImage(RenderImage* image, | |
| uint32_t width, | |
| uint32_t height) override | |
| { | |
| uint32_t handle = | |
| rive_gpu_image_view(wasmModuleImageHandle(image), width, height); | |
| if (handle == 0) | |
| { | |
| setLastError("host rejected image view"); | |
| return nullptr; | |
| } | |
| ore::TextureDesc textureDesc; | |
| textureDesc.width = width; | |
| textureDesc.height = height; | |
| ore::TextureViewDesc viewDesc; | |
| auto texture = make_rcp<ModuleOreTexture>(0, textureDesc); | |
| viewDesc.texture = texture.get(); | |
| return make_rcp<ModuleOreTextureView>(handle, | |
| std::move(texture), | |
| viewDesc); | |
| } | |
| void beginFrame(const FrameDescriptor&) override {} | |
| void endFrame() override {} | |
| void waitForGPU() override {} | |
| // Mirrors the host answer so RSTB entry selection matches the replay | |
| // backend. | |
| ore::ShaderTarget shaderTarget() const override | |
| { | |
| if (!m_shaderTargetKnown) | |
| { | |
| m_shaderTarget = (ore::ShaderTarget)rive_gpu_shader_target(); | |
| m_shaderTargetKnown = true; | |
| } | |
| return m_shaderTarget; | |
| } | |
| // Same contract as a recording context with no replay device bound: | |
| // capability gates cannot be decided module side, so they let the call | |
| // through and the real backend stays the authority. | |
| bool featuresKnown() const override { return false; } | |
| // The host context this proxies is the deferred recorder; binding paths | |
| // that must not touch a driver gate on this. | |
| bool isRecording() const override { return true; } | |
| private: | |
| template <typename T> rcp<T> notPortedYet(const char* what) | |
| { | |
| setLastError("%s are not available module side yet", what); | |
| return nullptr; | |
| } | |
| mutable ore::ShaderTarget m_shaderTarget = ore::ShaderTarget::wgsl; | |
| mutable bool m_shaderTargetKnown = false; | |
| }; | |
| } // namespace | |
| namespace | |
| { | |
| // Owns the host canvas handle: RenderCanvas has no virtual destructor, so | |
| // release rides the target it retains instead. | |
| class ModuleRenderTarget : public gpu::RenderTarget | |
| { | |
| public: | |
| ModuleRenderTarget(uint32_t canvasHandle, uint32_t width, uint32_t height) : | |
| gpu::RenderTarget(width, height), m_handle(canvasHandle) | |
| {} | |
| ~ModuleRenderTarget() override | |
| { | |
| if (m_handle != 0) | |
| { | |
| rive_gpu_canvas_release(m_handle); | |
| } | |
| } | |
| uint32_t handle() const { return m_handle; } | |
| // Transfers canvas handle ownership to a successor target on resize. | |
| uint32_t disownHandle() | |
| { | |
| uint32_t handle = m_handle; | |
| m_handle = 0; | |
| return handle; | |
| } | |
| private: | |
| uint32_t m_handle; | |
| }; | |
| } // namespace | |
| WasmModuleCanvas wasmModuleWrapCanvas(uint32_t canvasHandle) | |
| { | |
| uint32_t props[4] = {}; | |
| uint32_t viewHandle = rive_gpu_canvas_color_view(canvasHandle, props, 4); | |
| if (viewHandle == 0) | |
| { | |
| return {}; | |
| } | |
| ore::TextureDesc textureDesc; | |
| textureDesc.width = props[0]; | |
| textureDesc.height = props[1]; | |
| textureDesc.format = (ore::TextureFormat)props[2]; | |
| textureDesc.sampleCount = props[3]; | |
| textureDesc.renderTarget = true; | |
| ore::TextureViewDesc viewDesc; | |
| // Metadata-only texture wrapper so attachment validation sees the | |
| // canvas's real format and sample count. | |
| auto texture = make_rcp<ModuleOreTexture>(0, textureDesc); | |
| viewDesc.texture = texture.get(); | |
| WasmModuleCanvas out; | |
| // Backing installs unbacked (null image texture, module semantics); | |
| // construction alone no longer carries the render target. | |
| out.canvas = make_rcp<gpu::RenderCanvas>(props[0], props[1]); | |
| out.canvas->setBacking( | |
| nullptr, | |
| make_rcp<ModuleRenderTarget>(canvasHandle, props[0], props[1])); | |
| out.colorView = make_rcp<ModuleOreTextureView>(viewHandle, | |
| std::move(texture), | |
| viewDesc); | |
| return out; | |
| } | |
| WasmModuleCanvas wasmModuleResizeCanvas(const rcp<gpu::RenderCanvas>& canvas, | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| if (canvas == nullptr) | |
| { | |
| return {}; | |
| } | |
| auto* target = static_cast<ModuleRenderTarget*>(canvas->renderTarget()); | |
| uint32_t props[4] = {}; | |
| uint32_t viewHandle = | |
| rive_gpu_canvas_resize(target->handle(), width, height, props, 4); | |
| if (viewHandle == 0) | |
| { | |
| return {}; | |
| } | |
| ore::TextureDesc textureDesc; | |
| textureDesc.width = props[0]; | |
| textureDesc.height = props[1]; | |
| textureDesc.format = (ore::TextureFormat)props[2]; | |
| textureDesc.sampleCount = props[3]; | |
| textureDesc.renderTarget = true; | |
| ore::TextureViewDesc viewDesc; | |
| auto texture = make_rcp<ModuleOreTexture>(0, textureDesc); | |
| viewDesc.texture = texture.get(); | |
| WasmModuleCanvas out; | |
| out.canvas = make_rcp<gpu::RenderCanvas>(props[0], props[1]); | |
| out.canvas->setBacking(nullptr, | |
| make_rcp<ModuleRenderTarget>(target->disownHandle(), | |
| props[0], | |
| props[1])); | |
| out.colorView = make_rcp<ModuleOreTextureView>(viewHandle, | |
| std::move(texture), | |
| viewDesc); | |
| return out; | |
| } | |
| uint32_t wasmModuleCanvasImageHandle(const rcp<gpu::RenderCanvas>& canvas) | |
| { | |
| if (canvas == nullptr) | |
| { | |
| return 0; | |
| } | |
| auto* target = static_cast<ModuleRenderTarget*>(canvas->renderTarget()); | |
| return rive_gpu_canvas_image(target->handle()); | |
| } | |
| ore::Context* wasmModuleOreContext() | |
| { | |
| static ModuleOreContext context; | |
| return &context; | |
| } | |
| } // namespace rive | |
| #endif | |
| #endif |