#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