fix(ios): Don't call abort when the unexpected happens. (#10472) 1adc508ecd
* removed aborts and properly handle the error case instead. Don't use printf because nslog allows for proper output to log files in ios

* more converts to nslog

* made checks more specific

* Wiring up multiple synthesized failure types

* Wiring through the new synthesized failure modes to D3D11, 12, and GL

* Move the Metal ubershaderLoad failure synthesis to a better spot

* clang format

* Missed wrapping a variable in #ifdef WITH_RIVE_TOOLS

* Correction: missed *multiple* #ifdef WITH_RIVE_TOOLSes

* Still more

* Testing to see if the D3D12/GL errors are related to the ubershaderLoad synthesis or not

* Removing additional pieces of the testing to see what's causing the issues

* It's important to write your preprocessor directives correctly 🙃

* Trying to figure out what the fence value is that is coming out wrong on the D3D12 tests and why GL is failing

* trying something dumb to see if this re-breaks the oneplus7

* Split the render test up into three tests to see if it is any better on the oneplus7

* Trying to see where the d3d12 device is getting removed (and why), and also what happens if I run 2 of the same synth test on oneplus7

* Sorry everyone it's effectively printf debugging time 🫤

* Changed the CreateEvent call to see if that works for D3D12 and also more printing for GL

* More

* Okay testing some other dumb stuff - this might resolve oneplus 7, still no idea on D3D12 yet

* Testing an alternate fix for the oneplus7 issue plus a different initial frame value for the copy fence

* Adding a comment before push

* Clean up the testing code (the D3D and oneplus7 issues are fixed but now there's a GL issue on windows. sigh.

* clang format again

I'm good at this lol

* Okay I think this will fix the windows GLFW issue at the cost of it might break all the android tests or something (but I hope not!)

* Now debugging why glfw window creation is failing for windows unit tests

* Okay this should "fix" the GL issues on github by just not creating a GL window if GL is not supported.

* Some minor cleanup

* Clarifying a comment, mostly to get the tests to re-kick

Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
Co-authored-by: Josh Jersild <joshua@rive.app>
diff --git a/.rive_head b/.rive_head
index 04c276a..5b54a48 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-b555c57747b50c47b02819206816f8dda2df3b57
+1adc508ecd865d2a7c8187ce6d6ce04f8d5229b3
diff --git a/renderer/include/rive/renderer/async_pipeline_manager.hpp b/renderer/include/rive/renderer/async_pipeline_manager.hpp
index 8989a36..e71457f 100644
--- a/renderer/include/rive/renderer/async_pipeline_manager.hpp
+++ b/renderer/include/rive/renderer/async_pipeline_manager.hpp
@@ -43,7 +43,8 @@
         rive::gpu::InterlockMode interlockMode;
         rive::gpu::ShaderMiscFlags shaderMiscFlags;
 #ifdef WITH_RIVE_TOOLS
-        bool synthesizeCompilationFailures = false;
+        rive::gpu::SynthesizedFailureType synthesizedFailureType =
+            rive::gpu::SynthesizedFailureType::none;
 #endif
     };
 
@@ -68,7 +69,7 @@
         assert(!m_jobThread.joinable());
     }
 
-    const PipelineType& getPipeline(const PipelineProps& propsIn)
+    const PipelineType* tryGetPipeline(const PipelineProps& propsIn)
     {
         PipelineProps props = propsIn;
 
@@ -111,10 +112,23 @@
         auto iter = m_pipelines.find(key);
 
 #ifdef WITH_RIVE_TOOLS
+        // If requested, synthesize a complete failure to get an ubershader
+        // (i.e. pretend we attempted to load the current shader asynchronously
+        // and tried to fall back on an uber, which failed) (Don't fail on
+        // "atomicResolve" because if we fail that one the unit test won't see
+        // the clear color)
+        if (props.synthesizedFailureType ==
+                gpu::SynthesizedFailureType::ubershaderLoad &&
+            props.drawType != DrawType::atomicResolve)
+        {
+            return nullptr;
+        }
+
         if (props.shaderFeatures == ubershaderFeatures)
         {
-            // Never synthesize compilation failure for an ubershader.
-            props.synthesizeCompilationFailures = false;
+            // Otherwise, do not synthesize compilation failure for an
+            // ubershader.
+            props.synthesizedFailureType = gpu::SynthesizedFailureType::none;
         }
 #endif
 
@@ -140,11 +154,17 @@
 
                 if (getPipelineStatus(*iter->second) != PipelineStatus::errored)
                 {
-                    return *iter->second;
+                    return &*iter->second;
                 }
 
-                // It's bad to have a creation error for an ubershader, but
-                //  otherwise we can still fall back on the ubershader.
+                if (props.shaderFeatures == ubershaderFeatures)
+                {
+                    // Ubershader creation failed
+                    return nullptr;
+                }
+
+                // This pipeline failed to build for some reason, but we can
+                // (potentially) fall back on the ubershader.
                 assert(props.shaderFeatures != ubershaderFeatures);
             }
         }
@@ -171,7 +191,7 @@
                 status == PipelineStatus::ready)
             {
                 // The program is present and ready to go!
-                return *iter->second;
+                return &*iter->second;
             }
             else if (status != PipelineStatus::errored)
             {
@@ -182,7 +202,7 @@
                 if (advanceCreation(*iter->second, props))
                 {
                     // The program was not previously ready, but it is now.
-                    return *iter->second;
+                    return &*iter->second;
                 }
             }
         }
@@ -191,13 +211,13 @@
         // version (with all functionality enabled). This will create
         // synchronously so we're guaranteed to have a valid return from this
         // call.
-        // NOTE: intentionally not passing along synthesizeCompilationFailures
+        // NOTE: intentionally not passing along synthesizedFailureType
         //  here because we don't pay attention to it for ubershaders anyway
         assert(props.shaderFeatures != ubershaderFeatures);
-        return getPipeline({props.drawType,
-                            ubershaderFeatures,
-                            props.interlockMode,
-                            props.shaderMiscFlags});
+        return tryGetPipeline({props.drawType,
+                               ubershaderFeatures,
+                               props.interlockMode,
+                               props.shaderMiscFlags});
     }
 
 protected:
