From f502f9c980ce8c71ac0a8e326637a7bc71b06c03 Mon Sep 17 00:00:00 2001 From: tqjxlm Date: Tue, 6 Oct 2026 14:09:10 +0100 Subject: [PATCH] Backends: Metal, Metal4: support rendering inside passes with multiple color attachments. (#9574) Key the pipeline cache on all color attachment pixel formats of the render pass descriptor passed to backend's NewFrame(). --- backends/imgui_impl_metal.mm | 32 ++++++++++++++++++++++++++------ backends/imgui_impl_metal4.mm | 32 ++++++++++++++++++++++++++------ docs/CHANGELOG.txt | 6 ++++-- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 55b33de14..43019b54f 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-10-06: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. (#9574) // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) @@ -58,14 +59,16 @@ - (instancetype)initWithBuffer:(id)buffer; @end +#define IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS 8 // Metal's limit (see Metal Feature Set Tables). Not configurable. + // An object that encapsulates the data necessary to uniquely identify a // render pipeline state. These are used as cache keys. @interface FramebufferDescriptor : NSObject @property (nonatomic, assign) unsigned long sampleCount; -@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index; @end @interface MetalTexture : NSObject @@ -477,23 +480,33 @@ void ImGui_ImplMetal_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor +{ + MTLPixelFormat _colorPixelFormats[IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS]; +} + - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; - _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + for (NSUInteger n = 0; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + _colorPixelFormats[n] = renderPassDescriptor.colorAttachments[n].texture.pixelFormat; _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } return self; } +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index +{ + return _colorPixelFormats[index]; +} + - (nonnull id)copyWithZone:(nullable NSZone*)zone { FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; copy.sampleCount = self.sampleCount; - copy.colorPixelFormat = self.colorPixelFormat; + memcpy(copy->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)); copy.depthPixelFormat = self.depthPixelFormat; copy.stencilPixelFormat = self.stencilPixelFormat; return copy; @@ -502,10 +515,12 @@ void ImGui_ImplMetal_Shutdown() - (NSUInteger)hash { NSUInteger sc = _sampleCount & 0x3; - NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger cf = _colorPixelFormats[0] & 0x3FF; NSUInteger df = _depthPixelFormat & 0x3FF; NSUInteger sf = _stencilPixelFormat & 0x3FF; NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + hash = hash * 31 + _colorPixelFormats[n]; return hash; } @@ -515,7 +530,7 @@ void ImGui_ImplMetal_Shutdown() if (![other isKindOfClass:[FramebufferDescriptor class]]) return NO; return other.sampleCount == self.sampleCount && - other.colorPixelFormat == self.colorPixelFormat && + memcmp(other->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)) == 0 && other.depthPixelFormat == self.depthPixelFormat && other.stencilPixelFormat == self.stencilPixelFormat; } @@ -661,7 +676,7 @@ void ImGui_ImplMetal_Shutdown() pipelineDescriptor.fragmentFunction = fragmentFunction; pipelineDescriptor.vertexDescriptor = vertexDescriptor; pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; - pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:0]; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; @@ -669,6 +684,11 @@ void ImGui_ImplMetal_Shutdown() pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) // Don't write to other color attachments, if any. + { + pipelineDescriptor.colorAttachments[n].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:n]; + pipelineDescriptor.colorAttachments[n].writeMask = MTLColorWriteMaskNone; + } pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 73f8c5905..2f9a53bdb 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -19,6 +19,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-10-06: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. (#9574) // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-07: Metal 4: Added metal-cpp support. (#9461) // 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) @@ -46,14 +47,16 @@ struct ImGui_Metal4_ConstantData - (instancetype)initWithBuffer:(id)buffer; @end +#define IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS 8 // Metal's limit (see Metal Feature Set Tables). Not configurable. + // An object that encapsulates the data necessary to uniquely identify a // render pipeline state. These are used as cache keys. @interface FramebufferDescriptor : NSObject @property (nonatomic, assign) unsigned long sampleCount; -@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; - (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor; +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index; @end @interface MetalTexture : NSObject @@ -514,23 +517,33 @@ void ImGui_ImplMetal4_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor +{ + MTLPixelFormat _colorPixelFormats[IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS]; +} + - (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; - _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + for (NSUInteger n = 0; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + _colorPixelFormats[n] = renderPassDescriptor.colorAttachments[n].texture.pixelFormat; _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } return self; } +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index +{ + return _colorPixelFormats[index]; +} + - (nonnull id)copyWithZone:(nullable NSZone*)zone { FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; copy.sampleCount = self.sampleCount; - copy.colorPixelFormat = self.colorPixelFormat; + memcpy(copy->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)); copy.depthPixelFormat = self.depthPixelFormat; copy.stencilPixelFormat = self.stencilPixelFormat; return copy; @@ -539,10 +552,12 @@ void ImGui_ImplMetal4_Shutdown() - (NSUInteger)hash { NSUInteger sc = _sampleCount & 0x3; - NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger cf = _colorPixelFormats[0] & 0x3FF; NSUInteger df = _depthPixelFormat & 0x3FF; NSUInteger sf = _stencilPixelFormat & 0x3FF; NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + hash = hash * 31 + _colorPixelFormats[n]; return hash; } @@ -552,7 +567,7 @@ void ImGui_ImplMetal4_Shutdown() if (![other isKindOfClass:[FramebufferDescriptor class]]) return NO; return other.sampleCount == self.sampleCount && - other.colorPixelFormat == self.colorPixelFormat && + memcmp(other->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)) == 0 && other.depthPixelFormat == self.depthPixelFormat && other.stencilPixelFormat == self.stencilPixelFormat; } @@ -719,7 +734,7 @@ fragment half4 fragment_main(VertexOut in [[stage_in]], pipelineDescriptor.fragmentFunction = fragmentFunction; pipelineDescriptor.vertexDescriptor = vertexDescriptor; pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; - pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:0]; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; @@ -727,6 +742,11 @@ fragment half4 fragment_main(VertexOut in [[stage_in]], pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) // Don't write to other color attachments, if any. + { + pipelineDescriptor.colorAttachments[n].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:n]; + pipelineDescriptor.colorAttachments[n].writeMask = MTLColorWriteMaskNone; + } pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9f595ba55..12c9de64c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -152,6 +152,9 @@ Other Changes: - Style Editor: fixed style/colors combo box applying style when opening Combo. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - Metal, Metal4: added support for rendering inside a render pass with multiple color + attachments (e.g. when merged with other passes): we render to the first color attachment + and leave the other ones untouched. (#9574) [@tqjxlm] - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw callback to request using the sampler associated already associated to a texture. (#9378) - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states @@ -173,8 +176,7 @@ Other Changes: - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - Vulkan: added support for rendering inside a render pass with multiple color attachments (e.g. when merged with other passes): we render to the first color attachment and leave the - other ones untouched. (with dynamic rendering, `PipelineRenderingCreateInfo` may describe multiple - color attachments. This requires the `independentBlend` device feature). (#9574) [@tqjxlm] + other ones untouched. (#9574) [@tqjxlm] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev]