From 8ca1aa2c8da3304116041a80fcc0bf3428ad2503 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 19 Jun 2026 13:46:33 +0100 Subject: [PATCH] Expose memory bindings for buffers/textures on Vulkan and D3D12 * This only applies to explicit memory binding APIs - so D3D11 and GL are excluded. On D3D12, committed resources will not have anything listed here. --- renderdoc/api/replay/data_types.h | 43 ++++++++++++++++- renderdoc/driver/d3d12/d3d12_replay.cpp | 34 +++++++++++-- renderdoc/driver/d3d12/d3d12_resources.h | 3 ++ renderdoc/driver/vulkan/vk_info.h | 3 ++ renderdoc/driver/vulkan/vk_replay.cpp | 48 ++++++++++++++++--- .../vulkan/wrappers/vk_resource_funcs.cpp | 6 +++ renderdoc/replay/renderdoc_serialise.inl | 8 +++- 7 files changed, 130 insertions(+), 15 deletions(-) diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index b746a81aa..1d4308b3e 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -975,7 +975,8 @@ struct BufferDescription bool operator==(const BufferDescription &o) const { return resourceId == o.resourceId && creationFlags == o.creationFlags && - gpuAddress == o.gpuAddress && length == o.length; + gpuAddress == o.gpuAddress && length == o.length && memory == o.memory && + memoryOffset == o.memoryOffset; } bool operator<(const BufferDescription &o) const { @@ -987,6 +988,10 @@ struct BufferDescription return gpuAddress < o.gpuAddress; if(!(length == o.length)) return length < o.length; + if(!(memory == o.memory)) + return memory < o.memory; + if(!(memoryOffset == o.memoryOffset)) + return memoryOffset < o.memoryOffset; return false; } DOCUMENT(R"(The unique :class:`ResourceId` that identifies this buffer. @@ -1012,6 +1017,21 @@ struct BufferDescription :type: int )"); uint64_t length = 0; + + DOCUMENT(R"(The unique :class:`ResourceId` that identifies the memory object this +buffer has a fixed binding to. For objects that do not have an explicit memory binding +or for sparse buffers, this will not be set. + +:type: ResourceId +)"); + ResourceId memory; + + DOCUMENT(R"(The byte offset in :data:`memory` where the buffer is bound. If +:data:`memory` is unset, this will be 0. + +:type: int +)"); + uint64_t memoryOffset = 0; }; DECLARE_REFLECTION_STRUCT(BufferDescription); @@ -1035,7 +1055,7 @@ struct TextureDescription height == o.height && depth == o.depth && resourceId == o.resourceId && cubemap == o.cubemap && mips == o.mips && arraysize == o.arraysize && creationFlags == o.creationFlags && msQual == o.msQual && msSamp == o.msSamp && - byteSize == o.byteSize; + byteSize == o.byteSize && memory == o.memory && memoryOffset == o.memoryOffset; } bool operator<(const TextureDescription &o) const { @@ -1067,6 +1087,10 @@ struct TextureDescription return msSamp < o.msSamp; if(!(byteSize == o.byteSize)) return byteSize < o.byteSize; + if(!(memory == o.memory)) + return memory < o.memory; + if(!(memoryOffset == o.memoryOffset)) + return memoryOffset < o.memoryOffset; return false; } DOCUMENT(R"(The format of each pixel in the texture. @@ -1152,6 +1176,21 @@ struct TextureDescription :type: int )"); uint64_t byteSize; + + DOCUMENT(R"(The unique :class:`ResourceId` that identifies the memory object this +texture has a fixed binding to. For objects that do not have an explicit memory binding +or for sparse textures, this will not be set. + +:type: ResourceId +)"); + ResourceId memory; + + DOCUMENT(R"(The byte offset in :data:`memory` where the texture is bound. If +:data:`memory` is unset, this will be 0. + +:type: int +)"); + uint64_t memoryOffset = 0; }; DECLARE_REFLECTION_STRUCT(TextureDescription); diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 9376d7703..4f2689710 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -432,6 +432,12 @@ BufferDescription D3D12Replay::GetBuffer(ResourceId id) ret.creationFlags = BufferCategory::NoFlags; ret.gpuAddress = it->second->GetOriginalVA(); + if(it->second->GetHeap()) + { + ret.memory = it->second->GetHeap()->GetResourceID(); + ret.memoryOffset = it->second->GetHeapOffset(); + } + const rdcarray &usage = m_pDevice->GetQueue()->GetUsage(id); for(size_t i = 0; i < usage.size(); i++) @@ -479,11 +485,29 @@ TextureDescription D3D12Replay::GetTexture(ResourceId id) ret.mips = desc.MipLevels; ret.msQual = desc.SampleDesc.Quality; ret.msSamp = RDCMAX(1U, desc.SampleDesc.Count); - ret.byteSize = 0; - for(uint32_t i = 0; i < ret.mips; i++) - ret.byteSize += GetByteSize(ret.width, ret.height, ret.depth, desc.Format, i); - ret.byteSize *= ret.arraysize; - ret.byteSize *= ret.msSamp; + + if(desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + ret.byteSize = desc.Width; + } + else + { + ret.byteSize = m_pDevice->GetResourceAllocationInfo(0, 1, &desc).SizeInBytes; + } + + if(ret.byteSize == 0) + { + for(uint32_t i = 0; i < ret.mips; i++) + ret.byteSize += GetByteSize(ret.width, ret.height, ret.depth, desc.Format, i); + ret.byteSize *= ret.arraysize; + ret.byteSize *= ret.msSamp; + } + + if(it->second->GetHeap()) + { + ret.memory = it->second->GetHeap()->GetResourceID(); + ret.memoryOffset = it->second->GetHeapOffset(); + } switch(ret.dimension) { diff --git a/renderdoc/driver/d3d12/d3d12_resources.h b/renderdoc/driver/d3d12/d3d12_resources.h index 86d17cb68..0ff82f73a 100644 --- a/renderdoc/driver/d3d12/d3d12_resources.h +++ b/renderdoc/driver/d3d12/d3d12_resources.h @@ -1480,6 +1480,7 @@ class WrappedID3D12Resource size_t DeleteOverlappingAccStructsInRangeAtOffset(D3D12BufferOffset bufferOffset); WrappedID3D12Heap *m_Heap = NULL; + uint64_t m_HeapOffset = 0; Threading::CriticalSection m_accStructResourcesCS; rdcflatmap m_accelerationStructMap; @@ -1498,6 +1499,7 @@ public: } WrappedID3D12Heap *GetHeap() { return m_Heap; } + uint64_t GetHeapOffset() { return m_HeapOffset; } ID3D12Pageable *UnwrappedResidencyPageable() { @@ -1575,6 +1577,7 @@ public: device->AddReplayResource(GetResourceID(), this); m_Heap = (WrappedID3D12Heap *)heap; + m_HeapOffset = HeapOffset; SAFE_ADDREF(m_Heap); // assuming only valid for buffers diff --git a/renderdoc/driver/vulkan/vk_info.h b/renderdoc/driver/vulkan/vk_info.h index 974a99642..b352da76c 100644 --- a/renderdoc/driver/vulkan/vk_info.h +++ b/renderdoc/driver/vulkan/vk_info.h @@ -656,6 +656,9 @@ struct VulkanCreationInfo VkMemoryRequirements mrq; + ResourceId boundMemory; + uint64_t boundMemoryOffset; + ResourceId inlineDescriptorId; }; std::unordered_map m_Buffer; diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index f31ad27cb..d58ea128b 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -345,7 +345,16 @@ rdcarray VulkanReplay::GetBuffers() TextureDescription VulkanReplay::GetTexture(ResourceId id) { - VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[id]; + auto imit = m_pDriver->m_CreationInfo.m_Image.find(id); + if(imit == m_pDriver->m_CreationInfo.m_Image.end()) + { + RDCERR("Invalid image id"); + return {}; + } + + VulkanCreationInfo::Image &iminfo = imit->second; + + VkImage liveWrappedImage = GetResourceManager()->GetHandle(id); TextureDescription ret = {}; ret.resourceId = id; @@ -357,10 +366,27 @@ TextureDescription VulkanReplay::GetTexture(ResourceId id) ret.depth = iminfo.extent.depth; ret.mips = iminfo.mipLevels; - ret.byteSize = 0; - for(uint32_t s = 0; s < ret.mips; s++) - ret.byteSize += GetByteSize(ret.width, ret.height, ret.depth, iminfo.format, s); - ret.byteSize *= ret.arraysize; + VkMemoryRequirements mrq = {0}; + VkDevice dev = m_pDriver->GetDev(); + + // some entries in m_Images don't have a live handle - e.g. swapchains + if(liveWrappedImage != VK_NULL_HANDLE) + ObjDisp(dev)->GetImageMemoryRequirements(Unwrap(dev), Unwrap(liveWrappedImage), &mrq); + + ret.byteSize = mrq.size; + if(ret.byteSize == 0) + { + for(uint32_t s = 0; s < ret.mips; s++) + ret.byteSize += GetByteSize(ret.width, ret.height, ret.depth, iminfo.format, s); + ret.byteSize *= ret.arraysize; + } + + LockedConstImageStateRef lockedImage = m_pDriver->FindConstImageState(id); + if(lockedImage && lockedImage->isMemoryBound) + { + ret.memory = lockedImage->boundMemory; + ret.memoryOffset = lockedImage->boundMemoryOffset; + } ret.msQual = 0; ret.msSamp = RDCMAX(1U, (uint32_t)iminfo.samples); @@ -399,7 +425,14 @@ TextureDescription VulkanReplay::GetTexture(ResourceId id) BufferDescription VulkanReplay::GetBuffer(ResourceId id) { - VulkanCreationInfo::Buffer &bufinfo = m_pDriver->m_CreationInfo.m_Buffer[id]; + auto bufit = m_pDriver->m_CreationInfo.m_Buffer.find(id); + if(bufit == m_pDriver->m_CreationInfo.m_Buffer.end()) + { + RDCERR("Invalid image id"); + return {}; + } + + VulkanCreationInfo::Buffer &bufinfo = bufit->second; BufferDescription ret; ret.resourceId = id; @@ -407,6 +440,9 @@ BufferDescription VulkanReplay::GetBuffer(ResourceId id) ret.creationFlags = BufferCategory::NoFlags; ret.gpuAddress = bufinfo.gpuAddress; + ret.memory = bufinfo.boundMemory; + ret.memoryOffset = bufinfo.boundMemoryOffset; + if(bufinfo.usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) ret.creationFlags |= BufferCategory::ReadWrite; if(bufinfo.usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT)) diff --git a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp index f7cff5393..8560fe2ca 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp @@ -1734,6 +1734,9 @@ bool WrappedVulkan::Serialise_vkBindBufferMemory(SerialiserType &ser, VkDevice d if(bufInfo.usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) TrackReplayBufferAddress(device, buffer, memory, memoryOffset); + bufInfo.boundMemory = memId; + bufInfo.boundMemoryOffset = memoryOffset; + m_CreationInfo.m_Memory[GetResID(memory)].BindMemory(memoryOffset, mrq.size, VulkanCreationInfo::Memory::Linear); } @@ -3534,6 +3537,9 @@ bool WrappedVulkan::Serialise_vkBindBufferMemory2(SerialiserType &ser, VkDevice if(bufInfo.usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) TrackReplayBufferAddress(device, bindInfo.buffer, bindInfo.memory, bindInfo.memoryOffset); + bufInfo.boundMemory = memId; + bufInfo.boundMemoryOffset = bindInfo.memoryOffset; + // the memory is immediately dirty because we don't use dirty tracking, it's too expensive to // follow all frame refs in the background and it's pointless because memory almost always // immediately becomes dirty anyway. The one case we might care about non-dirty memory is diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 00f94f899..6f16ed191 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -465,8 +465,10 @@ void DoSerialise(SerialiserType &ser, TextureDescription &el) SERIALISE_MEMBER(msQual); SERIALISE_MEMBER(msSamp); SERIALISE_MEMBER(byteSize); + SERIALISE_MEMBER(memory); + SERIALISE_MEMBER(memoryOffset); - SIZE_CHECK(72); + SIZE_CHECK(88); } template @@ -476,8 +478,10 @@ void DoSerialise(SerialiserType &ser, BufferDescription &el) SERIALISE_MEMBER(creationFlags); SERIALISE_MEMBER(gpuAddress); SERIALISE_MEMBER(length); + SERIALISE_MEMBER(memory); + SERIALISE_MEMBER(memoryOffset); - SIZE_CHECK(32); + SIZE_CHECK(48); } template