diff --git a/renderer/include/rive/renderer/d3d11/render_context_d3d_impl.hpp b/renderer/include/rive/renderer/d3d11/render_context_d3d_impl.hpp
index 61afef7..96f81b2 100644
--- a/renderer/include/rive/renderer/d3d11/render_context_d3d_impl.hpp
+++ b/renderer/include/rive/renderer/d3d11/render_context_d3d_impl.hpp
@@ -92,13 +92,13 @@
 
     ~D3D11PipelineManager() { shutdownBackgroundThread(); }
 
-    void setPipelineState(rive::gpu::DrawType,
-                          rive::gpu::ShaderFeatures,
-                          rive::gpu::InterlockMode,
-                          rive::gpu::ShaderMiscFlags
+    [[nodiscard]] bool setPipelineState(rive::gpu::DrawType,
+                                        rive::gpu::ShaderFeatures,
+                                        rive::gpu::InterlockMode,
+                                        rive::gpu::ShaderMiscFlags
 #ifdef WITH_RIVE_TOOLS
-                          ,
-                          bool synthesizeCompilationFailures
+                                        ,
+                                        SynthesizedFailureType
 #endif
     );
 
diff --git a/renderer/include/rive/renderer/gl/render_context_gl_impl.hpp b/renderer/include/rive/renderer/gl/render_context_gl_impl.hpp
index ddb4a56..5d3427c 100644
--- a/renderer/include/rive/renderer/gl/render_context_gl_impl.hpp
+++ b/renderer/include/rive/renderer/gl/render_context_gl_impl.hpp
@@ -321,7 +321,7 @@
                     gpu::ShaderMiscFlags
 #ifdef WITH_RIVE_TOOLS
                     ,
-                    bool synthesizeCompilationFailures
+                    SynthesizedFailureType
 #endif
         );
         ~DrawProgram();
@@ -372,6 +372,10 @@
         GLuint m_id = 0;
         GLint m_baseInstanceUniformLocation = -1;
         const rcp<GLState> m_state;
+#ifdef WITH_RIVE_TOOLS
+        SynthesizedFailureType m_synthesizedFailureType =
+            SynthesizedFailureType::none;
+#endif
     };
 
     class GLPipelineManager : public AsyncPipelineManager<DrawProgram>
diff --git a/renderer/include/rive/renderer/gpu.hpp b/renderer/include/rive/renderer/gpu.hpp
index bfe2b9e..aa44c71 100644
--- a/renderer/include/rive/renderer/gpu.hpp
+++ b/renderer/include/rive/renderer/gpu.hpp
@@ -1051,6 +1051,18 @@
 };
 static_assert(sizeof(TwoTexelRamp) == 8 * sizeof(uint8_t));
 
+#ifdef WITH_RIVE_TOOLS
+
+enum class SynthesizedFailureType
+{
+    none,
+    ubershaderLoad,
+    shaderCompilation,
+    pipelineCreation,
+};
+
+#endif
+
 // Detailed description of exactly how a RenderContextImpl should bind its
 // buffers and draw a flush. A typical flush is done in 4 steps:
 //
@@ -1132,7 +1144,8 @@
     // gracefully. (e.g., by falling back on an uber shader or at least not
     // crashing.) Valid compilations may fail in the real world if the device is
     // pressed for resources or in a bad state.
-    bool synthesizeCompilationFailures = false;
+    SynthesizedFailureType synthesizedFailureType =
+        SynthesizedFailureType::none;
 #endif
 
     // Command buffer that rendering commands will be added to.
diff --git a/renderer/include/rive/renderer/metal/render_context_metal_impl.h b/renderer/include/rive/renderer/metal/render_context_metal_impl.h
index c92ae14..8390431 100644
--- a/renderer/include/rive/renderer/metal/render_context_metal_impl.h
+++ b/renderer/include/rive/renderer/metal/render_context_metal_impl.h
@@ -101,6 +101,11 @@
         // m_platformFeatures.supportsRasterOrdering to false, forcing us to
         // always render in atomic mode.
         bool disableFramebufferReads = false;
+
+#ifdef WITH_RIVE_TOOLS
+        SynthesizedFailureType synthesizedFailureType =
+            SynthesizedFailureType::none;
+#endif
     };
 
     static std::unique_ptr<RenderContext> MakeContext(id<MTLDevice>,
diff --git a/renderer/include/rive/renderer/render_context.hpp b/renderer/include/rive/renderer/render_context.hpp
index a956145..bdd993d 100644
--- a/renderer/include/rive/renderer/render_context.hpp
+++ b/renderer/include/rive/renderer/render_context.hpp
@@ -117,7 +117,8 @@
         // gracefully. (e.g., by falling back on an uber shader or at least not
         // crashing.) Valid compilations may fail in the real world if the
         // device is pressed for resources or in a bad state.
-        bool synthesizeCompilationFailures = false;
+        gpu::SynthesizedFailureType synthesizedFailureType =
+            gpu::SynthesizedFailureType::none;
 #endif
     };
 
diff --git a/renderer/path_fiddle/fiddle_context_d3d12.cpp b/renderer/path_fiddle/fiddle_context_d3d12.cpp
index 1833002..c0bc02d 100644
--- a/renderer/path_fiddle/fiddle_context_d3d12.cpp
+++ b/renderer/path_fiddle/fiddle_context_d3d12.cpp
@@ -156,25 +156,44 @@
         ID3D12CommandList* ppCommandLists[] = {m_copyCommandList.Get()};
         m_copyCommandQueue->ExecuteCommandLists(1, ppCommandLists);
 
