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)); + } } } }