146 lines
8.9 KiB
C++
146 lines
8.9 KiB
C++
#ifndef CORE_SHADER_RECOMPILER_FRAGMENTOUTPUTSPECIALIZATION_HPP
|
|
#define CORE_SHADER_RECOMPILER_FRAGMENTOUTPUTSPECIALIZATION_HPP
|
|
|
|
#include "PipelineSpecialization.hpp"
|
|
#include "Recompiler.hpp"
|
|
#include <limits>
|
|
#include <map>
|
|
#include <set>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <spirv/unified1/spirv.hpp>
|
|
|
|
namespace ShaderRecompiler {
|
|
|
|
inline std::vector<std::uint32_t> SpecializeFragmentOutputs(std::vector<std::uint32_t> words, std::span<const PipelineSpecializationConstant> constants, const std::set<std::uint32_t>& specializedIds) {
|
|
std::set<std::uint32_t> 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<std::uint32_t>(ColorExportPacking::Unorm10_11_11)) throw std::runtime_error("invalid prepared fragment export packing");
|
|
if (constant.value != static_cast<std::uint32_t>(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<std::uint32_t, std::uint32_t> locations;
|
|
std::set<std::uint32_t> indexed;
|
|
std::map<std::uint32_t, std::vector<std::uint32_t>> typeKeys;
|
|
std::map<std::vector<std::uint32_t>, std::uint32_t> types;
|
|
std::map<std::uint32_t, std::uint32_t> outputs;
|
|
for (std::size_t cursor = 5; cursor < words.size();) {
|
|
const auto count = words[cursor] >> 16u;
|
|
const auto op = static_cast<spv::Op>(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<std::uint32_t> key{static_cast<std::uint32_t>(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<std::uint32_t>{};
|
|
};
|
|
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<std::uint32_t>{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<std::uint32_t> 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<std::vector<std::uint32_t>> declarations;
|
|
const auto declare = [&](std::vector<std::uint32_t> declaration) {
|
|
const auto found = types.find(declaration);
|
|
if (found != types.end()) return found->second;
|
|
if (bound == std::numeric_limits<std::uint32_t>::max()) throw std::runtime_error("fragment output specialization module ID overflow");
|
|
const auto id = bound++;
|
|
std::vector<std::uint32_t> instruction{(static_cast<std::uint32_t>(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<std::uint32_t> result(words.begin(), words.begin() + 5);
|
|
std::vector<std::vector<std::uint32_t>> variables;
|
|
bool inserted = false;
|
|
for (std::size_t cursor = 5; cursor < words.size();) {
|
|
const auto count = words[cursor] >> 16u;
|
|
const auto op = static_cast<spv::Op>(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<std::uint32_t> 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<std::uint32_t>::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
|