-        // set the initial state to 0 and inc frame to 1
-        VERIFY_OK(m_device->CreateFence(m_currentFrame++,
+        VERIFY_OK(m_device->CreateFence(m_currentFrame,
                                         D3D12_FENCE_FLAG_NONE,
                                         IID_PPV_ARGS(&m_fence)));
 
-        // set the initial state to -1 so that we can wait for 0
-        VERIFY_OK(m_device->CreateFence(-1,
+        // NOTE: Originally the code was setting this to -1 and then waiting on
+        // a signal of 0, but this does not work in practice because some D3D12
+        // implementations only allow the signaled value to increase - so
+        // starting the fence at unsigned -1 meant that it can never be changed
+        // again.
+        VERIFY_OK(m_device->CreateFence(m_currentFrame,
                                         D3D12_FENCE_FLAG_NONE,
                                         IID_PPV_ARGS(&m_copyFence)));
 
+        // Increment m_currentFrame since the value we initialized the fences to
+        // cannot be waited on (it'll return immediately).
+        m_currentFrame++;
+
         m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
         VERIFY_OK(HRESULT_FROM_WIN32(GetLastError()));
 
         assert(m_fenceEvent);
-        VERIFY_OK(m_copyCommandQueue->Signal(m_copyFence.Get(), 0));
-        VERIFY_OK(m_copyFence->SetEventOnCompletion(0, m_fenceEvent));
-        WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
 
-        assert(m_copyFence->GetCompletedValue() == 0);
+        // Signal the fence to ensure that we wait for creation to be complete.
+        // Start at m_currentFrame which should currently be set to 1.
+        VERIFY_OK(
+            m_copyCommandQueue->Signal(m_copyFence.Get(), m_currentFrame));
+
+        if (m_copyFence->GetCompletedValue() != m_currentFrame)
+        {
+            VERIFY_OK(m_copyFence->SetEventOnCompletion(m_currentFrame,
+                                                        m_fenceEvent));
+            WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
+
+            assert(m_copyFence->GetCompletedValue() == m_currentFrame);
+        }
+
+        // Increment the current frame one more to get past this wait.
+        m_currentFrame++;
     }
 
     float dpiScale(GLFWwindow*) const override { return 1; }
@@ -319,9 +338,9 @@
         auto copySafeFrame = m_copyFence->GetCompletedValue();
         if (copySafeFrame < m_previousFrames[m_frameIndex])
         {
-            VERIFY_OK(
-                m_fence->SetEventOnCompletion(m_previousFrames[m_frameIndex],
-                                              m_fenceEvent));
+            VERIFY_OK(m_copyFence->SetEventOnCompletion(
+                m_previousFrames[m_frameIndex],
+                m_fenceEvent));
             WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
             copySafeFrame = m_previousFrames[m_frameIndex];
         }
@@ -339,7 +358,7 @@
         VERIFY_OK(m_fence->SetEventOnCompletion(frame, m_fenceEvent));
         WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
 
-        VERIFY_OK(m_fence->SetEventOnCompletion(frame, m_fenceEvent));
+        VERIFY_OK(m_copyFence->SetEventOnCompletion(frame, m_fenceEvent));
         WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
     }
 
diff --git a/renderer/src/d3d11/render_context_d3d_impl.cpp b/renderer/src/d3d11/render_context_d3d_impl.cpp
index 0130f18..2c58cd9 100644
--- a/renderer/src/d3d11/render_context_d3d_impl.cpp
+++ b/renderer/src/d3d11/render_context_d3d_impl.cpp
@@ -218,31 +218,36 @@
         &m_atlasStrokePixelShader));
 }
 
