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>
This commit is contained in:
JoshJRive
2025-09-04 17:16:54 +00:00
parent e049c47e1b
commit 1e4015f41a
26 changed files with 391 additions and 167 deletions

View File

@@ -1 +1 @@
b555c57747b50c47b02819206816f8dda2df3b57
1adc508ecd865d2a7c8187ce6d6ce04f8d5229b3

View File

@@ -43,7 +43,8 @@ public:
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 @@ public:
assert(!m_jobThread.joinable());
}
const PipelineType& getPipeline(const PipelineProps& propsIn)
const PipelineType* tryGetPipeline(const PipelineProps& propsIn)
{
PipelineProps props = propsIn;
@@ -111,10 +112,23 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
// 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:

View File

@@ -92,13 +92,13 @@ public:
~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
);

View File

@@ -321,7 +321,7 @@ private:
gpu::ShaderMiscFlags
#ifdef WITH_RIVE_TOOLS
,
bool synthesizeCompilationFailures
SynthesizedFailureType
#endif
);
~DrawProgram();
@@ -372,6 +372,10 @@ private:
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>

View File

@@ -1051,6 +1051,18 @@ struct TwoTexelRamp
};
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 @@ struct FlushDescriptor
// 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.

View File

@@ -101,6 +101,11 @@ public:
// 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>,

View File

@@ -117,7 +117,8 @@ public:
// 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
};

View File

@@ -156,25 +156,44 @@ public:
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 @@ public:
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 @@ public:
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);
}

View File

@@ -218,31 +218,36 @@ D3D11PipelineManager::D3D11PipelineManager(
&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 @@ D3D11DrawPipeline D3D11PipelineManager::linkPipeline(
// 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 @@ void RenderContextD3DImpl::flush(const FlushDescriptor& desc)
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 @@ void RenderContextD3DImpl::flush(const FlushDescriptor& desc)
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))

View File

@@ -235,7 +235,10 @@ D3D12Pipeline D3D12PipelineManager::linkPipeline(const PipelineProps& props,
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;

View File

@@ -1415,16 +1415,24 @@ void RenderContextD3D12Impl::flush(const FlushDescriptor& desc)
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 &

View File

@@ -1223,7 +1223,7 @@ RenderContextGLImpl::DrawProgram::DrawProgram(
gpu::ShaderMiscFlags shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
,
bool synthesizeCompilationFailures
SynthesizedFailureType synthesizedFailureType
#endif
) :
m_fragmentShader(renderContextImpl,
@@ -1235,9 +1235,9 @@ RenderContextGLImpl::DrawProgram::DrawProgram(
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 @@ bool RenderContextGLImpl::DrawProgram::advanceCreation(
}
}
#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 @@ void RenderContextGLImpl::flush(const FlushDescriptor& desc)
{
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 @@ void RenderContextGLImpl::flush(const FlushDescriptor& desc)
gpu::PatchBaseIndex(drawType),
batch.elementCount,
batch.baseElement,
drawProgram.baseInstanceUniformLocation());
drawProgram->baseInstanceUniformLocation());
break;
}
@@ -2818,7 +2833,7 @@ std::unique_ptr<RenderContextGLImpl::DrawProgram> RenderContextGLImpl::
props.shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
,
props.synthesizeCompilationFailures
props.synthesizedFailureType
#endif
);
}

View File

@@ -24,7 +24,8 @@ struct BackgroundCompileJob
gpu::ShaderMiscFlags shaderMiscFlags;
id<MTLLibrary> compiledLibrary = nil;
#ifdef WITH_RIVE_TOOLS
bool synthesizeCompilationFailure = false;
gpu::SynthesizedFailureType synthesizedFailureType =
gpu::SynthesizedFailureType::none;
#endif
};

View File

