feature (Unreal) Ore Support (#12941) eeb280d7f9
* started getting ore to build with unreal

* building with ore but not using ore

* now subclass context and stub functions

* started implementing rhi ore backend

* make work with rhi

* updated view desc to be rcp for texture

* builds in unreal

* added asset id flag

* started using new internal listener to intercept shader assetts for ore shaders

* shaders now apear to compile

* formatting

* updating to new hlslblob type

* pppipelines now potentially setup

* all ore is stbbued

* revert to master

* format

* no exceptions on load

* re enable canvas draw

* reseet back to master

* reset to master

* updated to use new ore dynamic classes

* revert

* implemented buffer upload.

* started updating to new format

* wip

* started implementing new rstb

* wip ore

* wip ore

* basics of ore seems to be working

* asset cooking started with versioned asstes

* run PIE ore nows work

* ore working in packaged build for now

* shadow maps now working. also added better logging for rhi breadcrumbs

* cleaned up logs

* ore rhi now works across d3d11-12 nad vulkan

* updated to build with master

* render targets now draw canvas

* console 1 working

* hid all console references

* builds for xbox ps5

* text

* one

* two

* now works

* proper obfuscation

* final

* platform specific files

* compiles for 5.7

* resolve shader

* metal now works

* fixed open widget in unreal editor

* comments cleanup

* debug logging

* removed dead code, fixed some comments

* re worked previous workaround condition to match new way

* missing files

* fix some compile errors

* premake changes

* more premake changes

* more premgake

* wagyu premake

* more premake

* correct primitive count

* set primitive type

* fixed pointer issue

Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
diff --git a/.rive_head b/.rive_head
index f8536ed..bbf97f1 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-e4c22c3ef7ca63dd8ee5d2e4068fae887c7521e1
+eeb280d7f906f2e47bb77c02600a301dda7e600d
diff --git a/cg_renderer/src/cg_factory.cpp b/cg_renderer/src/cg_factory.cpp
index 301eb26..8139797 100644
--- a/cg_renderer/src/cg_factory.cpp
+++ b/cg_renderer/src/cg_factory.cpp
@@ -609,7 +609,8 @@
     auto w = CGImageGetWidth(image);
     auto h = CGImageGetHeight(image);
     AutoCF space = CGColorSpaceCreateDeviceRGB();
-    auto info = kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
+    auto info = static_cast<uint>(kCGBitmapByteOrder32Big) |
+                static_cast<uint>(kCGImageAlphaPremultipliedLast);
     AutoCF ctx = CGBitmapContextCreate(nullptr, w, h, 8, 0, space, info);
     CGContextConcatCTM(ctx, CGAffineTransformMake(1, 0, 0, -1, 0, h));
     CGContextDrawImage(ctx, CGRectMake(0, 0, w, h), image);
diff --git a/include/rive/assets/shader_asset.hpp b/include/rive/assets/shader_asset.hpp
index 7f14f8a..c7e0afd 100644
--- a/include/rive/assets/shader_asset.hpp
+++ b/include/rive/assets/shader_asset.hpp
@@ -33,10 +33,10 @@
 public:
     bool decode(SimpleArray<uint8_t>& data, Factory* factory) override
     {
-        return decode(Span<uint8_t>(data.data(), data.size()), factory);
+        return decode(Span<const uint8_t>(data.data(), data.size()), factory);
     }
 
-    bool decode(Span<uint8_t> data, Factory* factory);
+    bool decode(Span<const uint8_t> data, Factory* factory);
     std::string fileExtension() const override { return "rstb"; }
 
     /// Returns the blob for the given target, or an empty span if not present.
diff --git a/renderer/premake5.lua b/renderer/premake5.lua
index 308f1f4..8de4030 100644
--- a/renderer/premake5.lua
+++ b/renderer/premake5.lua
@@ -50,6 +50,7 @@
             'rive_harfbuzz',
             'rive_sheenbidi',
             'rive_yoga',
+            'luau_vm'
         })
         filter({ 'options:not no_rive_png' })
         do
@@ -100,7 +101,7 @@
             externalincludedirs({ optick .. '/src'})
         end
 
