| #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 <string> | |
| #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 uploadImpl(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); | |
| recordDynamicRanges(desc); | |
| 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; | |
| }; | |
| // A view over a host color target, described by the props the host fills: | |
| // width, height, format and sample count. The texture is metadata only, so | |
| // attachment validation sees the real format and sample count. | |
| rcp<ore::TextureView> makeModuleColorView( | |
| uint32_t handle, | |
| const uint32_t props[4]) | |
| { | |
| 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(); | |
| return make_rcp<ModuleOreTextureView>(handle, std::move(texture), viewDesc); | |
| } | |
| 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> makeTextureViewImpl( | |
| 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 ore::describedSampler(make_rcp<ModuleOreSampler>(handle), desc); | |
| } | |
| // The host builds shaders only from admitted assets, through | |
| // wasmModuleShaderModule. | |
| rcp<ore::ShaderModule> makeShaderModule(const ore::ShaderModuleDesc&) override | |
| { | |
| setLastError("host builds shader modules only from shader assets"); | |
| return nullptr; | |
| } | |
| 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; | |
| pod.samplerNonFiltering = entry.samplerNonFiltering ? 1 : 0; | |
| } | |
| 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; | |
| } | |
| if (std::string err; !ore::validateBindGroupDesc(desc, &err)) | |
| { | |
| setLastError("makeBindGroup: %s", err.c_str()); | |
| 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; | |
| } | |
| pod.labelSize = desc.label != nullptr ? (uint32_t)strlen(desc.label) : 0; | |
| const uint32_t colorBytes = std::min(desc.colorCount, 4u) * | |
| (uint32_t)sizeof(colors[0]); | |
| auto blob = reinterpret_cast<const uint8_t*>(colors); | |
| alignas(rive_gpu_pass_color_attachment_v1) | |
| uint8_t stackBlob[sizeof(colors) + 128]; | |
| std::vector<uint8_t> heapBlob; | |
| if (pod.labelSize != 0) | |
| { | |
| uint8_t* bytes = stackBlob; | |
| if (colorBytes + pod.labelSize > sizeof(stackBlob)) | |
| { | |
| heapBlob.resize(colorBytes + pod.labelSize); | |
| bytes = heapBlob.data(); | |
| } | |
| memcpy(bytes, colors, colorBytes); | |
| memcpy(bytes + colorBytes, desc.label, pod.labelSize); | |
| blob = bytes; | |
| } | |
| uint32_t handle = rive_gpu_pass_begin( | |
| &pod, | |
| sizeof(pod), | |
| blob, | |
| colorBytes + pod.labelSize); | |
| 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"); | |
| } | |
| // The host hands back the same handle until its target changes. | |
| rcp<ore::TextureView> targetView() override | |
| { | |
| uint32_t current = m_targetView != nullptr | |
| ? static_cast<ModuleOreTextureView*>( | |
| m_targetView.get()) | |
| ->handle() | |
| : 0; | |
| uint32_t props[4] = {}; | |
| uint32_t handle = rive_gpu_target_view(current, props, 4); | |
| if (handle != current) | |
| { | |
| m_targetView = handle != 0 ? makeModuleColorView(handle, props) : nullptr; | |
| } | |
| return m_targetView; | |
| } | |
| 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 | |
| // dispatch the Luau binding does, so a canvas and a foreign image both | |
| // forward as their image handle and differ only on the host side. | |
| rcp<ore::TextureView> recordWrapCanvasImage( | |
| gpu::RenderCanvas* canvas, | |
| uint32_t width, | |
| uint32_t height) override | |
| { | |
| return recordImageViewByHandle( | |
| canvas != nullptr ? canvas->renderImage() : nullptr, | |
| width, | |
| height); | |
| } | |
| rcp<ore::TextureView> recordWrapForeignImageView( | |
| RenderImage* image, | |
| uint32_t width, | |
| uint32_t height) override | |
| { | |
| return recordImageViewByHandle(image, width, height); | |
| } | |
| private: | |
| rcp<ore::TextureView> recordImageViewByHandle( | |
| RenderImage* image, | |
| uint32_t width, | |
| uint32_t height) | |
| { | |
| 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); | |
| } | |
| public: | |
| 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; | |
| rcp<ore::TextureView> m_targetView; | |
| }; | |
| } // 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 {}; | |
| } | |
| 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 = makeModuleColorView(viewHandle, props); | |
| 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 {}; | |
| } | |
| 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 = makeModuleColorView(viewHandle, props); | |
| 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()); | |
| } | |
| namespace | |
| { | |
| uint32_t g_shaderObject = 0; | |
| std::string g_shaderName; | |
| } // namespace | |
| void wasmModuleShaderSource(uint32_t object, const std::string& name) | |
| { | |
| g_shaderObject = object; | |
| g_shaderName = name; | |
| } | |
| rcp<ore::ShaderModule> wasmModuleShaderModule( | |
| const ore::ShaderModuleDesc& desc, | |
| uint32_t entry) | |
| { | |
| uint32_t handle = rive_gpu_shader_module_from_asset( | |
| g_shaderObject, | |
| g_shaderName.data(), | |
| (uint32_t)g_shaderName.size(), | |
| entry); | |
| if (handle == 0) | |
| { | |
| return nullptr; | |
| } | |
| return make_rcp<ModuleOreShaderModule>(handle, desc); | |
| } | |
| ore::Context* wasmModuleOreContext() | |
| { | |
| static ModuleOreContext context; | |
| return &context; | |
| } | |
| } // namespace rive | |
| #endif | |
| #endif |