From a25d07cf59e1b509444eb4d607f87b6b3bf7302d Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 5 Oct 2015 11:34:25 +0200 Subject: [PATCH] Display cbuffer contents within vulkan pipe viewer * For now, we just assume that cbuffers are tightly packed according to D3D11 rules (matrices, structs, float3/4 are all float4 aligned), and once final SPIR-V is generated everything should have explicit offsets, strides, and sizes --- renderdoc/api/replay/vk_pipestate.h | 8 +- renderdoc/driver/vulkan/vk_info.cpp | 12 + renderdoc/driver/vulkan/vk_info.h | 18 ++ renderdoc/driver/vulkan/vk_replay.cpp | 287 +++++++++++++++++- renderdoc/driver/vulkan/vk_replay.h | 2 + .../vulkan/wrappers/vk_resource_funcs.cpp | 6 + renderdocui/Code/CommonPipelineState.cs | 33 ++ renderdocui/Interop/VulkanPipelineState.cs | 4 +- .../VulkanPipelineStateViewer.cs | 84 ++++- 9 files changed, 440 insertions(+), 14 deletions(-) diff --git a/renderdoc/api/replay/vk_pipestate.h b/renderdoc/api/replay/vk_pipestate.h index 2d438816c..9fd5da5da 100644 --- a/renderdoc/api/replay/vk_pipestate.h +++ b/renderdoc/api/replay/vk_pipestate.h @@ -46,11 +46,11 @@ struct VulkanPipelineState struct BindingElement { - ResourceId view; // buffer, image, attachment + ResourceId view; // bufferview, imageview, attachmentview + ResourceId res; // buffer, image, attachment ResourceId sampler; - uint32_t offset; // for dynamic offsets - - // VKTODOLOW do we want image layout here? + uint64_t offset; + uint64_t size; }; // may only be one element if not an array diff --git a/renderdoc/driver/vulkan/vk_info.cpp b/renderdoc/driver/vulkan/vk_info.cpp index 4eba93933..979fbc7ad 100644 --- a/renderdoc/driver/vulkan/vk_info.cpp +++ b/renderdoc/driver/vulkan/vk_info.cpp @@ -180,6 +180,18 @@ void VulkanCreationInfo::AttachmentView::Init(const VkAttachmentViewCreateInfo* image = VKMGR()->GetNonDispWrapper(pCreateInfo->image)->id; } +void VulkanCreationInfo::BufferView::Init(const VkBufferViewCreateInfo* pCreateInfo) +{ + buffer = VKMGR()->GetNonDispWrapper(pCreateInfo->buffer)->id; + offset = pCreateInfo->offset; + size = pCreateInfo->range; +} + +void VulkanCreationInfo::ImageView::Init(const VkImageViewCreateInfo* pCreateInfo) +{ + image = VKMGR()->GetNonDispWrapper(pCreateInfo->image)->id; +} + void VulkanCreationInfo::DescSetLayout::Init(const VkDescriptorSetLayoutCreateInfo* pCreateInfo) { bindings.resize(pCreateInfo->count); diff --git a/renderdoc/driver/vulkan/vk_info.h b/renderdoc/driver/vulkan/vk_info.h index 747a3a46a..9ce0e558a 100644 --- a/renderdoc/driver/vulkan/vk_info.h +++ b/renderdoc/driver/vulkan/vk_info.h @@ -175,6 +175,24 @@ struct VulkanCreationInfo uint32_t width, height, layers; }; map m_Framebuffer; + + struct BufferView + { + void Init(const VkBufferViewCreateInfo* pCreateInfo); + + ResourceId buffer; + uint64_t offset; + uint64_t size; + }; + map m_BufferView; + + struct ImageView + { + void Init(const VkImageViewCreateInfo* pCreateInfo); + + ResourceId image; + }; + map m_ImageView; struct AttachmentView { diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 337094f39..6315b3680 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -1145,7 +1145,16 @@ vector VulkanReplay::GetBufferData(ResourceId buff, uint32_t offset, uint3 VkQueue q = m_pDriver->GetQ(); const VkLayerDispatchTable *vt = ObjDisp(dev); - ResourceId memid = m_pDriver->m_BufferMemBinds[buff]; + ResourceId memid; + + { + auto it = m_pDriver->m_BufferMemBinds.find(buff); + if(it == m_pDriver->m_BufferMemBinds.end()) + { + RDCWARN("Buffer has no memory bound, or no buffer of this ID"); + return vector(); + } + } VkBuffer srcBuf = m_pDriver->GetResourceManager()->GetCurrentHandle(buff); @@ -1454,11 +1463,23 @@ void VulkanReplay::SavePipelineState() // only one of these is ever set if(info->imageView != VK_NULL_HANDLE) + { dst.bindings[b].binds[a].view = rm->GetOriginalID(VKMGR()->GetNonDispWrapper(info->imageView)->id); + dst.bindings[b].binds[a].res = rm->GetOriginalID(c.m_ImageView[dst.bindings[b].binds[a].view].image); + } if(info->bufferView != VK_NULL_HANDLE) + { dst.bindings[b].binds[a].view = rm->GetOriginalID(VKMGR()->GetNonDispWrapper(info->bufferView)->id); + dst.bindings[b].binds[a].res = rm->GetOriginalID(c.m_BufferView[dst.bindings[b].binds[a].view].buffer); + dst.bindings[b].binds[a].offset = *(uint32_t *)&info->imageLayout; + dst.bindings[b].binds[a].offset += c.m_BufferView[dst.bindings[b].binds[a].view].offset; + dst.bindings[b].binds[a].size = c.m_BufferView[dst.bindings[b].binds[a].view].size; + } if(info->attachmentView != VK_NULL_HANDLE) + { dst.bindings[b].binds[a].view = rm->GetOriginalID(VKMGR()->GetNonDispWrapper(info->attachmentView)->id); + dst.bindings[b].binds[a].res = rm->GetOriginalID(c.m_AttachmentView[dst.bindings[b].binds[a].view].image); + } } } } @@ -1621,9 +1642,271 @@ void VulkanReplay::SavePipelineState() } } +void VulkanReplay::FillCBufferVariables(rdctype::array invars, vector &outvars, const vector &data, size_t &offset) +{ + for(int v=0; v < invars.count; v++) + { + string basename = invars[v].name.elems; + + uint32_t rows = invars[v].type.descriptor.rows; + uint32_t cols = invars[v].type.descriptor.cols; + uint32_t elems = RDCMAX(1U,invars[v].type.descriptor.elements); + bool rowMajor = invars[v].type.descriptor.rowMajorStorage != 0; + bool isArray = elems > 1; + + if(invars[v].type.members.count > 0) + { + // structs are aligned + offset = AlignUp16(offset); + + ShaderVariable var; + var.name = basename; + var.rows = var.columns = 0; + var.type = eVar_Float; + + vector varmembers; + + if(isArray) + { + for(uint32_t i=0; i < elems; i++) + { + // each struct in the array is aligned + offset = AlignUp16(offset); + + ShaderVariable vr; + vr.name = StringFormat::Fmt("%s[%u]", basename.c_str(), i); + vr.rows = vr.columns = 0; + vr.type = eVar_Float; + + vector mems; + + FillCBufferVariables(invars[v].type.members, mems, data, offset); + + vr.isStruct = true; + + vr.members = mems; + + varmembers.push_back(vr); + } + + var.isStruct = false; + } + else + { + var.isStruct = true; + + FillCBufferVariables(invars[v].type.members, varmembers, data, offset); + } + + { + var.members = varmembers; + outvars.push_back(var); + } + + continue; + } + + // NOTE this won't work as-is for doubles, the below logic + // assumes 32-bit values. This code will all go away anyway + // once offsets & strides are correctly listed per-element + size_t elemByteSize = sizeof(uint32_t); + size_t sz = elemByteSize; + + // vector + if(cols == 1) + { + if(isArray) + { + // arrays are aligned to float4 boundary + offset = AlignUp16(offset); + + // array elements are also aligned - note, last + // element only takes up however much space it would + // so e.g. a float3 array leaves one float at the end + // that could be there + sz *= 4*elems; + sz -= (4-rows)*elemByteSize; + } + else if(rows > 2) + { + // float3s and float4s are aligned + offset = AlignUp16(offset); + sz *= rows; + } + } + else + { + // matrices are aligned to float4 boundary + offset = AlignUp16(offset); + + // matrices act like an array of vectors, whether they + // are an array or not. We just need to determine if + // those vectors are the matrix's rows, or its columns, + // and adjust number of elements - even a float2x2 is + // stored in two float4s. + if(rowMajor) + { + // account for array elems as well as columns. + // Note the last array elem can have space after + // it which can be filled with another element, so + // need to ensure the 'stride' accounts for that. + sz *= 4*elems*cols; + sz -= (4-rows)*elemByteSize; + } + else + { + sz *= 4*elems*rows; + sz -= (4-cols)*elemByteSize; + } + } + + // after alignment, this is where we'll read from + size_t dataOffset = offset; + + offset += sz; + + size_t outIdx = outvars.size(); + outvars.resize(outvars.size()+1); + + { + outvars[outIdx].name = basename; + outvars[outIdx].rows = 1; + outvars[outIdx].type = invars[v].type.descriptor.type; + outvars[outIdx].isStruct = false; + outvars[outIdx].columns = cols; + + ShaderVariable &var = outvars[outIdx]; + + if(!isArray) + { + outvars[outIdx].rows = rows; + + if(dataOffset < data.size()) + { + const byte *d = &data[dataOffset]; + + RDCASSERT(rows <= 4 && rows*cols <= 16); + + if(!rowMajor) + { + uint32_t tmp[16] = {0}; + + for(uint32_t r=0; r < rows; r++) + { + size_t srcoffs = 4*elemByteSize*r; + size_t dstoffs = cols*elemByteSize*r; + memcpy((byte *)(tmp) + dstoffs, d + srcoffs, + RDCMIN(data.size()-dataOffset + srcoffs, elemByteSize*cols)); + } + + // transpose + for(size_t r=0; r < rows; r++) + for(size_t c=0; c < cols; c++) + outvars[outIdx].value.uv[r*cols+c] = tmp[c*rows+r]; + } + else + { + for(uint32_t r=0; r < rows; r++) + { + size_t srcoffs = 4*elemByteSize*r; + size_t dstoffs = cols*elemByteSize*r; + memcpy((byte *)(&outvars[outIdx].value.uv[0]) + dstoffs, d + srcoffs, + RDCMIN(data.size()-dataOffset + srcoffs, elemByteSize*cols)); + } + } + } + } + else + { + char buf[64] = {0}; + + var.name = outvars[outIdx].name; + var.rows = 0; + var.columns = 0; + + bool isMatrix = rows > 1 && cols > 1; + + vector varmembers; + varmembers.resize(elems); + + string base = outvars[outIdx].name.elems; + + uint32_t primaryDim = cols; + uint32_t secondaryDim = rows; + if(rowMajor) + { + primaryDim = rows; + secondaryDim = cols; + } + + for(uint32_t e=0; e < elems; e++) + { + varmembers[e].name = StringFormat::Fmt("%s[%u]", base.c_str(), e); + varmembers[e].rows = rows; + varmembers[e].type = invars[v].type.descriptor.type; + varmembers[e].isStruct = false; + varmembers[e].columns = cols; + + size_t rowDataOffset = dataOffset+e*primaryDim*4*elemByteSize; + + if(rowDataOffset < data.size()) + { + const byte *d = &data[rowDataOffset]; + + // each primary element (row or column) is stored in a float4. + // we copy some padding here, but that will come out in the wash + // when we transpose + for(uint32_t p=0; p < primaryDim; p++) + { + memcpy(&(varmembers[e].value.uv[secondaryDim*p]), d + 4*elemByteSize*p, + RDCMIN(data.size()- rowDataOffset, elemByteSize*secondaryDim)); + } + + if(!rowMajor) + { + ShaderVariable tmp = varmembers[e]; + // transpose + for(size_t ri=0; ri < rows; ri++) + for(size_t ci=0; ci < cols; ci++) + varmembers[e].value.uv[ri*cols+ci] = tmp.value.uv[ci*rows+ri]; + } + } + } + + { + var.isStruct = false; + var.members = varmembers; + } + } + } + } +} + void VulkanReplay::FillCBufferVariables(ResourceId shader, uint32_t cbufSlot, vector &outvars, const vector &data) { - RDCUNIMPLEMENTED("FillCBufferVariables"); + // Correct SPIR-V will ultimately need to set explicit layout information for each type. + // For now, just assume D3D11 packing (float4 alignment on float4s, float3s, matrices, arrays and structures) + + auto it = m_pDriver->m_ShaderInfo.find(shader); + + if(it == m_pDriver->m_ShaderInfo.end()) + { + RDCERR("Can't get shader details"); + return; + } + + ShaderReflection &refl = it->second.refl; + + if(cbufSlot >= (uint32_t)refl.ConstantBlocks.count) + { + RDCERR("Invalid cbuffer slot"); + return; + } + + ConstantBlock &c = refl.ConstantBlocks[cbufSlot]; + + size_t zero = 0; + FillCBufferVariables(c.variables, outvars, data, zero); } bool VulkanReplay::GetMinMax(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample, float *minval, float *maxval) diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index 999378fac..1a1019f58 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -215,5 +215,7 @@ class VulkanReplay : public IReplayDriver WrappedVulkan *m_pDriver; + void FillCBufferVariables(rdctype::array, vector &outvars, const vector &data, size_t &offset); + VulkanDebugManager *GetDebugManager(); }; diff --git a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp index f65c87600..b94c42838 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_resource_funcs.cpp @@ -565,6 +565,9 @@ bool WrappedVulkan::Serialise_vkCreateBufferView( { device = GetResourceManager()->GetLiveHandle(devId); VkBufferView view = VK_NULL_HANDLE; + + // use original ID + m_CreationInfo.m_BufferView[id].Init(&info); VkResult ret = ObjDisp(device)->CreateBufferView(Unwrap(device), &info, &view); @@ -748,6 +751,9 @@ bool WrappedVulkan::Serialise_vkCreateImageView( { device = GetResourceManager()->GetLiveHandle(devId); VkImageView view = VK_NULL_HANDLE; + + // use original ID + m_CreationInfo.m_ImageView[id].Init(&info); VkResult ret = ObjDisp(device)->CreateImageView(Unwrap(device), &info, &view); diff --git a/renderdocui/Code/CommonPipelineState.cs b/renderdocui/Code/CommonPipelineState.cs index 9361875fc..30fbd3912 100644 --- a/renderdocui/Code/CommonPipelineState.cs +++ b/renderdocui/Code/CommonPipelineState.cs @@ -665,6 +665,39 @@ namespace renderdocui.Code } } } + else if (IsLogVK) + { + VulkanPipelineState.Pipeline pipe = m_Vulkan.graphics; + if (stage == ShaderStageType.Compute) + pipe = m_Vulkan.compute; + + VulkanPipelineState.ShaderStage s = null; + + switch (stage) + { + case ShaderStageType.Vertex: s = m_Vulkan.VS; break; + case ShaderStageType.Tess_Control: s = m_Vulkan.TCS; break; + case ShaderStageType.Tess_Eval: s = m_Vulkan.TES; break; + case ShaderStageType.Geometry: s = m_Vulkan.GS; break; + case ShaderStageType.Fragment: s = m_Vulkan.FS; break; + case ShaderStageType.Compute: s = m_Vulkan.CS; break; + } + + if (s.ShaderDetails != null && BufIdx < s.ShaderDetails.ConstantBlocks.Length) + { + var bind = s.BindpointMapping.ConstantBlocks[s.ShaderDetails.ConstantBlocks[BufIdx].bindPoint]; + + // TODO do we need to worry about arrays of uniform buffers? + var descriptorBind = pipe.DescSets[bind.bindset].bindings[bind.bind].binds[0]; + + buf = descriptorBind.res; + // VKTODOLOW maybe increase parameter to ulong and upcast others? + ByteOffset = (uint)descriptorBind.offset; + ByteSize = (uint)descriptorBind.size; + + return; + } + } } buf = ResourceId.Null; diff --git a/renderdocui/Interop/VulkanPipelineState.cs b/renderdocui/Interop/VulkanPipelineState.cs index 10d50e6ae..a6ee5fc4b 100644 --- a/renderdocui/Interop/VulkanPipelineState.cs +++ b/renderdocui/Interop/VulkanPipelineState.cs @@ -53,8 +53,10 @@ namespace renderdoc public class BindingElement { public ResourceId view; + public ResourceId res; public ResourceId sampler; - public UInt32 offset; + public UInt64 offset; + public UInt64 size; }; [CustomMarshalAs(CustomUnmanagedType.TemplatedArray)] public BindingElement[] binds; diff --git a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs index 1fe55db72..ed419f6ce 100644 --- a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs +++ b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs @@ -204,7 +204,7 @@ namespace renderdocui.Windows.PipelineState // Set a shader stage's resources and values private void SetShaderState(FetchTexture[] texs, FetchBuffer[] bufs, - VulkanPipelineState.ShaderStage stage, + VulkanPipelineState.ShaderStage stage, VulkanPipelineState.Pipeline pipe, Label shader, TreelistView.TreeListView resources, TreelistView.TreeListView samplers, TreelistView.TreeListView cbuffers, TreelistView.TreeListView classes) { @@ -249,7 +249,63 @@ namespace renderdocui.Windows.PipelineState vs = cbuffers.VScrollValue(); cbuffers.BeginUpdate(); cbuffers.Nodes.Clear(); + if(stage.ShaderDetails != null) + { + UInt32 i = 0; + foreach (var b in shaderDetails.ConstantBlocks) + { + BindpointMap bindMap = stage.BindpointMapping.ConstantBlocks[b.bindPoint]; + // TODO do we need to worry about arrays of uniform buffers? + var descriptorBind = pipe.DescSets[bindMap.bindset].bindings[bindMap.bind].binds[0]; + + bool filledSlot = (descriptorBind.res != ResourceId.Null); + bool usedSlot = bindMap.used; + + // show if + if (usedSlot || // it's referenced by the shader - regardless of empty or not + (showDisabled.Checked && !usedSlot && filledSlot) || // it's bound, but not referenced, and we have "show disabled" + (showEmpty.Checked && !filledSlot) // it's empty, and we have "show empty" + ) + { + string name = "Constant Buffer " + descriptorBind.res.ToString(); + UInt64 length = descriptorBind.size; + int numvars = b.variables.Length; + + if (!filledSlot) + { + name = "Empty"; + length = 0; + } + + for (int t = 0; t < bufs.Length; t++) + if (bufs[t].ID == descriptorBind.res) + name = bufs[t].name; + + if (name == "") + name = "Constant Buffer " + descriptorBind.res.ToString(); + + string slotname = i.ToString(); + slotname += ": " + b.name; + + string sizestr = String.Format("{0} Variables, {1} bytes", numvars, length); + string vecrange = String.Format("{0} - {1}", descriptorBind.offset, descriptorBind.offset + descriptorBind.size); + + var node = cbuffers.Nodes.Add(new object[] { slotname, name, vecrange, sizestr }); + + node.Image = global::renderdocui.Properties.Resources.action; + node.HoverImage = global::renderdocui.Properties.Resources.action_hover; + node.Tag = i; + + if (!filledSlot) + EmptyRow(node); + + if (!usedSlot) + InactiveRow(node); + } + i++; + } + } cbuffers.EndUpdate(); cbuffers.NodesSelection.Clear(); cbuffers.SetVScrollValue(vs); @@ -562,12 +618,12 @@ namespace renderdocui.Windows.PipelineState viBuffers.EndUpdate(); viBuffers.SetVScrollValue(vs); - SetShaderState(texs, bufs, state.VS, vsShader, vsResources, vsSamplers, vsCBuffers, vsClasses); - SetShaderState(texs, bufs, state.GS, gsShader, gsResources, gsSamplers, gsCBuffers, gsClasses); - SetShaderState(texs, bufs, state.TCS, hsShader, hsResources, hsSamplers, hsCBuffers, hsClasses); - SetShaderState(texs, bufs, state.TES, dsShader, dsResources, dsSamplers, dsCBuffers, dsClasses); - SetShaderState(texs, bufs, state.FS, psShader, psResources, psSamplers, psCBuffers, psClasses); - SetShaderState(texs, bufs, state.CS, csShader, csResources, csSamplers, csCBuffers, csClasses); + SetShaderState(texs, bufs, state.VS, state.graphics, vsShader, vsResources, vsSamplers, vsCBuffers, vsClasses); + SetShaderState(texs, bufs, state.GS, state.graphics, gsShader, gsResources, gsSamplers, gsCBuffers, gsClasses); + SetShaderState(texs, bufs, state.TCS, state.graphics, hsShader, hsResources, hsSamplers, hsCBuffers, hsClasses); + SetShaderState(texs, bufs, state.TES, state.graphics, dsShader, dsResources, dsSamplers, dsCBuffers, dsClasses); + SetShaderState(texs, bufs, state.FS, state.graphics, psShader, psResources, psSamplers, psCBuffers, psClasses); + SetShaderState(texs, bufs, state.CS, state.compute, csShader, csResources, csSamplers, csCBuffers, csClasses); vs = csUAVs.VScrollValue(); csUAVs.Nodes.Clear(); @@ -1288,6 +1344,20 @@ namespace renderdocui.Windows.PipelineState private void ShowCBuffer(VulkanPipelineState.ShaderStage stage, UInt32 slot) { + VulkanPipelineState.Pipeline pipe = m_Core.CurVulkanPipelineState.graphics; + if(stage.stage == ShaderStageType.Compute) + pipe = m_Core.CurVulkanPipelineState.compute; + + var existing = ConstantBufferPreviewer.Has(stage.stage, slot); + if (existing != null) + { + existing.Show(); + return; + } + + var prev = new ConstantBufferPreviewer(m_Core, stage.stage, slot); + + prev.ShowDock(m_DockContent.Pane, DockAlignment.Right, 0.3); } private void cbuffers_NodeDoubleClicked(TreelistView.Node node)