-void D3D11PipelineManager::setPipelineState(
+bool D3D11PipelineManager::setPipelineState(
     rive::gpu::DrawType drawType,
     rive::gpu::ShaderFeatures features,
     rive::gpu::InterlockMode interlockMode,
     rive::gpu::ShaderMiscFlags miscFlags
 #ifdef WITH_RIVE_TOOLS
     ,
-    bool synthesizeCompilationFailures
+    SynthesizedFailureType synthesizedFailureType
 #endif
 )
 {
-    auto& result = getPipeline({
+    auto* pipeline = tryGetPipeline({
         .drawType = drawType,
         .shaderFeatures = features,
         .interlockMode = interlockMode,
         .shaderMiscFlags = miscFlags,
 #ifdef WITH_RIVE_TOOLS
-        .synthesizeCompilationFailures = synthesizeCompilationFailures,
+        .synthesizedFailureType = synthesizedFailureType,
 #endif
-
     });
 
-    m_context->IASetInputLayout(result.m_vertexShader.layout.Get());
-    m_context->VSSetShader(result.m_vertexShader.shader.Get(), nullptr, 0);
-    m_context->PSSetShader(result.m_pixelShader.Get(), nullptr, 0);
+    if (pipeline == nullptr)
+    {
+        return false;
+    }
+
+    m_context->IASetInputLayout(pipeline->m_vertexShader.layout.Get());
+    m_context->VSSetShader(pipeline->m_vertexShader.shader.Get(), nullptr, 0);
+    m_context->PSSetShader(pipeline->m_pixelShader.Get(), nullptr, 0);
+    return true;
 }
 
 D3D11DrawVertexShader D3D11PipelineManager ::compileVertexShaderBlobToFinalType(
@@ -360,7 +365,10 @@
     // For D3D11 this just puts the vs and ps into a single structure together.
     D3D11DrawPipeline pipeline;
 #ifdef WITH_RIVE_TOOLS
-    if (props.synthesizeCompilationFailures)
+    if (props.synthesizedFailureType ==
+            SynthesizedFailureType::pipelineCreation ||
+        props.synthesizedFailureType ==
+            SynthesizedFailureType::shaderCompilation)
     {
         // An empty result is what counts as "failed"
         return pipeline;
@@ -1824,6 +1832,7 @@
     for (const DrawBatch& batch : *desc.drawList)
     {
         DrawType drawType = batch.drawType;
+
         auto shaderFeatures = desc.interlockMode == gpu::InterlockMode::atomics
                                   ? desc.combinedShaderFeatures
                                   : batch.shaderFeatures;
@@ -1844,15 +1853,20 @@
             shaderMiscFlags |= gpu::ShaderMiscFlags::clockwiseFill;
         }
 
-        m_pipelineManager.setPipelineState(drawType,
-                                           shaderFeatures,
-                                           desc.interlockMode,
-                                           shaderMiscFlags
+        if (!m_pipelineManager.setPipelineState(drawType,
+                                                shaderFeatures,
+                                                desc.interlockMode,
+                                                shaderMiscFlags
 #ifdef WITH_RIVE_TOOLS
-                                           ,
-                                           desc.synthesizeCompilationFailures
+                                                ,
+                                                desc.synthesizedFailureType
 #endif
-        );
+                                                ))
+        {
+            // There was an issue getting either the requested pipeline state or
+            // its ubershader counterpart so we cannot draw anything.
+            continue;
+        }
 
         if (auto imageTextureD3D =
                 static_cast<const TextureD3DImpl*>(batch.imageTexture))
diff --git a/renderer/src/d3d12/d3d12_pipeline_manager.cpp b/renderer/src/d3d12/d3d12_pipeline_manager.cpp
index 41eaf22..5d31aed 100644
--- a/renderer/src/d3d12/d3d12_pipeline_manager.cpp
+++ b/renderer/src/d3d12/d3d12_pipeline_manager.cpp
@@ -235,7 +235,10 @@
 
     D3D12Pipeline result;
 #ifdef WITH_RIVE_TOOLS
-    if (props.synthesizeCompilationFailures)
+    if (props.synthesizedFailureType ==
+            SynthesizedFailureType::pipelineCreation ||
+        props.synthesizedFailureType ==
+            SynthesizedFailureType::shaderCompilation)
     {
         // An empty result is what counts as "failed"
         return result;
diff --git a/renderer/src/d3d12/render_context_d3d12_impl.cpp b/renderer/src/d3d12/render_context_d3d12_impl.cpp
index e1c18cb..6b7e65e 100644
--- a/renderer/src/d3d12/render_context_d3d12_impl.cpp
+++ b/renderer/src/d3d12/render_context_d3d12_impl.cpp
@@ -1415,16 +1415,24 @@
             shaderMiscFlags |= gpu::ShaderMiscFlags::clockwiseFill;
         }
 
-        auto pipeline = m_pipelineManager.getPipeline({
+        auto* pipeline = m_pipelineManager.tryGetPipeline({
             .drawType = drawType,
             .shaderFeatures = shaderFeatures,
             .interlockMode = desc.interlockMode,
             .shaderMiscFlags = shaderMiscFlags,
 #ifdef WITH_RIVE_TOOLS
-            .synthesizeCompilationFailures = desc.synthesizeCompilationFailures,
+            .synthesizedFailureType = desc.synthesizedFailureType,
 #endif
         });
-        cmdList->SetPipelineState(pipeline.m_d3dPipelineState.Get());
+
+        if (pipeline == nullptr)
+        {
+            // There was an issue getting either the requested pipeline state or
+            // its ubershader counterpart so we cannot draw anything.
+            continue;
+        }
+
+        cmdList->SetPipelineState(pipeline->m_d3dPipelineState.Get());
 
         // all atomic barriers are the same for dx12
         if (batch.barriers &
diff --git a/renderer/src/gl/render_context_gl_impl.cpp b/renderer/src/gl/render_context_gl_impl.cpp
index 80d2b77..443ba33 100644
--- a/renderer/src/gl/render_context_gl_impl.cpp
+++ b/renderer/src/gl/render_context_gl_impl.cpp
@@ -1223,7 +1223,7 @@
     gpu::ShaderMiscFlags shaderMiscFlags
 #ifdef WITH_RIVE_TOOLS
     ,
-    bool synthesizeCompilationFailures
+    SynthesizedFailureType synthesizedFailureType
 #endif
     ) :
     m_fragmentShader(renderContextImpl,
@@ -1235,9 +1235,9 @@
     m_state(renderContextImpl->m_state)
 {
 #ifdef WITH_RIVE_TOOLS
-    if (synthesizeCompilationFailures)
+    m_synthesizedFailureType = synthesizedFailureType;
+    if (m_synthesizedFailureType == SynthesizedFailureType::shaderCompilation)
     {
-        // An empty result is what counts as "failed"
         m_creationState = CreationState::error;
         return;
     }
@@ -1349,6 +1349,14 @@
         }
     }
 
+#ifdef WITH_RIVE_TOOLS
+    if (m_synthesizedFailureType == SynthesizedFailureType::pipelineCreation)
+    {
+        m_creationState = CreationState::error;
+        return false;
+    }
+#endif
+
     {
         GLint successfullyLinked = 0;
         glGetProgramiv(m_id, GL_LINK_STATUS, &successfullyLinked);
@@ -1959,16 +1967,23 @@
         {
             shaderMiscFlags |= gpu::ShaderMiscFlags::clockwiseFill;
         }
-        const DrawProgram& drawProgram = m_pipelineManager.getPipeline({
+        const DrawProgram* drawProgram = m_pipelineManager.tryGetPipeline({
             .drawType = drawType,
             .shaderFeatures = shaderFeatures,
             .interlockMode = desc.interlockMode,
             .shaderMiscFlags = shaderMiscFlags,
 #ifdef WITH_RIVE_TOOLS
-            .synthesizeCompilationFailures = desc.synthesizeCompilationFailures,
+            .synthesizedFailureType = desc.synthesizedFailureType,
 #endif
         });
-        m_state->bindProgram(drawProgram.id());
+        if (drawProgram == nullptr)
+        {
+            // There was an issue getting either the requested draw program or
+            // its ubershader counterpart so we cannot draw anything.
+            continue;
+        }
+
+        m_state->bindProgram(drawProgram->id());
 
         if (auto imageTextureGL =
                 static_cast<const TextureGLImpl*>(batch.imageTexture))
@@ -2069,7 +2084,7 @@
                     gpu::PatchBaseIndex(drawType),
                     batch.elementCount,
                     batch.baseElement,
-                    drawProgram.baseInstanceUniformLocation());
+                    drawProgram->baseInstanceUniformLocation());
                 break;
             }
 
@@ -2818,7 +2833,7 @@
                                          props.shaderMiscFlags
 #ifdef WITH_RIVE_TOOLS
                                          ,
-                                         props.synthesizeCompilationFailures
+                                         props.synthesizedFailureType
 #endif
     );
 }
diff --git a/renderer/src/metal/background_shader_compiler.h b/renderer/src/metal/background_shader_compiler.h
index 00aa24e..115969d 100644
--- a/renderer/src/metal/background_shader_compiler.h
+++ b/renderer/src/metal/background_shader_compiler.h
@@ -24,7 +24,8 @@
     gpu::ShaderMiscFlags shaderMiscFlags;
     id<MTLLibrary> compiledLibrary = nil;
 #ifdef WITH_RIVE_TOOLS
-    bool synthesizeCompilationFailure = false;
+    gpu::SynthesizedFailureType synthesizedFailureType =
+        gpu::SynthesizedFailureType::none;
 #endif
 };
 
diff --git a/renderer/src/metal/background_shader_compiler.mm b/renderer/src/metal/background_shader_compiler.mm
index 2ee1a66..0a6aaad 100644
--- a/renderer/src/metal/background_shader_compiler.mm
+++ b/renderer/src/metal/background_shader_compiler.mm
@@ -263,7 +263,8 @@
         }
         compileOptions.preprocessorMacros = defines;
 #ifdef WITH_RIVE_TOOLS
-        if (job.synthesizeCompilationFailure)
+        if (job.synthesizedFailureType ==
+            SynthesizedFailureType::shaderCompilation)
         {
             assert(job.compiledLibrary == nil);
         }
@@ -280,9 +281,10 @@
         if (job.compiledLibrary == nil)
         {
 #ifdef WITH_RIVE_TOOLS
-            if (job.synthesizeCompilationFailure)
+            if (job.synthesizedFailureType ==
+                SynthesizedFailureType::shaderCompilation)
             {
-                fprintf(stderr, "Synthesizing shader compilation failure...\n");
+                NSLog(@"Synthesizing shader compilation failure...");
             }
             else
 #endif
@@ -295,15 +297,16 @@
                 std::string lineStr;
                 while (std::getline(stream, lineStr, '\n'))
                 {
-                    fprintf(stderr, "%4i| %s\n", lineNumber++, lineStr.c_str());
+                    NSLog(@"%4i| %s", lineNumber++, lineStr.c_str());
                 }
-                fprintf(stderr, "%s\n", err.localizedDescription.UTF8String);
+                NSLog(@"%@", err.localizedDescription);
             }
 
-            fprintf(stderr, "Failed to compile shader.\n");
+            NSLog(@"Failed to compile shader.");
             assert(false
 #ifdef WITH_RIVE_TOOLS
-                   || job.synthesizeCompilationFailure
+                   || job.synthesizedFailureType ==
+                          SynthesizedFailureType::shaderCompilation
 #endif
             );
         }
diff --git a/renderer/src/metal/render_context_metal_impl.mm b/renderer/src/metal/render_context_metal_impl.mm
index 24e39f4..79fa4bd 100644
--- a/renderer/src/metal/render_context_metal_impl.mm
+++ b/renderer/src/metal/render_context_metal_impl.mm
@@ -39,15 +39,12 @@
 static id<MTLRenderPipelineState> make_pipeline_state(
     id<MTLDevice> gpu, MTLRenderPipelineDescriptor* desc)
 {
-    NSError* err = [NSError errorWithDomain:@"pipeline_create"
-                                       code:201
-                                   userInfo:nil];
+    NSError* err = nil;
     id<MTLRenderPipelineState> state =
         [gpu newRenderPipelineStateWithDescriptor:desc error:&err];
-    if (!state)
+    if (err)
     {
-        fprintf(stderr, "%s\n", err.localizedDescription.UTF8String);
-        abort();
+        NSLog(@"make_pipeline_state error %@", err.localizedDescription);
     }
     return state;
 }
@@ -261,7 +258,12 @@
                  gpu::DrawType drawType,
                  gpu::InterlockMode interlockMode,
                  gpu::ShaderFeatures shaderFeatures,
-                 gpu::ShaderMiscFlags shaderMiscFlags)
+                 gpu::ShaderMiscFlags shaderMiscFlags
+#ifdef WITH_RIVE_TOOLS
+                 ,
+                 gpu::SynthesizedFailureType synthesizedFailureType
+#endif
+    )
     {
         if (library == nil)
         {
@@ -270,6 +272,14 @@
             return;
         }
 
+#ifdef WITH_RIVE_TOOLS
+        if (synthesizedFailureType == SynthesizedFailureType::pipelineCreation)
+        {
+            NSLog(@"Synthesizing pipeline creation failure...");
+            return;
+        }
+#endif
+
         auto makePipelineState = [=](id<MTLFunction> vertexMain,
                                      id<MTLFunction> fragmentMain,
                                      MTLPixelFormat pixelFormat) {
@@ -568,16 +578,14 @@
 #endif
         nil,
         nil);
-    NSError* err = [NSError errorWithDomain:@"metallib_load"
-                                       code:200
-                                   userInfo:nil];
+    NSError* err = nil;
     m_plsPrecompiledLibrary = [m_gpu newLibraryWithData:metallibData
                                                   error:&err];
-    if (m_plsPrecompiledLibrary == nil)
+    if (err)
     {
-        fprintf(stderr, "Failed to load pls metallib.\n");
-        fprintf(stderr, "%s\n", err.localizedDescription.UTF8String);
-        abort();
+        NSLog(@"Failed to load pls metallib error: %@",
+              err.localizedDescription);
+        return;
     }
 
     m_colorRampPipeline =
@@ -658,7 +666,12 @@
                     drawType,
                     gpu::InterlockMode::rasterOrdering,
                     allShaderFeatures,
-                    shaderMiscFlags);
+                    shaderMiscFlags
+#ifdef WITH_RIVE_TOOLS
+                    ,
+                    SynthesizedFailureType::none
+#endif
+                );
             }
         }
     }
