diff --git a/renderdoc/driver/d3d12/d3d12_debug.cpp b/renderdoc/driver/d3d12/d3d12_debug.cpp index e40cab2d3..a599499e4 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.cpp +++ b/renderdoc/driver/d3d12/d3d12_debug.cpp @@ -1843,15 +1843,15 @@ void MoveRootSignatureElementsToRegisterSpace(D3D12RootSignature &sig, uint32_t } } -void AddDebugDescriptorToRenderState(WrappedID3D12Device *pDevice, D3D12RenderState &rs, - const PortableHandle &handle, - D3D12_DESCRIPTOR_HEAP_TYPE heapType, uint32_t sigElem, - std::set &copiedHeaps) +void AddDebugDescriptorsToRenderState(WrappedID3D12Device *pDevice, D3D12RenderState &rs, + const rdcarray &handles, + D3D12_DESCRIPTOR_HEAP_TYPE heapType, uint32_t sigElem, + std::set &copiedHeaps) { if(rs.graphics.sigelems.size() <= sigElem) rs.graphics.sigelems.resize(sigElem + 1); - PortableHandle newHandle = handle; + PortableHandle newHandle = handles[0]; // If a CBV_SRV_UAV heap is already set and hasn't had a debug descriptor copied in, // copy the desired descriptor in and add the heap to the set of heaps that have had @@ -1866,33 +1866,38 @@ void AddDebugDescriptorToRenderState(WrappedID3D12Device *pDevice, D3D12RenderSt pDevice->GetResourceManager()->GetCurrentAs(rs.heaps[i]); if(h->GetDesc().Type == heapType) { - // use the last descriptor + // use the last descriptors D3D12_CPU_DESCRIPTOR_HANDLE dst = h->GetCPUDescriptorHandleForHeapStart(); - dst.ptr += (h->GetDesc().NumDescriptors - 1) * sizeof(D3D12Descriptor); + dst.ptr += (h->GetDesc().NumDescriptors - handles.size()) * sizeof(D3D12Descriptor); + + newHandle = ToPortableHandle(dst); if(copiedHeaps.find(rs.heaps[i]) == copiedHeaps.end()) { - WrappedID3D12DescriptorHeap *h2 = - pDevice->GetResourceManager()->GetCurrentAs(handle.heap); - D3D12_CPU_DESCRIPTOR_HANDLE src = h2->GetCPUDescriptorHandleForHeapStart(); - src.ptr += handle.index * sizeof(D3D12Descriptor); + for(size_t j = 0; j < handles.size(); ++j) + { + WrappedID3D12DescriptorHeap *h2 = + pDevice->GetResourceManager()->GetCurrentAs( + handles[j].heap); + D3D12_CPU_DESCRIPTOR_HANDLE src = h2->GetCPUDescriptorHandleForHeapStart(); + src.ptr += handles[j].index * sizeof(D3D12Descriptor); - // can't do a copy because the src heap is CPU write-only (shader visible). So instead, - // create directly - D3D12Descriptor *srcDesc = (D3D12Descriptor *)src.ptr; - srcDesc->Create(heapType, pDevice, dst); + // can't do a copy because the src heap is CPU write-only (shader visible). So instead, + // create directly + D3D12Descriptor *srcDesc = (D3D12Descriptor *)src.ptr; + srcDesc->Create(heapType, pDevice, dst); + dst.ptr += sizeof(D3D12Descriptor); + } copiedHeaps.insert(rs.heaps[i]); } - newHandle = ToPortableHandle(dst); - break; } } - if(newHandle.heap == handle.heap) - rs.heaps.push_back(handle.heap); + if(newHandle.heap == handles[0].heap) + rs.heaps.push_back(handles[0].heap); rs.graphics.sigelems[sigElem] = D3D12RenderState::SignatureElement(eRootTable, newHandle.heap, newHandle.index); diff --git a/renderdoc/driver/d3d12/d3d12_debug.h b/renderdoc/driver/d3d12/d3d12_debug.h index 915da2ad9..ddac38d3a 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.h +++ b/renderdoc/driver/d3d12/d3d12_debug.h @@ -60,6 +60,7 @@ enum CBVUAVSRVSlot PICK_RESULT_CLEAR_UAV, SHADER_DEBUG_UAV, + SHADER_DEBUG_MSAA_UAV, TMP_UAV, @@ -222,7 +223,7 @@ void MoveRootSignatureElementsToRegisterSpace(D3D12RootSignature &sig, uint32_t D3D12DescriptorType type, D3D12_SHADER_VISIBILITY visibility); -void AddDebugDescriptorToRenderState(WrappedID3D12Device *pDevice, D3D12RenderState &rs, - const PortableHandle &handle, - D3D12_DESCRIPTOR_HEAP_TYPE heapType, uint32_t sigElem, - std::set &copiedHeaps); +void AddDebugDescriptorsToRenderState(WrappedID3D12Device *pDevice, D3D12RenderState &rs, + const rdcarray &handles, + D3D12_DESCRIPTOR_HEAP_TYPE heapType, uint32_t sigElem, + std::set &copiedHeaps); diff --git a/renderdoc/driver/d3d12/d3d12_overlay.cpp b/renderdoc/driver/d3d12/d3d12_overlay.cpp index 05627391a..a37ff1671 100644 --- a/renderdoc/driver/d3d12/d3d12_overlay.cpp +++ b/renderdoc/driver/d3d12/d3d12_overlay.cpp @@ -145,8 +145,8 @@ struct D3D12QuadOverdrawCallback : public D3D12DrawcallCallback rs.pipe = GetResID(cache.pipe); rs.graphics.rootsig = GetResID(cache.sig); - AddDebugDescriptorToRenderState(m_pDevice, rs, m_UAV, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, - cache.sigElem, m_CopiedHeaps); + AddDebugDescriptorsToRenderState(m_pDevice, rs, {m_UAV}, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + cache.sigElem, m_CopiedHeaps); // as we're changing the root signature, we need to reapply all elements, // so just apply all state diff --git a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp index 178666713..de807c329 100644 --- a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp +++ b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp @@ -2025,15 +2025,102 @@ ShaderDebugTrace *D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t } } - // get the multisample count - uint32_t outputSampleCount = RDCMAX(1U, pipelineState->outputMerger.multiSampleCount); + // Store a copy of the event's render state to restore later + D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState; + D3D12RenderState prevState = rs; + + // Fetch the multisample count from the PSO + WrappedID3D12PipelineState *origPSO = + m_pDevice->GetResourceManager()->GetCurrentAs(rs.pipe); + + D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc; + origPSO->Fill(pipeDesc); + uint32_t outputSampleCount = RDCMAX(1U, pipeDesc.SampleDesc.Count); + + std::set evalSampleCacheData; + uint64_t sampleEvalRegisterMask = 0; // if we're not rendering at MSAA, no need to fill the cache because evaluates will all return the // plain input anyway. if(outputSampleCount > 1) { - RDCUNIMPLEMENTED("MSAA debugging not yet implemented for D3D12"); - return new ShaderDebugTrace; + // scan the instructions to see if it contains any evaluates. + size_t numInstructions = dxbc->GetDXBCByteCode()->GetNumInstructions(); + for(size_t i = 0; i < numInstructions; ++i) + { + const Operation &op = dxbc->GetDXBCByteCode()->GetInstruction(i); + + // skip any non-eval opcodes + if(op.operation != OPCODE_EVAL_CENTROID && op.operation != OPCODE_EVAL_SAMPLE_INDEX && + op.operation != OPCODE_EVAL_SNAPPED) + continue; + + // the generation of this key must match what we'll generate in the corresponding lookup + GlobalState::SampleEvalCacheKey key; + + // all the eval opcodes have rDst, vIn as the first two operands + key.inputRegisterIndex = (int32_t)op.operands[1].indices[0].index; + + for(int c = 0; c < 4; c++) + { + if(op.operands[0].comps[c] == 0xff) + break; + + key.numComponents = c + 1; + } + + key.firstComponent = op.operands[1].comps[op.operands[0].comps[0]]; + + sampleEvalRegisterMask |= 1ULL << key.inputRegisterIndex; + + if(op.operation == OPCODE_EVAL_CENTROID) + { + // nothing to do - default key is centroid, sample is -1 and offset x/y is 0 + evalSampleCacheData.insert(key); + } + else if(op.operation == OPCODE_EVAL_SAMPLE_INDEX) + { + if(op.operands[2].type == TYPE_IMMEDIATE32 || op.operands[2].type == TYPE_IMMEDIATE64) + { + // hooray, only sampling a single index, just add this key + key.sample = (int32_t)op.operands[2].values[0]; + + evalSampleCacheData.insert(key); + } + else + { + // parameter is a register and we don't know which sample will be needed, fetch them all. + // In most cases this will be a loop over them all, so they'll all be needed anyway + for(uint32_t c = 0; c < outputSampleCount; c++) + { + key.sample = (int32_t)c; + evalSampleCacheData.insert(key); + } + } + } + else if(op.operation == OPCODE_EVAL_SNAPPED) + { + if(op.operands[2].type == TYPE_IMMEDIATE32 || op.operands[2].type == TYPE_IMMEDIATE64) + { + // hooray, only sampling a single offset, just add this key + key.offsetx = (int32_t)op.operands[2].values[0]; + key.offsety = (int32_t)op.operands[2].values[1]; + + evalSampleCacheData.insert(key); + } + else + { + m_pDevice->AddDebugMessage( + MessageCategory::Shaders, MessageSeverity::Medium, MessageSource::RuntimeWarning, + "EvaluateAttributeSnapped called with dynamic parameter, caching all possible " + "evaluations which could have performance impact."); + + for(key.offsetx = -8; key.offsetx <= 7; key.offsetx++) + for(key.offsety = -8; key.offsety <= 7; key.offsety++) + evalSampleCacheData.insert(key); + } + } + } } extractHlsl += R"( @@ -2058,7 +2145,15 @@ struct PSInitialData )"; - extractHlsl += "RWStructuredBuffer PSInitialBuffer : register(u0);\n\n"; + // If this event uses MSAA, then at least one render target must be preserved to get + // multisampling info. leave u0 alone and start with register u1 + extractHlsl += "RWStructuredBuffer PSInitialBuffer : register(u1);\n\n"; + + if(!evalSampleCacheData.empty()) + { + // float4 is wasteful in some cases but it's easier than using byte buffers and manual packing + extractHlsl += "RWBuffer PSEvalBuffer : register(u2);\n\n"; + } if(usePrimitiveID) { @@ -2101,6 +2196,69 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, extractHlsl += " PSInitialBuffer[idx].INddxfine = (PSInput)0;\n"; extractHlsl += " PSInitialBuffer[idx].INddyfine = (PSInput)0;\n"; + if(!evalSampleCacheData.empty()) + { + extractHlsl += StringFormat::Fmt(" uint evalIndex = idx * %zu;\n", evalSampleCacheData.size()); + + uint32_t evalIdx = 0; + for(const GlobalState::SampleEvalCacheKey &key : evalSampleCacheData) + { + uint32_t keyMask = 0; + + for(int32_t i = 0; i < key.numComponents; i++) + keyMask |= (1 << (key.firstComponent + i)); + + // find the name of the variable matching the operand, in the case of merged input variables. + rdcstr name, swizzle = "xyzw"; + for(size_t i = 0; i < dxbc->GetReflection()->InputSig.size(); i++) + { + if(dxbc->GetReflection()->InputSig[i].regIndex == (uint32_t)key.inputRegisterIndex && + dxbc->GetReflection()->InputSig[i].systemValue == ShaderBuiltin::Undefined && + (dxbc->GetReflection()->InputSig[i].regChannelMask & keyMask) == keyMask) + { + name = inputVarNames[i]; + + if(!name.empty()) + break; + } + } + + swizzle.resize(key.numComponents); + + if(name.empty()) + { + RDCERR("Couldn't find matching input variable for v%d [%d:%d]", key.inputRegisterIndex, + key.firstComponent, key.numComponents); + extractHlsl += StringFormat::Fmt(" PSEvalBuffer[evalIndex+%u] = 0;\n", evalIdx); + evalIdx++; + continue; + } + + name = StringFormat::Fmt("IN.%s.%s", name.c_str(), swizzle.c_str()); + + // we must write all components, so just swizzle the values - they'll be ignored later. + rdcstr expandSwizzle = swizzle; + while(expandSwizzle.size() < 4) + expandSwizzle.push_back('x'); + + if(key.sample >= 0) + { + extractHlsl += StringFormat::Fmt( + " PSEvalBuffer[evalIndex+%u] = EvaluateAttributeAtSample(%s, %d).%s;\n", evalIdx, + name.c_str(), key.sample, expandSwizzle.c_str()); + } + else + { + // we don't need to special-case EvaluateAttributeAtCentroid, since it's just a case with + // 0,0 + extractHlsl += StringFormat::Fmt( + " PSEvalBuffer[evalIndex+%u] = EvaluateAttributeSnapped(%s, int2(%d, %d)).%s;\n", + evalIdx, name.c_str(), key.offsetx, key.offsety, expandSwizzle.c_str()); + } + evalIdx++; + } + } + for(size_t i = 0; i < floatInputs.size(); i++) { const rdcstr &name = floatInputs[i]; @@ -2166,6 +2324,24 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, return new ShaderDebugTrace; } + // Create buffer to store MSAA evaluations captured in pixel shader + ID3D12Resource *pMsaaEvalBuffer = NULL; + if(!evalSampleCacheData.empty()) + { + rdesc.Width = UINT(evalSampleCacheData.size() * sizeof(Vec4f) * (overdrawLevels + 1)); + hr = m_pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &rdesc, resourceState, + NULL, __uuidof(ID3D12Resource), + (void **)&pMsaaEvalBuffer); + if(FAILED(hr)) + { + RDCERR("Failed to create MSAA buffer for pixel shader debugging HRESULT: %s", + ToStr(hr).c_str()); + SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(psBlob); + return new ShaderDebugTrace; + } + } + // Create UAV of initial values buffer D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc; ZeroMemory(&uavDesc, sizeof(D3D12_UNORDERED_ACCESS_VIEW_DESC)); @@ -2185,9 +2361,22 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, m_pDevice->GetDebugManager()->GetUAVClearHandle(SHADER_DEBUG_UAV); m_pDevice->CreateUnorderedAccessView(pInitialValuesBuffer, NULL, &uavDesc, clearUav); - // Store a copy of the event's render state to restore later - D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState; - D3D12RenderState prevState = rs; + // Create UAV of MSAA eval buffer + D3D12_CPU_DESCRIPTOR_HANDLE msaaClearUav = + m_pDevice->GetDebugManager()->GetUAVClearHandle(SHADER_DEBUG_MSAA_UAV); + if(pMsaaEvalBuffer) + { + D3D12_CPU_DESCRIPTOR_HANDLE msaaUav = + m_pDevice->GetDebugManager()->GetCPUHandle(SHADER_DEBUG_MSAA_UAV); + uavDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; + uavDesc.Buffer.NumElements = (overdrawLevels + 1) * (uint32_t)evalSampleCacheData.size(); + m_pDevice->CreateUnorderedAccessView(pMsaaEvalBuffer, NULL, &uavDesc, msaaUav); + + uavDesc.Format = DXGI_FORMAT_R32_UINT; + uavDesc.Buffer.NumElements = + (UINT)evalSampleCacheData.size() * (overdrawLevels + 1) / sizeof(uint32_t); + m_pDevice->CreateUnorderedAccessView(pMsaaEvalBuffer, NULL, &uavDesc, msaaClearUav); + } WrappedID3D12RootSignature *sig = m_pDevice->GetResourceManager()->GetCurrentAs(rs.graphics.rootsig); @@ -2203,8 +2392,8 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, // Create the descriptor table for our UAV D3D12_DESCRIPTOR_RANGE1 descRange; descRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; - descRange.NumDescriptors = 1; - descRange.BaseShaderRegister = 0; + descRange.NumDescriptors = pMsaaEvalBuffer ? 2 : 1; + descRange.BaseShaderRegister = 1; descRange.RegisterSpace = 0; descRange.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; descRange.OffsetInDescriptorsFromTableStart = 0; @@ -2230,18 +2419,11 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, SAFE_RELEASE(root); SAFE_RELEASE(psBlob); SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pMsaaEvalBuffer); return new ShaderDebugTrace; } SAFE_RELEASE(root); - WrappedID3D12PipelineState *origPSO = - m_pDevice->GetResourceManager()->GetCurrentAs(rs.pipe); - - RDCASSERT(origPSO->IsGraphics()); - - D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC pipeDesc; - origPSO->Fill(pipeDesc); - // All PSO state is the same as the event's, except for the pixel shader and root signature pipeDesc.PS.BytecodeLength = psBlob->GetBufferSize(); pipeDesc.PS.pShaderBytecode = psBlob->GetBufferPointer(); @@ -2254,15 +2436,19 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, RDCERR("Failed to create PSO for pixel shader debugging HRESULT: %s", ToStr(hr).c_str()); SAFE_RELEASE(psBlob); SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pMsaaEvalBuffer); SAFE_RELEASE(pRootSignature); return new ShaderDebugTrace; } // Add the descriptor for our UAV, then clear it std::set copiedHeaps; - PortableHandle shaderDebugUav = ToPortableHandle(GetDebugManager()->GetCPUHandle(SHADER_DEBUG_UAV)); - AddDebugDescriptorToRenderState(m_pDevice, rs, shaderDebugUav, - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, sigElem, copiedHeaps); + rdcarray debugHandles; + debugHandles.push_back(ToPortableHandle(GetDebugManager()->GetCPUHandle(SHADER_DEBUG_UAV))); + if(pMsaaEvalBuffer) + debugHandles.push_back(ToPortableHandle(GetDebugManager()->GetCPUHandle(SHADER_DEBUG_MSAA_UAV))); + AddDebugDescriptorsToRenderState(m_pDevice, rs, debugHandles, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, sigElem, copiedHeaps); ID3D12GraphicsCommandListX *cmdList = m_pDevice->GetDebugManager()->ResetDebugList(); rs.ApplyDescriptorHeaps(cmdList); @@ -2270,6 +2456,13 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, UINT zero[4] = {0, 0, 0, 0}; cmdList->ClearUnorderedAccessViewUint(gpuUav, clearUav, pInitialValuesBuffer, zero, 0, NULL); + if(pMsaaEvalBuffer) + { + D3D12_GPU_DESCRIPTOR_HANDLE gpuMsaaUav = + m_pDevice->GetDebugManager()->GetGPUHandle(SHADER_DEBUG_MSAA_UAV); + cmdList->ClearUnorderedAccessViewUint(gpuMsaaUav, msaaClearUav, pMsaaEvalBuffer, zero, 0, NULL); + } + // Execute the command to ensure that UAV clear and resource creation occur before replay hr = cmdList->Close(); if(FAILED(hr)) @@ -2277,6 +2470,7 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, RDCERR("Failed to close command list HRESULT: %s", ToStr(hr).c_str()); SAFE_RELEASE(psBlob); SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pMsaaEvalBuffer); SAFE_RELEASE(pRootSignature); SAFE_RELEASE(initialPso); return new ShaderDebugTrace; @@ -2306,11 +2500,16 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, bytebuf initialData; m_pDevice->GetDebugManager()->GetBufferData(pInitialValuesBuffer, 0, 0, initialData); + bytebuf evalData; + if(pMsaaEvalBuffer) + m_pDevice->GetDebugManager()->GetBufferData(pMsaaEvalBuffer, 0, 0, evalData); + // Replaying the event has finished, and the data has been copied out. // Free all the resources that were created. SAFE_RELEASE(psBlob); SAFE_RELEASE(pRootSignature); SAFE_RELEASE(pInitialValuesBuffer); + SAFE_RELEASE(pMsaaEvalBuffer); SAFE_RELEASE(initialPso); DebugHit *buf = (DebugHit *)initialData.data(); @@ -2340,6 +2539,7 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, // Get depth func and determine "winner" pixel D3D12_COMPARISON_FUNC depthFunc = pipeDesc.DepthStencilState.DepthFunc; DebugHit *pWinnerHit = NULL; + float *evalSampleCache = (float *)evalData.data(); if(sample == ~0U) sample = 0; @@ -2353,6 +2553,7 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, if(pHit->primitive == primitive && pHit->sample == sample) { pWinnerHit = pHit; + evalSampleCache = ((float *)evalData.data() + evalSampleCacheData.size() * 4 * i); } } } @@ -2363,22 +2564,36 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, { DebugHit *pHit = (DebugHit *)(initialData.data() + i * structStride); - if(pWinnerHit == NULL || (pWinnerHit->sample != sample && pHit->sample == sample) || - depthFunc == D3D12_COMPARISON_FUNC_ALWAYS || depthFunc == D3D12_COMPARISON_FUNC_NEVER || - depthFunc == D3D12_COMPARISON_FUNC_NOT_EQUAL || depthFunc == D3D12_COMPARISON_FUNC_EQUAL) + if(pWinnerHit == NULL) { + // If we haven't picked a winner at all yet, use the first one pWinnerHit = pHit; - continue; + evalSampleCache = ((float *)evalData.data()) + evalSampleCacheData.size() * 4 * i; } - - if((depthFunc == D3D12_COMPARISON_FUNC_LESS && pHit->depth < pWinnerHit->depth) || - (depthFunc == D3D12_COMPARISON_FUNC_LESS_EQUAL && pHit->depth <= pWinnerHit->depth) || - (depthFunc == D3D12_COMPARISON_FUNC_GREATER && pHit->depth > pWinnerHit->depth) || - (depthFunc == D3D12_COMPARISON_FUNC_GREATER_EQUAL && pHit->depth >= pWinnerHit->depth)) + else if(pHit->sample == sample) { - if(pHit->sample == sample) + // If this hit is for the sample we want, check whether it's a better pick + if(pWinnerHit->sample != sample) { + // The previously selected winner was for the wrong sample, use this one pWinnerHit = pHit; + evalSampleCache = ((float *)evalData.data()) + evalSampleCacheData.size() * 4 * i; + } + else if((depthFunc == D3D11_COMPARISON_ALWAYS || depthFunc == D3D11_COMPARISON_NEVER || + depthFunc == D3D11_COMPARISON_NOT_EQUAL || depthFunc == D3D11_COMPARISON_EQUAL)) + { + // For depth functions without an inequality comparison, use the last sample encountered + pWinnerHit = pHit; + evalSampleCache = ((float *)evalData.data()) + evalSampleCacheData.size() * 4 * i; + } + else if((depthFunc == D3D11_COMPARISON_LESS && pHit->depth < pWinnerHit->depth) || + (depthFunc == D3D11_COMPARISON_LESS_EQUAL && pHit->depth <= pWinnerHit->depth) || + (depthFunc == D3D11_COMPARISON_GREATER && pHit->depth > pWinnerHit->depth) || + (depthFunc == D3D11_COMPARISON_GREATER_EQUAL && pHit->depth >= pWinnerHit->depth)) + { + // For depth functions with an inequality, find the hit that "wins" the most + pWinnerHit = pHit; + evalSampleCache = ((float *)evalData.data()) + evalSampleCacheData.size() * 4 * i; } } } @@ -2399,6 +2614,8 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, GatherConstantBuffers(m_pDevice, *dxbc->GetDXBCByteCode(), rs.graphics, refl, origPSO->PS()->GetMapping(), global, ret->sourceVars); + global.sampleEvalRegisterMask = sampleEvalRegisterMask; + { DebugHit *pHit = pWinnerHit; @@ -2472,7 +2689,26 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, } } - // TODO: Handle inputs that were evaluated at sample granularity (MSAA) + // Fetch any inputs that were evaluated at sample granularity + for(const GlobalState::SampleEvalCacheKey &key : evalSampleCacheData) + { + // start with the basic input value + ShaderVariable var = state.inputs[key.inputRegisterIndex]; + + // copy over the value into the variable + memcpy(var.value.fv, evalSampleCache, var.columns * sizeof(float)); + + // store in the global cache for each quad. We'll apply derivatives below to adjust for each + GlobalState::SampleEvalCacheKey k = key; + for(int i = 0; i < 4; i++) + { + k.quadIndex = i; + global.sampleEvalCache[k] = var; + } + + // advance past this data - always by float4 as that's the buffer stride + evalSampleCache += 4; + } ApplyAllDerivatives(global, interpreter->workgroup, destIdx, initialValues, (float *)data); } diff --git a/util/test/demos/d3d12/d3d12_shader_debug_zoo.cpp b/util/test/demos/d3d12/d3d12_shader_debug_zoo.cpp index ecb25ff84..e866e02b2 100644 --- a/util/test/demos/d3d12/d3d12_shader_debug_zoo.cpp +++ b/util/test/demos/d3d12/d3d12_shader_debug_zoo.cpp @@ -576,6 +576,31 @@ float4 main(v2f IN) : SV_Target0 return float4(0.4f, 0.4f, 0.4f, 0.4f); } +)EOSHADER"; + + std::string msaaPixel = R"EOSHADER( + +struct v2f +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 +{ + float2 uvCentroid = EvaluateAttributeCentroid(IN.uv); + float2 uvSamp0 = EvaluateAttributeAtSample(IN.uv, 0) - IN.uv; + float2 uvSampThis = EvaluateAttributeAtSample(IN.uv, samp) - IN.uv; + float2 uvOffset = EvaluateAttributeSnapped(IN.uv, int2(1, 1)); + + float x = (uvCentroid.x + uvCentroid.y) * 0.5f; + float y = (uvSamp0.x + uvSamp0.y) * 0.5f; + float z = (uvSampThis.x + uvSampThis.y) * 0.5f; + float w = (uvOffset.x + uvOffset.y) * 0.5f; + return float4(x, y, z, w); +} + )EOSHADER"; int main() @@ -705,6 +730,22 @@ float4 main(v2f IN) : SV_Target0 D3D12_CPU_DESCRIPTOR_HANDLE uav2cpu = uavView2.CreateClearCPU(5); D3D12_GPU_DESCRIPTOR_HANDLE uav2gpu = uavView2.CreateGPU(5); + // Create resources for MSAA draw + ID3DBlobPtr vsmsaablob = Compile(D3DDefaultVertex, "main", "vs_5_0"); + ID3DBlobPtr psmsaablob = Compile(msaaPixel, "main", "ps_5_0"); + + ID3D12RootSignaturePtr sigmsaa = MakeSig({}); + + ID3D12PipelineStatePtr psomsaa = + MakePSO().RootSig(sigmsaa).InputLayout().VS(vsmsaablob).PS(psmsaablob).SampleCount(4); + ID3D12ResourcePtr vbmsaa = MakeBuffer().Data(DefaultTri); + + ID3D12ResourcePtr msaaTex = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, 8, 8) + .RTV() + .Multisampled(4) + .InitialState(D3D12_RESOURCE_STATE_RENDER_TARGET); + D3D12_CPU_DESCRIPTOR_HANDLE msaaRTV = MakeRTV(msaaTex).CreateCPU(1); + vsblob = Compile(D3DFullscreenQuadVertex, "main", "vs_4_0"); psblob = Compile(pixelBlit, "main", "ps_5_0"); ID3D12RootSignaturePtr blitSig = MakeSig({ @@ -721,7 +762,7 @@ float4 main(v2f IN) : SV_Target0 ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); D3D12_CPU_DESCRIPTOR_HANDLE rtv = - MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(1); + MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(2); ClearRenderTargetView(cmd, rtv, {0.2f, 0.2f, 0.2f, 1.0f}); ID3D12PipelineStatePtr psos[2] = {pso_5_0, pso_5_1}; @@ -732,13 +773,12 @@ float4 main(v2f IN) : SV_Target0 // Clear, draw, and blit to backbuffer twice - once for SM 5.0 and again for SM 5.1 for(int i = 0; i < 2; ++i) { + OMSetRenderTargets(cmd, {fltRTV}, {}); ClearRenderTargetView(cmd, fltRTV, {0.2f, 0.2f, 0.2f, 1.0f}); IASetVertexBuffer(cmd, vb, sizeof(ConstsA2V), 0); cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - OMSetRenderTargets(cmd, {fltRTV}, {}); - cmd->SetGraphicsRootSignature(sig); cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr()); cmd->SetGraphicsRootDescriptorTable(0, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart()); @@ -775,6 +815,21 @@ float4 main(v2f IN) : SV_Target0 D3D12_RESOURCE_STATE_RENDER_TARGET); } + // Render MSAA test + OMSetRenderTargets(cmd, {msaaRTV}, {}); + ClearRenderTargetView(cmd, msaaRTV, {0.2f, 0.2f, 0.2f, 1.0f}); + IASetVertexBuffer(cmd, vbmsaa, sizeof(DefaultA2V), 0); + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + cmd->SetGraphicsRootSignature(sigmsaa); + cmd->SetPipelineState(psomsaa); + RSSetViewport(cmd, {0.0f, 0.0f, 8.0f, 8.0f, 0.0f, 1.0f}); + RSSetScissorRect(cmd, {0, 0, 8, 8}); + + // Add a marker so we can easily locate this draw + cmd->SetMarker(1, "MSAA", 4); + cmd->DrawInstanced(3, 1, 0, 0); + FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); cmd->Close(); diff --git a/util/test/tests/D3D12/D3D12_Shader_Debug_Zoo.py b/util/test/tests/D3D12/D3D12_Shader_Debug_Zoo.py index 4d5b680b8..349161812 100644 --- a/util/test/tests/D3D12/D3D12_Shader_Debug_Zoo.py +++ b/util/test/tests/D3D12/D3D12_Shader_Debug_Zoo.py @@ -21,7 +21,7 @@ class D3D12_Shader_Debug_Zoo(rdtest.TestCase): # Jump to the draw test_marker: rd.DrawcallDescription = self.find_draw(shaderModels[sm]) draw = test_marker.next - self.controller.SetFrameEvent(draw.eventId, True) + self.controller.SetFrameEvent(draw.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() @@ -50,6 +50,40 @@ class D3D12_Shader_Debug_Zoo(rdtest.TestCase): rdtest.log.dedent() + rdtest.log.print("Performing MSAA tests:") + rdtest.log.indent() + test_marker: rd.DrawcallDescription = self.find_draw("MSAA") + draw = test_marker.next + self.controller.SetFrameEvent(draw.eventId, False) + pipe: rd.PipeState = self.controller.GetPipelineState() + for test in range(4): + # Debug the shader + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(4, 4, test, + rd.ReplayController.NoPreference) + + # Validate that the correct sample index was debugged + inputs: List[rd.ShaderVariable] = list(trace.inputs) + sampRegister = self.find_input_source_var(trace, rd.ShaderBuiltin.MSAASampleIndex) + sampInput = [var for var in inputs if var.name == sampRegister.variables[0].name][0] + if sampInput.value.uv[0] != test: + rdtest.log.error("Test {} did not pick the correct sample.".format(test)) + + cycles, variables = self.process_trace(trace) + + output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) + + debugged = self.evalute_source_var(output, variables) + + # Validate the debug output result + try: + self.check_pixel_sample_value(pipe.GetOutputTargets()[0].resourceId, 4, 4, test, debugged.value.fv[0:4], 0.0) + except rdtest.TestFailureException as ex: + failed = True + rdtest.log.error("Test {} did not match. {}".format(test, str(ex))) + continue + + rdtest.log.dedent() + if failed: raise rdtest.TestFailureException("Some tests were not as expected")