perf(shader): restore prepared vertex inputs + harden buffer specialization

This commit is contained in:
boykopovar 2026-10-06 21:29:14 +03:00
parent 7f56e04157
commit 7431e5a6e1
24 changed files with 460 additions and 90 deletions

View File

@ -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));
}
}

View File

@ -479,6 +479,7 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> 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);
}

View File

@ -198,7 +198,13 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const st
for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) {
const auto& semantic = semantics[i];
if (semantic.static_vb_index == 1 || semantic.static_attribute == 1) throw std::runtime_error("AGC graphics: statically bound vertex attributes are not implemented");
if (staticAbi) continue;
if (staticAbi) {
auto& destination = info.resourcesDst.at(info.resourcesNum++);
destination.registerStart = static_cast<std::int32_t>(semantic.hardware_mapping);
destination.registersNum = static_cast<std::int32_t>(semantic.size_in_elements);
destination.attrId = static_cast<std::int32_t>(semantic.semantic);
continue;
}
std::array<std::byte, 4> attribWordBytes{};
const auto attribWordAddress = attribTableAddr + static_cast<std::uint64_t>(semantic.semantic) * 4u;
AgcDriver::GuestMemory::Read(attribWordAddress, attribWordBytes, 4);

View File

@ -1,5 +1,6 @@
#include "ShaderDiskCache.hpp"
#include "CacheKey.hpp"
#include "VertexInputSpecialization.hpp"
#include "Optimization/ResourceMaterializer.hpp"
#include "ShaderCacheDirectory.hpp"
#include <algorithm>
@ -12,6 +13,7 @@
#include <functional>
#include <future>
#include <iostream>
#include <map>
#include <random>
#include <stdexcept>
#include <string>
@ -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<std::uint32_t>{
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<std::uint32_t, std::uint32_t> scalarKinds;
std::uint32_t checked = 0;
for (std::size_t cursor = 5; cursor < words.size(); cursor += words[cursor] >> 16u) {
const auto op = static_cast<spv::Op>(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<std::uint32_t>(original.size());
bool rejected = false;
try { static_cast<void>(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";

View File

@ -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<IrBufferFormat>((value.resources[i].fields[3] >> 12u) & 0x7fu)));
append(key, value.resourcesDst[i]);
if (!value.fetchEmbedded) append(key, VertexInputNumericClass(static_cast<IrBufferFormat>((value.resources[i].fields[3] >> 12u) & 0x7fu)));
auto destination = value.resourcesDst[i];
if (value.fetchEmbedded) destination.fetchIndex = 0;
append(key, destination);
}
}

View File

@ -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;

View File

@ -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<std::uint32_t>(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");
}

View File

@ -806,6 +806,7 @@ private:
if (resource == std::numeric_limits<std::uint32_t>::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;
}

View File

@ -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<std::uint32_t>() == 1u ? 4u : channel + 1u);
}
}
}

View File

@ -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;
}

View File