@@ -956,6 +969,15 @@
         shaderFeatures = fullyFeaturedPipelineFeatures;
     }
 
+#ifdef WITH_RIVE_TOOLS
+    if (desc.synthesizedFailureType == SynthesizedFailureType::ubershaderLoad)
+    {
+        // Pretend that the requested shader is not ready yet and the ubershader
+        // compilation failed
+        return nil;
+    }
+#endif
+
     uint32_t pipelineKey = gpu::ShaderUniqueKey(
         drawType, shaderFeatures, desc.interlockMode, shaderMiscFlags);
     auto pipelineIter = m_drawPipelines.find(pipelineKey);
@@ -969,7 +991,7 @@
             .interlockMode = desc.interlockMode,
             .shaderMiscFlags = shaderMiscFlags,
 #ifdef WITH_RIVE_TOOLS
-            .synthesizeCompilationFailure = desc.synthesizeCompilationFailures,
+            .synthesizedFailureType = desc.synthesizedFailureType,
 #endif
         });
         pipelineIter = m_drawPipelines.insert({pipelineKey, nullptr}).first;
@@ -1006,7 +1028,12 @@
                                                job.drawType,
                                                job.interlockMode,
                                                job.shaderFeatures,
-                                               job.shaderMiscFlags);
+                                               job.shaderMiscFlags
+#ifdef WITH_RIVE_TOOLS
+                                               ,
+                                               desc.synthesizedFailureType
+#endif
+                );
             if (jobKey == pipelineKey)
             {
                 // The shader we wanted was actually done compiling and pending
@@ -1181,6 +1208,19 @@
     // Render the color ramps to the gradient texture.
     if (desc.gradSpanCount > 0)
     {
+        // We failed to load the precompiled library and therefore do not have
+        // the abililty to draw anything.
+        if (!m_colorRampPipeline)
+        {
+            return;
+        }
+        // We are removing the abort in the case this doesn't build. So give up
+        // drawing if we still don't have a pipeline here.
+        auto pipelineState = m_colorRampPipeline->pipelineState();
+        if (!pipelineState)
+        {
+            return;
+        }
         MTLRenderPassDescriptor* gradPass =
             [MTLRenderPassDescriptor renderPassDescriptor];
         gradPass.renderTargetWidth = kGradTextureWidth;
@@ -1196,8 +1236,7 @@
                                       0,
                                       kGradTextureWidth,
                                       static_cast<float>(desc.gradDataHeight))];
