From 3539f756d986ac1233e390680df5b9cc871881db Mon Sep 17 00:00:00 2001 From: Untold Engine Date: Wed, 21 Oct 2026 06:06:56 -0700 Subject: [PATCH] [Patch] Fix IBL creation failure fallback and spurious bake attempts Zero-fill IBL textures when generateHDR fails, instead of leaving uninitialized/garbage data bound to the render pass, so a failed bake is ignored rather than turning models black. Also skip the bake attempt entirely when hdrURL is empty (no environment configured), which stops the spurious iBLCreationFailed error previously logged on unrelated viewport resizes after loading an HDR-less scene. Fixes #1380 --- .../Renderer/RenderInitializer.swift | 45 +++++++++++++++++++ Sources/UntoldEngine/Utils/FuncUtils.swift | 3 ++ 2 files changed, 48 insertions(+) diff --git a/Sources/UntoldEngine/Renderer/RenderInitializer.swift b/Sources/UntoldEngine/Renderer/RenderInitializer.swift index 28b015e05..c6c44a8be 100644 --- a/Sources/UntoldEngine/Renderer/RenderInitializer.swift +++ b/Sources/UntoldEngine/Renderer/RenderInitializer.swift @@ -110,6 +110,30 @@ func createTexture( return texture } +/// Clears an IBL lookup texture (irradiance/specular/BRDF map) to all-zero so a failed or +/// skipped bake leaves it contributing nothing to shading, instead of the uninitialized +/// garbage Metal may leave in a freshly allocated `.shared` texture. IBL maps are always +/// `.rgba16Float` (see `WorkingColorFormats`), 8 bytes/pixel. +func zeroFillIBLTexture(_ texture: MTLTexture?) { + guard let texture else { return } + let bytesPerPixel = 8 + var mipWidth = texture.width + var mipHeight = texture.height + let zeroBuffer = [UInt8](repeating: 0, count: mipWidth * mipHeight * bytesPerPixel) + for level in 0 ..< texture.mipmapLevelCount { + zeroBuffer.withUnsafeBytes { bytes in + texture.replace( + region: MTLRegionMake2D(0, 0, mipWidth, mipHeight), + mipmapLevel: level, + withBytes: bytes.baseAddress!, + bytesPerRow: mipWidth * bytesPerPixel + ) + } + mipWidth = max(1, mipWidth / 2) + mipHeight = max(1, mipHeight / 2) + } +} + @inline(__always) func requestedOpaqueSampleCount() -> Int { #if targetEnvironment(simulator) @@ -1352,8 +1376,29 @@ func initIBLResources() { renderInfo.iblOffscreenRenderPassDescriptor.colorAttachments[2].texture = textureResources.iblBRDFMap + guard !hdrURL.isEmpty else { + // No environment is configured for the current scene (e.g. deserializeScene cleared + // hdrURL because the scene has no HDR). This is not a failure — there is simply + // nothing to bake — so skip generateHDR entirely rather than attempting it and + // logging a spurious iBLCreationFailed error on the next resize/init. + zeroFillIBLTexture(textureResources.irradianceMap) + zeroFillIBLTexture(textureResources.specularMap) + zeroFillIBLTexture(textureResources.iblBRDFMap) + iblSuccessful = false + return + } + generateHDR(hdrURL, from: hdrDirectory) + if !iblSuccessful { + // The bake failed partway through: the maps above may hold uninitialized data from + // `createTexture`, or partially-written data from an incomplete prefilter pass. Zero + // them so a failed IBL bake is ignored rather than corrupting shading with garbage. + zeroFillIBLTexture(textureResources.irradianceMap) + zeroFillIBLTexture(textureResources.specularMap) + zeroFillIBLTexture(textureResources.iblBRDFMap) + } + if iblSuccessful, let cacheKey, let irradianceMap = textureResources.irradianceMap, let specularMap = textureResources.specularMap, diff --git a/Sources/UntoldEngine/Utils/FuncUtils.swift b/Sources/UntoldEngine/Utils/FuncUtils.swift index 2b1cc09ad..6d961c742 100644 --- a/Sources/UntoldEngine/Utils/FuncUtils.swift +++ b/Sources/UntoldEngine/Utils/FuncUtils.swift @@ -344,6 +344,7 @@ public func generateHDR(_ hdrName: String, from directory: URL? = nil) { guard let envMipMapCommandBuffer: MTLCommandBuffer = renderInfo.commandQueue.makeCommandBuffer() else { handleError(.iblMipMapCreationFailed) + iblSuccessful = false return } @@ -352,6 +353,7 @@ public func generateHDR(_ hdrName: String, from directory: URL? = nil) { envMipMapCommandBuffer.makeBlitCommandEncoder() else { handleError(.iblMipMapBlitCreationFailed) + iblSuccessful = false return } @@ -371,6 +373,7 @@ public func generateHDR(_ hdrName: String, from directory: URL? = nil) { let iblPreFilterCommandBuffer: MTLCommandBuffer = renderInfo.commandQueue.makeCommandBuffer() else { handleError(.iblPreFilterCreationFailed) + iblSuccessful = false return }