From 541f62617f90205e0d9172c5f639fde81f4ad4c6 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Fri, 9 Jan 2026 13:23:27 +1100 Subject: [PATCH] Metal: Fix dynamic uniform buffer offset corruption when rebinding sets When the same uniform set is bound multiple times within a render pass (e.g., OPAQUE pass then ALPHA pass with different instance buffers), the dynamic offset bits were being OR'd together, corrupting the packed frame indices. For example, if OPAQUE binds set 1 with frame_idx=1 (0x10) and ALPHA binds set 1 with frame_idx=2 (0x20), the OR produces 0x30 instead of the correct 0x20, causing the shader to read from the wrong buffer offset. This fix clears the relevant bits for each set being bound before OR'ing the new values, ensuring only the latest frame indices are used. Fixes rendering artifacts in the mobile renderer which uses two dynamic uniforms (uniform buffer + storage buffer) in set 1, unlike the clustered renderer which only has one. --- drivers/metal/metal_objects.mm | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm index e65d915484..76a3f67d0d 100644 --- a/drivers/metal/metal_objects.mm +++ b/drivers/metal/metal_objects.mm @@ -722,8 +722,6 @@ void MDCommandBuffer::encodeRenderCommandEncoderWithDescriptor(MTLRenderPassDesc void MDCommandBuffer::render_bind_uniform_sets(VectorView p_uniform_sets, RDD::ShaderID p_shader, uint32_t p_first_set_index, uint32_t p_set_count, uint32_t p_dynamic_offsets) { DEV_ASSERT(type == MDCommandBufferStateType::Render); - render.dynamic_offsets |= p_dynamic_offsets; - if (uint32_t new_size = p_first_set_index + p_set_count; render.uniform_sets.size() < new_size) { uint32_t s = render.uniform_sets.size(); render.uniform_sets.resize(new_size); @@ -734,6 +732,20 @@ void MDCommandBuffer::render_bind_uniform_sets(VectorView p_u const MDShader *shader = (const MDShader *)p_shader.id; DynamicOffsetLayout layout = shader->dynamic_offset_layout; + // Clear bits for sets being rebound before OR'ing new values. + // This prevents corruption when the same set is bound multiple times + // with different frame indices (e.g., OPAQUE pass then ALPHA pass). + for (uint32_t i = 0; i < p_set_count && render.dynamic_offsets != 0; i++) { + uint32_t set_index = p_first_set_index + i; + uint32_t count = layout.get_count(set_index); + if (count > 0) { + uint32_t shift = layout.get_offset_index_shift(set_index); + uint32_t mask = ((1u << (count * 4u)) - 1u) << shift; + render.dynamic_offsets &= ~mask; + } + } + render.dynamic_offsets |= p_dynamic_offsets; + for (size_t i = 0; i < p_set_count; ++i) { MDUniformSet *set = (MDUniformSet *)(p_uniform_sets[i].id); @@ -1620,8 +1632,6 @@ void MDCommandBuffer::ComputeState::reset() { void MDCommandBuffer::compute_bind_uniform_sets(VectorView p_uniform_sets, RDD::ShaderID p_shader, uint32_t p_first_set_index, uint32_t p_set_count, uint32_t p_dynamic_offsets) { DEV_ASSERT(type == MDCommandBufferStateType::Compute); - compute.dynamic_offsets |= p_dynamic_offsets; - if (uint32_t new_size = p_first_set_index + p_set_count; compute.uniform_sets.size() < new_size) { uint32_t s = compute.uniform_sets.size(); compute.uniform_sets.resize(new_size); @@ -1632,6 +1642,20 @@ void MDCommandBuffer::compute_bind_uniform_sets(VectorView p_ const MDShader *shader = (const MDShader *)p_shader.id; DynamicOffsetLayout layout = shader->dynamic_offset_layout; + // Clear bits for sets being rebound before OR'ing new values. + // This prevents corruption when the same set is bound multiple times + // with different frame indices. + for (uint32_t i = 0; i < p_set_count && compute.dynamic_offsets != 0; i++) { + uint32_t set_index = p_first_set_index + i; + uint32_t count = layout.get_count(set_index); + if (count > 0) { + uint32_t shift = layout.get_offset_index_shift(set_index); + uint32_t mask = ((1u << (count * 4u)) - 1u) << shift; + compute.dynamic_offsets &= ~mask; + } + } + compute.dynamic_offsets |= p_dynamic_offsets; + for (size_t i = 0; i < p_set_count; ++i) { MDUniformSet *set = (MDUniformSet *)(p_uniform_sets[i].id);