From 7431e5a6e1e2fdccc30db7e43fe9fc64a736e2a0 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Tue, 6 Oct 2026 21:29:14 +0300 Subject: [PATCH] perf(shader): restore prepared vertex inputs + harden buffer specialization --- .../Graphics/include/VertexInput.hpp | 104 +++++++------- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 1 + .../Graphics/src/ShaderInputState.cpp | 8 +- .../libSceAgcDriver/tests/ShaderDiskCache.cpp | 86 +++++++++++- core/shader/recompiler/CacheKey.hpp | 7 +- .../IrMetadata/Resources.hpp | 2 + .../src/DescriptorBindingBuilder.cpp | 9 +- .../Optimization/src/ResourceTracker.cpp | 3 + .../Optimization/src/ShaderInfoCollector.cpp | 2 +- .../recompiler/PipelineSpecialization.hpp | 1 + core/shader/recompiler/Recompiler.cpp | 86 ++++++++++-- core/shader/recompiler/Recompiler.hpp | 11 ++ core/shader/recompiler/ShaderDiskCache.cpp | 24 +++- core/shader/recompiler/ShaderDiskCache.hpp | 2 +- .../include/SpirvBackend/SpirvOptimizer.hpp | 2 +- .../SpirvBackend/src/SpirvModuleEmitter.cpp | 13 ++ .../SpirvBackend/src/SpirvOptimizer.cpp | 3 +- .../SpirvBackend/src/SpirvRuntimeBuffer.cpp | 17 ++- .../Translation/InstructionTranslator.hpp | 2 + .../Translation/TranslationContext.hpp | 1 + .../Translation/src/AttributeInstructions.cpp | 9 ++ .../Translation/src/InstructionTranslator.cpp | 21 +++ .../src/ShaderInputInfoBuilder.cpp | 5 +- .../recompiler/VertexInputSpecialization.hpp | 131 ++++++++++++++++++ 24 files changed, 460 insertions(+), 90 deletions(-) create mode 100644 core/shader/recompiler/VertexInputSpecialization.hpp diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index 883dbac0e..1964f46be 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -20,37 +20,39 @@ struct VertexFormat { inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& attribute) { Require(attribute.components >= 1 && attribute.components <= 4, "invalid vertex attribute component count"); + const auto components = attribute.formatComponents == 0u ? attribute.components : attribute.formatComponents; + Require(components >= 1u && components <= 4u, "invalid vertex format component count"); const auto format = (attribute.resource.fields[3] >> 12u) & 0x7fu; switch (format) { - case 1: { const std::array formats{VK_FORMAT_R8_UNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 2: { const std::array formats{VK_FORMAT_R8_SNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 3: { const std::array formats{VK_FORMAT_R8_USCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 4: { const std::array formats{VK_FORMAT_R8_SSCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 5: { const std::array formats{VK_FORMAT_R8_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "u32"}; } - case 6: { const std::array formats{VK_FORMAT_R8_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 1u, 1u, "i32"}; } - case 7: { const std::array formats{VK_FORMAT_R16_UNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 8: { const std::array formats{VK_FORMAT_R16_SNORM}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 9: { const std::array formats{VK_FORMAT_R16_USCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 10: { const std::array formats{VK_FORMAT_R16_SSCALED}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 11: { const std::array formats{VK_FORMAT_R16_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "u32"}; } - case 12: { const std::array formats{VK_FORMAT_R16_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "i32"}; } - case 13: { const std::array formats{VK_FORMAT_R16_SFLOAT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 14: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 15: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 16: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 17: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 18: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "u32"}; } - case 19: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 1u, 1u, "i32"}; } - case 20: { const std::array formats{VK_FORMAT_R32_UINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "u32"}; } - case 21: { const std::array formats{VK_FORMAT_R32_SINT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "i32"}; } - case 22: { const std::array formats{VK_FORMAT_R32_SFLOAT}; const auto count = std::min(attribute.components, 1u); return {formats[count - 1], count * 4u, 4u, "f32"}; } - case 23: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 24: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 25: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 26: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 27: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "u32"}; } - case 28: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "i32"}; } - case 29: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 1: { const std::array formats{VK_FORMAT_R8_UNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 2: { const std::array formats{VK_FORMAT_R8_SNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 3: { const std::array formats{VK_FORMAT_R8_USCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 4: { const std::array formats{VK_FORMAT_R8_SSCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 5: { const std::array formats{VK_FORMAT_R8_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "u32"}; } + case 6: { const std::array formats{VK_FORMAT_R8_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 1u, 1u, "i32"}; } + case 7: { const std::array formats{VK_FORMAT_R16_UNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 8: { const std::array formats{VK_FORMAT_R16_SNORM}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 9: { const std::array formats{VK_FORMAT_R16_USCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 10: { const std::array formats{VK_FORMAT_R16_SSCALED}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 11: { const std::array formats{VK_FORMAT_R16_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "u32"}; } + case 12: { const std::array formats{VK_FORMAT_R16_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "i32"}; } + case 13: { const std::array formats{VK_FORMAT_R16_SFLOAT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 14: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 15: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 16: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 17: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 18: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "u32"}; } + case 19: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 1u, 1u, "i32"}; } + case 20: { const std::array formats{VK_FORMAT_R32_UINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "u32"}; } + case 21: { const std::array formats{VK_FORMAT_R32_SINT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "i32"}; } + case 22: { const std::array formats{VK_FORMAT_R32_SFLOAT}; const auto count = std::min(components, 1u); return {formats[count - 1], count * 4u, 4u, "f32"}; } + case 23: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 24: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 25: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 26: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 27: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "u32"}; } + case 28: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "i32"}; } + case 29: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 2u, 2u, "f32"}; } case 36: return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4u, 4u, "f32"}; case 50: return {VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4u, 4u, "f32"}; case 51: return {VK_FORMAT_A2B10G10R10_SNORM_PACK32, 4u, 4u, "f32"}; @@ -58,28 +60,28 @@ inline VertexFormat DecodeVertexFormat(const ShaderRecompiler::VertexAttribute& case 53: return {VK_FORMAT_A2B10G10R10_SSCALED_PACK32, 4u, 4u, "f32"}; case 54: return {VK_FORMAT_A2B10G10R10_UINT_PACK32, 4u, 4u, "u32"}; case 55: return {VK_FORMAT_A2B10G10R10_SINT_PACK32, 4u, 4u, "i32"}; - case 56: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8A8_UNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 57: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8A8_SNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 58: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8A8_USCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 59: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8A8_SSCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } - case 60: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "u32"}; } - case 61: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 1u, 1u, "i32"}; } - case 62: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "u32"}; } - case 63: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "i32"}; } - case 64: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT}; const auto count = std::min(attribute.components, 2u); return {formats[count - 1], count * 4u, 4u, "f32"}; } - case 65: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16A16_UNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 66: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16A16_SNORM}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 67: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16A16_USCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 68: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16A16_SSCALED}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 69: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16A16_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "u32"}; } - case 70: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16A16_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "i32"}; } - case 71: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } - case 72: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "u32"}; } - case 73: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "i32"}; } - case 74: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT}; const auto count = std::min(attribute.components, 3u); return {formats[count - 1], count * 4u, 4u, "f32"}; } - case 75: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32A32_UINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "u32"}; } - case 76: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32A32_SINT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "i32"}; } - case 77: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; const auto count = std::min(attribute.components, 4u); return {formats[count - 1], count * 4u, 4u, "f32"}; } + case 56: { const std::array formats{VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8A8_UNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 57: { const std::array formats{VK_FORMAT_R8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_R8G8B8_SNORM, VK_FORMAT_R8G8B8A8_SNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 58: { const std::array formats{VK_FORMAT_R8_USCALED, VK_FORMAT_R8G8_USCALED, VK_FORMAT_R8G8B8_USCALED, VK_FORMAT_R8G8B8A8_USCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 59: { const std::array formats{VK_FORMAT_R8_SSCALED, VK_FORMAT_R8G8_SSCALED, VK_FORMAT_R8G8B8_SSCALED, VK_FORMAT_R8G8B8A8_SSCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "f32"}; } + case 60: { const std::array formats{VK_FORMAT_R8_UINT, VK_FORMAT_R8G8_UINT, VK_FORMAT_R8G8B8_UINT, VK_FORMAT_R8G8B8A8_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "u32"}; } + case 61: { const std::array formats{VK_FORMAT_R8_SINT, VK_FORMAT_R8G8_SINT, VK_FORMAT_R8G8B8_SINT, VK_FORMAT_R8G8B8A8_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 1u, 1u, "i32"}; } + case 62: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "u32"}; } + case 63: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "i32"}; } + case 64: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT}; const auto count = std::min(components, 2u); return {formats[count - 1], count * 4u, 4u, "f32"}; } + case 65: { const std::array formats{VK_FORMAT_R16_UNORM, VK_FORMAT_R16G16_UNORM, VK_FORMAT_R16G16B16_UNORM, VK_FORMAT_R16G16B16A16_UNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 66: { const std::array formats{VK_FORMAT_R16_SNORM, VK_FORMAT_R16G16_SNORM, VK_FORMAT_R16G16B16_SNORM, VK_FORMAT_R16G16B16A16_SNORM}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 67: { const std::array formats{VK_FORMAT_R16_USCALED, VK_FORMAT_R16G16_USCALED, VK_FORMAT_R16G16B16_USCALED, VK_FORMAT_R16G16B16A16_USCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 68: { const std::array formats{VK_FORMAT_R16_SSCALED, VK_FORMAT_R16G16_SSCALED, VK_FORMAT_R16G16B16_SSCALED, VK_FORMAT_R16G16B16A16_SSCALED}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 69: { const std::array formats{VK_FORMAT_R16_UINT, VK_FORMAT_R16G16_UINT, VK_FORMAT_R16G16B16_UINT, VK_FORMAT_R16G16B16A16_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "u32"}; } + case 70: { const std::array formats{VK_FORMAT_R16_SINT, VK_FORMAT_R16G16_SINT, VK_FORMAT_R16G16B16_SINT, VK_FORMAT_R16G16B16A16_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "i32"}; } + case 71: { const std::array formats{VK_FORMAT_R16_SFLOAT, VK_FORMAT_R16G16_SFLOAT, VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R16G16B16A16_SFLOAT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 2u, 2u, "f32"}; } + case 72: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "u32"}; } + case 73: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "i32"}; } + case 74: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT}; const auto count = std::min(components, 3u); return {formats[count - 1], count * 4u, 4u, "f32"}; } + case 75: { const std::array formats{VK_FORMAT_R32_UINT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32B32_UINT, VK_FORMAT_R32G32B32A32_UINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "u32"}; } + case 76: { const std::array formats{VK_FORMAT_R32_SINT, VK_FORMAT_R32G32_SINT, VK_FORMAT_R32G32B32_SINT, VK_FORMAT_R32G32B32A32_SINT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "i32"}; } + case 77: { const std::array formats{VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT}; const auto count = std::min(components, 4u); return {formats[count - 1], count * 4u, 4u, "f32"}; } default: throw std::runtime_error("AGC graphics: unsupported vertex format " + std::to_string(format)); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 7a3bae45f..7e94a8675 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -479,6 +479,7 @@ bool ValidationKey(const Context& context, std::span shade add(attribute.location); add(attribute.components); add(attribute.fetchIndex); + add(attribute.formatComponents); // The data format bits of the V# decide the attribute's signature. add((attribute.resource.fields[3] >> 12u) & 0x7fu); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 60fb46171..2d7b65e3c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -198,7 +198,13 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span(semantic.hardware_mapping); + destination.registersNum = static_cast(semantic.size_in_elements); + destination.attrId = static_cast(semantic.semantic); + continue; + } std::array attribWordBytes{}; const auto attribWordAddress = attribTableAddr + static_cast(semantic.semantic) * 4u; AgcDriver::GuestMemory::Read(attribWordAddress, attribWordBytes, 4); diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index f91502f3c..0c82c7f0c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -1,5 +1,6 @@ #include "ShaderDiskCache.hpp" #include "CacheKey.hpp" +#include "VertexInputSpecialization.hpp" #include "Optimization/ResourceMaterializer.hpp" #include "ShaderCacheDirectory.hpp" #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +56,7 @@ void requireSameArtifact(const CompiledShaderArtifact& left, const CompiledShade require(left.memoryOffsetDword == right.memoryOffsetDword, prefix + "memory offset differs"); require(left.hostSubgroupSize == right.hostSubgroupSize, prefix + "host subgroup size differs"); require(left.vertexInputs == right.vertexInputs, prefix + "vertex inputs differ"); + require(left.vertexInputPatches == right.vertexInputPatches, prefix + "vertex type patches differ"); require(left.vertexOffsetSgpr == right.vertexOffsetSgpr && left.instanceOffsetSgpr == right.instanceOffsetSgpr, prefix + "offset SGPRs differ"); require(left.vertexOffsetShared == right.vertexOffsetShared && left.instanceOffsetShared == right.instanceOffsetShared && left.vertexOffsetConflict == right.vertexOffsetConflict && left.instanceOffsetConflict == right.instanceOffsetConflict, prefix + "offset flags differ"); require(left.parameterExports == right.parameterExports, prefix + "parameter exports differ"); @@ -70,11 +73,12 @@ void requireSameResult(const RecompileResult& left, const RecompileResult& right const std::string prefix = std::string(what) + ": "; require(sameBindings(left.bindings, right.bindings), prefix + "bindings differ"); require(left.pushConstants == right.pushConstants, prefix + "push constants differ"); + require(left.specialization == right.specialization, prefix + "specialization constants differ"); require(left.vertexAttributes.size() == right.vertexAttributes.size(), prefix + "vertex attribute count differs"); for (std::size_t i = 0; i < left.vertexAttributes.size(); ++i) { const auto& a = left.vertexAttributes[i]; const auto& b = right.vertexAttributes[i]; - require(a.location == b.location && a.components == b.components && a.resource.fields == b.resource.fields && a.fetchIndex == b.fetchIndex, prefix + "vertex attribute differs"); + require(a.location == b.location && a.components == b.components && a.resource.fields == b.resource.fields && a.fetchIndex == b.fetchIndex && a.formatComponents == b.formatComponents, prefix + "vertex attribute differs"); } } @@ -127,7 +131,11 @@ RecompileResult sampleResult() { result.memoryOffsetDword = 7; result.hostSubgroupSize = 32; result.vertexInputs = {{1, 4, 2}, {5, 2, 0}}; + result.vertexInputPatches = {{1, 20, {7, 8, 9}}, {5, 40, {10, 11, 12}}}; + result.specialization = {{512, 4}, {516, 0x3f800000u}, {517, 0}}; result.vertexAttributes = {{1, 4, {{0x1000u, 0x20000u, 0x30u, 0x4u}}, 2}, {5, 2, {{9u, 8u, 7u, 6u}}, 0}}; + result.vertexInputs[0].outputMask = 5u; + result.vertexAttributes[0].formatComponents = 3u; result.vertexOffsetSgpr = 12; result.instanceOffsetSgpr = -1; result.vertexOffsetShared = true; @@ -162,6 +170,8 @@ CompiledVariant sampleVariant() { buffer.read = true; buffer.atomic = true; buffer.scalar = true; + buffer.descriptorFormatted = true; + buffer.formattedReadMask = 5u; info.info.buffers = {buffer, BufferResource{}}; ImageResource image{}; image.source = 5; @@ -409,8 +419,12 @@ void verifyKeySensitivity() { input.fetchEmbedded = true; const auto embeddedKey = vertex.Key(); input.resources[0].fields = {}; - input.resourcesNum = 0u; + input.resourcesDst[0].fetchIndex = 1u; require(vertex.Key() == embeddedKey, "runtime vertex fetch descriptors changed the artifact key"); + input.resources[0].fields[3] = (20u << 12u) | 0xfacu; + require(vertex.Key() == embeddedKey, "embedded numeric class changed the prepared template key"); + input.resourcesNum = 0u; + require(vertex.Key() != embeddedKey, "embedded semantic interface did not change the artifact key"); SampleRequest fragment; fragment.request.shader.stage = ShaderStage::Fragment; fragment.request.context.compute.reset(); @@ -634,6 +648,73 @@ void verifyInvocationIsolation() { require(rejected, "an unsupported buffer descriptor type was accepted"); } +void verifyVertexTypeSpecialization() { + CompiledShaderArtifact artifact; + artifact.vertexInputs = {{0, 4, 0, 1}, {1, 4, 0, 1}}; + artifact.spirv = std::vector{ + spv::MagicNumber, 0x00010000u, 0, 28, 0, + (2u << 16u) | spv::OpCapability, spv::CapabilityShader, + (3u << 16u) | spv::OpMemoryModel, spv::AddressingModelLogical, spv::MemoryModelGLSL450, + (7u << 16u) | spv::OpEntryPoint, spv::ExecutionModelVertex, 20, 0x6e69616du, 0, 10, 11, + (4u << 16u) | spv::OpDecorate, 10, spv::DecorationLocation, 0, + (4u << 16u) | spv::OpDecorate, 11, spv::DecorationLocation, 1, + (2u << 16u) | spv::OpTypeVoid, 1, + (3u << 16u) | spv::OpTypeFloat, 2, 32, + (4u << 16u) | spv::OpTypeInt, 3, 32, 0, + (4u << 16u) | spv::OpTypeVector, 4, 2, 4, + (4u << 16u) | spv::OpTypePointer, 5, spv::StorageClassInput, 4, + (4u << 16u) | spv::OpTypePointer, 6, spv::StorageClassInput, 2, + (3u << 16u) | spv::OpTypeFunction, 7, 1, + (4u << 16u) | spv::OpConstant, 3, 8, 0, + (4u << 16u) | spv::OpVariable, 5, 10, spv::StorageClassInput, + (4u << 16u) | spv::OpVariable, 5, 11, spv::StorageClassInput, + (5u << 16u) | spv::OpFunction, 1, 20, spv::FunctionControlMaskNone, 7, + (2u << 16u) | spv::OpLabel, 21, + (5u << 16u) | spv::OpAccessChain, 6, 22, 10, 8, + (4u << 16u) | spv::OpLoad, 2, 23, 22, + (4u << 16u) | spv::OpBitcast, 3, 24, 23, + (5u << 16u) | spv::OpAccessChain, 6, 25, 11, 8, + (4u << 16u) | spv::OpLoad, 2, 26, 25, + (4u << 16u) | spv::OpBitcast, 3, 27, 26, + (1u << 16u) | spv::OpReturn, + (1u << 16u) | spv::OpFunctionEnd + }; + PrepareVertexInputSpecialization(artifact); + const auto original = artifact.spirv.Words(); + for (std::uint32_t first = 0; first < 3u; ++first) { + for (std::uint32_t second = 0; second < 3u; ++second) { + const std::array classes{first, second}; + const auto specialized = SpecializeVertexInputTypes(artifact, classes); + const auto& words = specialized.Words(); + std::map scalarKinds; + std::uint32_t checked = 0; + for (std::size_t cursor = 5; cursor < words.size(); cursor += words[cursor] >> 16u) { + const auto op = static_cast(words[cursor] & 0xffffu); + if (op == spv::OpTypeFloat) scalarKinds.emplace(words[cursor + 1u], 0u); + if (op == spv::OpTypeInt) scalarKinds.emplace(words[cursor + 1u], words[cursor + 3u] != 0u ? 1u : 2u); + if (op == spv::OpLoad) { + const auto attribute = words[cursor + 2u] == 23u ? 0u : 1u; + require(scalarKinds.at(words[cursor + 1u]) == classes[attribute], "vertex input loaded the wrong numeric type"); + ++checked; + } + if (op == spv::OpBitcast || op == spv::OpCopyObject) { + const auto attribute = words[cursor + 2u] == 24u ? 0u : 1u; + require(op == (classes[attribute] == 2u ? spv::OpCopyObject : spv::OpBitcast), "vertex input did not preserve raw component bits"); + ++checked; + } + } + require(checked == 4u, "vertex specialization lost attribute loads"); + require(artifact.spirv.Words() == original, "vertex specialization mutated the shared template"); + } + } + auto invalid = artifact; + invalid.vertexInputPatches.front().word = static_cast(original.size()); + bool rejected = false; + try { static_cast(SpecializeVertexInputTypes(invalid, std::array{0u, 0u})); } + catch (const std::runtime_error&) { rejected = true; } + require(rejected, "out-of-range vertex type patch was accepted"); +} + void verifyDefaultDirectory(const char* self) { setEnvironment("ANYPS5_SHADER_CACHE_DIR", ""); setEnvironment("ANYPS5_NO_SHADER_CACHE", "1"); @@ -665,6 +746,7 @@ int main(int argc, char** argv) { verifyEmissionFailureMemo(); verifyFailureMemoSwitch(argv[0]); verifyInvocationIsolation(); + verifyVertexTypeSpecialization(); std::error_code error; std::filesystem::remove_all(directory, error); std::cout << "shader disk cache tests passed\n"; diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index d0e0201e1..3afac6b4e 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -182,11 +182,12 @@ private: append(key, value.fetchBufferReg); append(key, value.fetchEmbedded); if (value.resourcesNum > value.resources.size()) throw std::runtime_error("Shader cache: invalid vertex resource count"); - if (value.fetchEmbedded) return; append(key, value.resourcesNum); for (std::uint32_t i = 0; i < value.resourcesNum; ++i) { - append(key, VertexInputNumericClass(static_cast((value.resources[i].fields[3] >> 12u) & 0x7fu))); - append(key, value.resourcesDst[i]); + if (!value.fetchEmbedded) append(key, VertexInputNumericClass(static_cast((value.resources[i].fields[3] >> 12u) & 0x7fu))); + auto destination = value.resourcesDst[i]; + if (value.fetchEmbedded) destination.fetchIndex = 0; + append(key, destination); } } diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp index af420baa7..bc2c4c3b9 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp @@ -22,6 +22,8 @@ struct BufferResource { bool written = false; bool atomic = false; bool formatted = false; + bool descriptorFormatted = false; + std::uint32_t formattedReadMask = 0; bool scalar = false; bool operator==(const BufferResource& other) const = default; diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 6ab728cce..1e5c91244 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -217,12 +217,17 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con for (std::uint32_t index = 0; index < info.buffers.size(); ++index) { const auto& descriptor = snapshot.buffers.at(index); if (descriptor.dwordCount != 4u) fail("buffer specialization requires four descriptor words"); + const auto& buffer = info.buffers[index]; + const auto format = (descriptor.dwords[3] >> 12u) & 0x7fu; + const bool present = descriptor.dwords[0] != 0u || (descriptor.dwords[1] & 0xffffu) != 0u; + if (buffer.descriptorFormatted && (format > static_cast(IrBufferFormat::Format32_32_32_32Float) || (present && format == 0u))) fail("buffer specialization has an unsupported format"); const auto first = PipelineSpecialization::BufferBase + index * PipelineSpecialization::BufferWords; allocation.specialization.push_back({first, descriptor.dwords[1] & 0xffff0000u}); allocation.specialization.push_back({first + 1u, descriptor.dwords[3]}); - allocation.specialization.push_back({first + 2u, descriptor.dwords[0] != 0u || (descriptor.dwords[1] & 0xffffu) != 0u ? 1u : 0u}); - if (info.buffers[index].formatted) { + allocation.specialization.push_back({first + 2u, present ? 1u : 0u}); + if (buffer.formattedReadMask != 0u) { for (std::uint32_t component = 0; component < 4u; ++component) { + if ((buffer.formattedReadMask & (1u << component)) == 0u) continue; const auto selector = (descriptor.dwords[3] >> (component * 3u)) & 7u; if (selector == 2u || selector == 3u) fail("buffer specialization has a reserved component selector"); } diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 382dce2c4..e3b1c76ee 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -806,6 +806,7 @@ private: if (resource == std::numeric_limits::max()) fail("buffer resource limit exceeded"); AddMemoryPatch(memoryIndex, resource, 0u, false); memory.gpuDescriptor = false; + m_info.usesDma = m_info.usesDma || access == BufferAccess::Atomic; return true; } memory.gpuDescriptor = true; @@ -849,6 +850,8 @@ private: resource.written = resource.written || write; resource.atomic = resource.atomic || atomic; resource.formatted = resource.formatted || memory.formatted; + resource.descriptorFormatted = resource.descriptorFormatted || (memory.formatted && !memory.typed); + if (memory.formatted && !memory.typed && !write) resource.formattedReadMask |= (1u << std::min(memory.dataDwords, 4u)) - 1u; resource.scalar = resource.scalar || op == IrOpcode::ReadConstBuffer || memory.kind == ResourceKind::ScalarBuffer; } diff --git a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp index e8a2c6ec0..8e329c4e3 100644 --- a/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp +++ b/core/shader/recompiler/Optimization/src/ShaderInfoCollector.cpp @@ -189,7 +189,7 @@ void CollectVertexInputs(const IrProgram& program, const ShaderVertexInputInfo* if (inst->Opcode() == IrOpcode::GetAttribute) { const auto attribute = inst->Argument(0)->Resolve()->ImmediateU32(); const auto channel = inst->Argument(1)->Resolve()->ImmediateU32(); - usedComponents[attribute] = std::max(usedComponents[attribute], channel + 1u); + usedComponents[attribute] = std::max(usedComponents[attribute], inst->Flags() == 1u ? 4u : channel + 1u); } } } diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index c9f745cf0..f825ac7a2 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -19,6 +19,7 @@ inline constexpr std::uint32_t BufferWords = 3u; inline constexpr std::uint32_t ImageBase = 128u; inline constexpr std::uint32_t ImageWords = 5u; inline constexpr std::uint32_t VertexBase = 512u; +inline constexpr std::uint32_t VertexWords = 6u; } diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 96cde87d7..00d581276 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -6,6 +6,8 @@ #include #include "CacheKey.hpp" #include "CompiledVariant.hpp" +#include "VertexInputSpecialization.hpp" +#include "RdnaDecoder/RdnaDescriptorFormat.hpp" #include "ShaderDiskCache.hpp" #include #include @@ -118,6 +120,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) { constexpr InstructionTranslator translator; + EmbeddedFetchPlan embeddedFetch; + if ((stageKind == ShaderStageKind::Vertex || stageKind == ShaderStageKind::Local) && inputInfo.vertex != nullptr && inputInfo.vertex->fetchEmbedded) { + embeddedFetch = EmbeddedVertexFetchAnalyzer{}.Analyze(decoded, inputInfo.vertex->fetchAttribReg, inputInfo.vertex->fetchBufferReg, request.context.userDataBaseRegister, static_cast(request.context.userData.size()), request.context.waveSize); + translateOptions.embeddedFetch = &embeddedFetch; + } auto program = translator.Translate(decoded, cfg, translateOptions); // Debug aid: APS5_DUMP_IR= (or "all") prints the program after each front-end pass. const auto dumpIr = [&](const char* pass) { @@ -299,7 +306,7 @@ std::uint64_t nextVariantId() { return variants.fetch_add(1, std::memory_order_relaxed) + 1; } -CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, std::exception_ptr* emissionFailure = nullptr) { +CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, std::exception_ptr* emissionFailure = nullptr) try { const auto inputInfo = RequestInputInfo(request); constexpr DeadCodeEliminator deadCodeEliminator; constexpr ResourceMaterializer resourceMaterializer; @@ -327,17 +334,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra constexpr SpirvEmitter spirvEmitter; CompiledShaderArtifact result; result.variantId = nextVariantId(); - try { - result.spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions); -#if ANYPS5_ENABLE_SPIRV_TOOLS - result.spirv = ValidateAndOptimizeSpirv(result.spirv, request.target.vulkanVersion, request.target.spirvVersion, request.target.nonConstantImageOffsets); -#endif - } catch (const std::bad_alloc&) { - throw; - } catch (...) { - if (emissionFailure != nullptr) *emissionFailure = std::current_exception(); - throw; - } + result.spirv = spirvEmitter.Emit(program, inputInfo, bindings, targetOptions); result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u; result.memoryOffsetDword = bindings.layout.memoryOffsetDword; @@ -358,10 +355,36 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra if (input.kind != StageInputKind::Parameter) continue; if (input.location >= static_cast(inputInfo.vertex->resourcesNum)) throw std::runtime_error("vertex attribute location exceeds resource count"); result.vertexInputs.push_back({input.location, input.componentCount, inputInfo.vertex->resourcesDst[input.location].fetchIndex}); + if (inputInfo.vertex->fetchEmbedded) { + for (const auto& block : program.Blocks()) { + for (const auto* instruction : block->Instructions()) { + if (instruction->Opcode() == IrOpcode::GetAttribute && instruction->Argument(0)->Resolve()->ImmediateU32() == input.location) result.vertexInputs.back().outputMask |= 1u << instruction->Argument(1)->Resolve()->ImmediateU32(); + } + } + } } } + const bool embedded = !result.vertexInputs.empty() && inputInfo.vertex != nullptr && inputInfo.vertex->fetchEmbedded; + if (embedded) PrepareVertexInputSpecialization(result); +#if ANYPS5_ENABLE_SPIRV_TOOLS + result.spirv = ValidateAndOptimizeSpirv(result.spirv, request.target.vulkanVersion, request.target.spirvVersion, request.target.nonConstantImageOffsets, !embedded); + if (embedded) { + std::array classes{}; + for (std::uint32_t kind = 1u; kind < 3u; ++kind) { + classes.fill(kind); + const auto specialized = SpecializeVertexInputTypes(result, classes); + static_cast(ValidateAndOptimizeSpirv(specialized, request.target.vulkanVersion, request.target.spirvVersion, request.target.nonConstantImageOffsets, false)); + } + } +#endif + return {request.layout, std::move(program).TakeCompiledInfo(), std::move(static_cast(bindings)), std::move(result)}; +} catch (const std::bad_alloc&) { + throw; +} catch (...) { + if (emissionFailure != nullptr) *emissionFailure = std::current_exception(); + throw; } std::uint64_t specializationId(std::uint64_t artifact, std::span constants) { @@ -377,6 +400,16 @@ std::uint64_t specializationId(std::uint64_t artifact, std::spansecond; } +SharedSpirv specializeVertexInputs(const CompiledShaderArtifact& artifact, std::span classes) { + if (artifact.vertexInputPatches.empty()) return artifact.spirv; + static std::mutex mutex; + static std::map>, SharedSpirv> modules; + std::pair key{artifact.variantId, std::vector(classes.begin(), classes.end())}; + std::lock_guard lock(mutex); + if (const auto found = modules.find(key); found != modules.end()) return found->second; + return modules.emplace(std::move(key), SpecializeVertexInputTypes(artifact, classes)).first->second; +} + RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot) { RecompileResult result; static_cast(result) = variant.artifact; @@ -387,13 +420,35 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil DescriptorBindingBuilder{}.Populate(bindings, variant.info.info, variant.info.stage, variant.info.userDataBase, snapshot, partialThreads(request), request.context.pixel ? std::span(request.context.pixel->targetExportMapping) : std::span{}); result.bindings = std::move(bindings.bindings); result.specialization = std::move(bindings.specialization); - result.specializationId = specializationId(result.variantId, result.specialization); result.pushConstants = std::move(bindings.pushConstants); result.vertexAttributes.reserve(result.vertexInputs.size()); + std::array vertexClasses{}; for (const auto& input : result.vertexInputs) { if (!request.context.vertex || input.location >= request.context.vertex->resourcesNum || input.location >= request.context.vertex->resources.size()) throw std::runtime_error("Shader cache: invalid vertex attribute metadata"); - result.vertexAttributes.push_back({input.location, input.components, request.context.vertex->resources[input.location], input.fetchIndex}); + const auto& vertex = *request.context.vertex; + const auto& resource = vertex.resources[input.location]; + const auto fetchIndex = vertex.fetchEmbedded ? vertex.resourcesDst[input.location].fetchIndex : input.fetchIndex; + result.vertexAttributes.push_back({input.location, input.components, resource, fetchIndex}); + if (!result.vertexInputPatches.empty()) { + const auto numeric = VertexInputNumericClass(static_cast((resource.fields[3] >> 12u) & 0x7fu)); + if (numeric == IrTextureNumericClass::Unsupported) throw std::runtime_error("unsupported prepared vertex format"); + const auto kind = numeric == IrTextureNumericClass::Float ? 0u : numeric == IrTextureNumericClass::Sint ? 1u : 2u; + vertexClasses.at(input.location) = kind; + const auto first = PipelineSpecialization::VertexBase + input.location * PipelineSpecialization::VertexWords; + auto& formatComponents = result.vertexAttributes.back().formatComponents; + formatComponents = 1u; + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto selector = (input.outputMask & (1u << component)) != 0u ? (resource.fields[3] >> (component * 3u)) & 7u : 0u; + if (selector == 2u || selector == 3u) throw std::runtime_error("reserved prepared vertex component selector"); + if (selector >= 4u) formatComponents = std::max(formatComponents, selector - 3u); + result.specialization.push_back({first + component, selector}); + } + result.specialization.push_back({first + 4u, kind == 0u ? 0x3f800000u : 1u}); + result.specialization.push_back({first + 5u, kind}); + } } + result.spirv = specializeVertexInputs(variant.artifact, vertexClasses); + result.specializationId = specializationId(result.variantId, result.specialization); return result; } @@ -541,12 +596,13 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh if (request.context.pixel) { for (const auto mapping : request.context.pixel->targetExportMapping) mix(mapping); } - if (request.context.vertex && !request.context.vertex->fetchEmbedded) { + if (request.context.vertex) { const auto& vertex = *request.context.vertex; const auto count = std::min(vertex.resourcesNum, ShaderVertexStageInfo::MaxResources); mix(count); for (std::uint32_t i = 0; i < count; ++i) { for (const auto field : vertex.resources[i].fields) mix(field); + mix(vertex.resourcesDst[i].fetchIndex); } } else { mix(1ull << 32u); diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 3c1693c17..00463662d 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -346,6 +346,7 @@ struct VertexAttribute { std::uint32_t components; ShaderVertexBufferResource resource; std::uint32_t fetchIndex; + std::uint32_t formatComponents = 0; }; struct FragmentParameter { @@ -405,11 +406,21 @@ struct VertexInput { std::uint32_t location; std::uint32_t components; std::uint32_t fetchIndex; + std::uint32_t outputMask = 0; bool operator==(const VertexInput& other) const = default; }; +struct VertexInputPatch { + std::uint32_t location; + std::uint32_t word; + std::array values; + + bool operator==(const VertexInputPatch& other) const = default; +}; + struct CompiledShaderArtifact { + std::vector vertexInputPatches; SharedSpirv spirv; std::uint32_t memoryOffsetDword = 0; std::uint32_t bdaAbiVersion = 0; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 8411fb3a2..4809b5b5d 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -287,10 +287,16 @@ void encodeArtifact(Writer& writer, const CompiledShaderArtifact& result) { writer.Values(std::span(result.spirv.Words())); writer.Value(result.bdaAbiVersion); writer.Value(result.memoryOffsetDword); + writer.List(result.vertexInputPatches, [](Writer& out, const VertexInputPatch& patch) { + out.Value(patch.location); + out.Value(patch.word); + for (const auto value : patch.values) out.Value(value); + }); writer.List(result.vertexInputs, [](Writer& out, const VertexInput& attribute) { out.Value(attribute.location); out.Value(attribute.components); out.Value(attribute.fetchIndex); + out.Value(attribute.outputMask); }); writer.Value(result.vertexOffsetSgpr); writer.Value(result.instanceOffsetSgpr); @@ -316,10 +322,16 @@ void decodeArtifact(Reader& reader, CompiledShaderArtifact& result) { result.spirv = std::move(words); reader.Value(result.bdaAbiVersion); reader.Value(result.memoryOffsetDword); - reader.List(result.vertexInputs, 12, [](Reader& in, VertexInput& attribute) { + reader.List(result.vertexInputPatches, 20, [](Reader& in, VertexInputPatch& patch) { + in.Value(patch.location); + in.Value(patch.word); + for (auto& value : patch.values) in.Value(value); + }); + reader.List(result.vertexInputs, 16, [](Reader& in, VertexInput& attribute) { in.Value(attribute.location); in.Value(attribute.components); in.Value(attribute.fetchIndex); + in.Value(attribute.outputMask); }); reader.Value(result.vertexOffsetSgpr); reader.Value(result.instanceOffsetSgpr); @@ -352,6 +364,7 @@ void encodeInvocation(Writer& writer, const ShaderInvocation& invocation) { out.Value(attribute.components); for (const auto field : attribute.resource.fields) out.Value(field); out.Value(attribute.fetchIndex); + out.Value(attribute.formatComponents); }); } @@ -366,11 +379,12 @@ void decodeInvocation(Reader& reader, ShaderInvocation& invocation) { reader.Values(pushConstants); invocation.pushConstants.resize(pushConstants.size()); if (!pushConstants.empty()) std::memcpy(invocation.pushConstants.data(), pushConstants.data(), pushConstants.size()); - reader.List(invocation.vertexAttributes, 28, [](Reader& in, VertexAttribute& attribute) { + reader.List(invocation.vertexAttributes, 32, [](Reader& in, VertexAttribute& attribute) { in.Value(attribute.location); in.Value(attribute.components); for (auto& field : attribute.resource.fields) in.Value(field); in.Value(attribute.fetchIndex); + in.Value(attribute.formatComponents); }); } @@ -418,6 +432,8 @@ void encodeInfo(Writer& writer, const CompiledShaderInfo& compiled) { out.Value(buffer.written); out.Value(buffer.atomic); out.Value(buffer.formatted); + out.Value(buffer.descriptorFormatted); + out.Value(buffer.formattedReadMask); out.Value(buffer.scalar); }); writer.List(info.images, [](Writer& out, const ImageResource& image) { @@ -502,7 +518,7 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) { auto& info = compiled.info; reader.Value(info.scratchDwords); reader.Value(info.sharedMemoryBytes); - reader.List(info.buffers, 21, [](Reader& in, BufferResource& buffer) { + reader.List(info.buffers, 26, [](Reader& in, BufferResource& buffer) { in.Value(buffer.source); in.Value(buffer.firstUsePc); in.Value(buffer.maxByteExtent); @@ -511,6 +527,8 @@ void decodeInfo(Reader& reader, CompiledShaderInfo& compiled) { in.Value(buffer.written); in.Value(buffer.atomic); in.Value(buffer.formatted); + in.Value(buffer.descriptorFormatted); + in.Value(buffer.formattedReadMask); in.Value(buffer.scalar); }); reader.List(info.images, 65, [](Reader& in, ImageResource& image) { diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 5e42b26b6..83ad09313 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -12,7 +12,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 14; +inline constexpr std::uint32_t FormatVersion = 15; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp index 670851c15..23f7e561c 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvOptimizer.hpp @@ -7,7 +7,7 @@ namespace ShaderRecompiler { -[[nodiscard]] std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand = false); +[[nodiscard]] std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand = false, bool optimize = true); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index d789c2fbd..4e66c91fc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -5,6 +5,7 @@ #include "SpirvBackend/SpirvBda.hpp" #include #include +#include "PipelineSpecialization.hpp" #include #include #include @@ -1065,6 +1066,18 @@ std::uint32_t EmitGetBuiltin(SpirvValueEmitContext& ctx, const IrValue* kind, co } std::uint32_t EmitGetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) { + if (inst.Flags() == 1u) { + auto& state = ctx.state; + const auto attribute = inst.Argument(0)->ImmediateU32(); + const auto component = inst.Argument(1)->ImmediateU32(); + if (state.inputInfo.vertex == nullptr || !state.inputInfo.vertex->fetchEmbedded || component >= 4u) ctx.Fail(inst, "invalid prepared vertex attribute"); + const auto first = PipelineSpecialization::VertexBase + attribute * PipelineSpecialization::VertexWords; + const auto selector = state.module.SpecializationConstant(TypeU32(state), first + component, component + 4u); + const auto one = state.module.SpecializationConstant(TypeU32(state), first + 4u, 0x3f800000u); + auto value = Select(state, TypeU32(state), Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, 1u)), one, ConstantU32(state, 0u)); + for (std::uint32_t channel = 0; channel < 4u; ++channel) value = Select(state, TypeU32(state), Binary(state, spv::OpIEqual, TypeBool(state), selector, ConstantU32(state, channel + 4u)), EmitAttributeValue(state, attribute, channel), value); + return value; + } return EmitAttributeValue(ctx.state, inst.Argument(0)->ImmediateU32(), inst.Argument(1)->ImmediateU32()); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp index f03c6d3a5..1271997e5 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp @@ -56,7 +56,7 @@ bool HasLimitedUseTypes(std::span spirv) { } -std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand) { +std::vector ValidateAndOptimizeSpirv(std::span spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand, bool optimize) { spv_target_env environment; const auto apiVersion = vulkanVersion & ~0xfffu; std::uint32_t maxSpirvVersion = 0; @@ -89,6 +89,7 @@ std::vector ValidateAndOptimizeSpirv(std::span(spirv.begin(), spirv.end()); spvtools::Optimizer optimizer(environment); optimizer.SetMessageConsumer(consumer); static const char* mode = std::getenv("APS5_SPIRV_OPT"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp index 19b631ab8..57f5d6af0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp @@ -4,6 +4,7 @@ #include "SpirvBackend/SpirvBufferFormat.hpp" #include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" #include "PipelineSpecialization.hpp" +#include #include #include @@ -57,9 +58,9 @@ RuntimeDescriptor descriptor(SpirvValueEmitContext& context, const IrValue& inst const auto word3 = memory.gpuDescriptor ? context.Arg(*handle, 3u) : state.module.SpecializationConstant(TypeU32(state), first + 1u, 0u); const auto u32 = TypeU32(state); auto base = ConstantU32(state, 0u); - if (memory.gpuDescriptor) { + if (memory.gpuDescriptor || BufferAccessOf(instruction.Opcode()) == BufferAccess::Atomic) { const auto u64 = TypeScalarU64(state); - const auto high = Binary(state, spv::OpShiftLeftLogical, u64, Unary(state, spv::OpUConvert, u64, field(state, word1, 0u, 16u)), BdaConstant(state, 32u)); + const auto high = Binary(state, spv::OpShiftLeftLogical, u64, Unary(state, spv::OpUConvert, u64, field(state, context.Arg(*handle, 1u), 0u, 16u)), BdaConstant(state, 32u)); base = Binary(state, spv::OpBitwiseOr, u64, Unary(state, spv::OpUConvert, u64, context.Arg(*handle, 0u)), high); } auto invalid = nonzero(state, field(state, word3, 30u, 2u)); @@ -383,11 +384,9 @@ void EmitRuntimeBufferStore(SpirvValueEmitContext& context, const IrValue& instr std::uint32_t EmitRuntimeScalarBufferLoad(SpirvValueEmitContext& context, const IrValue& instruction) { auto& state = context.state; + if (std::ranges::find(state.supportedCapabilities, spv::CapabilityInt64) == state.supportedCapabilities.end()) context.Fail(instruction, "scalar buffer bounds require shaderInt64"); + state.module.EmitCapability(spv::CapabilityInt64); const auto resource = descriptor(context, instruction); - if (!context.Memory(instruction).gpuDescriptor) { - const auto byte = EmitAndConstant(state, EmitAddU32(state, context.Arg(instruction, 1u), ConstantU32(state, context.Memory(instruction).offset)), ~3u); - return EmitValueOrZeroIfCondition(state, resource.valid, [&] { return readBuffer(context, instruction, byte, 32u); }); - } const auto u64 = TypeScalarU64(state); const auto size = Select(state, u64, equal(state, resource.stride, ConstantU32(state, 0u)), Unary(state, spv::OpUConvert, u64, resource.records), Binary(state, spv::OpIMul, u64, Unary(state, spv::OpUConvert, u64, resource.stride), Unary(state, spv::OpUConvert, u64, resource.records))); const auto address = EmitAddU32(state, context.Arg(instruction, 1u), ConstantU32(state, context.Memory(instruction).offset)); @@ -395,6 +394,7 @@ std::uint32_t EmitRuntimeScalarBufferLoad(SpirvValueEmitContext& context, const const auto byte = EmitAndConstant(state, address, ~3u); const auto offset = Unary(state, spv::OpUConvert, u64, byte); const auto inBounds = both(state, noCarry, both(state, resource.valid, Binary(state, spv::OpULessThanEqual, TypeBool(state), Binary(state, spv::OpIAdd, u64, offset, BdaConstant(state, 4u)), size))); + if (!context.Memory(instruction).gpuDescriptor) return EmitValueOrZeroIfCondition(state, inBounds, [&] { return readBuffer(context, instruction, byte, 32u); }); return EmitValueOrZeroIfCondition(state, inBounds, [&] { return readBuffer(context, instruction, AddBdaAddress(context, instruction, resource.base, offset, false), 32u); }); } @@ -412,8 +412,11 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa if (!context.Memory(instruction).gpuDescriptor) { const auto storage = directAccess(context, instruction, bits == 64u); const auto byte = EmitAddU32(state, access.guest, storage.byteOffset); + const auto unaligned = nonzero(state, EmitAndConstant(state, byte, bits / 8u - 1u)); + const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), resource.base, Unary(state, spv::OpUConvert, TypeScalarU64(state), access.guest)); + faultIf(context, instruction, unaligned, guest, bits / 8u, BdaAbi::FaultReason::Unaligned); const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte, ConstantU32(state, bits == 64u ? 3u : 2u)); - return EmitValueOrDefaultIfCondition(state, EmitMemoryElementInBounds(state, storage, index), type, zero, [&] { + return EmitValueOrDefaultIfCondition(state, both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), EmitMemoryElementInBounds(state, storage, index)), type, zero, [&] { const auto pointer = EmitStorageBufferElementPointer(state, storage, index, bits == 64u ? TypeStorageBufferU64ElementPointer(state) : TypeStorageBufferElementPointer(state)); return operation(pointer); }); diff --git a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp index 0a2817655..481f5b83a 100644 --- a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp +++ b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp @@ -6,6 +6,7 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "IntermediateRepresentation/IrProgram.hpp" #include "Optimization/ShaderStageInputInfo.hpp" +#include "Translation/EmbeddedVertexFetch.hpp" #include namespace ShaderRecompiler { @@ -31,6 +32,7 @@ struct TranslateOptions { std::uint64_t shaderHash = 0; bool fragmentShaderBarycentricEnabled = false; ShaderStageInputInfo inputInfo; + const EmbeddedFetchPlan* embeddedFetch = nullptr; }; class InstructionTranslator { diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 301ea9e3d..cd332977f 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -12,6 +12,7 @@ class TranslationContext { public: TranslationContext(IrProgram& program, IrBlock& block, std::uint32_t vectorLimit); + void TranslateEmbeddedFetch(const RdnaInstruction& instruction, std::uint32_t attribute, std::uint32_t components); void TranslateInstruction(const RdnaInstruction& instruction); void SetPixelInput(const ShaderPixelInputInfo* info, bool barycentricEnabled) { pixelInput = info; fragmentShaderBarycentricEnabled = barycentricEnabled; } void AddBranchCondition(const BasicBlock& source, BlockInfo& info); diff --git a/core/shader/recompiler/Translation/src/AttributeInstructions.cpp b/core/shader/recompiler/Translation/src/AttributeInstructions.cpp index e96393ab9..32a84d1b4 100644 --- a/core/shader/recompiler/Translation/src/AttributeInstructions.cpp +++ b/core/shader/recompiler/Translation/src/AttributeInstructions.cpp @@ -127,6 +127,15 @@ bool TranslationContext::emitInterpolation(const RdnaInstruction& inst) { } } +void TranslationContext::TranslateEmbeddedFetch(const RdnaInstruction& instruction, std::uint32_t attribute, std::uint32_t components) { + if (!instruction.formatted || instruction.typed || components == 0u || components > 4u) throw std::runtime_error("invalid prepared vertex fetch"); + for (std::uint32_t component = 0; component < components; ++component) { + auto& value = ir.Emit(IrOpcode::GetAttribute, IrType::U32, {&ir.Constant(attribute), &ir.Constant(component)}); + value.SetFlags(1u); + writeOperand(offsetOperand(instruction.destination, component), &value); + } +} + void TranslateAttributeInstruction(TranslationContext& context, const RdnaInstruction& instruction) { throw std::runtime_error("TranslateAttributeInstruction not implemented"); } diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index a1637381d..a22d72651 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -38,6 +38,7 @@ IrShaderStage toIrShaderStage(ShaderStageKind stage) { } void validateTranslateOptions(const TranslateOptions& options) { + if (options.embeddedFetch != nullptr && options.stage != ShaderStageKind::Vertex && options.stage != ShaderStageKind::Local) throw std::runtime_error("embedded vertex fetch requires a vertex or local shader"); if (options.userDataBaseRegister >= NumScalarRegs || options.userDataCount > NumScalarRegs - options.userDataBaseRegister) { throw std::runtime_error("shader user data exceeds the scalar register bank"); } @@ -351,6 +352,14 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con program.Resources().userDataBase = options.userDataBaseRegister; program.Resources().userDataCount = options.userDataCount; program.Info().scratchDwords = options.scratchDwords; + if (options.embeddedFetch != nullptr) { + program.Info().vertexOffsetSgpr = options.embeddedFetch->vertexOffsetSgpr; + program.Info().instanceOffsetSgpr = options.embeddedFetch->instanceOffsetSgpr; + program.Info().vertexOffsetShared = options.embeddedFetch->vertexOffsetShared; + program.Info().instanceOffsetShared = options.embeddedFetch->instanceOffsetShared; + program.Info().vertexOffsetConflict = options.embeddedFetch->vertexOffsetConflict; + program.Info().instanceOffsetConflict = options.embeddedFetch->instanceOffsetConflict; + } program.Metadata().cfgFailureKind = cfg.failureKind; program.Metadata().failureReason = cfg.unsupportedReason; @@ -409,6 +418,18 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con context.TranslateCodeTableLoad(instruction, *table); continue; } + if (options.embeddedFetch != nullptr) { + const auto load = std::ranges::find(options.embeddedFetch->loads, instruction.programCounter, &EmbeddedFetchLoad::programCounter); + if (load != options.embeddedFetch->loads.end()) { + if (options.inputInfo.vertex == nullptr || instruction.destination.kind != RdnaOperandKind::VectorRegister || instruction.dataDwordCount != load->componentCount) throw std::runtime_error("prepared vertex fetch does not match its instruction"); + const auto& vertex = *options.inputInfo.vertex; + std::uint32_t attribute = 0; + while (attribute < static_cast(vertex.resourcesNum) && vertex.resourcesDst[attribute].attrId != load->attributeId) ++attribute; + if (attribute == static_cast(vertex.resourcesNum)) throw std::runtime_error("prepared vertex fetch has no matching semantic"); + context.TranslateEmbeddedFetch(instruction, attribute, load->componentCount); + continue; + } + } context.TranslateInstruction(instruction); } context.AddBranchCondition(cfgBlock, blockInfos[typedIndex]); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 5124da84d..c75a92eec 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -161,13 +161,14 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues vertexStorage.fetchExternal = false; vertexStorage.fetchAttribReg = static_cast(vertex.fetchAttribReg); vertexStorage.fetchBufferReg = static_cast(vertex.fetchBufferReg); - vertexStorage.resourcesNum = vertex.fetchEmbedded ? 0 : static_cast(vertex.resourcesNum); + vertexStorage.resourcesNum = static_cast(vertex.resourcesNum); for (std::uint32_t i = 0; i < static_cast(vertexStorage.resourcesNum); ++i) { vertexStorage.resources[i].fields = vertex.resources[i].fields; + if (vertex.fetchEmbedded) vertexStorage.resources[i].fields = {0u, 0u, 0u, (static_cast(IrBufferFormat::Format32_32_32_32Float) << 12u) | ShaderImageIdentitySwizzle}; vertexStorage.resourcesDst[i].registerStart = vertex.resourcesDst[i].registerStart; vertexStorage.resourcesDst[i].registersNum = vertex.resourcesDst[i].registersNum; vertexStorage.resourcesDst[i].attrId = vertex.resourcesDst[i].attrId; - vertexStorage.resourcesDst[i].fetchIndex = vertex.resourcesDst[i].fetchIndex; + vertexStorage.resourcesDst[i].fetchIndex = vertex.fetchEmbedded ? 0u : vertex.resourcesDst[i].fetchIndex; } _detectVertexBuffers(vertexStorage); if (stage == ShaderStageKind::Local || stage == ShaderStageKind::TessellationControl || stage == ShaderStageKind::TessellationEvaluation) { diff --git a/core/shader/recompiler/VertexInputSpecialization.hpp b/core/shader/recompiler/VertexInputSpecialization.hpp new file mode 100644 index 000000000..2b2722eed --- /dev/null +++ b/core/shader/recompiler/VertexInputSpecialization.hpp @@ -0,0 +1,131 @@ +#ifndef CORE_SHADER_RECOMPILER_VERTEXINPUTSPECIALIZATION_HPP +#define CORE_SHADER_RECOMPILER_VERTEXINPUTSPECIALIZATION_HPP + +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ShaderRecompiler { + +inline SharedSpirv SpecializeVertexInputTypes(const CompiledShaderArtifact& artifact, std::span classes) { + if (artifact.vertexInputPatches.empty()) return artifact.spirv; + auto words = artifact.spirv.Words(); + for (const auto& patch : artifact.vertexInputPatches) { + if (patch.location >= classes.size() || classes[patch.location] >= patch.values.size() || patch.word >= words.size()) throw std::runtime_error("invalid prepared vertex type patch"); + words[patch.word] = patch.values[classes[patch.location]]; + } + return SharedSpirv(std::move(words)); +} + +inline void PrepareVertexInputSpecialization(CompiledShaderArtifact& artifact) { + const auto& source = artifact.spirv.Words(); + if (source.size() < 5u || source[0] != spv::MagicNumber) throw std::runtime_error("invalid vertex specialization module"); + std::map locations; + std::map, std::uint32_t> types; + std::vector> declarations; + std::vector> variables; + std::map pointers; + std::map loads; + std::map> variableTypes; + std::map components; + for (const auto& input : artifact.vertexInputs) { + if (input.location >= ShaderVertexStageInfo::MaxResources || input.components == 0u || input.components > 4u || !components.emplace(input.location, input.components).second) throw std::runtime_error("invalid vertex specialization input"); + } + auto bound = source[3]; + const auto declare = [&](std::vector key) { + const auto found = types.find(key); + if (found != types.end()) return found->second; + if (bound == std::numeric_limits::max()) throw std::runtime_error("vertex specialization module ID overflow"); + const auto id = bound++; + std::vector words{(static_cast(key.size() + 1u) << 16u) | key.front(), id}; + words.insert(words.end(), key.begin() + 1, key.end()); + types.emplace(std::move(key), id); + declarations.push_back(std::move(words)); + return id; + }; + for (std::size_t cursor = 5; cursor < source.size();) { + const auto count = source[cursor] >> 16u; + const auto op = static_cast(source[cursor] & 0xffffu); + if (count == 0u || count > source.size() - cursor) throw std::runtime_error("truncated vertex specialization instruction"); + if (op == spv::OpDecorate && count == 4u && source[cursor + 2u] == spv::DecorationLocation) locations.emplace(source[cursor + 1u], source[cursor + 3u]); + if (op == spv::OpTypeInt || op == spv::OpTypeFloat || op == spv::OpTypeVector || op == spv::OpTypePointer) { + std::vector key{static_cast(op)}; + key.insert(key.end(), source.begin() + cursor + 2u, source.begin() + cursor + count); + types.emplace(std::move(key), source[cursor + 1u]); + } + cursor += count; + } + const std::array scalarTypes{declare({spv::OpTypeFloat, 32u}), declare({spv::OpTypeInt, 32u, 1u}), declare({spv::OpTypeInt, 32u, 0u})}; + std::array scalarPointers{}; + for (std::size_t kind = 0; kind < 3u; ++kind) scalarPointers[kind] = declare({spv::OpTypePointer, spv::StorageClassInput, scalarTypes[kind]}); + for (std::size_t cursor = 5; cursor < source.size();) { + const auto count = source[cursor] >> 16u; + const auto op = static_cast(source[cursor] & 0xffffu); + if (op == spv::OpVariable && count == 4u && source[cursor + 3u] == spv::StorageClassInput) { + const auto location = locations.find(source[cursor + 2u]); + if (location != locations.end() && components.contains(location->second)) { + const auto id = source[cursor + 2u]; + pointers.emplace(id, location->second); + auto& choices = variableTypes[id]; + for (std::size_t kind = 0; kind < 3u; ++kind) { + const auto count = components.at(location->second); + const auto type = count == 1u ? scalarTypes[kind] : declare({spv::OpTypeVector, scalarTypes[kind], count}); + choices[kind] = declare({spv::OpTypePointer, spv::StorageClassInput, type}); + } + variables.emplace_back(source.begin() + cursor, source.begin() + cursor + count); + } + } + cursor += count; + } + if (variables.size() != artifact.vertexInputs.size()) throw std::runtime_error("vertex specialization inputs disagree with metadata"); + std::vector result(source.begin(), source.begin() + 5); + result[3] = bound; + bool inserted = false; + const auto patch = [&](std::uint32_t location, std::size_t word, const std::array& values) { + if (word > std::numeric_limits::max()) throw std::runtime_error("vertex specialization offset overflow"); + artifact.vertexInputPatches.push_back({location, static_cast(word), values}); + }; + artifact.vertexInputPatches.clear(); + for (std::size_t cursor = 5; cursor < source.size();) { + const auto count = source[cursor] >> 16u; + const auto op = static_cast(source[cursor] & 0xffffu); + if (op == spv::OpFunction && !inserted) { + for (const auto& declaration : declarations) result.insert(result.end(), declaration.begin(), declaration.end()); + for (const auto& variable : variables) { + patch(pointers.at(variable[2]), result.size() + 1u, variableTypes.at(variable[2])); + result.insert(result.end(), variable.begin(), variable.end()); + } + inserted = true; + } + if (op == spv::OpVariable && variableTypes.contains(source[cursor + 2u])) { + cursor += count; + continue; + } + if (op == spv::OpAccessChain && count == 5u && pointers.contains(source[cursor + 3u])) { + const auto location = pointers.at(source[cursor + 3u]); + pointers.emplace(source[cursor + 2u], location); + patch(location, result.size() + 1u, scalarPointers); + } else if (op == spv::OpLoad && count == 4u && pointers.contains(source[cursor + 3u])) { + const auto location = pointers.at(source[cursor + 3u]); + loads.emplace(source[cursor + 2u], location); + patch(location, result.size() + 1u, scalarTypes); + } else if (op == spv::OpBitcast && count == 4u && loads.contains(source[cursor + 3u])) { + patch(loads.at(source[cursor + 3u]), result.size(), {(4u << 16u) | spv::OpBitcast, (4u << 16u) | spv::OpBitcast, (4u << 16u) | spv::OpCopyObject}); + } + result.insert(result.end(), source.begin() + cursor, source.begin() + cursor + count); + cursor += count; + } + if (!inserted) throw std::runtime_error("vertex specialization module has no function"); + artifact.spirv = std::move(result); +} + +} + +#endif