diff --git a/renderdoc/driver/d3d11/d3d11_analyse.cpp b/renderdoc/driver/d3d11/d3d11_analyse.cpp index 10b61587c..a32623f25 100644 --- a/renderdoc/driver/d3d11/d3d11_analyse.cpp +++ b/renderdoc/driver/d3d11/d3d11_analyse.cpp @@ -2318,10 +2318,9 @@ uint32_t D3D11DebugManager::PickVertex(uint32_t eventID, const MeshDisplay &cfg, Vec3f testDir = (cameraToWorldFarPosition - cameraToWorldNearPosition); testDir.Normalise(); - /* Calculate the ray direction first in the regular way (above), so we can use the - the output for testing if the ray we are picking is negative or not. This is similar - to checking against the forward direction of the camera, but more robust - */ + // Calculate the ray direction first in the regular way (above), so we can use the + // the output for testing if the ray we are picking is negative or not. This is similar + // to checking against the forward direction of the camera, but more robust if(cfg.position.unproject) { Matrix4f inversePickMVPGuess = pickMVPProj.Inverse(); @@ -2358,27 +2357,27 @@ uint32_t D3D11DebugManager::PickVertex(uint32_t eventID, const MeshDisplay &cfg, { cbuf.MeshMode = MESH_TRIANGLE_LIST; break; - }; + } case eTopology_TriangleStrip: { cbuf.MeshMode = MESH_TRIANGLE_STRIP; break; - }; + } case eTopology_TriangleList_Adj: { cbuf.MeshMode = MESH_TRIANGLE_LIST_ADJ; break; - }; + } case eTopology_TriangleStrip_Adj: { cbuf.MeshMode = MESH_TRIANGLE_STRIP_ADJ; break; - }; + } default: // points, lines, patchlists, unknown { cbuf.MeshMode = MESH_OTHER; isTriangleMesh = false; - }; + } } ID3D11Buffer *vb = NULL, *ib = NULL; diff --git a/renderdoc/driver/d3d12/d3d12_debug.cpp b/renderdoc/driver/d3d12/d3d12_debug.cpp index 8935d48dc..10a3d8a77 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.cpp +++ b/renderdoc/driver/d3d12/d3d12_debug.cpp @@ -200,6 +200,9 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper) m_WrappedDevice->CreateRenderTargetView(m_PickPixelTex, NULL, m_PickPixelRTV); } + m_PickVB = NULL; + m_PickSize = 0; + { D3D12_RESOURCE_DESC soBufDesc; soBufDesc.Alignment = 0; @@ -516,6 +519,44 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper) SAFE_RELEASE(root); + rootSig.clear(); + + // 0: CBV + param.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + param.Descriptor.ShaderRegister = 0; + param.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE; + + rootSig.push_back(param); + + // 0: SRVs + srvrange.NumDescriptors = 2; + + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.DescriptorTable.pDescriptorRanges = &srvrange; + param.DescriptorTable.NumDescriptorRanges = 1; + + rootSig.push_back(param); + + // 0: UAV + uavrange.NumDescriptors = 1; + + param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + param.DescriptorTable.pDescriptorRanges = &uavrange; + param.DescriptorTable.NumDescriptorRanges = 1; + + rootSig.push_back(param); + + root = MakeRootSig(rootSig); + + RDCASSERT(root); + + hr = m_WrappedDevice->CreateRootSignature(0, root->GetBufferPointer(), root->GetBufferSize(), + __uuidof(ID3D12RootSignature), + (void **)&m_MeshPickRootSig); + + SAFE_RELEASE(root); + RenderDoc::Inst().SetProgress(DebugManagerInit, 0.6f); D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeDesc; @@ -688,6 +729,26 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper) D3D12_COMPUTE_PIPELINE_STATE_DESC compPipeDesc; RDCEraseEl(compPipeDesc); + compPipeDesc.pRootSignature = m_MeshPickRootSig; + + ID3DBlob *meshPickCS; + + GetShaderBlob(meshhlsl.c_str(), "RENDERDOC_MeshPickCS", D3DCOMPILE_WARNINGS_ARE_ERRORS, "cs_5_0", + &meshPickCS); + + RDCASSERT(meshPickCS); + + compPipeDesc.CS.BytecodeLength = meshPickCS->GetBufferSize(); + compPipeDesc.CS.pShaderBytecode = meshPickCS->GetBufferPointer(); + + hr = m_WrappedDevice->CreateComputePipelineState(&compPipeDesc, __uuidof(ID3D12PipelineState), + (void **)&m_MeshPickPipe); + + if(FAILED(hr)) + { + RDCERR("Couldn't create m_MeshPickPipe! 0x%08x", hr); + } + compPipeDesc.pRootSignature = m_HistogramRootSig; RDCEraseEl(m_TileMinMaxPipe); @@ -770,6 +831,60 @@ D3D12DebugManager::D3D12DebugManager(WrappedID3D12Device *wrapper) SAFE_RELEASE(QOResolvePS); SAFE_RELEASE(CheckerboardPS); + { + D3D12_RESOURCE_DESC pickResultDesc = {}; + pickResultDesc.Alignment = 0; + pickResultDesc.DepthOrArraySize = 1; + pickResultDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + pickResultDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + pickResultDesc.Format = DXGI_FORMAT_UNKNOWN; + pickResultDesc.Height = 1; + pickResultDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + pickResultDesc.MipLevels = 1; + pickResultDesc.SampleDesc.Count = 1; + pickResultDesc.SampleDesc.Quality = 0; + // add an extra 64 bytes for the counter at the start + pickResultDesc.Width = m_MaxMeshPicks * sizeof(Vec4f) + 64; + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + hr = m_WrappedDevice->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &pickResultDesc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + NULL, __uuidof(ID3D12Resource), (void **)&m_PickResultBuf); + + if(FAILED(hr)) + { + RDCERR("Failed to create tile buffer for min/max, HRESULT: 0x%08x", hr); + } + + D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Format = DXGI_FORMAT_UNKNOWN; + uavDesc.Buffer.CounterOffsetInBytes = 0; + // start with elements after the counter + uavDesc.Buffer.FirstElement = 64 / sizeof(Vec4f); + uavDesc.Buffer.NumElements = m_MaxMeshPicks; + uavDesc.Buffer.StructureByteStride = sizeof(Vec4f); + + m_WrappedDevice->CreateUnorderedAccessView(m_PickResultBuf, m_PickResultBuf, &uavDesc, + GetCPUHandle(PICK_RESULT_UAV)); + + // this UAV is used for clearing everything back to 0 + + uavDesc.Format = DXGI_FORMAT_R32G32B32A32_UINT; + uavDesc.Buffer.FirstElement = 0; + uavDesc.Buffer.NumElements = m_MaxMeshPicks + 64 / sizeof(Vec4f); + uavDesc.Buffer.StructureByteStride = 0; + + m_WrappedDevice->CreateUnorderedAccessView(m_PickResultBuf, NULL, &uavDesc, + GetCPUHandle(PICK_RESULT_CLEAR_UAV)); + } + { const uint64_t maxTexDim = 16384; const uint64_t blockPixSize = HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK; @@ -1210,6 +1325,11 @@ D3D12DebugManager::~D3D12DebugManager() SAFE_RELEASE(m_PickPixelTex); + SAFE_RELEASE(m_MeshPickRootSig); + SAFE_RELEASE(m_MeshPickPipe); + SAFE_RELEASE(m_PickResultBuf); + SAFE_RELEASE(m_PickVB); + SAFE_RELEASE(m_SOBuffer); SAFE_RELEASE(m_SOStagingBuffer); @@ -2024,6 +2144,354 @@ void D3D12DebugManager::PickPixel(ResourceId texture, uint32_t x, uint32_t y, ui m_ReadbackBuffer->Unmap(0, &range); } +uint32_t D3D12DebugManager::PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x, + uint32_t y) +{ + if(cfg.position.numVerts == 0) + return ~0U; + + struct MeshPickData + { + Vec3f RayPos; + uint32_t PickIdx; + + Vec3f RayDir; + uint32_t PickNumVerts; + + Vec2f PickCoords; + Vec2f PickViewport; + + uint32_t MeshMode; + uint32_t PickUnproject; + Vec2f Padding; + + Matrix4f PickMVP; + + } cbuf; + + cbuf.PickCoords = Vec2f((float)x, (float)y); + cbuf.PickViewport = Vec2f((float)GetWidth(), (float)GetHeight()); + cbuf.PickIdx = cfg.position.idxByteWidth ? 1 : 0; + cbuf.PickNumVerts = cfg.position.numVerts; + cbuf.PickUnproject = cfg.position.unproject ? 1 : 0; + + Matrix4f projMat = + Matrix4f::Perspective(90.0f, 0.1f, 100000.0f, float(GetWidth()) / float(GetHeight())); + + Matrix4f camMat = cfg.cam ? cfg.cam->GetMatrix() : Matrix4f::Identity(); + + Matrix4f pickMVP = projMat.Mul(camMat); + + ResourceFormat resFmt; + resFmt.compByteWidth = cfg.position.compByteWidth; + resFmt.compCount = cfg.position.compCount; + resFmt.compType = cfg.position.compType; + resFmt.special = false; + if(cfg.position.specialFormat != eSpecial_Unknown) + { + resFmt.special = true; + resFmt.specialFormat = cfg.position.specialFormat; + } + + Matrix4f pickMVPProj; + if(cfg.position.unproject) + { + // the derivation of the projection matrix might not be right (hell, it could be an + // orthographic projection). But it'll be close enough likely. + Matrix4f guessProj = + Matrix4f::Perspective(cfg.fov, cfg.position.nearPlane, cfg.position.farPlane, cfg.aspect); + + if(cfg.ortho) + guessProj = Matrix4f::Orthographic(cfg.position.nearPlane, cfg.position.farPlane); + + pickMVPProj = projMat.Mul(camMat.Mul(guessProj.Inverse())); + } + + Vec3f rayPos; + Vec3f rayDir; + // convert mouse pos to world space ray + { + Matrix4f inversePickMVP = pickMVP.Inverse(); + + float pickX = ((float)x) / ((float)GetWidth()); + float pickXCanonical = RDCLERP(-1.0f, 1.0f, pickX); + + float pickY = ((float)y) / ((float)GetHeight()); + // flip the Y axis + float pickYCanonical = RDCLERP(1.0f, -1.0f, pickY); + + Vec3f cameraToWorldNearPosition = + inversePickMVP.Transform(Vec3f(pickXCanonical, pickYCanonical, -1), 1); + + Vec3f cameraToWorldFarPosition = + inversePickMVP.Transform(Vec3f(pickXCanonical, pickYCanonical, 1), 1); + + Vec3f testDir = (cameraToWorldFarPosition - cameraToWorldNearPosition); + testDir.Normalise(); + + // Calculate the ray direction first in the regular way (above), so we can use the + // the output for testing if the ray we are picking is negative or not. This is similar + // to checking against the forward direction of the camera, but more robust + if(cfg.position.unproject) + { + Matrix4f inversePickMVPGuess = pickMVPProj.Inverse(); + + Vec3f nearPosProj = inversePickMVPGuess.Transform(Vec3f(pickXCanonical, pickYCanonical, -1), 1); + + Vec3f farPosProj = inversePickMVPGuess.Transform(Vec3f(pickXCanonical, pickYCanonical, 1), 1); + + rayDir = (farPosProj - nearPosProj); + rayDir.Normalise(); + + if(testDir.z < 0) + { + rayDir = -rayDir; + } + rayPos = nearPosProj; + } + else + { + rayDir = testDir; + rayPos = cameraToWorldNearPosition; + } + } + + cbuf.RayPos = rayPos; + cbuf.RayDir = rayDir; + + cbuf.PickMVP = cfg.position.unproject ? pickMVPProj : pickMVP; + + bool isTriangleMesh = true; + switch(cfg.position.topo) + { + case eTopology_TriangleList: + { + cbuf.MeshMode = MESH_TRIANGLE_LIST; + break; + } + case eTopology_TriangleStrip: + { + cbuf.MeshMode = MESH_TRIANGLE_STRIP; + break; + } + case eTopology_TriangleList_Adj: + { + cbuf.MeshMode = MESH_TRIANGLE_LIST_ADJ; + break; + } + case eTopology_TriangleStrip_Adj: + { + cbuf.MeshMode = MESH_TRIANGLE_STRIP_ADJ; + break; + } + default: // points, lines, patchlists, unknown + { + cbuf.MeshMode = MESH_OTHER; + isTriangleMesh = false; + } + } + + ID3D12Resource *vb = NULL, *ib = NULL; + DXGI_FORMAT ifmt = cfg.position.idxByteWidth == 4 ? DXGI_FORMAT_R32_UINT : DXGI_FORMAT_R16_UINT; + + if(cfg.position.buf != ResourceId()) + vb = m_WrappedDevice->GetResourceManager()->GetCurrentAs(cfg.position.buf); + + if(cfg.position.idxbuf != ResourceId()) + ib = m_WrappedDevice->GetResourceManager()->GetCurrentAs(cfg.position.idxbuf); + + HRESULT hr = S_OK; + + // most IB/VBs will not be available as SRVs. So, we copy into our own buffers. + // In the case of VB we also tightly pack and unpack the data. IB can just be + // read as R16 or R32 via the SRV so it is just a straight copy + + D3D12_SHADER_RESOURCE_VIEW_DESC sdesc = {}; + sdesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + sdesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + sdesc.Format = ifmt; + + if(cfg.position.idxByteWidth && ib) + { + sdesc.Buffer.NumElements = cfg.position.numVerts; + m_WrappedDevice->CreateShaderResourceView(ib, &sdesc, GetCPUHandle(PICK_IB_SRV)); + } + else + { + sdesc.Buffer.NumElements = 4; + m_WrappedDevice->CreateShaderResourceView(NULL, &sdesc, GetCPUHandle(PICK_IB_SRV)); + } + + sdesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + + if(vb) + { + if(m_PickVB == NULL || m_PickSize < cfg.position.numVerts) + { + SAFE_RELEASE(m_PickVB); + + m_PickSize = cfg.position.numVerts; + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC vbDesc; + vbDesc.Alignment = 0; + vbDesc.DepthOrArraySize = 1; + vbDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + vbDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + vbDesc.Format = DXGI_FORMAT_UNKNOWN; + vbDesc.Height = 1; + vbDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + vbDesc.MipLevels = 1; + vbDesc.SampleDesc.Count = 1; + vbDesc.SampleDesc.Quality = 0; + vbDesc.Width = sizeof(Vec4f) * cfg.position.numVerts; + + hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &vbDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, + __uuidof(ID3D12Resource), (void **)&m_PickVB); + + if(FAILED(hr)) + { + RDCERR("Couldn't create pick vertex buffer: %08x", hr); + return ~0U; + } + + sdesc.Buffer.NumElements = cfg.position.numVerts; + m_WrappedDevice->CreateShaderResourceView(m_PickVB, &sdesc, GetCPUHandle(PICK_VB_SRV)); + } + } + else + { + sdesc.Buffer.NumElements = 4; + m_WrappedDevice->CreateShaderResourceView(NULL, &sdesc, GetCPUHandle(PICK_VB_SRV)); + } + + // unpack and linearise the data + { + FloatVector *vbData = new FloatVector[cfg.position.numVerts]; + + vector oldData; + GetBufferData(vb, cfg.position.offset, 0, oldData); + + byte *data = &oldData[0]; + byte *dataEnd = data + oldData.size(); + + bool valid = true; + + for(uint32_t i = 0; i < cfg.position.numVerts; i++) + vbData[i] = InterpretVertex(data, i, cfg, dataEnd, false, valid); + + FillBuffer(m_PickVB, 0, vbData, sizeof(Vec4f) * cfg.position.numVerts); + + delete[] vbData; + } + + ID3D12GraphicsCommandList *list = m_WrappedDevice->GetNewList(); + + list->SetPipelineState(m_MeshPickPipe); + + list->SetComputeRootSignature(m_MeshPickRootSig); + + list->SetDescriptorHeaps(1, &cbvsrvuavHeap); + + list->SetComputeRootConstantBufferView(0, UploadConstants(&cbuf, sizeof(cbuf))); + list->SetComputeRootDescriptorTable(1, GetGPUHandle(PICK_IB_SRV)); + list->SetComputeRootDescriptorTable(2, GetGPUHandle(PICK_RESULT_UAV)); + + list->Dispatch(cfg.position.numVerts / 1024 + 1, 1, 1); + + list->Close(); + m_WrappedDevice->ExecuteLists(); + + vector results; + GetBufferData(m_PickResultBuf, 0, 0, results); + + list = m_WrappedDevice->GetNewList(); + + UINT zeroes[4] = {0, 0, 0, 0}; + list->ClearUnorderedAccessViewUint(GetGPUHandle(PICK_RESULT_CLEAR_UAV), + GetCPUHandle(PICK_RESULT_CLEAR_UAV), m_PickResultBuf, zeroes, + 0, NULL); + + list->Close(); + + byte *data = &results[0]; + + uint32_t numResults = *(uint32_t *)data; + + if(numResults > 0) + { + if(isTriangleMesh) + { + struct PickResult + { + uint32_t vertid; + Vec3f intersectionPoint; + }; + + PickResult *pickResults = (PickResult *)(data + 64); + + PickResult *closest = pickResults; + + // distance from raycast hit to nearest worldspace position of the mouse + float closestPickDistance = (closest->intersectionPoint - rayPos).Length(); + + // min with size of results buffer to protect against overflows + for(uint32_t i = 1; i < RDCMIN(m_MaxMeshPicks, numResults); i++) + { + float pickDistance = (pickResults[i].intersectionPoint - rayPos).Length(); + if(pickDistance < closestPickDistance) + { + closest = pickResults + i; + } + } + + return closest->vertid; + } + else + { + struct PickResult + { + uint32_t vertid; + uint32_t idx; + float len; + float depth; + }; + + PickResult *pickResults = (PickResult *)(data + 64); + + PickResult *closest = pickResults; + + // min with size of results buffer to protect against overflows + for(uint32_t i = 1; i < RDCMIN(m_MaxMeshPicks, numResults); i++) + { + // We need to keep the picking order consistent in the face + // of random buffer appends, when multiple vertices have the + // identical position (e.g. if UVs or normals are different). + // + // We could do something to try and disambiguate, but it's + // never going to be intuitive, it's just going to flicker + // confusingly. + if(pickResults[i].len < closest->len || + (pickResults[i].len == closest->len && pickResults[i].depth < closest->depth) || + (pickResults[i].len == closest->len && pickResults[i].depth == closest->depth && + pickResults[i].vertid < closest->vertid)) + closest = pickResults + i; + } + + return closest->vertid; + } + } + + return ~0U; +} + void D3D12DebugManager::FillCBufferVariables(const string &prefix, size_t &offset, bool flatten, const vector &invars, vector &outvars, diff --git a/renderdoc/driver/d3d12/d3d12_debug.h b/renderdoc/driver/d3d12/d3d12_debug.h index b9e45b890..70a12fd50 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.h +++ b/renderdoc/driver/d3d12/d3d12_debug.h @@ -87,6 +87,7 @@ public: void PickPixel(ResourceId texture, uint32_t x, uint32_t y, uint32_t sliceFace, uint32_t mip, uint32_t sample, FormatComponentType typeHint, float pixel[4]); + uint32_t PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x, uint32_t y); void FillCBufferVariables(const vector &invars, vector &outvars, bool flattenVec4s, @@ -163,6 +164,11 @@ private: OVERDRAW_SRV, OVERDRAW_UAV, STREAM_OUT_UAV, + + PICK_IB_SRV, + PICK_VB_SRV, + PICK_RESULT_UAV, + PICK_RESULT_CLEAR_UAV, }; enum RTVSlot @@ -255,6 +261,9 @@ private: ID3D12Resource *m_PickPixelTex; D3D12_CPU_DESCRIPTOR_HANDLE m_PickPixelRTV; + ID3D12RootSignature *m_MeshPickRootSig; + ID3D12PipelineState *m_MeshPickPipe; + ID3D12RootSignature *m_HistogramRootSig; // one per texture type, one per int/uint/float ID3D12PipelineState *m_TileMinMaxPipe[10][3]; @@ -363,6 +372,11 @@ private: map m_CachedMeshPipelines; + static const uint32_t m_MaxMeshPicks = 500; + ID3D12Resource *m_PickVB; + uint32_t m_PickSize; + ID3D12Resource *m_PickResultBuf; + static const int m_SOBufferSize = 32 * 1024 * 1024; ID3D12Resource *m_SOBuffer; ID3D12Resource *m_SOStagingBuffer; diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index 2b9766ea7..dedffd567 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -1469,6 +1469,11 @@ MeshFormat D3D12Replay::GetPostVSBuffers(uint32_t eventID, uint32_t instID, Mesh return m_pDevice->GetDebugManager()->GetPostVSBuffers(eventID, instID, stage); } +uint32_t D3D12Replay::PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x, uint32_t y) +{ + return m_pDevice->GetDebugManager()->PickVertex(eventID, cfg, x, y); +} + uint64_t D3D12Replay::MakeOutputWindow(WindowingSystem system, void *data, bool depth) { return m_pDevice->GetDebugManager()->MakeOutputWindow(system, data, depth); @@ -1673,11 +1678,6 @@ ShaderDebugTrace D3D12Replay::DebugThread(uint32_t eventID, uint32_t groupid[3], return ShaderDebugTrace(); } -uint32_t D3D12Replay::PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x, uint32_t y) -{ - return ~0U; -} - ResourceId D3D12Replay::ApplyCustomShader(ResourceId shader, ResourceId texid, uint32_t mip, uint32_t arrayIdx, uint32_t sampleIdx, FormatComponentType typeHint)