From 4462ecfdacd19087b6a8e01ddfdd2d9aec4cea3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fan=20=C3=96zcan?= <232228291+yorgunkral31@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:36:25 +0300 Subject: [PATCH] fix: skip draws whose render pipeline failed to compile Encoding a draw with a null MTLRenderPipelineState is undefined behavior; when newRenderPipelineState fails (for example on a vertex attribute/shader type mismatch that only some OS versions reject), the failed pipeline's draws now get skipped instead of encoding garbage, and the state cache no longer binds a null pipeline. --- plume_metal.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plume_metal.cpp b/plume_metal.cpp index b670ed3..246edb6 100644 --- a/plume_metal.cpp +++ b/plume_metal.cpp @@ -2491,6 +2491,10 @@ namespace plume { void MetalCommandList::drawInstanced(const uint32_t vertexCountPerInstance, const uint32_t instanceCount, const uint32_t startVertexLocation, const uint32_t startInstanceLocation) { assert(activeGraphicsPipelineLayout != nullptr); + // Skip draws whose pipeline failed to compile; encoding with a null PSO is undefined behavior. + if (activeRenderState == nullptr || activeRenderState->renderPipelineState == nullptr) + return; + MetalAutoreleasePool releasePool; checkActiveRenderEncoder(); checkForUpdatesInGraphicsState(); @@ -2501,6 +2505,10 @@ namespace plume { void MetalCommandList::drawIndexedInstanced(const uint32_t indexCountPerInstance, const uint32_t instanceCount, const uint32_t startIndexLocation, const int32_t baseVertexLocation, const uint32_t startInstanceLocation) { assert(activeGraphicsPipelineLayout != nullptr); + // Skip draws whose pipeline failed to compile; encoding with a null PSO is undefined behavior. + if (activeRenderState == nullptr || activeRenderState->renderPipelineState == nullptr) + return; + MetalAutoreleasePool releasePool; checkActiveRenderEncoder(); checkForUpdatesInGraphicsState(); @@ -3395,7 +3403,7 @@ namespace plume { void MetalCommandList::checkForUpdatesInGraphicsState() { // Pipeline state - only update if the actual pipeline object changed if (dirtyGraphicsState.pipelineState) { - if (activeRenderState && activeRenderState->renderPipelineState != stateCache.lastPipelineState) { + if (activeRenderState && activeRenderState->renderPipelineState != nullptr && activeRenderState->renderPipelineState != stateCache.lastPipelineState) { activeRenderEncoder->setRenderPipelineState(activeRenderState->renderPipelineState); stateCache.lastPipelineState = activeRenderState->renderPipelineState; }