#ifndef CORE_SHADER_RECOMPILER_FRAGMENTOUTPUTSPECIALIZATION_HPP #define CORE_SHADER_RECOMPILER_FRAGMENTOUTPUTSPECIALIZATION_HPP #include "PipelineSpecialization.hpp" #include "Recompiler.hpp" #include #include #include #include #include #include #include #include namespace ShaderRecompiler { inline std::vector SpecializeFragmentOutputs(std::vector words, std::span constants, const std::set& specializedIds) { std::set packed; bool dualSource = false; for (const auto& constant : constants) { if (constant.id == PipelineSpecialization::DualSourceBlend) { if (constant.value > 1u) throw std::runtime_error("invalid prepared dual-source blend flag"); dualSource = constant.value != 0u; } if (constant.id < PipelineSpecialization::ExportPackingBase || constant.id >= PipelineSpecialization::ExportPackingBase + 8u) continue; if (constant.value > static_cast(ColorExportPacking::Unorm10_11_11)) throw std::runtime_error("invalid prepared fragment export packing"); if (constant.value != static_cast(ColorExportPacking::None)) packed.insert(constant.id - PipelineSpecialization::ExportPackingBase); } if (packed.empty() && !dualSource) return words; if (words.size() < 5u || words[0] != spv::MagicNumber) throw std::runtime_error("invalid fragment output specialization module"); std::map locations; std::set indexed; std::map> typeKeys; std::map, std::uint32_t> types; std::map outputs; for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> 16u; const auto op = static_cast(words[cursor] & 0xffffu); if (count == 0u || count > words.size() - cursor) throw std::runtime_error("truncated fragment output specialization instruction"); if (op == spv::OpDecorate && count == 4u && words[cursor + 2u] == spv::DecorationLocation) locations.emplace(words[cursor + 1u], words[cursor + 3u]); if (op == spv::OpDecorate && count == 4u && words[cursor + 2u] == spv::DecorationIndex) indexed.insert(words[cursor + 1u]); if ((op == spv::OpTypeInt || op == spv::OpTypeFloat || op == spv::OpTypeVector || op == spv::OpTypePointer) && count >= 3u) { std::vector key{static_cast(op)}; key.insert(key.end(), words.begin() + cursor + 2u, words.begin() + cursor + count); typeKeys.emplace(words[cursor + 1u], key); types.emplace(std::move(key), words[cursor + 1u]); } if (op == spv::OpVariable && count >= 4u && words[cursor + 3u] == spv::StorageClassOutput) outputs.emplace(words[cursor + 2u], words[cursor + 1u]); cursor += count; } const auto key = [&](std::uint32_t id) { const auto found = typeKeys.find(id); return found != typeKeys.end() ? found->second : std::vector{}; }; const auto floatVectorPointer = [&](std::uint32_t pointer) { const auto outer = key(pointer); if (outer.size() != 3u || outer[0] != spv::OpTypePointer || outer[1] != spv::StorageClassOutput) return false; const auto vector = key(outer[2]); return vector.size() == 3u && vector[0] == spv::OpTypeVector && vector[2] == 4u && key(vector[1]) == std::vector{spv::OpTypeFloat, 32u}; }; const auto output = [&](std::uint32_t location) { std::uint32_t variable = 0; for (const auto& [id, pointer] : outputs) { const auto found = locations.find(id); if (found == locations.end() || found->second != location || indexed.contains(id)) continue; if (variable != 0u) throw std::runtime_error("fragment outputs share location " + std::to_string(location)); variable = id; } return variable; }; std::set retyped; for (const auto location : packed) { const auto variable = output(location); if (variable == 0u) continue; if (!specializedIds.contains(PipelineSpecialization::ExportPackingBase + location)) throw std::runtime_error("fragment output " + std::to_string(location) + " has no 10_11_11 unorm packing path"); if (!floatVectorPointer(outputs.at(variable))) throw std::runtime_error("packed fragment output " + std::to_string(location) + " is not a float4"); retyped.insert(variable); } const auto secondSource = dualSource ? output(1u) : 0u; if (retyped.contains(secondSource)) throw std::runtime_error("the second dual-source blend color cannot be packed"); if (retyped.empty() && secondSource == 0u) return words; auto bound = words[3]; std::vector> declarations; const auto declare = [&](std::vector declaration) { const auto found = types.find(declaration); if (found != types.end()) return found->second; if (bound == std::numeric_limits::max()) throw std::runtime_error("fragment output specialization module ID overflow"); const auto id = bound++; std::vector instruction{(static_cast(declaration.size() + 1u) << 16u) | declaration.front(), id}; instruction.insert(instruction.end(), declaration.begin() + 1, declaration.end()); types.emplace(std::move(declaration), id); declarations.push_back(std::move(instruction)); return id; }; const auto wordType = retyped.empty() ? 0u : declare({spv::OpTypeInt, 32u, 0u}); const auto vectorType = retyped.empty() ? 0u : declare({spv::OpTypeVector, wordType, 4u}); const auto pointerType = retyped.empty() ? 0u : declare({spv::OpTypePointer, spv::StorageClassOutput, vectorType}); std::vector result(words.begin(), words.begin() + 5); std::vector> variables; bool inserted = false; for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> 16u; const auto op = static_cast(words[cursor] & 0xffffu); const auto operand = [&](std::size_t index) { return index < count ? words[cursor + index] : 0u; }; if (op == spv::OpVariable && retyped.contains(operand(2u))) { if (count != 4u) throw std::runtime_error("a packed fragment output has an initializer"); std::vector variable(words.begin() + cursor, words.begin() + cursor + count); variable[1] = pointerType; variables.push_back(std::move(variable)); cursor += count; continue; } if (op == spv::OpDecorate && count == 4u && secondSource != 0u && operand(1u) == secondSource && operand(2u) == spv::DecorationLocation) { result.insert(result.end(), {(4u << 16u) | spv::OpDecorate, secondSource, spv::DecorationLocation, 0u, (4u << 16u) | spv::OpDecorate, secondSource, spv::DecorationIndex, 1u}); cursor += count; continue; } if (op == spv::OpFunction && !inserted) { for (const auto& declaration : declarations) result.insert(result.end(), declaration.begin(), declaration.end()); for (const auto& variable : variables) result.insert(result.end(), variable.begin(), variable.end()); inserted = true; } if (op == spv::OpStore && count >= 3u && retyped.contains(operand(1u))) { if (bound == std::numeric_limits::max()) throw std::runtime_error("fragment output specialization module ID overflow"); const auto bits = bound++; result.insert(result.end(), {(4u << 16u) | spv::OpBitcast, vectorType, bits, operand(2u)}); result.insert(result.end(), words.begin() + cursor, words.begin() + cursor + count); result[result.size() - count + 2u] = bits; cursor += count; continue; } const bool pointerUse = ((op == spv::OpLoad || op == spv::OpAccessChain || op == spv::OpInBoundsAccessChain || op == spv::OpPtrAccessChain || op == spv::OpInBoundsPtrAccessChain || op == spv::OpCopyObject) && retyped.contains(operand(3u))) || ((op == spv::OpCopyMemory || op == spv::OpCopyMemorySized) && (retyped.contains(operand(1u)) || retyped.contains(operand(2u)))); if (pointerUse) throw std::runtime_error("a packed fragment output is accessed other than by whole stores"); result.insert(result.end(), words.begin() + cursor, words.begin() + cursor + count); cursor += count; } if (!inserted) throw std::runtime_error("fragment output specialization module has no function"); result[3] = bound; return result; } } #endif