Add pipeline (de)serialisation support to bundle

See comments in code for details on the on-disk format.
This commit is contained in:
Billy Laws 2022-12-10 15:32:23 +00:00
parent 937eff392f
commit 755f7c75af
2 changed files with 164 additions and 10 deletions

View File

@ -30,7 +30,7 @@ namespace skyline::gpu::interconnect {
}
void PipelineStateBundle::AddTextureType(u32 index, Shader::TextureType type) {
textureTypes.emplace_back(index, type);
textureTypes.push_back({index, type});
}
void PipelineStateBundle::AddConstantBufferValue(u32 shaderStage, u32 index, u32 offset, u32 value) {
@ -46,19 +46,156 @@ namespace skyline::gpu::interconnect {
return {stageInfo.binary, stageInfo.binaryBaseOffset};
}
Shader::TextureType PipelineStateBundle::LookupTextureType(u32 offset) {
auto it{ranges::find_if(textureTypes, [offset](auto &pair) { return pair.first == offset; })};
Shader::TextureType PipelineStateBundle::LookupTextureType(u32 index) {
auto it{ranges::find_if(textureTypes, [index](const auto &entry) { return entry.index == index; })};
if (it == textureTypes.end())
throw exception("Failed to find texture type for offset: 0x{:X}", offset);
throw exception("Failed to find texture type for index: 0x{:X}", index);
return it->second;
return it->type;
}
u32 PipelineStateBundle::LookupConstantBufferValue(u32 shaderStage, u32 index, u32 offset) {
auto it{ranges::find_if(constantBufferValues, [index, offset, shaderStage](auto &val) { return val.index == index && val.offset == offset && val.shaderStage == shaderStage; })};
auto it{ranges::find_if(constantBufferValues, [index, offset, shaderStage](const auto &val) { return val.index == index && val.offset == offset && val.shaderStage == shaderStage; })};
if (it == constantBufferValues.end())
throw exception("Failed to find constant buffer value for offset: 0x{:X}", offset);
return it->value;
}
static constexpr u32 MaxSerialisedBundleSize{1 << 20}; ///< The maximum size of a serialised bundle (1 MiB)
/* Bundle header format pseudocode:
u64 hash
u32 bundleSize
u32 keySize;
u32 constantBufferValueCount
u32 textureTypeCount
u32 pipelineStageCount
u8 key[keySize];
struct ConstantBufferValue {
u32 shaderStage;
u32 index;
u32 offset;
u32 value;
} constantBufferValues[constantBufferValueCount];
struct TextureType {
u32 index;
u32 (Shader::TextureType) type;
} textureType[textureTypeCount];
struct PipelineStage {
u32 binaryBaseOffset
u32 binarySize
u8 binary[binarySize]
} pipelineStages[pipelineStageCount];
*/
struct BundleDataHeader {
u32 keySize;
u32 constantBufferValueCount;
u32 textureTypeCount;
u32 pipelineStageCount;
};
struct PipelineBinaryDataHeader {
u32 binaryBaseOffset;
u32 binarySize;
};
bool PipelineStateBundle::Deserialise(std::ifstream &stream) {
if (stream.peek() == EOF)
return false;
u64 hash{};
stream.read(reinterpret_cast<char *>(&hash), sizeof(hash));
u32 bundleSize{};
stream.read(reinterpret_cast<char *>(&bundleSize), sizeof(bundleSize));
if (bundleSize > MaxSerialisedBundleSize)
throw exception("Pipeline state bundle is too large: 0x{:X}", bundleSize);
fileBuffer.resize(static_cast<size_t>(bundleSize));
stream.read(reinterpret_cast<char *>(fileBuffer.data()), static_cast<std::streamsize>(bundleSize));
if (XXH64(fileBuffer.data(), bundleSize, 0) != hash)
throw exception("Pipeline state bundle hash mismatch");
auto data{span(fileBuffer)};
const auto &header{data.as<BundleDataHeader>()};
size_t offset{sizeof(BundleDataHeader)};
Reset(data.subspan(offset, header.keySize));
offset += header.keySize;
auto readConstantBufferValues{data.subspan(offset, header.constantBufferValueCount * sizeof(ConstantBufferValue)).cast<ConstantBufferValue>()};
textureTypes.reserve(header.textureTypeCount);
constantBufferValues.insert(constantBufferValues.end(), readConstantBufferValues.begin(), readConstantBufferValues.end());
offset += header.constantBufferValueCount * sizeof(ConstantBufferValue);
auto readTextureTypes{data.subspan(offset, header.textureTypeCount * sizeof(TextureTypeEntry)).cast<TextureTypeEntry>()};
textureTypes.reserve(header.textureTypeCount);
textureTypes.insert(textureTypes.end(), readTextureTypes.begin(), readTextureTypes.end());
offset += header.textureTypeCount * sizeof(TextureTypeEntry);
pipelineStages.resize(header.pipelineStageCount);
for (u32 i{}; i < header.pipelineStageCount; i++) {
const auto &pipelineHeader{data.subspan(offset).as<PipelineBinaryDataHeader>()};
offset += sizeof(PipelineBinaryDataHeader);
pipelineStages[i].binaryBaseOffset = pipelineHeader.binaryBaseOffset;
pipelineStages[i].binary.resize(pipelineHeader.binarySize);
span(pipelineStages[i].binary).copy_from(data.subspan(offset, pipelineHeader.binarySize));
offset += pipelineHeader.binarySize;
}
return true;
}
void PipelineStateBundle::Serialise(std::ofstream &stream) {
u32 bundleSize{static_cast<u32>(sizeof(BundleDataHeader) +
key.size() +
constantBufferValues.size() * sizeof(ConstantBufferValue) +
textureTypes.size() * sizeof(TextureTypeEntry) +
std::accumulate(pipelineStages.begin(), pipelineStages.end(), 0UL, [](size_t acc, const auto &stage) {
return acc + sizeof(PipelineBinaryDataHeader) + stage.binary.size();
}))};
fileBuffer.resize(bundleSize);
auto data{span(fileBuffer)};
auto &header{data.as<BundleDataHeader>()};
size_t offset{sizeof(BundleDataHeader)};
header.keySize = static_cast<u32>(key.size());
header.constantBufferValueCount = static_cast<u32>(constantBufferValues.size());
header.textureTypeCount = static_cast<u32>(textureTypes.size());
header.pipelineStageCount = static_cast<u32>(pipelineStages.size());
data.subspan(offset, header.keySize).copy_from(key);
offset += header.keySize;
data.subspan(offset, header.constantBufferValueCount * sizeof(ConstantBufferValue)).copy_from(constantBufferValues);
offset += header.constantBufferValueCount * sizeof(ConstantBufferValue);
data.subspan(offset, header.textureTypeCount * sizeof(TextureTypeEntry)).copy_from(textureTypes);
offset += header.textureTypeCount * sizeof(TextureTypeEntry);
for (const auto &stage : pipelineStages) {
auto &pipelineHeader{data.subspan(offset).as<PipelineBinaryDataHeader>()};
offset += sizeof(PipelineBinaryDataHeader);
pipelineHeader.binaryBaseOffset = stage.binaryBaseOffset;
pipelineHeader.binarySize = static_cast<u32>(stage.binary.size());
data.subspan(offset, pipelineHeader.binarySize).copy_from(stage.binary);
offset += pipelineHeader.binarySize;
}
u64 hash{XXH64(fileBuffer.data(), bundleSize, 0)};
stream.write(reinterpret_cast<const char *>(&hash), sizeof(hash));
stream.write(reinterpret_cast<const char *>(&bundleSize), sizeof(bundleSize));
stream.write(reinterpret_cast<const char *>(fileBuffer.data()), static_cast<std::streamsize>(bundleSize));
}
}

View File

@ -13,6 +13,7 @@ namespace skyline::gpu::interconnect {
class PipelineStateBundle {
private:
std::vector<u8> key; //!< Byte array containing the pipeline key, this is interpreted by the the user and two different keys might refer to the same pipeline
std::vector<u8> fileBuffer;
/**
* @brief Holds the raw binary and associated info for a pipeline stage
@ -26,6 +27,7 @@ namespace skyline::gpu::interconnect {
/**
* @brief Holds a value of a constant buffer read from memory at pipeline creation time
* @note This struct *MUST* not be modified without a pipeline cache version bump
*/
struct ConstantBufferValue {
u32 shaderStage;
@ -33,9 +35,20 @@ namespace skyline::gpu::interconnect {
u32 offset;
u32 value;
};
static_assert(sizeof(ConstantBufferValue) == 0x10);
/**
* @brief Holds a the texture type of a TIC entry read at pipeline creation time
* @note This struct *MUST* not be modified without a pipeline cache version bump
*/
struct TextureTypeEntry {
u32 index;
Shader::TextureType type;
};
static_assert(sizeof(TextureTypeEntry) == 0x8);
boost::container::small_vector<ConstantBufferValue, 4> constantBufferValues;
boost::container::small_vector<std::pair<u32, Shader::TextureType>, 4> textureTypes;
boost::container::small_vector<TextureTypeEntry, 4> textureTypes;
std::vector<PipelineStage> pipelineStages{};
@ -47,7 +60,7 @@ namespace skyline::gpu::interconnect {
*/
void Reset(span<const u8> newKey);
template<typename T> requires std::is_trivially_copyable_v<T>
template<typename T> requires std::is_trivially_copyable_v<T> && (!requires (T t){ t.size(); })
void Reset(const T &value) {
Reset(span<const u8>(reinterpret_cast<const u8 *>(&value), sizeof(T)));
}
@ -74,7 +87,7 @@ namespace skyline::gpu::interconnect {
template<typename T> requires std::is_trivially_copyable_v<T>
const T &GetKey() {
return *reinterpret_cast<const T *>(key.data());
return GetKey().as<T>();
}
/**
@ -85,11 +98,15 @@ namespace skyline::gpu::interconnect {
/**
* @brief Returns the texture type for a given offset
*/
Shader::TextureType LookupTextureType(u32 offset);
Shader::TextureType LookupTextureType(u32 index);
/**
* @brief Returns the constant buffer value for a given offset and shader stage
*/
u32 LookupConstantBufferValue(u32 shaderStage, u32 index, u32 offset);
bool Deserialise(std::ifstream &stream);
void Serialise(std::ofstream &stream);
};
}