Fix: Properly capture outputTexture in completion handler
Some checks are pending
Auto Tag on Push / auto-tag (push) Waiting to run

This commit is contained in:
ewen 2026-01-16 00:21:06 +01:00
parent 87e3d99290
commit b2ffd9edaf

View file

@ -108,16 +108,12 @@ final class MetalImageRenderer {
if params.algorithm == 7, let pipe1 = pipelineStateFS_Pass1, let pipe2 = pipelineStateFS_Pass2 { if params.algorithm == 7, let pipe1 = pipelineStateFS_Pass1, let pipe2 = pipelineStateFS_Pass2 {
print("🔄 Using Floyd-Steinberg two-pass rendering") print("🔄 Using Floyd-Steinberg two-pass rendering")
// FLOYD-STEINBERG MULTI-PASS
// Create Error Texture (Float16 or Float32 for precision)
let errorDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float, let errorDesc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float,
width: inputTexture.width, width: inputTexture.width,
height: inputTexture.height, height: inputTexture.height,
mipmapped: false) mipmapped: false)
errorDesc.usage = [.shaderWrite, .shaderRead] errorDesc.usage = [.shaderWrite, .shaderRead]
// CRITICAL: Use autoreleasepool check for error texture too
guard let errorTexture = device.makeTexture(descriptor: errorDesc) else { guard let errorTexture = device.makeTexture(descriptor: errorDesc) else {
computeEncoder.endEncoding() computeEncoder.endEncoding()
continuation.resume(returning: nil) continuation.resume(returning: nil)
@ -131,14 +127,11 @@ final class MetalImageRenderer {
computeEncoder.setTexture(errorTexture, index: 2) computeEncoder.setTexture(errorTexture, index: 2)
computeEncoder.setBytes(&params, length: MemoryLayout<RenderParameters>.stride, index: 0) computeEncoder.setBytes(&params, length: MemoryLayout<RenderParameters>.stride, index: 0)
// Dispatch (1, H/2, 1) -> Each thread handles one full row
let h = (inputTexture.height + 1) / 2 let h = (inputTexture.height + 1) / 2
let threadsPerGrid = MTLSizeMake(1, h, 1) let threadsPerGrid = MTLSizeMake(1, h, 1)
let threadsPerThreadgroup = MTLSizeMake(1, min(h, pipe1.maxTotalThreadsPerThreadgroup), 1) let threadsPerThreadgroup = MTLSizeMake(1, min(h, pipe1.maxTotalThreadsPerThreadgroup), 1)
computeEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) computeEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup)
// Memory Barrier (Ensure Pass 1 writes are visible to Pass 2)
computeEncoder.memoryBarrier(scope: .textures) computeEncoder.memoryBarrier(scope: .textures)
// PASS 2: Odd Rows // PASS 2: Odd Rows
@ -153,7 +146,6 @@ final class MetalImageRenderer {
} else { } else {
print("🔄 Using standard dithering algorithm") print("🔄 Using standard dithering algorithm")
// STANDARD ALGORITHMS
computeEncoder.setComputePipelineState(pipelineState) computeEncoder.setComputePipelineState(pipelineState)
computeEncoder.setTexture(inputTexture, index: 0) computeEncoder.setTexture(inputTexture, index: 0)
computeEncoder.setTexture(outputTexture, index: 1) computeEncoder.setTexture(outputTexture, index: 1)
@ -169,30 +161,28 @@ final class MetalImageRenderer {
computeEncoder.endEncoding() computeEncoder.endEncoding()
// Add completion handler properly inside the closure // CRITICAL FIX: Capture outputTexture dans les deux closures
commandBuffer.addCompletedHandler { [weak self] buffer in commandBuffer.addCompletedHandler { [outputTexture] buffer in
// CRITICAL: Dispatch back to MainActor because self (MetalImageRenderer) is isolated // Metal completion s'exécute sur com.Metal.CompletionQueueDispatch
// and createCGImage is isolated to MainActor. // Dispatch vers MainActor car self et createCGImage() sont @MainActor
Task { @MainActor in Task { @MainActor [outputTexture] in
guard let self = self else { if let error = buffer.error {
print("❌ Metal command buffer error: \(error)")
continuation.resume(returning: nil) continuation.resume(returning: nil)
return return
} }
if let error = buffer.error {
print("❌ Metal command buffer error: \(error)")
continuation.resume(returning: nil)
} else {
print("✅ Metal render completed successfully") print("✅ Metal render completed successfully")
// Now we are on MainActor, we can safely call self.createCGImage
// Maintenant on est sur MainActor ET outputTexture est capturée
let result = self.createCGImage(from: outputTexture) let result = self.createCGImage(from: outputTexture)
if result == nil { if result == nil {
print("❌ Failed to create CGImage from output texture") print("❌ Failed to create CGImage from output texture")
} }
continuation.resume(returning: result) continuation.resume(returning: result)
} }
} }
}
commandBuffer.commit() commandBuffer.commit()
} }