-        [gradEncoder
-            setRenderPipelineState:m_colorRampPipeline->pipelineState()];
+        [gradEncoder setRenderPipelineState:pipelineState];
         [gradEncoder
             setVertexBuffer:mtl_buffer(flushUniformBufferRing())
                      offset:desc.flushUniformDataOffsetInBytes
@@ -1217,6 +1256,20 @@
     // Tessellate all curves into vertices in the tessellation texture.
     if (desc.tessVertexSpanCount > 0)
     {
+        // We failed to load the precompiled library and therefore do not have
+        // the abililty to draw anything.
+        if (!m_tessPipeline)
+        {
+            return;
+        }
+        // We are removing the abort in the case this doesn't build. So give up
+        // drawing if we still don't have a pipeline here.
+        auto pipelineState = m_tessPipeline->pipelineState();
+        if (!pipelineState)
+        {
+            return;
+        }
+
         MTLRenderPassDescriptor* tessPass =
             [MTLRenderPassDescriptor renderPassDescriptor];
         tessPass.renderTargetWidth = kTessTextureWidth;
@@ -1230,7 +1283,7 @@
         [tessEncoder
             setViewport:make_viewport(
                             0, 0, kTessTextureWidth, desc.tessDataHeight)];
-        [tessEncoder setRenderPipelineState:m_tessPipeline->pipelineState()];
+        [tessEncoder setRenderPipelineState:pipelineState];
         [tessEncoder setVertexTexture:m_featherTexture
                               atIndex:FEATHER_TEXTURE_IDX];
         [tessEncoder
@@ -1263,6 +1316,26 @@
     // Render the atlas if we have any offscreen feathers.
     if ((desc.atlasFillBatchCount | desc.atlasStrokeBatchCount) != 0)
     {
+        // We failed to load the precompiled library and therefore do not have
+        // the abililty to draw anything.
+        if (!m_atlasStrokePipeline || !m_atlasFillPipeline)
+        {
+            return;
+        }
+        // We are removing the abort in the case this doesn't build. So give up
+        // drawing if we still don't have a pipeline here.
+        auto atlasFillpipelineState = m_atlasFillPipeline->pipelineState();
+        if (!atlasFillpipelineState)
+        {
+            return;
+        }
+
+        auto atlasStrokepipelineState = m_atlasStrokePipeline->pipelineState();
+        if (!atlasStrokepipelineState)
+        {
+            return;
+        }
+
         MTLRenderPassDescriptor* atlasPass =
             [MTLRenderPassDescriptor renderPassDescriptor];
         atlasPass.renderTargetWidth = desc.atlasContentWidth;
@@ -1323,8 +1396,7 @@
 
         if (desc.atlasFillBatchCount != 0)
         {
-            [atlasEncoder
-                setRenderPipelineState:m_atlasFillPipeline->pipelineState()];
+            [atlasEncoder setRenderPipelineState:atlasFillpipelineState];
             for (size_t i = 0; i < desc.atlasFillBatchCount; ++i)
             {
                 const gpu::AtlasDrawBatch& fillBatch = desc.atlasFillBatches[i];
@@ -1349,8 +1421,7 @@
 
         if (desc.atlasStrokeBatchCount != 0)
         {
-            [atlasEncoder
-                setRenderPipelineState:m_atlasStrokePipeline->pipelineState()];
+            [atlasEncoder setRenderPipelineState:atlasStrokepipelineState];
             for (size_t i = 0; i < desc.atlasStrokeBatchCount; ++i)
             {
                 const gpu::AtlasDrawBatch& strokeBatch =
diff --git a/renderer/src/render_context.cpp b/renderer/src/render_context.cpp
index 8a3a9ab..30306c6 100644
--- a/renderer/src/render_context.cpp
+++ b/renderer/src/render_context.cpp
@@ -1033,8 +1033,7 @@
     m_flushDesc.clockwiseFillOverride = frameDescriptor.clockwiseFillOverride;
     m_flushDesc.wireframe = frameDescriptor.wireframe;
 #ifdef WITH_RIVE_TOOLS
-    m_flushDesc.synthesizeCompilationFailures =
-        frameDescriptor.synthesizeCompilationFailures;
+    m_flushDesc.synthesizedFailureType = frameDescriptor.synthesizedFailureType;
 #endif
 
     m_flushDesc.externalCommandBuffer = flushResources.externalCommandBuffer;
diff --git a/tests/common/testing_gl_renderer.cpp b/tests/common/testing_gl_renderer.cpp
index 4875ec9..84ff074 100644
--- a/tests/common/testing_gl_renderer.cpp
+++ b/tests/common/testing_gl_renderer.cpp
@@ -93,8 +93,7 @@
                 .wireframe = options.wireframe,
                 .clockwiseFillOverride =
                     m_backendParams.clockwise || options.clockwiseFillOverride,
-                .synthesizeCompilationFailures =
-                    options.synthesizeCompilationFailures,
+                .synthesizedFailureType = options.synthesizedFailureType,
             };
             m_renderContext->beginFrame(frameDescriptor);
         }
diff --git a/tests/common/testing_window.hpp b/tests/common/testing_window.hpp
index 90a3454..a571cc2 100644
--- a/tests/common/testing_window.hpp
+++ b/tests/common/testing_window.hpp
@@ -6,6 +6,7 @@
 #define TESTING_WINDOW_HPP
 
 #include "common/offscreen_render_target.hpp"
+#include "rive/renderer/gpu.hpp"
 #include "rive/refcnt.hpp"
 #include <memory>
 #include <vector>
@@ -157,7 +158,8 @@
         bool disableRasterOrdering = false;
         bool wireframe = false;
         bool clockwiseFillOverride = false;
-        bool synthesizeCompilationFailures = false;
+        rive::gpu::SynthesizedFailureType synthesizedFailureType =
+            rive::gpu::SynthesizedFailureType::none;
     };
     virtual std::unique_ptr<rive::Renderer> beginFrame(const FrameOptions&) = 0;
     virtual void endFrame(std::vector<uint8_t>* pixelData = nullptr) = 0;
diff --git a/tests/common/testing_window_android_vulkan.cpp b/tests/common/testing_window_android_vulkan.cpp
index 77d7b40..b14c742 100644
--- a/tests/common/testing_window_android_vulkan.cpp
+++ b/tests/common/testing_window_android_vulkan.cpp
@@ -212,8 +212,7 @@
             .wireframe = options.wireframe,
             .clockwiseFillOverride =
                 m_backendParams.clockwise || options.clockwiseFillOverride,
-            .synthesizeCompilationFailures =
-                options.synthesizeCompilationFailures,
+            .synthesizedFailureType = options.synthesizedFailureType,
         });
 
         return std::make_unique<RiveRenderer>(m_renderContext.get());
diff --git a/tests/common/testing_window_egl.cpp b/tests/common/testing_window_egl.cpp
index bb58e61..97c35fa 100644
--- a/tests/common/testing_window_egl.cpp
+++ b/tests/common/testing_window_egl.cpp
@@ -476,10 +476,11 @@
 
     ~TestingWindowEGL()
     {
-        eglMakeCurrent(EGL_NO_DISPLAY,
+        eglMakeCurrent(m_Display,
                        EGL_NO_SURFACE,
                        EGL_NO_SURFACE,
                        EGL_NO_CONTEXT);
+
         if (m_Context)
         {
             eglDestroyContext(m_Display, m_Context);
diff --git a/tests/common/testing_window_fiddle_context.cpp b/tests/common/testing_window_fiddle_context.cpp
index 3b4ceca..0ecc602 100644
--- a/tests/common/testing_window_fiddle_context.cpp
+++ b/tests/common/testing_window_fiddle_context.cpp
@@ -220,8 +220,25 @@
                                         nullptr);
         if (!m_glfwWindow)
         {
-            glfwTerminate();
+#ifndef __EMSCRIPTEN__
+            const char* errorDescription;
+            int errorCode = glfwGetError(&errorDescription);
+            fprintf(stderr,
+                    "Failed to create GLFW window: %s\n",
+                    errorDescription);
+            if (errorCode == GLFW_API_UNAVAILABLE)
+            {
+                // This means that the driver does not support the given API and
+                // we cannot create a window. MakeFiddleContext will detect this
+                // object as non-valid and clean it up and return nullptr.
+                return;
+            }
+#else
+            // Emscripten doesn't support glfwGetError so print a generic
+            // message.
             fprintf(stderr, "Failed to create GLFW window.\n");
+#endif
+            glfwTerminate();
             abort();
         }
         glfwMakeContextCurrent(m_glfwWindow);
@@ -363,8 +380,7 @@
             .clockwiseFillOverride =
                 m_backendParams.clockwise || options.clockwiseFillOverride,
 #ifdef WITH_RIVE_TOOLS
-            .synthesizeCompilationFailures =
-                options.synthesizeCompilationFailures,
+            .synthesizedFailureType = options.synthesizedFailureType,
 #endif
         };
         m_fiddleContext->begin(std::move(frameDescriptor));
diff --git a/tests/common/testing_window_metal_texture.mm b/tests/common/testing_window_metal_texture.mm
index 5e0291c..3b3ad36 100644
--- a/tests/common/testing_window_metal_texture.mm
+++ b/tests/common/testing_window_metal_texture.mm
@@ -48,8 +48,7 @@
             .disableRasterOrdering = options.disableRasterOrdering,
             .wireframe = options.wireframe,
             .clockwiseFillOverride = options.clockwiseFillOverride,
-            .synthesizeCompilationFailures =
-                options.synthesizeCompilationFailures,
+            .synthesizedFailureType = options.synthesizedFailureType,
         };
         m_renderContext->beginFrame(frameDescriptor);
         m_flushCommandBuffer = [m_queue commandBuffer];
diff --git a/tests/common/testing_window_null.cpp b/tests/common/testing_window_null.cpp
index dff9725..3e0bfd6 100644
--- a/tests/common/testing_window_null.cpp
+++ b/tests/common/testing_window_null.cpp
@@ -33,8 +33,7 @@
             .disableRasterOrdering = options.disableRasterOrdering,
             .wireframe = options.wireframe,
             .clockwiseFillOverride = options.clockwiseFillOverride,
-            .synthesizeCompilationFailures =
-                options.synthesizeCompilationFailures,
+            .synthesizedFailureType = options.synthesizedFailureType,
         };
         m_renderContext->beginFrame(frameDescriptor);
         return std::make_unique<RiveRenderer>(m_renderContext.get());
diff --git a/tests/common/testing_window_vulkan_texture.cpp b/tests/common/testing_window_vulkan_texture.cpp
index 07253f8..7f470b1 100644
--- a/tests/common/testing_window_vulkan_texture.cpp
+++ b/tests/common/testing_window_vulkan_texture.cpp
@@ -134,8 +134,7 @@
             .wireframe = options.wireframe,
             .clockwiseFillOverride =
                 m_backendParams.clockwise || options.clockwiseFillOverride,
-            .synthesizeCompilationFailures =
-                options.synthesizeCompilationFailures,
+            .synthesizedFailureType = options.synthesizedFailureType,
         };
         m_renderContext->beginFrame(frameDescriptor);
         return std::make_unique<RiveRenderer>(m_renderContext.get());
diff --git a/tests/ios_tests/ios_tests/main.mm b/tests/ios_tests/ios_tests/main.mm
index 674451d..0ef6494 100644
--- a/tests/ios_tests/ios_tests/main.mm
+++ b/tests/ios_tests/ios_tests/main.mm
@@ -34,9 +34,9 @@
         {
             std::this_thread::sleep_for(std::chrono::milliseconds(1000));
             tcpCheck = TCPClient::Connect(pngServer);
-            printf("Ensure the device is connected to WiFi, is on the same "
+            NSLog(@"Ensure the device is connected to WiFi, is on the same "
                    "local network as the "
-                   "host, and app has local network permissions.\n");
+                   "host, and app has local network permissions.");
         }
     }
 