-        if rive_target_os == 'windows' then
+        if rive_target_os == 'windows' and _OPTIONS['for_unreal'] == nil then
             externalincludedirs({
                 dx12_headers .. '/include/directx',
             })
diff --git a/renderer/premake5_pls_renderer.lua b/renderer/premake5_pls_renderer.lua
index db6ef36..ad7a6e6 100644
--- a/renderer/premake5_pls_renderer.lua
+++ b/renderer/premake5_pls_renderer.lua
@@ -28,7 +28,7 @@
     })
 end
 
-if rive_target_os == 'windows' then
+if rive_target_os == 'windows' and _OPTIONS['for_unreal'] == nil then
     dx12_headers = dependency.github('microsoft/DirectX-Headers', 'v1.615.0')
 end
 
@@ -40,6 +40,20 @@
     defines({ 'RIVE_DESKTOP_GL' })
 end
 
+filter({ 'system:windows or macosx or linux', 'options:with_rive_canvas', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_GL', 'RIVE_ORE' })
+end
+
+-- Ore backend selection — defined globally so tests and consumers also see them.
+-- Only active when --with_rive_canvas is enabled.
+-- RIVE_ORE is defined whenever any ore backend is active, so C++ code can guard
+-- ore API calls without enumerating every backend.
+filter({ 'system:macosx or ios', 'options:with_rive_canvas', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_METAL', 'RIVE_ORE' })
+end
+
 -- Enable @try/@catch in Metal ORE for graceful error handling at runtime.
 -- Not used in tools/fuzz builds where crashing on Metal exceptions is preferred.
 filter({ 'system:macosx or ios', 'options:with_objc_exceptions' })
@@ -47,16 +61,82 @@
     defines({ 'RIVE_OBJC_EXCEPTIONS=1' })
 end
 
+-- macOS also gets ORE_BACKEND_GL so ore GMs work with ANGLE (GL on desktop).
+filter({ 'system:macosx', 'options:with_rive_canvas', 'options:not no_gl', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_GL' })
+end
+
+filter({ 'system:windows', 'options:with_rive_canvas', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_D3D11', 'ORE_BACKEND_D3D12', 'RIVE_ORE' })
+end
+
+filter({'options:with_rive_canvas', 'options:for_unreal' })
+do
+    defines({ 'ORE_BACKEND_RHI', 'RIVE_ORE' })
+end
+
+-- WebGPU ORE backend (Dawn, or native/emscripten WebGPU). Defined wherever the
+-- WebGPU renderer (src/webgpu) compiles, so RenderContextWebGPUImpl's
+-- makeOreContext() can resolve ContextWGPU::Make. Coexists with the platform's
+-- native ORE backend(s) (D3D/GL/…), mirroring how the renderers coexist.
+filter({ 'options:with-webgpu or with-dawn', 'options:with_rive_canvas', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_WGPU', 'RIVE_ORE' })
+end
+
 filter('system:android')
 do
     defines({ 'RIVE_ANDROID' })
 end
 
+filter({ 'system:android', 'options:with_rive_canvas', 'options:not no_gl', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_GL', 'RIVE_ORE' })
+end
+
+-- Android + wagyu: Ore routes through the WebGPU/wagyu device instead of raw GL.
+-- Runtime dispatch inside ORE_BACKEND_WGPU selects GLSLRAW (GLES) or GLSL (Vulkan).
+filter({ 'system:android', 'options:with_rive_canvas', 'options:with_wagyu', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_WGPU', 'RIVE_ORE' })
+end
+
+-- Android + native Vulkan (no wagyu): raw Vulkan ORE backend.
+-- Takes SPIR-V directly; traditional VkRenderPass for TBDR tile memory efficiency.
+-- When GL is also enabled (the default), both backends coexist in the same binary
+-- via VK+GL dispatch files (src/ore/vulkan/ore_*_vk_gl.cpp).
+filter({ 'system:android', 'options:with_rive_canvas', 'options:not with_wagyu', 'options:with_vulkan', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_VK' })
+end
+
+-- Linux: native Vulkan ORE backend (requires --with_vulkan for headers and VMA).
+-- Also enables the ORE GL backend so the same binary can service `--backend gl`
+-- via the VK+GL dispatch TUs, parallel to the Android VK+GL configuration.
+filter({ 'system:linux', 'options:with_rive_canvas', 'options:with_vulkan', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_VK', 'ORE_BACKEND_GL', 'RIVE_ORE' })
+end
+
+-- macOS / Windows: when the Vulkan renderer is compiled (--with_vulkan), the
+-- vulkan render-context impl references the Vulkan ORE backend via makeOreContext().
+-- Enable the backend so ContextVulkan::Make is defined and ORE_BACKEND_VK-gated
+-- sections compile. Mirrors the linux/android Vulkan ORE configuration.
+filter({ 'system:macosx or windows', 'options:with_rive_canvas', 'options:with_vulkan', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_VK', 'RIVE_ORE' })
+end
+
+
 newoption({
     trigger = 'with_objc_exceptions',
     description = 'enable @try/@catch in Metal ORE (requires -fobjc-exceptions)',
 })
 
+
+
 newoption({
     trigger = 'with-dawn',
     description = 'compile in support for webgpu via dawn',
@@ -72,6 +152,18 @@
     defines({ 'RIVE_WEBGL' })
 end
 
+-- Emscripten + wagyu: Ore routes through the WebGPU/wagyu device.
+-- Runtime dispatch inside ORE_BACKEND_WGPU selects GLSLRAW (GLES) or GLSL (Vulkan).
+filter({ 'system:emscripten', 'options:with_rive_canvas', 'options:with_wagyu', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_WGPU', 'RIVE_ORE' })
+end
+
+filter({ 'system:emscripten', 'options:with_rive_canvas', 'options:not no_gl', 'options:not for_unreal' })
+do
+    defines({ 'ORE_BACKEND_GL', 'RIVE_ORE' })
+end
+
 newoption({
     trigger = 'with-webgpu',
     description = 'compile in native support for webgpu',
@@ -171,12 +263,15 @@
         makecommand = makecommand .. ' rive_pls_macosx_metallib'
     end
 end
+
 if rive_target_os == 'windows' then
     makecommand = makecommand .. ' d3d'
 end
-if _OPTIONS['with_vulkan'] then
+
+if _OPTIONS['with_vulkan'] or _OPTIONS['with-dawn'] or _OPTIONS['with-webgpu'] then
     makecommand = makecommand .. ' spirv'
 end
+
 if _OPTIONS['with-dawn'] or _OPTIONS['with-webgpu'] then
     if _OPTIONS['raw_shaders'] then
         makecommand = makecommand .. ' WGSL_FLAGS="--raw"'
@@ -219,39 +314,6 @@
     microprofile = dependency.github(RIVE_MICROPROFILE_URL, RIVE_MICROPROFILE_VERSION)
 end
 
--- Ore defines outside of project because we want them to be defined for every proeject
-filter({ 'options:with_rive_canvas' })
-do
-    defines({ 'RIVE_ORE' })
-end
-
-filter({ 'system:macosx or ios', 'options:not nop-obj-c', 'options:with_rive_canvas' })
-do
-    defines({ 'ORE_BACKEND_METAL' })
-end
-
-filter({ 'system:windows', 'options:with_rive_canvas' })
-do
-    defines({ 'ORE_BACKEND_D3D11', 'ORE_BACKEND_D3D12' })
-end
-
--- No gl for iOS
-filter({ 'system:not ios', 'options:with_rive_canvas', 'options:not no_gl' })
-do
-    defines({ 'ORE_BACKEND_GL' })
-end
-
--- No vulkan for iOS
-filter({ 'system:not ios', 'options:with_rive_canvas', 'options:with_vulkan' })
-do
-    defines({ 'ORE_BACKEND_VK' })
-end
-
-filter({ 'options:with_rive_canvas', 'options:with-dawn or with-webgpu' })
-do
-    defines({ 'ORE_BACKEND_WGPU' })
-end
-
 project('rive_pls_renderer')
 do
     kind('StaticLib')
@@ -287,7 +349,7 @@
         files({ 'src/vulkan/*.cpp' })
     end
 
-    if rive_target_os == 'windows' then
+    if rive_target_os == 'windows' and _OPTIONS['for_unreal'] == nil then
         externalincludedirs({
             dx12_headers .. '/include/directx',
         })
@@ -321,7 +383,12 @@
         })
     end
 
-    filter({ 'system:windows or macosx or linux' })
+    filter({ 'system:not ios', 'options:not no_gl', 'options:with_rive_canvas', 'options:not for_unreal' })
+    do
+        files({'src/ore/gl/*.cpp'})
+    end
+
+    filter({ 'system:windows or macosx or linux', 'options:not for_unreal' })
     do
         files({
             'src/gl/pls_impl_webgl.cpp', -- Emulate WebGL with ANGLE.
@@ -332,7 +399,7 @@
         }) -- GL loader library for ANGLE.
     end
 
-    filter('system:android')
+    filter({'system:android', 'options:not for_unreal'})
     do
         files({
             'src/gl/load_gles_extensions.cpp',
@@ -342,10 +409,26 @@
 
     filter({ 'system:macosx or ios', 'options:not nop-obj-c' })
     do
-        files({ 'src/metal/*.mm' })
         buildoptions({ '-fobjc-arc' })
     end
 
+    filter({ 'system:macosx or ios', 'options:not nop-obj-c', 'options:not for_unreal' })
+    do
+        files({ 'src/metal/*.mm' })
+    end
+
+    -- Shared cross-backend Ore sources. Compiled whenever canvas is on,
+    -- regardless of which GPU backend(s) are active.
+    filter({ 'options:with_rive_canvas' })
+    do
+        files({ 'src/ore/*.cpp' })
+    end
+
+    filter({ 'system:macosx or ios', 'options:not nop-obj-c', 'options:with_rive_canvas', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/metal/*.mm' })
+    end
+
     -- Enable -fobjc-exceptions for .mm files when @try/@catch is active.
     -- Uses a files: filter so the flag doesn't hit .cpp compilations
     -- (clang warns on -fobjc-exceptions for non-ObjC++ sources).
@@ -359,41 +442,95 @@
         buildoptions({ '-fobjc-exceptions' })
     end
 
-    filter({ 'options:with_rive_canvas' })
+    -- Enable -fobjc-exceptions for .mm files when @try/@catch is active.
+    -- Uses a files: filter so the flag doesn't hit .cpp compilations
+    -- (clang warns on -fobjc-exceptions for non-ObjC++ sources).
+    filter({
+        'system:macosx or ios',
+        'options:not nop-obj-c',
+        'options:with_objc_exceptions',
+        'files:**.mm',
+    })
     do
-        files({ 'src/ore/*.cpp', 'src/ore/*.hpp' })
+        buildoptions({ '-fobjc-exceptions' })
     end
 
-    filter({ 'system:macosx or ios', 'options:not nop-obj-c', 'options:with_rive_canvas' })
-    do
-        files({ 'src/ore/metal/*.mm', 'src/ore/metal/*.hpp' })
-    end
 
-    filter({ 'system:windows', 'options:with_rive_canvas' })
-    do
-        files({ 'src/ore/d3d11/*.cpp', 'src/ore/d3d11/*.hpp' })
-        files({ 'src/ore/d3d12/*.cpp', 'src/ore/d3d12/*.hpp' })
-    end
-
-    -- No gl for iOS
-    filter({ 'system:not ios', 'options:with_rive_canvas', 'options:not no_gl' })
-    do
-        files({ 'src/ore/gl/*.cpp', 'src/ore/gl/*.hpp' })
-    end
-
-    filter({ 'system:macosx', 'options:not nop-obj-c', 'options:with_rive_canvas', 'options:not no_gl' })
+    -- macOS also compiles the ore GL backend (via ObjC++ wrappers) so ore
+    -- works with ANGLE. Both METAL and GL backends coexist in the same binary.
+    filter({ 'system:macosx', 'options:not nop-obj-c', 'options:with_rive_canvas', 'options:not no_gl', 'options:not for_unreal' })
     do
         files({ 'src/ore/gl/*.mm' })
     end
 
-    filter({ 'options:with_rive_canvas', 'options:with_vulkan' })
+
+    filter({ 'system:windows', 'options:with_rive_canvas', 'options:not for_unreal' })
     do
-        files({ 'src/ore/vulkan/*.cpp', 'src/ore/vulkan/*.hpp'})
+        files({ 'src/ore/d3d11/*.cpp' })
+        files({ 'src/ore/d3d12/*.cpp' })
     end
 
-    filter({ 'options:with_rive_canvas', 'options:with-dawn or with-webgpu' })
+    -- WebGPU ORE backend sources. Compiled wherever the WebGPU renderer
+    -- (src/webgpu) compiles, so ContextWGPU::Make is defined. Coexists with the
+    -- platform's native ORE backend(s).
+    filter({ 'options:with-webgpu or with-dawn', 'options:with_rive_canvas', 'options:not for_unreal' })
     do
-        files({ 'src/ore/wgpu/*.cpp', 'src/ore/wgpu/*.hpp' })
+        files({ 'src/ore/wgpu/*.cpp' })
+    end
+
+
+    filter({ 'system:android', 'options:with_rive_canvas', 'options:not with_wagyu', 'options:not with_vulkan', 'options:not no_gl', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/gl/*.cpp' })
+    end
+
+    filter({ 'system:android', 'options:with_rive_canvas', 'options:with_wagyu', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/wgpu/*.cpp' })
+    end
+
+    filter({ 'system:emscripten', 'options:with_rive_canvas', 'options:with_wagyu', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/wgpu/*.cpp' })
+    end
+
+    -- Android VK: always include Vulkan backend files.
+    filter({ 'system:android', 'options:with_rive_canvas', 'options:not with_wagyu', 'options:with_vulkan', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/vulkan/*.cpp' })
+    end
+
+    -- Android VK+GL: both backends coexist. Standalone files compile to
+    -- static-helper-only objects (method bodies guarded out by the other
+    -- backend's define). VK+GL dispatch files (ore_*_vk_gl.cpp) in the
+    -- vulkan/ directory provide the actual method implementations.
+    filter({ 'system:android', 'options:with_rive_canvas', 'options:not with_wagyu', 'options:with_vulkan', 'options:not no_gl', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/gl/*.cpp' })
+    end
+
+    if _OPTIONS['with_vulkan'] then
+        filter({ 'system:linux', 'options:with_rive_canvas', 'options:not for_unreal' })
+        do
+            externalincludedirs({
+                vulkan_headers .. '/include',
+                vulkan_memory_allocator .. '/include',
+            })
+            files({ 'src/ore/vulkan/*.cpp', 'src/ore/gl/*.cpp' })
+        end
+    end
+
+    -- macOS / Windows: compile the Vulkan ORE backend alongside the Vulkan
+    -- renderer. The vulkan/VMA include dirs are already added project-wide in
+    -- the `if _OPTIONS['with_vulkan']` block above.
+    filter({ 'system:macosx or windows', 'options:with_rive_canvas', 'options:with_vulkan', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/vulkan/*.cpp' })
+    end
+
+    filter({ 'system:emscripten', 'options:with_rive_canvas', 'options:not with_wagyu', 'options:not no_gl', 'options:not for_unreal' })
+    do
+        files({ 'src/ore/gl/*.cpp' })
     end
 
     filter({ 'options:with-dawn' })
@@ -432,12 +569,6 @@
         defines({ 'RIVE_DECODERS' })
     end
 
-    -- RIVE_KTX2 must also be visible to the renderer (not just the decoders
-    -- lib) so the `#ifdef RIVE_KTX2` dispatch block in render_context.cpp
-    -- compiles in. Gate it on `not no-rive-decoders` AS WELL — without the
-    -- decoders lib linked (e.g. the wasm webgl2 build) the renderer would
-    -- compile a call to rive::DecodeKtx2 that has no definition, breaking
-    -- the link.
     filter({ 'options:not no-rive-decoders', 'options:not no_rive_ktx2' })
     do
         defines({ 'RIVE_KTX2' })
@@ -464,6 +595,10 @@
     filter('system:windows')
     do
         architecture('x64')
+    end
+
+    filter({'system:windows', 'options:not for_unreal'})
+    do
         files({ 'src/d3d/*.cpp' })
         files({ 'src/d3d11/*.cpp' })
         files({ 'src/d3d12/*.cpp' })
diff --git a/renderer/src/shaders/tessellate.glsl b/renderer/src/shaders/tessellate.glsl
index 63a838f..b923fd7 100644
--- a/renderer/src/shaders/tessellate.glsl
+++ b/renderer/src/shaders/tessellate.glsl
@@ -14,7 +14,7 @@
 
 #ifdef @VERTEX
 ATTR_BLOCK_BEGIN(Attrs)
-// RHI Version > 5.8 failes to parse this when it's multi line
+// RHI Version > 5.8 fails to parse this when it's multi line
 // End in '_' because D3D interprets the '1' as a semantic index.
 ATTR(0, float4, @a_p0p1_);
 ATTR(1, float4, @a_p2p3_);
@@ -25,9 +25,10 @@
 ATTR(5, uint, @a_segmentCounts);
 ATTR(6, uint, @a_contourIDWithFlags);
 #else
-ATTR(3,
-     uint4,
-     @a_args); // [x0x1, reflectionX0X1, segmentCounts, contourIDWithFlags]
+// [x0x1, reflectionX0X1, segmentCounts,
+// contourIDWithFlags]
+// RHI Version > 5.8 fails to parse this when it's multi line
+ATTR(3, uint4, @a_args);
 #endif
 ATTR_BLOCK_END
 #endif
diff --git a/src/assets/shader_asset.cpp b/src/assets/shader_asset.cpp
index 6330c73..1dcf5ae 100644
--- a/src/assets/shader_asset.cpp
+++ b/src/assets/shader_asset.cpp
@@ -3,7 +3,7 @@
 
 using namespace rive;
 
-bool rive::ShaderAsset::decode(Span<uint8_t> data, Factory* factory)
+bool rive::ShaderAsset::decode(Span<const uint8_t> data, Factory* factory)
 {
     // `data` is always a SignedContentHeader envelope
     // (`[flags:1][sig:64?][RSTB...]`) shared with ScriptAsset. Raw-RSTB
diff --git a/src/command_server.cpp b/src/command_server.cpp
index 2a85442..cd88272 100644
--- a/src/command_server.cpp
+++ b/src/command_server.cpp
@@ -678,6 +678,7 @@
                 m_commandQueue->m_byteVectors >> rivBytes;
                 lock.unlock();
 #ifdef WITH_RIVE_SCRIPTING
+                std::cout << "Rive: Command Server Scripting Enabled.\n";
                 auto scriptingContext =
                     std::make_unique<CPPRuntimeScriptingContext>(m_factory);
                 scriptingContext->setRenderContext(m_factory);
diff --git a/tests/common/testing_window.hpp b/tests/common/testing_window.hpp
index 7d5c58e..0b8633c 100644
--- a/tests/common/testing_window.hpp
+++ b/tests/common/testing_window.hpp
@@ -235,7 +235,7 @@
 
     virtual void hotloadShaders() {}
 
-#if defined(__APPLE__) && !defined(RIVE_UNREAL)
+#if defined(__APPLE__)
     // Returns the Metal command queue as void* to avoid <Metal/Metal.h> in
     // this cross-platform header (same pattern as externalCommandBuffer).
     virtual void* metalQueue() const { return nullptr; }
diff --git a/tests/gm/interleaved_subpasses_with_dst_blend.cpp b/tests/gm/interleaved_subpasses_with_dst_blend.cpp
index 1f471ef..70c5e97 100644
--- a/tests/gm/interleaved_subpasses_with_dst_blend.cpp
+++ b/tests/gm/interleaved_subpasses_with_dst_blend.cpp
@@ -64,11 +64,11 @@
 
     // Disable KHR_blend_equation_advanced, which is necessary for this to
     // repro.
+#ifndef RIVE_TOOLS_NO_GL
     bool hadBlendAdvancedCoherentKHR = false;
     bool hadBlendAdvancedKHR = false;
     if (renderContext != nullptr)
     {
-#ifndef RIVE_TOOLS_NO_GL
         if (gpu::RenderContextGLImpl* glImpl =
                 TestingWindow::Get()->renderContextGLImpl())
         {
@@ -79,8 +79,8 @@
                 glImpl->testingOnly_setBlendAdvancedKHRSupported(false);
             renderContext->beginFrame(preserveRenderTargetFrameDesc);
         }
-#endif
     }
+#endif
 
     {
         AutoRestore ar(renderer, true);
@@ -156,11 +156,11 @@
 
     // Disable KHR_blend_equation_advanced, which is necessary for this to
     // repro.
+#ifndef RIVE_TOOLS_NO_GL
     bool hadBlendAdvancedCoherentKHR = false;
     bool hadBlendAdvancedKHR = false;
     if (renderContext != nullptr)
     {
-#ifndef RIVE_TOOLS_NO_GL
         if (gpu::RenderContextGLImpl* glImpl =
                 TestingWindow::Get()->renderContextGLImpl())
         {
@@ -171,8 +171,8 @@
                 glImpl->testingOnly_setBlendAdvancedKHRSupported(false);
             renderContext->beginFrame(preserveRenderTargetFrameDesc);
         }
-#endif
     }
+#endif
 
     {
         AutoRestore ar(renderer, true);
diff --git a/tests/gm/ore_binding_dynamic_ubo.cpp b/tests/gm/ore_binding_dynamic_ubo.cpp
index 2f43d1b..b47220d 100644
--- a/tests/gm/ore_binding_dynamic_ubo.cpp
+++ b/tests/gm/ore_binding_dynamic_ubo.cpp
@@ -41,7 +41,8 @@
 #include "ore_gm_helper.hpp"
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) ||                   \
     defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) ||                    \
-    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
+    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12) ||                \
+    defined(ORE_BACKEND_RHI)
 #include "rive/renderer/render_canvas.hpp"
 #include "rive/renderer/ore/ore_buffer.hpp"
 #include "rive/renderer/ore/ore_bind_group.hpp"
@@ -55,13 +56,15 @@
 using namespace rive::gpu;
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) ||                   \
     defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) ||                    \
-    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
+    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12) ||                \
+    defined(ORE_BACKEND_RHI)
 using namespace rive::ore;
 #endif
 
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) ||                   \
     defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) ||                    \
-    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
+    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12) ||                \
+    defined(ORE_BACKEND_RHI)
 #define ORE_BINDING_DYNAMIC_UBO_ACTIVE
 #endif
 
@@ -69,7 +72,9 @@
 // across our backends (D3D12 needs 256, D3D11.1 needs 256 enforced by
 // firstConstant×16, Vulkan minUniformBufferOffsetAlignment is at most 256
 // on every adapter we ship to, WebGPU spec mandates 256).
+#ifdef ORE_BINDING_DYNAMIC_UBO_ACTIVE
 static constexpr uint32_t kDynamicAlign = 256;
+#endif
 
 class OreBindingDynamicUBOGM : public GM
 {
diff --git a/tests/gm/ore_gm_helper.hpp b/tests/gm/ore_gm_helper.hpp
index ea2938a..4c3ce08 100644
--- a/tests/gm/ore_gm_helper.hpp
+++ b/tests/gm/ore_gm_helper.hpp
@@ -21,7 +21,8 @@
 // Multiple backends may be active simultaneously (e.g. Metal + GL on macOS).
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) ||                \
     defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) ||                   \
-    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK)
+    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) ||                    \
+    defined(ORE_BACKEND_RHI)
 #include "rive/renderer/ore/ore_context.hpp"
 #include <memory>
 #endif
@@ -68,7 +69,8 @@
 
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) ||                \
     defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) ||                   \
-    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK)
+    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) ||                    \
+    defined(ORE_BACKEND_RHI)
 #include "ore_gm_shaders.rstb.hpp"
 #include "rive/renderer/ore/ore_rstb_entry_container.hpp"
 #include "rive/assets/shader_asset.hpp"
@@ -121,6 +123,12 @@
         return true;
     }
 #endif
+#if defined(ORE_BACKEND_RHI)
+    if (b == TestingWindow::Backend::rhi)
+    {
+        return true;
+    }
+#endif
     return false;
 }
 
@@ -135,7 +143,8 @@
 
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) ||                \
     defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) ||                   \
-    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK)
+    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) ||                    \
+    defined(ORE_BACKEND_RHI)
         if (!renderContext || !isOreBackendActive())
             return false;
 
@@ -173,6 +182,12 @@
             return true;
         }
 #endif
+#if defined(ORE_BACKEND_RHI)
+        if (b == TestingWindow::Backend::rhi)
+        {
+            return true;
+        }
+#endif
 #if defined(ORE_BACKEND_WGPU)
         if (b == TestingWindow::Backend::wgpu ||
             b == TestingWindow::Backend::dawn)
@@ -301,7 +316,8 @@
 
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_D3D11) ||                \
     defined(ORE_BACKEND_D3D12) || defined(ORE_BACKEND_GL) ||                   \
-    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK)
+    defined(ORE_BACKEND_WGPU) || defined(ORE_BACKEND_VK) ||                    \
+    defined(ORE_BACKEND_RHI)
 
 enum OreGMShader : uint32_t
 {
@@ -373,6 +389,10 @@
     if (b == TestingWindow::Backend::d3d12)
         return 3; // HLSL SM5
 #endif
+#if defined(ORE_BACKEND_RHI)
+    if (b == TestingWindow::Backend::rhi)
+        return 3; // HLSL SM5
+#endif
 #if defined(ORE_BACKEND_WGPU)
     if (b == TestingWindow::Backend::wgpu || b == TestingWindow::Backend::dawn)
         return 0; // WGSL
diff --git a/tests/gm/ore_image_view.cpp b/tests/gm/ore_image_view.cpp
index ef7e57c..1279642 100644
--- a/tests/gm/ore_image_view.cpp
+++ b/tests/gm/ore_image_view.cpp
@@ -33,7 +33,8 @@
 using namespace rive::gpu;
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) ||                   \
     defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) ||                    \
-    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
+    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12) ||                \
+    defined(ORE_BACKEND_RHI)
 using namespace rive::ore;
 #endif
 
@@ -56,7 +57,8 @@
 
 #if defined(ORE_BACKEND_METAL) || defined(ORE_BACKEND_GL) ||                   \
     defined(ORE_BACKEND_VK) || defined(ORE_BACKEND_WGPU) ||                    \
-    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12)
+    defined(ORE_BACKEND_D3D11) || defined(ORE_BACKEND_D3D12) ||                \
+    defined(ORE_BACKEND_RHI)
 #define ORE_IMAGE_VIEW_ACTIVE
 #endif
 
diff --git a/tests/rive_tools_project.lua b/tests/rive_tools_project.lua
index 8f999e8..7ffd9d5 100644
--- a/tests/rive_tools_project.lua
+++ b/tests/rive_tools_project.lua
@@ -160,7 +160,7 @@
             '_WINSOCK_DEPRECATED_NO_WARNINGS',
             'UNICODE',
         })
-        if rive_target_os == 'windows' then
+        if rive_target_os == 'windows' and _OPTIONS['for_unreal'] == nil then
             externalincludedirs({
                 dx12_headers .. '/include/directx',
             })
@@ -351,6 +351,11 @@
         links({ 'skia', 'rive_skia_renderer' })
     end
 
+    filter({ 'options:for_unreal' })
+    do
+        defines({ 'RIVE_UNREAL', 'RIVE_TOOLS_NO_GLFW', 'RIVE_TOOLS_NO_GL' })
+    end
+
     filter('system:emscripten')
     do
         targetextension('.js')
@@ -422,22 +427,22 @@
         RIVE_PLS_DIR .. '/path_fiddle/fiddle_context_dawn.cpp',
     })
 
+    filter({'options:for_unreal'})
+    do
+        removefiles({'common/offscreen_render_target_*', 'common/offscreen_rendertarget_*',})
+    end
+
     filter({ 'options:not no_tools_shader_hotloading' })
     do
      files({ RIVE_PLS_DIR .. '/shader_hotload/**.cpp'} )
     end
     
-    if rive_target_os == 'windows' then
+    if rive_target_os == 'windows' and _OPTIONS['for_unreal'] == nil then
         externalincludedirs({
             dx12_headers .. '/include/directx',
         })
     end
 
-    filter({ 'options:for_unreal' })
-    do
-        defines({ 'RIVE_UNREAL', 'RIVE_TOOLS_NO_GLFW', 'RIVE_TOOLS_NO_GL' })
-    end
-
     filter({ 'toolset:not msc' })
     do
         buildoptions({ '-Wshorten-64-to-32' })