diff --git a/renderdoc/driver/shaders/dxbc/dxbc_reflect.cpp b/renderdoc/driver/shaders/dxbc/dxbc_reflect.cpp index df5c6d918..24a4c2463 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_reflect.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_reflect.cpp @@ -385,6 +385,19 @@ void MakeShaderReflection(DXBC::DXBCContainer *dxbc, const ShaderEntryPoint &ent else refl->debugInfo.debugStatus = "Shader contains no recognised bytecode"; } + + const DXBC::Reflection *dxbcRefl = dxbc->GetReflection(); + + for(const SigParameter &sig : dxbcRefl->InputSig) + { + if(sig.systemValue == ShaderBuiltin::PackedFragRate) + { + if(refl->debugInfo.debugStatus.empty()) + refl->debugInfo.debugStatus = "Unsupported input value SV_ShadingRate"; + break; + } + } + refl->debugInfo.debuggable = refl->debugInfo.debugStatus.empty(); refl->encoding = ShaderEncoding::DXBC; @@ -396,8 +409,6 @@ void MakeShaderReflection(DXBC::DXBCContainer *dxbc, const ShaderEntryPoint &ent } refl->rawBytes = dxbc->GetShaderBlob(); - const DXBC::Reflection *dxbcRefl = dxbc->GetReflection(); - refl->dispatchThreadsDimension[0] = dxbcRefl->DispatchThreadsDimension[0]; refl->dispatchThreadsDimension[1] = dxbcRefl->DispatchThreadsDimension[1]; refl->dispatchThreadsDimension[2] = dxbcRefl->DispatchThreadsDimension[2]; diff --git a/renderdoc/driver/shaders/spirv/spirv_common.cpp b/renderdoc/driver/shaders/spirv/spirv_common.cpp index f985dc728..abe8d95a1 100644 --- a/renderdoc/driver/shaders/spirv/spirv_common.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_common.cpp @@ -185,6 +185,7 @@ ShaderBuiltin MakeShaderBuiltin(ShaderStage stage, const rdcspv::BuiltIn el) case rdcspv::BuiltIn::PrimitiveLineIndicesEXT: return ShaderBuiltin::OutputIndices; case rdcspv::BuiltIn::PrimitiveTriangleIndicesEXT: return ShaderBuiltin::OutputIndices; case rdcspv::BuiltIn::CullPrimitiveEXT: return ShaderBuiltin::CullPrimitive; + case rdcspv::BuiltIn::PrimitiveShadingRateKHR: case rdcspv::BuiltIn::ShadingRateKHR: return ShaderBuiltin::PackedFragRate; default: break; } diff --git a/renderdoc/driver/shaders/spirv/spirv_debug.h b/renderdoc/driver/shaders/spirv/spirv_debug.h index c92553033..fc39849b1 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug.h +++ b/renderdoc/driver/shaders/spirv/spirv_debug.h @@ -183,7 +183,8 @@ struct ResultDataBase uint32_t helperBallot[4]; uint32_t numSubgroups; // may be packed oddly so we don't assume we can calculate - uint32_t padding[3]; + uint32_t shadRate; + uint32_t padding[2]; // LaneData lanes[N] // each LaneData is prefixed by the subgroup struct below if needed, and then the stage struct unconditionally diff --git a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp index 2d7db2d50..11da8d64f 100644 --- a/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_debug_setup.cpp @@ -447,6 +447,7 @@ void Reflector::CheckDebuggable(bool &debuggable, rdcstr &debugStatus) const "SPV_KHR_expect_assume", "SPV_KHR_float_controls", "SPV_KHR_fma", + "SPV_KHR_fragment_shading_rate", "SPV_KHR_maximal_reconvergence", "SPV_KHR_multiview", "SPV_KHR_no_integer_wrap_decoration", @@ -629,6 +630,7 @@ void Reflector::CheckDebuggable(bool &debuggable, rdcstr &debugStatus) const case Capability::ConstantDataKHR: case Capability::FMAKHR: case Capability::Shader64BitIndexingEXT: + case Capability::FragmentShadingRateKHR: { supported = true; break; @@ -682,13 +684,6 @@ void Reflector::CheckDebuggable(bool &debuggable, rdcstr &debugStatus) const break; } - // SPV_KHR_fragment_shading_rate - case Capability::FragmentShadingRateKHR: - { - supported = false; - break; - } - // SPV_KHR_untyped_pointers case Capability::UntypedPointersKHR: { diff --git a/renderdoc/driver/shaders/spirv/spirv_processor.h b/renderdoc/driver/shaders/spirv/spirv_processor.h index 95f6c6e77..752a31c07 100644 --- a/renderdoc/driver/shaders/spirv/spirv_processor.h +++ b/renderdoc/driver/shaders/spirv/spirv_processor.h @@ -378,6 +378,11 @@ struct DataType return type == Type::ScalarType && basicType.vector.scalar.width == 32 && basicType.vector.scalar.type == Op::TypeInt && !basicType.vector.scalar.signedness; } + bool IsS32() const + { + return type == Type::ScalarType && basicType.vector.scalar.width == 32 && + basicType.vector.scalar.type == Op::TypeInt && basicType.vector.scalar.signedness; + } bool IsOpaqueType() const { switch(type) diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index 9decac288..babaaa6c5 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -3908,8 +3908,10 @@ static void CreateInputFetcher(rdcarray &spv, const rdcarray()); + rdcspv::Id sint32Type = editor.DeclareType(rdcspv::scalar()); rdcspv::Id floatType = editor.DeclareType(rdcspv::scalar()); rdcspv::Id boolType = editor.DeclareType(rdcspv::scalar()); + rdcspv::Id uint2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 2)); rdcspv::Id uint3Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 3)); rdcspv::Id uint4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 4)); rdcspv::Id float4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 4)); @@ -3936,6 +3938,7 @@ static void CreateInputFetcher(rdcarray &spv, const rdcarray &spv, const rdcarray &spv, const rdcarray &spv, const rdcarray(1.0f); rdcspv::Id laneIndex; + rdcspv::Id shadRate = editor.AddConstantImmediate(0); + // identify the candidate thread in a stage-specific way rdcspv::Id candidateThread; @@ -4523,27 +4530,145 @@ static void CreateInputFetcher(rdcarray &spv, const rdcarray(1); + rdcspv::Id ymask = editor.AddConstantImmediate(1); + + // optionally we may need to shift, if shading rate makes the step larger than 1 to ensure + // we still get the 0, 1, 2, 3 quad indices we expect + rdcspv::Id xshift, yshift; + + rdcspv::Id half = editor.AddConstantImmediate(0.5f); + rdcspv::Id half2D = editor.AddConstant( + rdcspv::OpConstantComposite(float2Type, editor.MakeId(), {half, half})); + + rdcspv::Id destCentre; + + // when fragment shading rate is active, we need to adjust the expected co-ordinates for a + // quad, as only half/quarter of the pixels will be shaded so our normal calculations won't + // work. + // + // effectively the xmask and ymask above are taken from the shading rate in each direction, + // and the dest pixel co-ordinate is adjusted. so that instead of + // + // destCentre = destXY + 0.5, 0.5 + // + // we do: + // + // roundedDestXY = (destXY >> shadingRateXY) << shadingRateXY + // destCentre = roundedDestXY + (shadingRateXY/2.0f) + // + // to first truncate the lower bits first to get the 'quad' or 'oct-area' and then add on + // half the shading rate to get to the centre co-ordinate + // + // since pixels at 0,0 0,1 1,0 and 1,1 are all shaded as one and the 'centre' becomes 1,1 + // since that is the co-ordinate for exactly in the middle (normally when shading 1,1 at + // full rate the centre is 1.5, 1.5 as above). + // + // xmask/ymask is also adjusted to get the quadLaneIndex based on proportionally larger quads + if(editor.HasCapability(rdcspv::Capability::FragmentShadingRateKHR)) + { + shadRate = editor.AddBuiltinInputLoad(ops, newGlobals, stage, + rdcspv::BuiltIn::ShadingRateKHR, uint32Type); + + RDCCOMPILE_ASSERT(uint32_t(rdcspv::FragmentShadingRate::Vertical2Pixels) == 1, + "Vertical mask isn't as expected"); + RDCCOMPILE_ASSERT(uint32_t(rdcspv::FragmentShadingRate::Vertical4Pixels) == 2, + "Vertical mask isn't as expected"); + RDCCOMPILE_ASSERT(uint32_t(rdcspv::FragmentShadingRate::Horizontal2Pixels) == 4, + "Vertical mask isn't as expected"); + RDCCOMPILE_ASSERT(uint32_t(rdcspv::FragmentShadingRate::Horizontal4Pixels) == 8, + "Vertical mask isn't as expected"); + + const uint32_t vertMask = uint32_t(rdcspv::FragmentShadingRate::Vertical2Pixels | + rdcspv::FragmentShadingRate::Vertical4Pixels); + const uint32_t horizMask = uint32_t(rdcspv::FragmentShadingRate::Horizontal2Pixels | + rdcspv::FragmentShadingRate::Horizontal4Pixels); + + rdcspv::Id vertRate = + ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), shadRate, + editor.AddConstantImmediate(vertMask))); + editor.SetName(vertRate, "vertRate"); + + // shift horizRate to be in the right spot + rdcspv::Id horizRate = + ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), shadRate, + editor.AddConstantImmediate(horizMask))); + horizRate = ops.add(rdcspv::OpShiftRightLogical( + uint32Type, editor.MakeId(), horizRate, editor.AddConstantImmediate(2))); + editor.SetName(horizRate, "horizRate"); + + xshift = horizRate; + yshift = vertRate; + + rdcspv::Id shadingRateXY = ops.add( + rdcspv::OpCompositeConstruct(uint2Type, editor.MakeId(), {horizRate, vertRate})); + editor.SetName(shadingRateXY, "shadingRateXY"); + + rdcspv::Id destXYint = ops.add(rdcspv::OpConvertFToU(uint2Type, editor.MakeId(), destXY)); + editor.SetName(destXYint, "destXYint"); + rdcspv::Id destXYshifted = ops.add( + rdcspv::OpShiftRightLogical(uint2Type, editor.MakeId(), destXYint, shadingRateXY)); + rdcspv::Id roundedDestXY = ops.add( + rdcspv::OpShiftLeftLogical(uint2Type, editor.MakeId(), destXYshifted, shadingRateXY)); + roundedDestXY = ops.add(rdcspv::OpConvertUToF(float2Type, editor.MakeId(), roundedDestXY)); + editor.SetName(roundedDestXY, "roundedDestXY"); + + rdcspv::Id one = editor.AddConstantImmediate(1U); + + // the masks are 1 shifted from the rate, because rate is 0=full rate, 1=2x2, 2=4x4 + // we also need to max with 1 + xmask = ops.add(rdcspv::OpShiftLeftLogical(uint32Type, editor.MakeId(), horizRate, + editor.AddConstantImmediate(1))); + xmask = ops.add(rdcspv::OpGLSL450(uint32Type, editor.MakeId(), glsl450, + rdcspv::GLSLstd450::UMax, {xmask, one})); + ymask = ops.add(rdcspv::OpShiftLeftLogical(uint32Type, editor.MakeId(), vertRate, + editor.AddConstantImmediate(1))); + ymask = ops.add(rdcspv::OpGLSL450(uint32Type, editor.MakeId(), glsl450, + rdcspv::GLSLstd450::UMax, {ymask, one})); + + rdcspv::Id pixelWidthX = ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), xmask)); + rdcspv::Id pixelWidthY = ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), ymask)); + rdcspv::Id halfPixelX = + ops.add(rdcspv::OpFMul(floatType, editor.MakeId(), pixelWidthX, half)); + rdcspv::Id halfPixelY = + ops.add(rdcspv::OpFMul(floatType, editor.MakeId(), pixelWidthY, half)); + rdcspv::Id halfPixelWidth = ops.add( + rdcspv::OpCompositeConstruct(float2Type, editor.MakeId(), {halfPixelX, halfPixelY})); + + destCentre = + ops.add(rdcspv::OpFAdd(float2Type, editor.MakeId(), roundedDestXY, halfPixelWidth)); + } + else + { + // without shading rate the pixel centre is just 0.5, 0.5 from the dest integer co-ordinate + destCentre = ops.add(rdcspv::OpFAdd(float2Type, editor.MakeId(), destXY, half2D)); + } + + editor.SetName(xmask, "xmask"); + editor.SetName(ymask, "ymask"); + editor.SetName(destCentre, "destCentre"); + // figure out the TL pixel's coords and calculate our index relative to it. Assume even top // left (towards 0,0) though the spec does not guarantee this is the actual quad - int yTL = y & (~1); - - // get the index of our desired pixel - */ - - rdcspv::Id mask = editor.AddConstantImmediate(1); // int x01 = x & 1; rdcspv::Id xInt = ops.add(rdcspv::OpCompositeExtract(floatType, editor.MakeId(), fragXY, {0})); xInt = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), xInt)); - rdcspv::Id x01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), xInt, mask)); + rdcspv::Id x01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), xInt, xmask)); + + if(xshift) + x01 = ops.add(rdcspv::OpShiftRightLogical(uint32Type, editor.MakeId(), x01, xshift)); // int y01 = y & 1; rdcspv::Id yInt = ops.add(rdcspv::OpCompositeExtract(floatType, editor.MakeId(), fragXY, {1})); yInt = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), yInt)); - rdcspv::Id y01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), yInt, mask)); + rdcspv::Id y01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), yInt, ymask)); + + if(yshift) + y01 = ops.add(rdcspv::OpShiftRightLogical(uint32Type, editor.MakeId(), y01, yshift)); // int destIdx = x01 + 2 * y01; rdcspv::Id sum = ops.add(rdcspv::OpIMul(uint32Type, editor.MakeId(), @@ -4552,26 +4677,21 @@ static void CreateInputFetcher(rdcarray &spv, const rdcarray(), 2)); - // subtract frag coord from the destination co-ord in x-y to get relative rdcspv::Id fragXYRelative = - ops.add(rdcspv::OpFSub(float2Type, editor.MakeId(), fragXY, destXY)); + ops.add(rdcspv::OpFSub(float2Type, editor.MakeId(), fragXY, destCentre)); // abs() rdcspv::Id fragXYAbs = ops.add(rdcspv::OpGLSL450( float2Type, editor.MakeId(), glsl450, rdcspv::GLSLstd450::FAbs, {fragXYRelative})); - rdcspv::Id half = editor.AddConstantImmediate(0.5f); - rdcspv::Id threshold = editor.AddConstant( - rdcspv::OpConstantComposite(float2Type, editor.MakeId(), {half, half})); + rdcspv::Id bool2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar(), 2)); - // less than 0.5 + // less than 0.5 component-wise rdcspv::Id inPixelXY = - ops.add(rdcspv::OpFOrdLessThan(bool2Type, editor.MakeId(), fragXYAbs, threshold)); + ops.add(rdcspv::OpFOrdLessThan(bool2Type, editor.MakeId(), fragXYAbs, half2D)); - // both less than 0.5 + // both less than threshold candidateThread = ops.add(rdcspv::OpAll(boolType, editor.MakeId(), inPixelXY)); } else if(stage == ShaderStage::Compute || stage == ShaderStage::Task || @@ -4629,6 +4749,23 @@ static void CreateInputFetcher(rdcarray &spv, const rdcarray &spv, const rdcarray(ResultBase_numSubgroups)})); ops.add(rdcspv::OpStore(storePtr, numSubgroups, alignedAccess)); + storePtr = ops.add( + rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit, + {editor.AddConstantImmediate(ResultBase_shadRate)})); + ops.add(rdcspv::OpStore(storePtr, shadRate, alignedAccess)); // merge after doing the fixed data section ops.add(rdcspv::OpBranch(fixedDataMerge)); @@ -6014,6 +6155,21 @@ ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_ RDCLOG("useSampleID is %u because of bare capability", useSampleID); } + // don't fetch sample ID if it would interfere with fragment rate fetch and wouldn't be needed + // probably we could disable this entirely if MSAA is not in use, but this is the case that has + // side effects as it disabled the VRS rates + if(state.rastSamples == VK_SAMPLE_COUNT_1_BIT) + { + for(size_t i = 0; i < shadRefl.refl->inputSignature.size(); i++) + { + if(shadRefl.refl->inputSignature[i].systemValue == ShaderBuiltin::PackedFragRate) + { + useSampleID = false; + break; + } + } + } + bool useViewIndex = (view == ~0U) ? false : true; if(useViewIndex) { @@ -6072,8 +6228,8 @@ ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_ SpecData specData = {}; specData.arrayLength = overdrawLevels; - specData.destX = float(x) + 0.5f; - specData.destY = float(y) + 0.5f; + specData.destX = float(x); + specData.destY = float(y); // make copy of state to draw from VulkanRenderState modifiedstate = state; @@ -6187,7 +6343,13 @@ ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_ continue; } - if(hit->ddxDerivCheck != 1.0f) + float ddxExpectation = 1.0f; + if(hit->shadRate & (uint32_t)rdcspv::FragmentShadingRate::Horizontal2Pixels) + ddxExpectation = 2.0f; + else if(hit->shadRate & (uint32_t)rdcspv::FragmentShadingRate::Horizontal4Pixels) + ddxExpectation = 4.0f; + + if(hit->ddxDerivCheck != ddxExpectation) { RDCWARN("Hit %u doesn't have valid derivatives", i); continue; diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 30911ee24..861699db9 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -175,6 +175,7 @@ set(VULKAN_SRC vk/vk_vertex_attr_zoo.cpp vk/vk_video_textures.cpp vk/vk_vs_max_desc_set.cpp + vk/vk_vrs.cpp vk/vk_workgroup_zoo.cpp) set(OPENGL_SRC diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index ea7cdd1b8..601eac0ad 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -394,6 +394,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 70e53961b..c50edb48c 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -763,6 +763,9 @@ Vulkan\demos + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_helpers.cpp b/util/test/demos/vk/vk_helpers.cpp index a83af6e18..7668e3706 100644 --- a/util/test/demos/vk/vk_helpers.cpp +++ b/util/test/demos/vk/vk_helpers.cpp @@ -164,6 +164,11 @@ void cmdPushDescriptorSets(VkCommandBuffer cmd, VkPipelineBindPoint pipelineBind writes.data()); } +void cmdSetViewport(VkCommandBuffer cmd, VkViewport viewport) +{ + vkCmdSetViewport(cmd, 0, 1, &viewport); +} + GraphicsPipelineCreateInfo::GraphicsPipelineCreateInfo() { sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; diff --git a/util/test/demos/vk/vk_helpers.h b/util/test/demos/vk/vk_helpers.h index 1538adeba..8042c51dc 100644 --- a/util/test/demos/vk/vk_helpers.h +++ b/util/test/demos/vk/vk_helpers.h @@ -196,6 +196,8 @@ void cmdPushConstants(VkCommandBuffer cmd, VkPipelineLayout layout, const T &val cmdPushConstants(cmd, layout, VK_SHADER_STAGE_ALL, val); } +void cmdSetViewport(VkCommandBuffer cmd, VkViewport viewport); + struct ApplicationInfo : public VkApplicationInfo { ApplicationInfo() : VkApplicationInfo() { sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; } @@ -1244,6 +1246,13 @@ struct ClearValue clear.color.float32[2] = b; clear.color.float32[3] = a; } + ClearValue(uint32_t r, uint32_t g, uint32_t b, uint32_t a) + { + clear.color.uint32[0] = r; + clear.color.uint32[1] = g; + clear.color.uint32[2] = b; + clear.color.uint32[3] = a; + } ClearValue(float d, uint32_t s) { diff --git a/util/test/demos/vk/vk_separate_depth_stencil_layouts.cpp b/util/test/demos/vk/vk_separate_depth_stencil_layouts.cpp index 4633e2422..dd06cf8ed 100644 --- a/util/test/demos/vk/vk_separate_depth_stencil_layouts.cpp +++ b/util/test/demos/vk/vk_separate_depth_stencil_layouts.cpp @@ -463,11 +463,11 @@ void main() depthStencilImg.image, vkh::ImageSubresourceRange(VK_IMAGE_ASPECT_DEPTH_BIT)), }); - vkCmdBeginRenderPass( - cmd, - vkh::RenderPassBeginInfo(renderPass, frameBuffer, mainWindow->scissor, - {vkh::ClearValue(1, 0, 1, 1), vkh::ClearValue(1.0f, 0)}), - VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(cmd, + vkh::RenderPassBeginInfo( + renderPass, frameBuffer, mainWindow->scissor, + {vkh::ClearValue(1.0f, 0.0f, 1.0f, 1.0f), vkh::ClearValue(1.0f, 0)}), + VK_SUBPASS_CONTENTS_INLINE); vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); diff --git a/util/test/demos/vk/vk_vrs.cpp b/util/test/demos/vk/vk_vrs.cpp new file mode 100644 index 000000000..7385101de --- /dev/null +++ b/util/test/demos/vk/vk_vrs.cpp @@ -0,0 +1,518 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2026 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 "vk_test.h" + +RD_TEST(VK_VRS, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Checks that VRS is correctly replayed and that state is inspectable"; + + std::string pixel = R"EOSHADER( + +#extension GL_EXT_fragment_shading_rate : require + +uint wang_hash(uint seed) +{ + seed = (seed ^ 61) ^ (seed >> 16); + seed *= 9; + seed = seed ^ (seed >> 4); + seed *= 0x27d4eb2d; + seed = seed ^ (seed >> 15); + return seed; +} + +layout(location = 0, index = 0) out vec4 Color; + +void main() +{ + uint col = wang_hash(uint(gl_FragCoord.x * 10000.0f + gl_FragCoord.y)); + + Color.x = float((col & 0xff000000u) >> 24u) / 255.0f; + Color.y = float((col & 0x00ff0000u) >> 16u) / 255.0f; + Color.z = float((col & 0x0000ff00u) >> 8u) / 255.0f; + Color.w = gl_ShadingRateEXT * 100.0f + dFdxFine(Color.x); +} + +)EOSHADER"; + + std::string vertex = R"EOSHADER( + +#extension GL_EXT_fragment_shading_rate : require + +layout(location = 0) in vec3 Position; +layout(location = 1) in vec4 Color; + +void main() +{ + gl_Position = vec4(Position, 1.0); + +#ifdef VERT_VRS + gl_PrimitiveShadingRateEXT = int(Color.x) << 2 | int(Color.y); +#endif +} + +)EOSHADER"; + + void Prepare(int argc, char **argv) + { + instExts.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + devExts.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + devExts.push_back(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME); + + VulkanGraphicsTest::Prepare(argc, argv); + + if(!Avail.empty()) + return; + + static VkPhysicalDeviceFragmentShadingRateFeaturesKHR rate2Features = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR, + }; + + getPhysFeatures2(&rate2Features); + + if(!rate2Features.attachmentFragmentShadingRate) + Avail = "'attachmentFragmentShadingRate' not available"; + else if(!rate2Features.pipelineFragmentShadingRate) + Avail = "'pipelineFragmentShadingRate' not available"; + else if(!rate2Features.primitiveFragmentShadingRate) + Avail = "'primitiveFragmentShadingRate' not available"; + + devInfoNext = &rate2Features; + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + VkPhysicalDeviceFragmentShadingRatePropertiesKHR props = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR, + }; + + getPhysProperties2(&props); + + VkRenderPass renderPass = VK_NULL_HANDLE, imgRenderPass = VK_NULL_HANDLE; + { + vkh::RenderPassCreator2 renderPassCreateInfo; + + vkh::AttachmentDescription2KHR colorAtt(VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); + colorAtt.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + renderPassCreateInfo.attachments.push_back(colorAtt); + + vkh::AttachmentDescription2KHR rateAtt(VK_FORMAT_R8_UINT, VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_GENERAL); + rateAtt.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + renderPassCreateInfo.attachments.push_back(rateAtt); + + VkAttachmentReference2 rateAttRef = + vkh::AttachmentReference2KHR(1, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_ASPECT_COLOR_BIT); + + VkFragmentShadingRateAttachmentInfoKHR rateAttInfo = { + VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR, + }; + rateAttInfo.pFragmentShadingRateAttachment = &rateAttRef; + rateAttInfo.shadingRateAttachmentTexelSize = props.maxFragmentShadingRateAttachmentTexelSize; + + renderPassCreateInfo.addSubpass( + {vkh::AttachmentReference2KHR(0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT)}, + {vkh::AttachmentReference2KHR(VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED)}); + + // add deps to allow clear/copy on the main target before or after + renderPassCreateInfo.dependencies.push_back(vkh::SubpassDependency2KHR( + ~0U, 0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)); + renderPassCreateInfo.dependencies.push_back(vkh::SubpassDependency2KHR( + 0, ~0U, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT)); + + CHECK_VKR(vkCreateRenderPass2KHR(device, renderPassCreateInfo, NULL, &renderPass)); + + renderPassCreateInfo.subpasses.back().pNext = &rateAttInfo; + + CHECK_VKR(vkCreateRenderPass2KHR(device, renderPassCreateInfo, NULL, &imgRenderPass)); + } + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = renderPass; + + pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; + pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { + vkh::vertexAttr(0, 0, DefaultA2V, pos), + vkh::vertexAttr(1, 0, DefaultA2V, col), + }; + + std::string header = "#version 460 core\n"; + + pipeCreateInfo.stages = { + CompileShaderModule(header + vertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(header + pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + pipeCreateInfo.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR); + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + pipeCreateInfo.renderPass = imgRenderPass; + + VkPipeline imgpipe = createGraphicsPipeline(pipeCreateInfo); + + header += "#define VERT_VRS 1\n"; + + pipeCreateInfo.stages = { + CompileShaderModule(header + vertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(header + pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + pipeCreateInfo.renderPass = renderPass; + + VkPipeline vertpipe = createGraphicsPipeline(pipeCreateInfo); + + pipeCreateInfo.renderPass = imgRenderPass; + + VkPipeline imgvertpipe = createGraphicsPipeline(pipeCreateInfo); + + 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)}, + }; + + AllocatedBuffer vb(this, + vkh::BufferCreateInfo(sizeof(tris), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(tris); + + uint32_t offWidth = + AlignUp((uint32_t)screenWidth, props.maxFragmentShadingRateAttachmentTexelSize.width); + uint32_t offHeight = + AlignUp((uint32_t)screenHeight, props.maxFragmentShadingRateAttachmentTexelSize.height); + + AllocatedImage img( + this, + vkh::ImageCreateInfo(offWidth, offHeight, 0, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + VkImageView imgview = createImageView( + vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT)); + + AllocatedImage rateimg( + this, + vkh::ImageCreateInfo( + offWidth / props.maxFragmentShadingRateAttachmentTexelSize.width, + offHeight / props.maxFragmentShadingRateAttachmentTexelSize.height, 0, VK_FORMAT_R8_UINT, + VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + VkImageView rate_view = createImageView( + vkh::ImageViewCreateInfo(rateimg.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8_UINT)); + + VkFramebuffer framebuffer = createFramebuffer( + vkh::FramebufferCreateInfo(renderPass, {imgview, rate_view}, mainWindow->scissor.extent)); + + VkFramebuffer imgframebuffer = createFramebuffer(vkh::FramebufferCreateInfo( + imgRenderPass, {imgview, rate_view}, mainWindow->scissor.extent)); + + VkRenderPass clearRP; + { + vkh::RenderPassCreator renderPassCreateInfo; + + renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription( + VK_FORMAT_R8_UINT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL)); + + renderPassCreateInfo.addSubpass({VkAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL})}); + + // add deps to allow clear/copy on the main target before or after + renderPassCreateInfo.dependencies.push_back(vkh::SubpassDependency( + ~0U, 0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)); + renderPassCreateInfo.dependencies.push_back(vkh::SubpassDependency( + 0, ~0U, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT)); + + clearRP = createRenderPass(renderPassCreateInfo); + } + + VkExtent2D clearRect = {offWidth / props.maxFragmentShadingRateAttachmentTexelSize.width, + offHeight / props.maxFragmentShadingRateAttachmentTexelSize.height}; + + VkFramebuffer clearFB = + createFramebuffer(vkh::FramebufferCreateInfo(clearRP, {rate_view}, clearRect)); + + const VkFragmentShadingRateCombinerOpKHR combiners[2] = { + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR, + VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR, + }; + + const VkExtent2D frag2x2 = {2, 2}; + const VkExtent2D frag1x1 = {1, 1}; + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = StartUsingBackbuffer(cmd); + + vkh::cmdPipelineBarrier( + cmd, + { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, rateimg.image), + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, img.image), + }); + + pushMarker(cmd, "Clear"); + { + VkClearRect rect = {vkh::Rect2D({0, 0}, clearRect), 0, 1}; + + vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(clearRP, clearFB, rect.rect), + VK_SUBPASS_CONTENTS_INLINE); + + VkClearAttachment att = {VK_IMAGE_ASPECT_COLOR_BIT, 0, vkh::ClearValue(5U, 0U, 0U, 0U)}; + vkCmdClearAttachments(cmd, 1, &att, 1, &rect); + + rect.rect.extent.width /= 8; + rect.rect.extent.height = (clearRect.height * 3) / 4; + rect.rect.offset.x = clearRect.width - rect.rect.extent.width; + att.clearValue.color.uint32[0] = 0; + vkCmdClearAttachments(cmd, 1, &att, 1, &rect); + + rect.rect.extent.width--; + rect.rect.offset.x = clearRect.width - rect.rect.extent.width; + rect.rect.offset.y += rect.rect.extent.height; + rect.rect.extent.height = clearRect.height / 4; + att.clearValue.color.uint32[0] = 0; + vkCmdClearAttachments(cmd, 1, &att, 1, &rect); + + vkCmdEndRenderPass(cmd); + + vkCmdClearColorImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1, + vkh::ImageSubresourceRange()); + } + popMarker(cmd); + + vkh::cmdBindVertexBuffers(cmd, {vb.buffer}); + + mainWindow->setViewScissor(cmd); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + + const float x = (float)offWidth / 4.0f; + const float y = (float)offHeight / 4.0f; + + pushMarker(cmd, "First"); + { + vkCmdBeginRenderPass(cmd, + vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + { + setMarker(cmd, "Default"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag1x1, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 0.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + { + setMarker(cmd, "Base"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag2x2, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 1.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + { + setMarker(cmd, "Vertex"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vertpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag1x1, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 2.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + { + setMarker(cmd, "Base + Vertex"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, vertpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag2x2, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 0.0f, y, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + vkCmdEndRenderPass(cmd); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(imgRenderPass, imgframebuffer, mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + { + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, imgpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag1x1, combiners); + setMarker(cmd, "Image"); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 3.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + + setMarker(cmd, "Base + Image"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, imgpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag2x2, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 3.0f, y, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + + setMarker(cmd, "Vertex + Image"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, imgvertpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag1x1, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 3.0f, y * 2.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + { + setMarker(cmd, "Image (partial)"); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, imgpipe); + vkCmdSetFragmentShadingRateKHR(cmd, &frag1x1, combiners); + + vkh::cmdSetViewport(cmd, vkh::Viewport(x * 3.0f, y * 3.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmd, 6, 1, 0, 0); + } + + vkCmdEndRenderPass(cmd); + } + popMarker(cmd); + + vkEndCommandBuffer(cmd); + + VkCommandBuffer cmdB = GetCommandBuffer(); + + vkBeginCommandBuffer(cmdB, vkh::CommandBufferBeginInfo()); + + vkCmdBindPipeline(cmdB, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetFragmentShadingRateKHR(cmdB, &frag1x1, combiners); + + vkh::cmdBindVertexBuffers(cmdB, {vb.buffer}); + + mainWindow->setViewScissor(cmdB); + vkh::cmdSetViewport(cmdB, vkh::Viewport(0.0f, 0.0f, x, y, 0.0f, 1.0f)); + + vkCmdBeginRenderPass(cmdB, + vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + pushMarker(cmdB, "Second"); + { + { + setMarker(cmdB, "Default"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 0.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 6, 1, 0, 0); + } + + { + setMarker(cmdB, "Base"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 1.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + } + + { + setMarker(cmdB, "Vertex"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 2.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + + setMarker(cmdB, "Image"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 3.0f, 0.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + + setMarker(cmdB, "Base + Vertex"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 0.0f, y, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + + setMarker(cmdB, "Base + Image"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 3.0f, y, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + + setMarker(cmdB, "Vertex + Image"); + + vkh::cmdSetViewport(cmdB, vkh::Viewport(x * 3.0f, y * 2.0f, x, y, 0.0f, 1.0f)); + vkCmdDraw(cmdB, 0, 0, 0, 0); + } + } + popMarker(cmdB); + + vkCmdEndRenderPass(cmdB); + + blitToSwap(cmdB, img.image, VK_IMAGE_LAYOUT_GENERAL, swapimg, VK_IMAGE_LAYOUT_GENERAL); + + FinishUsingBackbuffer(cmdB); + + vkEndCommandBuffer(cmdB); + + SubmitAndPresent({cmd, cmdB}); + } + + vkDestroyRenderPass(device, renderPass, NULL); + vkDestroyRenderPass(device, imgRenderPass, NULL); + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/tests/Vulkan/VK_VRS.py b/util/test/tests/Vulkan/VK_VRS.py new file mode 100644 index 000000000..aeb880bd2 --- /dev/null +++ b/util/test/tests/Vulkan/VK_VRS.py @@ -0,0 +1,103 @@ +import renderdoc as rd +import rdtest + + +class VK_VRS(rdtest.TestCase): + demos_test_name = 'VK_VRS' + + def get_shading_rates(self): + pipe: rd.PipeState = self.controller.GetPipelineState() + + v = pipe.GetViewport(0) + tex = pipe.GetOutputTargets()[0].resource + + # 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 actions and second when we're in a second command buffer + # going over the same viewports but with dummy actions. To ensure the results are the same whether or not we're + # in the VRS command buffer + for pass_name in ["First", "Second"]: + pass_action = self.find_action(pass_name) + + action = self.find_action("Default", pass_action.eventId) + self.check(action is not None) + self.controller.SetFrameEvent(action.next.eventId, False) + + num_checks = 0 + + self.check(self.get_shading_rates() == ("1x1", "1x1"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + + action = self.find_action("Base", pass_action.eventId) + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + + action = self.find_action("Vertex", pass_action.eventId) + if action is not None: + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("1x1", "2x2"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + rdtest.log.success("Shading rates were as expected in per-vertex case") + + action = self.find_action("Image", pass_action.eventId) + if action is not None: + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "1x1"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + rdtest.log.success("Shading rates were as expected in image-based case") + + action = self.find_action("Base + Vertex", pass_action.eventId) + if action is not None: + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + + action = self.find_action("Base + Image", pass_action.eventId) + if action is not None: + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(action.customName, self.get_shading_rates())) + num_checks += 1 + + action = self.find_action("Vertex + Image", pass_action.eventId) + if action is not None: + self.controller.SetFrameEvent(action.next.eventId, False) + self.check(self.get_shading_rates() == ("2x2", "2x2"), + "{} shading rates unexpected: {}".format(action.customName, 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