diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 55b33de14..cf9eaed21 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-09-30: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. // 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 + // 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..a1de5d5c1 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-09-30: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. // 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 + // 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/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 80b9d58ab..fa2815cd7 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -27,6 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-30: Added support for dynamic rendering with multiple color attachments (PipelineRenderingCreateInfo.colorAttachmentCount > 1): we render to the first one and leave the others untouched. // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -1062,7 +1063,14 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; ms_info.rasterizationSamples = (info->MSAASamples != 0) ? info->MSAASamples : VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState color_attachment[1] = {}; + // Blend into the first color attachment. With dynamic rendering, other color attachments of the rendering are left untouched (requires independentBlend feature). + int color_attachment_count = 1; +#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING + if (bd->VulkanInitInfo.UseDynamicRendering && info->PipelineRenderingCreateInfo.colorAttachmentCount > 1) + color_attachment_count = (int)info->PipelineRenderingCreateInfo.colorAttachmentCount; +#endif + ImVector color_attachment; + color_attachment.resize(color_attachment_count, VkPipelineColorBlendAttachmentState()); // colorWriteMask = 0 color_attachment[0].blendEnable = VK_TRUE; color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; @@ -1077,8 +1085,8 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc VkPipelineColorBlendStateCreateInfo blend_info = {}; blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - blend_info.attachmentCount = 1; - blend_info.pAttachments = color_attachment; + blend_info.attachmentCount = (uint32_t)color_attachment.Size; + blend_info.pAttachments = color_attachment.Data; ImVector dynamic_states = info->ExtraDynamicStates; dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 0f2da5e2d..44c76c9a0 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -101,6 +101,7 @@ struct ImGui_ImplVulkan_PipelineInfo // - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you. // - About dynamic rendering: // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. +// - PipelineRenderingCreateInfo may describe multiple color attachments: we render to the first one and leave the others untouched. This requires the 'independentBlend' device feature. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 203922ab1..e21d05053 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -152,6 +152,13 @@ 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, 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. [@tqjxlm] + - Metal, Metal4: pipelines are keyed on all color attachment formats of the render pass + descriptor passed to `ImGui_ImplMetal_NewFrame()`/`ImGui_ImplMetal4_NewFrame()`. + - Vulkan: with dynamic rendering, `PipelineRenderingCreateInfo` may describe multiple color + attachments. This requires the `independentBlend` device feature. - 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