From 7a52637e2650bad034438f41c540ef120a0b4903 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 30 May 2026 06:45:47 +0300 Subject: [PATCH] Fix Metal stencil mismatch in mutable-image seed pass (#5103) CN1MetalEnsureMutableTexture's seed pass (used when an existing UIImage's pixels need to be carried into a freshly-allocated mutable texture) built its render pass with only a colour attachment, then bound the TexturedRGBA pipeline -- which declares stencilAttachmentPixelFormat=Stencil8 since the polygon-clip work in #3921. Metal's validation layer aborts setRenderPipelineState: with "For stencil attachment, the renderPipelineState pixelFormat must be MTLPixelFormatInvalid, as no texture is set", crashing any app that lands on a screen whose first paint touches a seeded mutable image (e.g. gausianBlurImage, FontImage thumbnails). Allocate a throwaway Stencil8 texture for the pass mirroring the pattern already used in CN1MetalBeginMutableImageDraw. The seed draw never engages the stencil test, so loadAction=Clear / storeAction=DontCare is enough. The sibling clearPass above doesn't need the same treatment -- it never calls setRenderPipelineState:, so the validation rule isn't triggered. Co-Authored-By: Claude Opus 4.7 (1M context) --- Ports/iOSPort/nativeSources/CN1Metalcompat.m | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Ports/iOSPort/nativeSources/CN1Metalcompat.m b/Ports/iOSPort/nativeSources/CN1Metalcompat.m index 92bf453791..90e4722a88 100644 --- a/Ports/iOSPort/nativeSources/CN1Metalcompat.m +++ b/Ports/iOSPort/nativeSources/CN1Metalcompat.m @@ -1431,7 +1431,32 @@ void CN1MetalEnsureMutableTexture(GLUIImage *image, int width, int height) { seedPass.colorAttachments[0].texture = tex; seedPass.colorAttachments[0].loadAction = MTLLoadActionLoad; seedPass.colorAttachments[0].storeAction = MTLStoreActionStore; + // Pipelines in CN1MetalPipelineCache declare + // stencilAttachmentPixelFormat=Stencil8 (polygon-clip #3921), + // so every pass that binds one must attach a Stencil8 texture + // or Metal aborts in setRenderPipelineState: with a pixel- + // format mismatch (issue #5103). The seed draw never engages + // the stencil test, so a throwaway clear-on-load attachment + // is sufficient. + id seedStencilTex = nil; + MTLTextureDescriptor *seedStencilDesc = [MTLTextureDescriptor + texture2DDescriptorWithPixelFormat:MTLPixelFormatStencil8 + width:(NSUInteger)width height:(NSUInteger)height mipmapped:NO]; + seedStencilDesc.usage = MTLTextureUsageRenderTarget; + seedStencilDesc.storageMode = MTLStorageModePrivate; + seedStencilTex = [device newTextureWithDescriptor:seedStencilDesc]; + if (seedStencilTex != nil) { + seedPass.stencilAttachment.texture = seedStencilTex; + seedPass.stencilAttachment.loadAction = MTLLoadActionClear; + seedPass.stencilAttachment.storeAction = MTLStoreActionDontCare; + seedPass.stencilAttachment.clearStencil = 0; + } id seedEnc = [setupCb renderCommandEncoderWithDescriptor:seedPass]; +#ifndef CN1_USE_ARC + // renderCommandEncoderWithDescriptor: retains attachments for + // the duration of the encoded pass; drop our +1 now. + [seedStencilTex release]; +#endif [seedEnc setViewport:(MTLViewport){0.0, 0.0, (double)width, (double)height, 0.0, 1.0}]; [seedEnc setRenderPipelineState:seedState];