diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cc7cfeb2a5..24fa51b8b0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,6 +9,7 @@ A new header is inserted each time a *tag* is created. - Added support for the latest version of emscripten. - gltfio: fixed blackness seen with default material. - Added ETC2 and BC compressed texture support to Metal backend. +- Rendering a SAMPLER_EXTERNAL texture before setting an external image no longer results in GPU errors. ## v1.4.2 diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index 8341c5f9ad..ba8fa48de6 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -85,6 +85,9 @@ struct MetalContext { // External textures. CVMetalTextureCacheRef textureCache = nullptr; + // Empty texture used to prevent GPU errors when a sampler has been bound without a texture. + id emptyTexture = nil; + MetalBlitter* blitter = nullptr; // Fences. @@ -101,6 +104,8 @@ id acquireDrawable(MetalContext* context); id acquireCommandBuffer(MetalContext* context); +id getOrCreateEmptyTexture(MetalContext* context); + } // namespace metal } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalContext.mm b/filament/backend/src/metal/MetalContext.mm index 5004276c60..9faa50bb4c 100644 --- a/filament/backend/src/metal/MetalContext.mm +++ b/filament/backend/src/metal/MetalContext.mm @@ -63,6 +63,29 @@ id acquireCommandBuffer(MetalContext* context) { return commandBuffer; } +id getOrCreateEmptyTexture(MetalContext* context) { + if (context->emptyTexture) { + return context->emptyTexture; + } + + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new]; + textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; + textureDescriptor.width = 1; + textureDescriptor.height = 1; + id texture = [context->device newTextureWithDescriptor:textureDescriptor]; + + MTLRegion region = { + { 0, 0, 0 }, // MTLOrigin + { 1, 1, 1 } // MTLSize + }; + uint8_t imageData[4] = {0, 0, 0, 0}; + [texture replaceRegion:region mipmapLevel:0 withBytes:imageData bytesPerRow:4]; + + context->emptyTexture = texture; + + return context->emptyTexture; +} + } // namespace metal } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 121eb9026f..93400c879a 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -73,6 +73,7 @@ MetalDriver::MetalDriver(backend::MetalPlatform* platform) noexcept MetalDriver::~MetalDriver() noexcept { mContext->device = nil; + mContext->emptyTexture = nil; CFRelease(mContext->textureCache); delete mContext->bufferPool; delete mContext->blitter; @@ -908,6 +909,12 @@ void MetalDriver::draw(backend::PipelineState ps, Handle rph) texturesToBind[binding] = metalTexture->externalImage.getMetalTextureForDraw(); } + if (!texturesToBind[binding]) { + utils::slog.w << "Warning: no texture bound at binding point " << (size_t) binding + << "." << utils::io::endl; + texturesToBind[binding] = getOrCreateEmptyTexture(mContext); + } + id samplerState = mContext->samplerStateCache.getOrCreateState(sampler->s); samplersToBind[binding] = samplerState; });