@@ -263,7 +263,8 @@ void BackgroundShaderCompiler::threadMain()
}
compileOptions.preprocessorMacros = defines;
#ifdef WITH_RIVE_TOOLS
if (job.synthesizeCompilationFailure)
if (job.synthesizedFailureType ==
SynthesizedFailureType::shaderCompilation)
{
assert(job.compiledLibrary == nil);
}
@@ -280,9 +281,10 @@ void BackgroundShaderCompiler::threadMain()
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 @@ void BackgroundShaderCompiler::threadMain()
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
);
}

View File

@@ -39,15 +39,12 @@ namespace rive::gpu
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 @@ public:
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 @@ public:
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 @@ RenderContextMetalImpl::RenderContextMetalImpl(
#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 @@ RenderContextMetalImpl::RenderContextMetalImpl(
drawType,
gpu::InterlockMode::rasterOrdering,
allShaderFeatures,
shaderMiscFlags);
shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
,
SynthesizedFailureType::none
#endif
);
}
}
}
@@ -956,6 +969,15 @@ const RenderContextMetalImpl::DrawPipeline* RenderContextMetalImpl::
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 @@ const RenderContextMetalImpl::DrawPipeline* RenderContextMetalImpl::
.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 @@ const RenderContextMetalImpl::DrawPipeline* RenderContextMetalImpl::
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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
// 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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
// 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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
[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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
// 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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
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 @@ void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
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 =

View File

@@ -1033,8 +1033,7 @@ void RenderContext::LogicalFlush::layoutResources(
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;

View File

@@ -93,8 +93,7 @@ std::unique_ptr<TestingGLRenderer> TestingGLRenderer::Make(
.wireframe = options.wireframe,
.clockwiseFillOverride =
m_backendParams.clockwise || options.clockwiseFillOverride,
.synthesizeCompilationFailures =
options.synthesizeCompilationFailures,
.synthesizedFailureType = options.synthesizedFailureType,
};
m_renderContext->beginFrame(frameDescriptor);
}

View File

@@ -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 @@ public:
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;

View File

@@ -212,8 +212,7 @@ public:
.wireframe = options.wireframe,
.clockwiseFillOverride =
m_backendParams.clockwise || options.clockwiseFillOverride,
.synthesizeCompilationFailures =
options.synthesizeCompilationFailures,
.synthesizedFailureType = options.synthesizedFailureType,
});
return std::make_unique<RiveRenderer>(m_renderContext.get());

View File

@@ -476,10 +476,11 @@ public:
~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);

View File

@@ -220,8 +220,25 @@ public:
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 @@ public:
.clockwiseFillOverride =
m_backendParams.clockwise || options.clockwiseFillOverride,
#ifdef WITH_RIVE_TOOLS
.synthesizeCompilationFailures =
options.synthesizeCompilationFailures,
.synthesizedFailureType = options.synthesizedFailureType,
#endif
};
m_fiddleContext->begin(std::move(frameDescriptor));

View File

@@ -48,8 +48,7 @@ public:
.disableRasterOrdering = options.disableRasterOrdering,
.wireframe = options.wireframe,
.clockwiseFillOverride = options.clockwiseFillOverride,
.synthesizeCompilationFailures =
options.synthesizeCompilationFailures,
.synthesizedFailureType = options.synthesizedFailureType,
};
m_renderContext->beginFrame(frameDescriptor);
m_flushCommandBuffer = [m_queue commandBuffer];

View File

@@ -33,8 +33,7 @@ public:
.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());

View File

@@ -134,8 +134,7 @@ public:
.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());

View File

@@ -34,9 +34,9 @@ static void execute_tool()
{
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 @@ int main(int argc, char* argv[])
{
if (argc <= 1)
{
printf("No arguments supplied.");
NSLog(@"No arguments supplied.");
return 0;
}

View File

@@ -31,19 +31,44 @@ static std::function<std::unique_ptr<TestingWindow>()>
},
#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 @@ static std::function<std::unique_ptr<TestingWindow>()>
// 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));
}
}
}
}