@@ -57,7 +57,7 @@
 {
     if (argc <= 1)
     {
-        printf("No arguments supplied.");
+        NSLog(@"No arguments supplied.");
         return 0;
     }
 
diff --git a/tests/unit_tests/renderer/rendering_tests.cpp b/tests/unit_tests/renderer/rendering_tests.cpp
index 41ffa6e..ef73b9c 100644
--- a/tests/unit_tests/renderer/rendering_tests.cpp
+++ b/tests/unit_tests/renderer/rendering_tests.cpp
@@ -31,19 +31,44 @@
         },
 #endif
 #ifdef _WIN32
-        // TODO: d3d12 currently fails with:
-        // Assertion failed: m_copyFence->GetCompletedValue() == 0, file
-        // C:\...\fiddle_context_d3d12.cpp, line 179
-        // []() {
-        //     return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
-        //         TestingWindow::Backend::d3d12,
-        //         TestingWindow::Visibility::headless,
-        //         {},
-        //         nullptr));
-        // },
+        []() {
+            return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
+                TestingWindow::Backend::d3d12,
+                {},
+                TestingWindow::Visibility::headless,
+                nullptr));
+        },
+        []() {
+            return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
+                TestingWindow::Backend::d3d12,
+                {.atomic = true},
+                TestingWindow::Visibility::headless,
+                nullptr));
+        },
         []() {
             return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
                 TestingWindow::Backend::d3d,
+                {},
+                TestingWindow::Visibility::headless,
+                nullptr));
+        },
+        []() {
+            return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
+                TestingWindow::Backend::d3d,
+                {.atomic = true},
+                TestingWindow::Visibility::headless,
+                nullptr));
+        },
+        []() {
+            return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
+                TestingWindow::Backend::gl,
+                {},
+                TestingWindow::Visibility::headless,
+                nullptr));
+        },
+        []() {
+            return rivestd::adopt_unique(TestingWindow::MakeFiddleContext(
+                TestingWindow::Backend::gl,
                 {.atomic = true},
                 TestingWindow::Visibility::headless,
                 nullptr));
@@ -62,57 +87,66 @@
 // Ensure that rendering still succeeds when compilations fail (e.g., by falling
 // back on an uber shader or at least not crashing). Valid compilations may fail
 // in the real world if the device is pressed for resources or in a bad state.
-TEST_CASE("synthesizeCompilationFailure", "[rendering]")
+TEST_CASE("synthesizedFailureType", "[rendering]")
 {
-    for (auto testingWindowFactory : testingWindowFactories)
+    // TODO: There are potentially stronger ways to build some of these
+    // synthesized failures if we were to pass SynthesizedFailureType as a
+    // creation option instead of on beginFrame
+    for (auto failureType : {SynthesizedFailureType::shaderCompilation,
+                             SynthesizedFailureType::ubershaderLoad,
+                             SynthesizedFailureType::pipelineCreation})
     {
-        std::unique_ptr<TestingWindow> window = testingWindowFactory();
-        if (window == nullptr)
+        for (auto testingWindowFactory : testingWindowFactories)
         {
-            continue;
-        }
-        Factory* factory = window->factory();
+            std::unique_ptr<TestingWindow> window = testingWindowFactory();
+            if (window == nullptr)
+            {
+                continue;
+            }
+            Factory* factory = window->factory();
 
-        window->resize(32, 32);
+            window->resize(32, 32);
 
-        // Expected colors after we draw a cyan rectangle.
-        std::vector<uint8_t> drawColors;
-        drawColors.reserve(32 * 32 * 4);
-        for (size_t i = 0; i < 32 * 32; ++i)
-            drawColors.insert(drawColors.end(), {0x00, 0xff, 0xff, 0xff});
+            // Expected colors after we draw a cyan rectangle.
+            std::vector<uint8_t> drawColors;
+            drawColors.reserve(32 * 32 * 4);
+            for (size_t i = 0; i < 32 * 32; ++i)
+                drawColors.insert(drawColors.end(), {0x00, 0xff, 0xff, 0xff});
 
-        // Expected colors when only the clear happens (because even the uber
-        // shader failed to compile).
-        std::vector<uint8_t> clearColors;
-        clearColors.reserve(32 * 32 * 4);
-        for (size_t i = 0; i < 32 * 32; ++i)
-            clearColors.insert(clearColors.end(), {0xff, 0x00, 0x00, 0xff});
+            // Expected colors when only the clear happens (because even the
+            // uber shader failed to compile).
+            std::vector<uint8_t> clearColors;
+            clearColors.reserve(32 * 32 * 4);
+            for (size_t i = 0; i < 32 * 32; ++i)
+                clearColors.insert(clearColors.end(), {0xff, 0x00, 0x00, 0xff});
 
-        for (bool disableRasterOrdering : {false, true})
-        {
-            auto renderer = window->beginFrame({
-                .clearColor = 0xffff0000,
-                .doClear = true,
-                .disableRasterOrdering = disableRasterOrdering,
-                .synthesizeCompilationFailures = true,
-            });
+            for (bool disableRasterOrdering : {false, true})
+            {
+                auto renderer = window->beginFrame({
+                    .clearColor = 0xffff0000,
+                    .doClear = true,
+                    .disableRasterOrdering = disableRasterOrdering,
+                    .synthesizedFailureType = failureType,
+                });
 
-            rcp<RenderPath> path = factory->makeRenderPath(AABB{0, 0, 32, 32});
-            rcp<RenderPaint> paint = factory->makeRenderPaint();
-            paint->color(0xff00ffff);
-            renderer->drawPath(path.get(), paint.get());
+                rcp<RenderPath> path =
+                    factory->makeRenderPath(AABB{0, 0, 32, 32});
+                rcp<RenderPaint> paint = factory->makeRenderPaint();
+                paint->color(0xff00ffff);
+                renderer->drawPath(path.get(), paint.get());
 
-            std::vector<uint8_t> pixels;
-            window->endFrame(&pixels);
+                std::vector<uint8_t> pixels;
+                window->endFrame(&pixels);
 
-            // There are two acceptable results to this test:
-            //
-            // 1) The draw happens anyway because we fell back on a precompiled
-            //    uber shader.
-            //
-            // 2) The uber shader also synthesizes a compilation faiulre, so
-            //    only the clear color makes it through.
-            CHECK((pixels == drawColors || pixels == clearColors));
+                // There are two acceptable results to this test:
+                //
+                // 1) The draw happens anyway because we fell back on a
+                //    precompiled uber shader.
+                //
+                // 2) The uber shader also synthesizes a compilation faiulre, so
+                //    only the clear color makes it through.
+                CHECK((pixels == drawColors || pixels == clearColors));
+            }
         }
     }
 }