diff --git a/util/test/demos/d3d12/d3d12_helpers.h b/util/test/demos/d3d12/d3d12_helpers.h index bc45db252..57aa625c1 100644 --- a/util/test/demos/d3d12/d3d12_helpers.h +++ b/util/test/demos/d3d12/d3d12_helpers.h @@ -54,6 +54,8 @@ COM_SMARTPTR(ID3D12GraphicsCommandList1); COM_SMARTPTR(ID3D12GraphicsCommandList2); COM_SMARTPTR(ID3D12GraphicsCommandList3); COM_SMARTPTR(ID3D12GraphicsCommandList4); +COM_SMARTPTR(ID3D12GraphicsCommandList5); +COM_SMARTPTR(ID3D12GraphicsCommandList6); COM_SMARTPTR(ID3D12CommandSignature); diff --git a/util/test/demos/d3d12/d3d12_vrs.cpp b/util/test/demos/d3d12/d3d12_vrs.cpp new file mode 100644 index 000000000..ee3dc5b22 --- /dev/null +++ b/util/test/demos/d3d12/d3d12_vrs.cpp @@ -0,0 +1,370 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2021 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "d3d12_test.h" + +RD_TEST(D3D12_VRS, D3D12GraphicsTest) +{ + static constexpr const char *Description = + "Checks that VRS is correctly replayed and that state is inspectable"; + + std::string pixel = R"EOSHADER( + +uint wang_hash(uint seed) +{ + seed = (seed ^ 61) ^ (seed >> 16); + seed *= 9; + seed = seed ^ (seed >> 4); + seed *= 0x27d4eb2d; + seed = seed ^ (seed >> 15); + return seed; +} + +float4 main(float4 pos : SV_Position) : SV_Target0 +{ + uint col = wang_hash(uint(pos.x * 10000.0f + pos.y)); + float4 outcol; + outcol.x = float((col & 0xff000000u) >> 24u) / 255.0f; + outcol.y = float((col & 0x00ff0000u) >> 16u) / 255.0f; + outcol.z = float((col & 0x0000ff00u) >> 8u) / 255.0f; + outcol.w = 1.0f; + return outcol; +} + +)EOSHADER"; + + std::string vertex = R"EOSHADER( + +struct OUT +{ +float4 pos : SV_Position; + +#ifdef VERT_VRS +uint rate : SV_ShadingRate; +#endif +}; + +OUT main(float3 pos : POSITION, float4 col : COLOR0) +{ + OUT o = (OUT)0; + + o.pos = float4(pos.xyz, 1); + +#ifdef VERT_VRS + o.rate = uint(col.x) << 2 | uint(col.y); +#endif + + return o; +} + +)EOSHADER"; + + void Prepare(int argc, char **argv) + { + D3D12GraphicsTest::Prepare(argc, argv); + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED) + Avail = "Variable shading rate is not supported"; + } + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + ID3DBlobPtr vsblob = Compile(vertex, "main", "vs_5_0"); + ID3DBlobPtr psblob = Compile(pixel, "main", "ps_5_0"); + + ID3D12RootSignaturePtr sig = MakeSig({}); + + ID3D12PipelineStatePtr pso = MakePSO().RootSig(sig).InputLayout().VS(vsblob).PS(psblob); + + ID3D12PipelineStatePtr vertpso; + // without DXIL we can't compile shaders with shading rate exported from the vertex + if(m_DXILSupport) + { + vsblob = Compile("#define VERT_VRS 1\n\n" + vertex, "main", "vs_6_4"); + psblob = Compile(pixel, "main", "ps_6_0"); + + vertpso = MakePSO().RootSig(sig).InputLayout().VS(vsblob).PS(psblob); + } + + const DefaultA2V tris[6] = { + {Vec3f(-1.0f, -0.6f, 0.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(-0.5f, 0.4f, 0.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.0f, -0.6f, 0.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + + {Vec3f(0.0f, -0.4f, 0.0f), Vec4f(1.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.5f, 0.6f, 0.0f), Vec4f(1.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(1.0f, -0.4f, 0.0f), Vec4f(1.0f, 1.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + + ID3D12ResourcePtr vb = MakeBuffer().Data(tris); + + ResourceBarrier(vb, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + + ID3D12ResourcePtr shadImage = + MakeTexture(DXGI_FORMAT_R8_UINT, screenWidth / opts6.ShadingRateImageTileSize, + screenHeight / opts6.ShadingRateImageTileSize) + .Mips(1) + .UAV() + .InitialState(D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + while(Running()) + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv = + MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(0); + + cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr()); + + ClearRenderTargetView(cmd, rtv, {0.2f, 0.2f, 0.2f, 1.0f}); + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + D3D12_RECT rect = {0, 0, LONG(screenWidth / opts6.ShadingRateImageTileSize), + LONG(screenHeight / opts6.ShadingRateImageTileSize)}; + uint32_t col[4] = {}; + col[0] = D3D12_SHADING_RATE_2X2; + + D3D12_CPU_DESCRIPTOR_HANDLE shadCPU = MakeUAV(shadImage).CreateClearCPU(1); + D3D12_GPU_DESCRIPTOR_HANDLE shadGPU = MakeUAV(shadImage).CreateGPU(1); + cmd->ClearUnorderedAccessViewUint(shadGPU, shadCPU, shadImage, col, 1, &rect); + + col[0] = D3D12_SHADING_RATE_1X1; + rect.left = LONG(screenWidth / opts6.ShadingRateImageTileSize) - + LONG((screenWidth / 8) / opts6.ShadingRateImageTileSize); + cmd->ClearUnorderedAccessViewUint(shadGPU, shadCPU, shadImage, col, 1, &rect); + + ResourceBarrier(cmd, shadImage, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE); + } + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + IASetVertexBuffer(cmd, vb, sizeof(DefaultA2V), 0); + cmd->SetGraphicsRootSignature(sig); + + OMSetRenderTargets(cmd, {rtv}, {}); + RSSetScissorRect(cmd, {0, 0, screenWidth, screenHeight}); + + float x = (float)screenWidth / 4.0f; + float y = (float)screenHeight / 4.0f; + + cmd->SetPipelineState(pso); + + ID3D12GraphicsCommandList5Ptr cmd5 = cmd; + + D3D12_SHADING_RATE_COMBINER combiners[] = { + D3D12_SHADING_RATE_COMBINER_MAX, D3D12_SHADING_RATE_COMBINER_MAX, + }; + + pushMarker(cmd, "First"); + + { + setMarker(cmd, "Default"); + + RSSetViewport(cmd, {x * 0.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + } + + { + setMarker(cmd, "Base"); + + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_2X2, combiners); + + RSSetViewport(cmd, {x * 1.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_1X1, combiners); + } + + if(vertpso) + { + setMarker(cmd, "Vertex"); + + cmd->SetPipelineState(vertpso); + + RSSetViewport(cmd, {x * 2.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd->SetPipelineState(pso); + } + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmd, "Image"); + + cmd5->RSSetShadingRateImage(shadImage); + + RSSetViewport(cmd, {x * 3.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd5->RSSetShadingRateImage(NULL); + } + + if(vertpso) + { + setMarker(cmd, "Base + Vertex"); + + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_2X2, combiners); + cmd->SetPipelineState(vertpso); + + RSSetViewport(cmd, {x * 0.0f, y, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd->SetPipelineState(pso); + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_1X1, combiners); + } + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmd, "Base + Image"); + + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_2X2, combiners); + cmd5->RSSetShadingRateImage(shadImage); + + RSSetViewport(cmd, {x * 3.0f, y, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd5->RSSetShadingRateImage(NULL); + cmd5->RSSetShadingRate(D3D12_SHADING_RATE_1X1, combiners); + } + + if(vertpso && opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmd, "Vertex + Image"); + + cmd5->RSSetShadingRateImage(shadImage); + cmd->SetPipelineState(vertpso); + + RSSetViewport(cmd, {x * 3.0f, y * 2.0f, x, y, 0.0f, 1.0f}); + cmd->DrawInstanced(6, 1, 0, 0); + + cmd->SetPipelineState(pso); + cmd5->RSSetShadingRateImage(NULL); + } + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + ResourceBarrier(cmd, shadImage, D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + } + + popMarker(cmd); + + cmd->Close(); + cmd5 = NULL; + + ID3D12GraphicsCommandListPtr cmdB = GetCommandBuffer(); + + Reset(cmdB); + + cmdB->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + cmdB->SetGraphicsRootSignature(sig); + cmdB->SetPipelineState(pso); + + OMSetRenderTargets(cmdB, {rtv}, {}); + RSSetScissorRect(cmdB, {0, 0, screenWidth, screenHeight}); + RSSetViewport(cmdB, {0.0f, 0.0f, x, y, 0.0f, 1.0f}); + + pushMarker(cmdB, "Second"); + + { + setMarker(cmdB, "Default"); + + RSSetViewport(cmdB, {x * 0.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(6, 1, 0, 0); + } + + { + setMarker(cmdB, "Base"); + + RSSetViewport(cmdB, {x * 1.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + if(vertpso) + { + setMarker(cmdB, "Vertex"); + + RSSetViewport(cmdB, {x * 2.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmdB, "Image"); + + RSSetViewport(cmdB, {x * 3.0f, 0.0f, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + if(vertpso) + { + setMarker(cmdB, "Base + Vertex"); + + RSSetViewport(cmdB, {x * 0.0f, y, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + if(opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmdB, "Base + Image"); + + RSSetViewport(cmdB, {x * 3.0f, y, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + if(vertpso && opts6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2) + { + setMarker(cmdB, "Vertex + Image"); + + RSSetViewport(cmdB, {x * 3.0f, y * 2.0f, x, y, 0.0f, 1.0f}); + cmdB->DrawInstanced(0, 0, 0, 0); + } + + popMarker(cmdB); + + FinishUsingBackbuffer(cmdB, D3D12_RESOURCE_STATE_RENDER_TARGET); + + cmdB->Close(); + + Submit({cmd, cmdB}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 7eaba3c1f..8ea7dac11 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -209,6 +209,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 456ca676e..eab4b7601 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -595,6 +595,9 @@ OpenGL\demos + + D3D12\demos + diff --git a/util/test/tests/D3D12/D3D12_VRS.py b/util/test/tests/D3D12/D3D12_VRS.py new file mode 100644 index 000000000..814d0c6ab --- /dev/null +++ b/util/test/tests/D3D12/D3D12_VRS.py @@ -0,0 +1,103 @@ +import renderdoc as rd +import rdtest + + +class D3D12_VRS(rdtest.TestCase): + demos_test_name = 'D3D12_VRS' + + def get_shading_rates(self): + pipe: rd.PipeState = self.controller.GetPipelineState() + + v = pipe.GetViewport(0) + tex = pipe.GetOutputTargets()[0].resourceId + + # Ensure we check even-based quads + x = int(v.x) - int(v.x % 2) + y = int(v.y) - int(v.y % 2) + + return (self.get_shading_rate_for_quad(tex, x + 24, y + 50), + self.get_shading_rate_for_quad(tex, x + 74, y + 42)) + + def get_shading_rate_for_quad(self, tex, x, y): + picked = [self.controller.PickPixel(tex, x+0, y+0, rd.Subresource(), rd.CompType.Typeless), + self.controller.PickPixel(tex, x+1, y+0, rd.Subresource(), rd.CompType.Typeless), + self.controller.PickPixel(tex, x+0, y+1, rd.Subresource(), rd.CompType.Typeless), + self.controller.PickPixel(tex, x+1, y+1, rd.Subresource(), rd.CompType.Typeless)] + + # all same - 2x2 + if all([p.floatValue == picked[0].floatValue for p in picked]): + return "2x2" + # X same Y diff - 2x1 + if (picked[0].floatValue == picked[1].floatValue) and (picked[2].floatValue == picked[3].floatValue) and \ + (picked[0].floatValue != picked[2].floatValue): + return "2x1" + # X diff Y same - 1x2 + if (picked[0].floatValue == picked[2].floatValue) and (picked[1].floatValue == picked[3].floatValue) and \ + (picked[0].floatValue != picked[1].floatValue): + return "1x2" + # all different - 1x1 + if all([p.floatValue != picked[0].floatValue for p in picked[1:]]): + return "1x1" + return "?x?" + + def check_capture(self): + # we do two passes, first when we're selecting the actual draws and second when we're in a second command buffer + # going over the same viewports but with dummy draws. To ensure the results are the same whether or not we're + # in the VRS command buffer + for pass_name in ["First", "Second"]: + pass_draw = self.find_draw(pass_name) + + draw = self.find_draw("Default", pass_draw.eventId) + self.check(draw is not None) + self.controller.SetFrameEvent(draw.next.eventId, False) + + num_checks = 0 + + self.check(self.get_shading_rates() == ("1x1", "1x1"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + + draw = self.find_draw("Base", pass_draw.eventId) + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + + draw = self.find_draw("Vertex", pass_draw.eventId) + if draw is not None: + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("1x1", "2x2"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + rdtest.log.success("Shading rates were as expected in per-vertex case") + + draw = self.find_draw("Image", pass_draw.eventId) + if draw is not None: + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "1x1"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + rdtest.log.success("Shading rates were as expected in image-based case") + + draw = self.find_draw("Base + Vertex", pass_draw.eventId) + if draw is not None: + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + + draw = self.find_draw("Base + Image", pass_draw.eventId) + if draw is not None: + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + + draw = self.find_draw("Vertex + Image", pass_draw.eventId) + if draw is not None: + self.controller.SetFrameEvent(draw.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(draw.name, self.get_shading_rates())) + num_checks += 1 + + rdtest.log.success("{}pass: Shading rates were as expected in {} test cases".format(pass_name, num_checks)) \ No newline at end of file