@ -6,6 +6,8 @@
#include <cstdio>
#include "CacheKey.hpp"
#include "CompiledVariant.hpp"
#include "VertexInputSpecialization.hpp"
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include "ShaderDiskCache.hpp"
#include <list>
#include <map>
@ -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<std::uint32_t>(request.context.userData.size()), request.context.waveSize);
translateOptions.embeddedFetch = &embeddedFetch;
}
auto program = translator.Translate(decoded, cfg, translateOptions);
// Debug aid: APS5_DUMP_IR=<hex code address> (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<std::uint32_t>(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<std::uint32_t, ShaderVertexStageInfo::MaxResources> classes{};
for (std::uint32_t kind = 1u; kind < 3u; ++kind) {
classes.fill(kind);
const auto specialized = SpecializeVertexInputTypes(result, classes);
static_cast<void>(ValidateAndOptimizeSpirv(specialized, request.target.vulkanVersion, request.target.spirvVersion, request.target.nonConstantImageOffsets, false));
}
}
#endif
return {request.layout, std::move(program).TakeCompiledInfo(), std::move(static_cast<CompiledBindingLayout&>(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<const PipelineSpecializationConstant> constants) {
@ -377,6 +400,16 @@ std::uint64_t specializationId(std::uint64_t artifact, std::span<const PipelineS
return entry->second;
}
SharedSpirv specializeVertexInputs(const CompiledShaderArtifact& artifact, std::span<const std::uint32_t> classes) {
if (artifact.vertexInputPatches.empty()) return artifact.spirv;
static std::mutex mutex;
static std::map<std::pair<std::uint64_t, std::vector<std::uint32_t>>, SharedSpirv> modules;
std::pair key{artifact.variantId, std::vector<std::uint32_t>(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<CompiledShaderArtifact&>(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<const std::uint8_t>(request.context.pixel->targetExportMapping) : std::span<const std::uint8_t>{});
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<std::uint32_t, ShaderVertexStageInfo::MaxResources> 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<IrBufferFormat>((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<std::uint32_t>(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);

View File

@ -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<std::uint32_t, 3> values;
bool operator==(const VertexInputPatch& other) const = default;
};
struct CompiledShaderArtifact {
std::vector<VertexInputPatch> vertexInputPatches;
SharedSpirv spirv;
std::uint32_t memoryOffsetDword = 0;
std::uint32_t bdaAbiVersion = 0;

View File

@ -287,10 +287,16 @@ void encodeArtifact(Writer& writer, const CompiledShaderArtifact& result) {
writer.Values(std::span<const std::uint32_t>(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) {

View File

@ -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,

View File

@ -7,7 +7,7 @@
namespace ShaderRecompiler {
[[nodiscard]] std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand = false);
[[nodiscard]] std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand = false, bool optimize = true);
}

View File

@ -5,6 +5,7 @@
#include "SpirvBackend/SpirvBda.hpp"
#include <spirv/unified1/GLSL.std.450.h>
#include <spirv/unified1/spirv.hpp>
#include "PipelineSpecialization.hpp"
#include <algorithm>
#include <array>
#include <limits>
@ -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<std::uint32_t>() == 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());
}

View File

@ -56,7 +56,7 @@ bool HasLimitedUseTypes(std::span<const std::uint32_t> spirv) {
}
std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> spirv, std::uint32_t vulkanVersion, std::uint32_t spirvVersion, bool allowOffsetTextureOperand) {
std::vector<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_t> 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<std::uint32_t> ValidateAndOptimizeSpirv(std::span<const std::uint32_
if (!tools.Validate(spirv.data(), spirv.size(), validatorOptions)) {
throw std::runtime_error("SPIR-V validation before optimization failed:\n" + diagnostics);
}
if (!optimize) return std::vector<std::uint32_t>(spirv.begin(), spirv.end());
spvtools::Optimizer optimizer(environment);
optimizer.SetMessageConsumer(consumer);
static const char* mode = std::getenv("APS5_SPIRV_OPT");

View File

@ -4,6 +4,7 @@
#include "SpirvBackend/SpirvBufferFormat.hpp"
#include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp"
#include "PipelineSpecialization.hpp"
#include <algorithm>
#include <array>
#include <vector>
@ -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);
});

View File

@ -6,6 +6,7 @@
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ShaderStageInputInfo.hpp"
#include "Translation/EmbeddedVertexFetch.hpp"
#include <cstdint>
namespace ShaderRecompiler {
@ -31,6 +32,7 @@ struct TranslateOptions {
std::uint64_t shaderHash = 0;
bool fragmentShaderBarycentricEnabled = false;
ShaderStageInputInfo inputInfo;
const EmbeddedFetchPlan* embeddedFetch = nullptr;
};
class InstructionTranslator {

View File

@ -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);

View File

@ -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<std::uint32_t>(1u);
writeOperand(offsetOperand(instruction.destination, component), &value);
}
}
void TranslateAttributeInstruction(TranslationContext& context, const RdnaInstruction& instruction) {
throw std::runtime_error("TranslateAttributeInstruction not implemented");
}

View File

@ -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<std::uint32_t>(vertex.resourcesNum) && vertex.resourcesDst[attribute].attrId != load->attributeId) ++attribute;
if (attribute == static_cast<std::uint32_t>(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]);

View File

@ -161,13 +161,14 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
vertexStorage.fetchExternal = false;
vertexStorage.fetchAttribReg = static_cast<int>(vertex.fetchAttribReg);
vertexStorage.fetchBufferReg = static_cast<int>(vertex.fetchBufferReg);
vertexStorage.resourcesNum = vertex.fetchEmbedded ? 0 : static_cast<int>(vertex.resourcesNum);
vertexStorage.resourcesNum = static_cast<int>(vertex.resourcesNum);
for (std::uint32_t i = 0; i < static_cast<std::uint32_t>(vertexStorage.resourcesNum); ++i) {
vertexStorage.resources[i].fields = vertex.resources[i].fields;
if (vertex.fetchEmbedded) vertexStorage.resources[i].fields = {0u, 0u, 0u, (static_cast<std::uint32_t>(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) {

View File

@ -0,0 +1,131 @@
#ifndef CORE_SHADER_RECOMPILER_VERTEXINPUTSPECIALIZATION_HPP
#define CORE_SHADER_RECOMPILER_VERTEXINPUTSPECIALIZATION_HPP
#include "Recompiler.hpp"
#include <algorithm>
#include <array>
#include <limits>
#include <map>
#include <span>
#include <stdexcept>
#include <utility>
#include <vector>
#include <spirv/unified1/spirv.hpp>
namespace ShaderRecompiler {
inline SharedSpirv SpecializeVertexInputTypes(const CompiledShaderArtifact& artifact, std::span<const std::uint32_t> 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<std::uint32_t, std::uint32_t> locations;
std::map<std::vector<std::uint32_t>, std::uint32_t> types;
std::vector<std::vector<std::uint32_t>> declarations;
std::vector<std::vector<std::uint32_t>> variables;
std::map<std::uint32_t, std::uint32_t> pointers;
std::map<std::uint32_t, std::uint32_t> loads;
std::map<std::uint32_t, std::array<std::uint32_t, 3>> variableTypes;
std::map<std::uint32_t, std::uint32_t> 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<std::uint32_t> key) {
const auto found = types.find(key);
if (found != types.end()) return found->second;
if (bound == std::numeric_limits<std::uint32_t>::max()) throw std::runtime_error("vertex specialization module ID overflow");
const auto id = bound++;
std::vector<std::uint32_t> words{(static_cast<std::uint32_t>(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<spv::Op>(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<std::uint32_t> key{static_cast<std::uint32_t>(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<std::uint32_t, 3> 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<spv::Op>(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<std::uint32_t> 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<std::uint32_t, 3>& values) {
if (word > std::numeric_limits<std::uint32_t>::max()) throw std::runtime_error("vertex specialization offset overflow");
artifact.vertexInputPatches.push_back({location, static_cast<std::uint32_t>(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<spv::Op>(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