diff --git a/renderdoc/api/replay/pipestate.inl b/renderdoc/api/replay/pipestate.inl index 23f69409c..92f70e465 100644 --- a/renderdoc/api/replay/pipestate.inl +++ b/renderdoc/api/replay/pipestate.inl @@ -1344,8 +1344,8 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, ret.back().firstIndex = (int32_t)firstIdx; } - val.reserve(val.size() + (count - firstIdx)); - for(size_t j = firstIdx; j < count; ++j) + val.reserve(val.size() + count); + for(size_t j = firstIdx; j < firstIdx + count; ++j) { const D3D12Pipe::View &view = element.views[j]; if(view.bind >= start && view.bind < end) @@ -1431,7 +1431,6 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, ret.back().dynamicallyUsedCount = 0; BoundResource res; - val.reserve(val.size() + (count - firstIdx)); for(uint32_t i = firstIdx; i < firstIdx + count; i++) { if(bind.binds[i].type == BindType::ImageSampler || @@ -1449,7 +1448,16 @@ rdcarray PipeState::GetReadOnlyResources(ShaderStage stage, if(bind.binds[i].dynamicallyUsed) ret.back().dynamicallyUsedCount++; } + else + { + // push empty resources so array indexing is still as expected + val.push_back(BoundResource()); + } } + + // if we didn't find any resources this is probably not a read-write bind, remove it + if(ret.back().dynamicallyUsedCount == 0) + ret.pop_back(); } } } @@ -1558,8 +1566,8 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, ret.back().firstIndex = (int32_t)firstIdx; } - val.reserve(val.size() + (count - firstIdx)); - for(size_t j = firstIdx; j < count; ++j) + val.reserve(val.size() + count); + for(size_t j = firstIdx; j < firstIdx + count; ++j) { const D3D12Pipe::View &view = element.views[j]; if(view.bind >= start && view.bind < end) @@ -1662,7 +1670,6 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, ret.back().dynamicallyUsedCount = 0; BoundResource res; - val.reserve(val.size() + (count - firstIdx)); for(uint32_t i = firstIdx; i < firstIdx + count; i++) { if(bind.binds[i].type == BindType::ReadWriteBuffer || @@ -1679,7 +1686,16 @@ rdcarray PipeState::GetReadWriteResources(ShaderStage stage, if(bind.binds[i].dynamicallyUsed) ret.back().dynamicallyUsedCount++; } + else + { + // push empty resources so array indexing is still as expected + val.push_back(BoundResource()); + } } + + // if we didn't find any resources this is probably not a read-write bind, remove it + if(ret.back().dynamicallyUsedCount == 0) + ret.pop_back(); } } } diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 00acf5114..3b3123e63 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -1317,7 +1317,11 @@ void D3D12Replay::FillRootElements(uint32_t eventId, const D3D12RenderState::Roo element.type = BindType::ReadWriteResource; if(usage.valid) + { + element.firstUsedIndex = -1; + element.lastUsedIndex = -1; element.dynamicallyUsedCount = 0; + } element.views.reserve(num); @@ -1387,6 +1391,13 @@ void D3D12Replay::FillRootElements(uint32_t eventId, const D3D12RenderState::Roo desc++; } } + + // if no bindings were set these will still be negative. Set them to something sensible. + if(element.firstUsedIndex < 0) + { + element.firstUsedIndex = 0; + element.lastUsedIndex = 0x7fffffff; + } } } } diff --git a/util/test/rdtest/testcase.py b/util/test/rdtest/testcase.py index 9e2d74923..94727cd2b 100644 --- a/util/test/rdtest/testcase.py +++ b/util/test/rdtest/testcase.py @@ -204,6 +204,9 @@ class TestCase: else: raise TestFailureException('Assertion Failure: {}'.format(msg)) + def check_eq(self, a, b): + self.check(a == b, '{} != {}'.format(a, b)) + def get_replay_options(self): """ Method to overload if you want to override the replay options used. diff --git a/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py b/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py index 9be5089f0..3c85ae463 100644 --- a/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py +++ b/util/test/tests/D3D12/D3D12_Descriptor_Indexing.py @@ -33,6 +33,28 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): if root.views[i].dynamicallyUsed: raise rdtest.TestFailureException("Compute root range 0[{}] i dynamically used".format(i)) + # these lists are only expected to be empty because the descriptors aren't written to, and so with mutable + # consideration these aren't considered read-only with unknown contents + pipe = self.controller.GetPipelineState() + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Compute, False) + self.check_eq(ro, []) + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Compute, True) + self.check_eq(ro, []) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Compute, False) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 0) + self.check_eq(len(rw[0].resources), 32) + self.check(rw[0].resources[15].dynamicallyUsed) + self.check_eq(rw[0].resources[15].resourceId, root.views[15].resourceId) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Compute, True) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 15) + self.check_eq(len(rw[0].resources), 1) + self.check_eq(rw[0].resources[0].resourceId, root.views[15].resourceId) + self.check(rw[0].resources[0].dynamicallyUsed) + def check_capture(self): for sm in ["sm_5_1", "sm_6_0", "sm_6_6"]: @@ -162,4 +184,57 @@ class D3D12_Descriptor_Indexing(rdtest.TestCase): raise rdtest.TestFailureException( "Sampler {} expected to be used, but isn't.".format(elemId)) + pipe = self.controller.GetPipelineState() + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel, False) + self.check_eq(len(ro), 5) + self.check_eq(ro[0].dynamicallyUsedCount, 1) + self.check_eq(ro[0].firstIndex, 0) + self.check_eq(len(ro[0].resources), 1) + self.check_eq(ro[1].dynamicallyUsedCount, 1) + self.check_eq(ro[1].firstIndex, 0) + self.check_eq(len(ro[1].resources), 1) + self.check_eq(ro[2].dynamicallyUsedCount, 3) + self.check_eq(ro[2].firstIndex, 0) + self.check_eq(len(ro[2].resources), 32) + self.check_eq(ro[3].dynamicallyUsedCount, 3) + self.check_eq(ro[3].firstIndex, 0) + self.check_eq(len(ro[3].resources), 32) + self.check_eq(ro[4].dynamicallyUsedCount, 2) + self.check_eq(ro[4].firstIndex, 0) + self.check_eq(len(ro[4].resources), 32) + self.check(not ro[2].resources[18].dynamicallyUsed) + self.check(ro[2].resources[19].dynamicallyUsed) + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel, True) + self.check_eq(len(ro), 5) + self.check_eq(ro[0].dynamicallyUsedCount, 1) + self.check_eq(ro[0].firstIndex, 0) + self.check_eq(len(ro[0].resources), 1) + self.check_eq(ro[1].dynamicallyUsedCount, 1) + self.check_eq(ro[1].firstIndex, 0) + self.check_eq(len(ro[1].resources), 1) + self.check_eq(ro[2].dynamicallyUsedCount, 3) + self.check_eq(ro[2].firstIndex, 19) + # this contains more resources after the dynamically used ones because of the mismatch between + # root signature elements and bindings + self.check_eq(len(ro[2].resources), 32-8) + self.check_eq(ro[3].dynamicallyUsedCount, 3) + self.check_eq(ro[3].firstIndex, 0) + # similar to above + self.check_eq(len(ro[3].resources), 32) + self.check_eq(ro[4].dynamicallyUsedCount, 2) + self.check_eq(ro[4].firstIndex, 0) + self.check_eq(len(ro[4].resources), 103-80+1) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Pixel, False) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 0) + self.check_eq(len(rw[0].resources), 32) + self.check(rw[0].resources[15].dynamicallyUsed) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Pixel, True) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 15) + self.check_eq(len(rw[0].resources), 1) + self.check(rw[0].resources[0].dynamicallyUsed) + rdtest.log.success("Dynamic usage is as expected for {}".format(sm)) diff --git a/util/test/tests/Vulkan/VK_Descriptor_Indexing.py b/util/test/tests/Vulkan/VK_Descriptor_Indexing.py index c6d59d1a6..33ced052c 100644 --- a/util/test/tests/Vulkan/VK_Descriptor_Indexing.py +++ b/util/test/tests/Vulkan/VK_Descriptor_Indexing.py @@ -11,13 +11,13 @@ class VK_Descriptor_Indexing(rdtest.TestCase): self.check(action is not None) self.controller.SetFrameEvent(action.eventId, False) - pipe: rd.VKState = self.controller.GetVulkanPipelineState() + vkpipe: rd.VKState = self.controller.GetVulkanPipelineState() - if len(pipe.compute.descriptorSets) != 1: + if len(vkpipe.compute.descriptorSets) != 1: raise rdtest.TestFailureException("Wrong number of compute sets is bound: {}, not 1" - .format(len(pipe.compute.descriptorSets))) + .format(len(vkpipe.compute.descriptorSets))) - binding = pipe.compute.descriptorSets[0].bindings[0] + binding = vkpipe.compute.descriptorSets[0].bindings[0] if binding.dynamicallyUsedCount != 1: raise rdtest.TestFailureException("Compute bind 0 doesn't have the right used count {}" @@ -26,11 +26,33 @@ class VK_Descriptor_Indexing(rdtest.TestCase): if not binding.binds[15].dynamicallyUsed: raise rdtest.TestFailureException("Compute bind 0[15] isn't dynamically used") + # these lists are only expected to be empty because the descriptors aren't written to, and so with mutable + # consideration these aren't considered read-only with unknown contents + pipe = self.controller.GetPipelineState() + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Compute, False) + self.check_eq(ro, []) + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Compute, True) + self.check_eq(ro, []) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Compute, False) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 0) + self.check_eq(len(rw[0].resources), 128) + self.check(rw[0].resources[15].dynamicallyUsed) + self.check_eq(rw[0].resources[15].resourceId, binding.binds[15].resourceResourceId) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Compute, True) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 15) + self.check_eq(len(rw[0].resources), 1) + self.check_eq(rw[0].resources[0].resourceId, binding.binds[15].resourceResourceId) + self.check(rw[0].resources[0].dynamicallyUsed) + action = self.find_action("Draw") self.check(action is not None) self.controller.SetFrameEvent(action.eventId, False) - pipe: rd.VKState = self.controller.GetVulkanPipelineState() + vkpipe: rd.VKState = self.controller.GetVulkanPipelineState() # Check bindings: # - buffer 15 in bind 0 should be used @@ -44,11 +66,11 @@ class VK_Descriptor_Indexing(rdtest.TestCase): 2: { 'dynamicallyUsedCount': 2, 'used': [381, 386] }, } - if len(pipe.graphics.descriptorSets) != 1: + if len(vkpipe.graphics.descriptorSets) != 1: raise rdtest.TestFailureException("Wrong number of sets is bound: {}, not 1" - .format(len(pipe.graphics.descriptorSets))) + .format(len(vkpipe.graphics.descriptorSets))) - desc_set: rd.VKDescriptorSet = pipe.graphics.descriptorSets[0] + desc_set: rd.VKDescriptorSet = vkpipe.graphics.descriptorSets[0] binding: rd.VKDescriptorBinding for bind, binding in enumerate(desc_set.bindings): @@ -67,4 +89,38 @@ class VK_Descriptor_Indexing(rdtest.TestCase): if not expected_used and actually_used: raise rdtest.TestFailureException("Bind {} element {} expected to be unused, but is.".format(bind, idx)) + pipe = self.controller.GetPipelineState() + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel, False) + self.check_eq(len(ro), 2) + self.check_eq(ro[0].dynamicallyUsedCount, 6) + self.check_eq(ro[0].firstIndex, 0) + self.check_eq(len(ro[0].resources), 128) + self.check_eq(ro[1].dynamicallyUsedCount, 2) + self.check_eq(ro[1].firstIndex, 0) + self.check_eq(len(ro[1].resources), 512) + self.check(not ro[0].resources[18].dynamicallyUsed) + self.check(ro[0].resources[19].dynamicallyUsed) + ro = pipe.GetReadOnlyResources(rd.ShaderStage.Pixel, True) + self.check_eq(len(ro), 2) + self.check_eq(ro[0].dynamicallyUsedCount, 6) + self.check_eq(ro[0].firstIndex, 4) + self.check_eq(len(ro[0].resources), 56) + self.check_eq(ro[1].dynamicallyUsedCount, 2) + self.check_eq(ro[1].firstIndex, 381) + self.check_eq(len(ro[1].resources), 6) + self.check(not ro[0].resources[14].dynamicallyUsed) + self.check(ro[0].resources[15].dynamicallyUsed) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Pixel, False) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 0) + self.check_eq(len(rw[0].resources), 128) + self.check(rw[0].resources[15].dynamicallyUsed) + + rw = pipe.GetReadWriteResources(rd.ShaderStage.Pixel, True) + self.check_eq(rw[0].dynamicallyUsedCount, 1) + self.check_eq(rw[0].firstIndex, 15) + self.check_eq(len(rw[0].resources), 1) + self.check(rw[0].resources[0].dynamicallyUsed) + rdtest.log.success("Dynamic usage is as expected")