#include "ControlFlow/RequestSerializer.hpp" #include namespace ShaderRecompiler { namespace { constexpr char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string base64Encode(std::string_view data) { std::string result; result.reserve(((data.size() + 2u) / 3u) * 4u); std::uint64_t i = 0u; const std::uint64_t size = data.size(); while (i + 3u <= size) { const std::uint8_t b0 = static_cast(data[i]); const std::uint8_t b1 = static_cast(data[i + 1u]); const std::uint8_t b2 = static_cast(data[i + 2u]); const std::uint32_t chunk = (static_cast(b0) << 16u) | (static_cast(b1) << 8u) | static_cast(b2); result.push_back(kBase64Alphabet[(chunk >> 18u) & 0x3Fu]); result.push_back(kBase64Alphabet[(chunk >> 12u) & 0x3Fu]); result.push_back(kBase64Alphabet[(chunk >> 6u) & 0x3Fu]); result.push_back(kBase64Alphabet[chunk & 0x3Fu]); i += 3u; } const std::uint64_t remaining = size - i; if (remaining == 1u) { const std::uint8_t b0 = static_cast(data[i]); const std::uint32_t chunk = static_cast(b0) << 16u; result.push_back(kBase64Alphabet[(chunk >> 18u) & 0x3Fu]); result.push_back(kBase64Alphabet[(chunk >> 12u) & 0x3Fu]); result.push_back('='); result.push_back('='); } else if (remaining == 2u) { const std::uint8_t b0 = static_cast(data[i]); const std::uint8_t b1 = static_cast(data[i + 1u]); const std::uint32_t chunk = (static_cast(b0) << 16u) | (static_cast(b1) << 8u); result.push_back(kBase64Alphabet[(chunk >> 18u) & 0x3Fu]); result.push_back(kBase64Alphabet[(chunk >> 12u) & 0x3Fu]); result.push_back(kBase64Alphabet[(chunk >> 6u) & 0x3Fu]); result.push_back('='); } return result; } std::uint8_t base64DecodeChar(char value) { if (value >= 'A' && value <= 'Z') { return static_cast(value - 'A'); } if (value >= 'a' && value <= 'z') { return static_cast(value - 'a' + 26); } if (value >= '0' && value <= '9') { return static_cast(value - '0' + 52); } if (value == '+') { return 62u; } if (value == '/') { return 63u; } throw std::runtime_error("RequestSerializer: invalid base64 character"); } std::string base64Decode(std::string_view text) { if (text.size() % 4u != 0u) { throw std::runtime_error("RequestSerializer: invalid base64 length"); } std::string result; result.reserve((text.size() / 4u) * 3u); for (std::uint64_t i = 0u; i < text.size(); i += 4u) { const char c0 = text[i]; const char c1 = text[i + 1u]; const char c2 = text[i + 2u]; const char c3 = text[i + 3u]; if (c0 == '=' || c1 == '=') { throw std::runtime_error("RequestSerializer: invalid base64 padding"); } if (c2 == '=' && c3 != '=') { throw std::runtime_error("RequestSerializer: invalid base64 padding"); } if ((c2 == '=' || c3 == '=') && i + 4u != text.size()) { throw std::runtime_error("RequestSerializer: invalid base64 padding position"); } const std::uint8_t v0 = base64DecodeChar(c0); const std::uint8_t v1 = base64DecodeChar(c1); const std::uint8_t v2 = c2 == '=' ? 0u : base64DecodeChar(c2); const std::uint8_t v3 = c3 == '=' ? 0u : base64DecodeChar(c3); const std::uint32_t chunk = (static_cast(v0) << 18u) | (static_cast(v1) << 12u) | (static_cast(v2) << 6u) | static_cast(v3); result.push_back(static_cast((chunk >> 16u) & 0xFFu)); if (c2 != '=') { result.push_back(static_cast((chunk >> 8u) & 0xFFu)); } if (c3 != '=') { result.push_back(static_cast(chunk & 0xFFu)); } } return result; } class Writer { public: explicit Writer(std::string& buffer) : m_buffer(buffer) {} void WriteU8(std::uint8_t value) { m_buffer.push_back(static_cast(value)); } void WriteBool(bool value) { WriteU8(value ? 1u : 0u); } void WriteU32(std::uint32_t value) { for (std::uint32_t i = 0; i < 4u; i++) { WriteU8(static_cast(value >> (i * 8u))); } } void WriteI32(std::int32_t value) { WriteU32(static_cast(value)); } void WriteU64(std::uint64_t value) { for (std::uint32_t i = 0; i < 8u; i++) { WriteU8(static_cast(value >> (i * 8u))); } } void WriteBytes(std::span bytes) { WriteU64(bytes.size()); for (const std::byte b : bytes) { WriteU8(static_cast(b)); } } void WriteU32Span(std::span values) { WriteU64(values.size()); for (const std::uint32_t value : values) { WriteU32(value); } } void WriteString(std::string_view text) { WriteU64(text.size()); m_buffer.append(text); } private: std::string& m_buffer; }; class Reader { public: explicit Reader(std::string_view data) : m_data(data) {} std::uint8_t ReadU8() { Require(1u); return static_cast(m_data[m_offset++]); } bool ReadBool() { return ReadU8() != 0u; } std::uint32_t ReadU32() { std::uint32_t value = 0u; for (std::uint32_t i = 0; i < 4u; i++) { value |= static_cast(ReadU8()) << (i * 8u); } return value; } std::int32_t ReadI32() { return static_cast(ReadU32()); } std::uint64_t ReadU64() { std::uint64_t value = 0u; for (std::uint32_t i = 0; i < 8u; i++) { value |= static_cast(ReadU8()) << (i * 8u); } return value; } std::vector ReadBytes() { const auto count = ReadU64(); Require(count); std::vector result(count); for (std::uint64_t i = 0; i < count; i++) { result[i] = static_cast(ReadU8()); } return result; } std::vector ReadU32Vector() { const auto count = ReadU64(); Require(count, 4u); std::vector result; result.reserve(count); for (std::uint64_t i = 0; i < count; i++) { result.push_back(ReadU32()); } return result; } std::string ReadString() { const auto count = ReadU64(); Require(count); std::string result(m_data.substr(m_offset, count)); m_offset += count; return result; } void Require(std::uint64_t count, std::uint64_t elementSize = 1u) const { if (count > (m_data.size() - m_offset) / elementSize) { throw std::runtime_error("RequestSerializer: truncated data"); } } private: std::string_view m_data; std::uint64_t m_offset = 0u; }; ShaderStage readShaderStage(Reader& reader) { const auto value = reader.ReadU8(); if (value > static_cast(ShaderStage::Mesh)) { throw std::runtime_error("RequestSerializer: invalid ShaderStage value"); } return static_cast(value); } ProgramRole readProgramRole(Reader& reader) { const auto value = reader.ReadU8(); if (value > static_cast(ProgramRole::Fragment)) { throw std::runtime_error("RequestSerializer: invalid ProgramRole value"); } return static_cast(value); } void writeShaderBinary(Writer& writer, const ShaderBinary& binary) { writer.WriteU8(static_cast(binary.stage)); writer.WriteU64(binary.codeAddress); writer.WriteU32Span(binary.code); writer.WriteU64(binary.headerAddress); writer.WriteBytes(binary.header); } ShaderBinary readShaderBinary(Reader& reader, std::vector& codeStorage, std::vector& headerStorage) { ShaderBinary binary{}; binary.stage = readShaderStage(reader); binary.codeAddress = reader.ReadU64(); codeStorage = reader.ReadU32Vector(); binary.code = codeStorage; binary.headerAddress = reader.ReadU64(); headerStorage = reader.ReadBytes(); binary.header = headerStorage; return binary; } void writeComputeInfo(Writer& writer, const ShaderComputeStageInfo& info) { for (const std::uint32_t value : info.numThreads) { writer.WriteU32(value); } writer.WriteU32(info.ldsSizeDwords); for (const bool value : info.groupIdEnable) { writer.WriteBool(value); } writer.WriteBool(info.tgSizeEnable); writer.WriteU32(info.threadIdComponentCount); } ShaderComputeStageInfo readComputeInfo(Reader& reader) { ShaderComputeStageInfo info{}; for (std::uint32_t& value : info.numThreads) { value = reader.ReadU32(); } info.ldsSizeDwords = reader.ReadU32(); for (auto value : {0u, 1u, 2u}) { info.groupIdEnable[value] = reader.ReadBool(); } info.tgSizeEnable = reader.ReadBool(); info.threadIdComponentCount = reader.ReadU32(); return info; } void writePixelInfo(Writer& writer, const ShaderPixelStageInfo& info) { writer.WriteU32(info.interpolatorCount); for (const std::uint32_t value : info.interpolatorSettings) { writer.WriteU32(value); } writer.WriteBool(info.wave32); writer.WriteU32(info.perspectiveCenterVgpr); writer.WriteBool(info.hasPerspectiveCenterVgpr); writer.WriteBool(info.posX); writer.WriteBool(info.posY); writer.WriteBool(info.posZ); writer.WriteBool(info.posW); writer.WriteBool(info.frontFace); writer.WriteBool(info.ancillary); writer.WriteBool(info.sampleShading); writer.WriteBool(info.noPerspective); writer.WriteBool(info.pixelKillEnable); writer.WriteBool(info.depthExportEnable); writer.WriteBool(info.sampleMaskExportEnable); writer.WriteBool(info.earlyZ); writer.WriteBool(info.executeOnNoop); for (const std::uint8_t value : info.targetOutputMode) { writer.WriteU8(value); } } ShaderPixelStageInfo readPixelInfo(Reader& reader) { ShaderPixelStageInfo info{}; info.interpolatorCount = reader.ReadU32(); for (std::uint32_t& value : info.interpolatorSettings) { value = reader.ReadU32(); } info.wave32 = reader.ReadBool(); info.perspectiveCenterVgpr = reader.ReadU32(); info.hasPerspectiveCenterVgpr = reader.ReadBool(); info.posX = reader.ReadBool(); info.posY = reader.ReadBool(); info.posZ = reader.ReadBool(); info.posW = reader.ReadBool(); info.frontFace = reader.ReadBool(); info.ancillary = reader.ReadBool(); info.sampleShading = reader.ReadBool(); info.noPerspective = reader.ReadBool(); info.pixelKillEnable = reader.ReadBool(); info.depthExportEnable = reader.ReadBool(); info.sampleMaskExportEnable = reader.ReadBool(); info.earlyZ = reader.ReadBool(); info.executeOnNoop = reader.ReadBool(); for (std::uint8_t& value : info.targetOutputMode) { value = reader.ReadU8(); } return info; } void writeVertexInfo(Writer& writer, const ShaderVertexStageInfo& info) { for (const auto& resource : info.resources) { for (const std::uint32_t value : resource.fields) { writer.WriteU32(value); } } for (const auto& destination : info.resourcesDst) { writer.WriteI32(destination.registerStart); writer.WriteI32(destination.registersNum); writer.WriteI32(destination.attrId); writer.WriteU32(destination.fetchIndex); } writer.WriteU32(info.resourcesNum); writer.WriteU32(info.fetchAttribReg); writer.WriteU32(info.fetchBufferReg); writer.WriteBool(info.fetchEmbedded); } ShaderVertexStageInfo readVertexInfo(Reader& reader) { ShaderVertexStageInfo info{}; for (auto& resource : info.resources) { for (std::uint32_t& value : resource.fields) { value = reader.ReadU32(); } } for (auto& destination : info.resourcesDst) { destination.registerStart = reader.ReadI32(); destination.registersNum = reader.ReadI32(); destination.attrId = reader.ReadI32(); destination.fetchIndex = reader.ReadU32(); } info.resourcesNum = reader.ReadU32(); info.fetchAttribReg = reader.ReadU32(); info.fetchBufferReg = reader.ReadU32(); info.fetchEmbedded = reader.ReadBool(); return info; } void writeMeshTargetLimits(Writer& writer, const MeshTargetLimits& limits) { for (const std::uint32_t value : limits.maxWorkgroupSize) { writer.WriteU32(value); } writer.WriteU32(limits.maxWorkgroupInvocations); writer.WriteU32(limits.maxSharedMemoryBytes); writer.WriteU32(limits.maxOutputVertices); writer.WriteU32(limits.maxOutputPrimitives); writer.WriteU32(limits.maxOutputComponents); writer.WriteU32(limits.maxOutputMemoryBytes); writer.WriteU32(limits.outputPerVertexGranularity); writer.WriteU32(limits.outputPerPrimitiveGranularity); } MeshTargetLimits readMeshTargetLimits(Reader& reader) { MeshTargetLimits limits{}; for (std::uint32_t& value : limits.maxWorkgroupSize) { value = reader.ReadU32(); } limits.maxWorkgroupInvocations = reader.ReadU32(); limits.maxSharedMemoryBytes = reader.ReadU32(); limits.maxOutputVertices = reader.ReadU32(); limits.maxOutputPrimitives = reader.ReadU32(); limits.maxOutputComponents = reader.ReadU32(); limits.maxOutputMemoryBytes = reader.ReadU32(); limits.outputPerVertexGranularity = reader.ReadU32(); limits.outputPerPrimitiveGranularity = reader.ReadU32(); return limits; } void writeTessellationTargetLimits(Writer& writer, const TessellationTargetLimits& limits) { writer.WriteU32(limits.maxPatchSize); writer.WriteU32(limits.maxControlPerVertexInputComponents); writer.WriteU32(limits.maxControlPerVertexOutputComponents); writer.WriteU32(limits.maxControlPerPatchOutputComponents); writer.WriteU32(limits.maxControlTotalOutputComponents); writer.WriteU32(limits.maxEvaluationInputComponents); writer.WriteU32(limits.maxEvaluationOutputComponents); } TessellationTargetLimits readTessellationTargetLimits(Reader& reader) { TessellationTargetLimits limits{}; limits.maxPatchSize = reader.ReadU32(); limits.maxControlPerVertexInputComponents = reader.ReadU32(); limits.maxControlPerVertexOutputComponents = reader.ReadU32(); limits.maxControlPerPatchOutputComponents = reader.ReadU32(); limits.maxControlTotalOutputComponents = reader.ReadU32(); limits.maxEvaluationInputComponents = reader.ReadU32(); limits.maxEvaluationOutputComponents = reader.ReadU32(); return limits; } void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configuration) { writer.WriteU32(configuration.inputPrimitive); writer.WriteU32(configuration.primitivesPerGroup); writer.WriteU32(configuration.verticesPerGroup); writer.WriteU32(configuration.maxVertices); writer.WriteU32(configuration.maxPrimitives); writer.WriteU32(configuration.threadsPerGroup); writer.WriteU32(configuration.ldsSizeDwords); writer.WriteU32(configuration.provokingVertex); writer.WriteU32(configuration.esgsItemSize); } MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { MeshConfiguration configuration{}; configuration.inputPrimitive = reader.ReadU32(); configuration.primitivesPerGroup = reader.ReadU32(); configuration.verticesPerGroup = reader.ReadU32(); configuration.maxVertices = reader.ReadU32(); configuration.maxPrimitives = reader.ReadU32(); configuration.threadsPerGroup = reader.ReadU32(); configuration.ldsSizeDwords = reader.ReadU32(); configuration.provokingVertex = reader.ReadU32(); configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u; return configuration; } void writeTessellationConfiguration(Writer& writer, const TessellationConfiguration& configuration) { writer.WriteU32(configuration.inputControlPoints); writer.WriteU32(configuration.outputControlPoints); writer.WriteU32(configuration.domain); writer.WriteU32(configuration.partitioning); writer.WriteU32(configuration.outputTopology); } TessellationConfiguration readTessellationConfiguration(Reader& reader) { TessellationConfiguration configuration{}; configuration.inputControlPoints = reader.ReadU32(); configuration.outputControlPoints = reader.ReadU32(); configuration.domain = reader.ReadU32(); configuration.partitioning = reader.ReadU32(); configuration.outputTopology = reader.ReadU32(); return configuration; } void writeGuestContext(Writer& writer, const GuestContext& context) { writer.WriteU32(context.waveSize); writer.WriteU32(context.userDataBaseRegister); writer.WriteU32Span(context.userData); writer.WriteBool(context.compute.has_value()); if (context.compute.has_value()) { writeComputeInfo(writer, *context.compute); } writer.WriteBool(context.pixel.has_value()); if (context.pixel.has_value()) { writePixelInfo(writer, *context.pixel); } writer.WriteBool(context.vertex.has_value()); if (context.vertex.has_value()) { writeVertexInfo(writer, *context.vertex); } writer.WriteU64(context.memory.size()); for (const auto& region : context.memory) { writer.WriteU64(region.guestAddress); writer.WriteBytes(region.bytes); } } GuestContext readGuestContext(Reader& reader, DeserializedRequest& result) { GuestContext context{}; context.waveSize = reader.ReadU32(); context.userDataBaseRegister = reader.ReadU32(); result.userData = reader.ReadU32Vector(); context.userData = result.userData; if (reader.ReadBool()) { result.compute = readComputeInfo(reader); context.compute = result.compute; } if (reader.ReadBool()) { result.pixel = readPixelInfo(reader); context.pixel = result.pixel; } if (reader.ReadBool()) { result.vertex = readVertexInfo(reader); context.vertex = result.vertex; } const auto regionCount = reader.ReadU64(); reader.Require(regionCount, 16u); result.memoryBytesStorage.reserve(regionCount); result.memory.reserve(regionCount); for (std::uint64_t i = 0; i < regionCount; i++) { MemoryRegion region{}; region.guestAddress = reader.ReadU64(); result.memoryBytesStorage.push_back(reader.ReadBytes()); region.bytes = result.memoryBytesStorage.back(); result.memory.push_back(region); } context.memory = result.memory; return context; } void writeSpirvTarget(Writer& writer, const SpirvTarget& target) { writer.WriteU32(target.vulkanVersion); writer.WriteU32(target.spirvVersion); writer.WriteU32(target.subgroupSize); writer.WriteU32(target.bdaAbiVersion); writer.WriteU32Span(target.supportedCapabilities); writer.WriteU64(target.supportedExtensions.size()); for (const std::string_view extension : target.supportedExtensions) { writer.WriteString(extension); } writer.WriteBool(target.fragmentShaderBarycentricEnabled); for (const std::uint32_t value : target.maxWorkgroupSize) { writer.WriteU32(value); } writer.WriteU32(target.maxWorkgroupInvocations); writer.WriteU32(target.maxWorkgroupSharedMemoryBytes); writer.WriteBool(target.mesh.has_value()); if (target.mesh.has_value()) { writeMeshTargetLimits(writer, *target.mesh); } writer.WriteBool(target.tessellation.has_value()); if (target.tessellation.has_value()) { writeTessellationTargetLimits(writer, *target.tessellation); } } SpirvTarget readSpirvTarget(Reader& reader, DeserializedRequest& result) { SpirvTarget target{}; target.vulkanVersion = reader.ReadU32(); target.spirvVersion = reader.ReadU32(); target.subgroupSize = reader.ReadU32(); target.bdaAbiVersion = reader.ReadU32(); result.supportedCapabilities = reader.ReadU32Vector(); target.supportedCapabilities = result.supportedCapabilities; const auto extensionCount = reader.ReadU64(); reader.Require(extensionCount, 8u); result.supportedExtensionStorage.reserve(extensionCount); result.supportedExtensions.reserve(extensionCount); for (std::uint64_t i = 0; i < extensionCount; i++) { result.supportedExtensionStorage.push_back(reader.ReadString()); result.supportedExtensions.push_back(result.supportedExtensionStorage.back()); } target.supportedExtensions = result.supportedExtensions; target.fragmentShaderBarycentricEnabled = reader.ReadBool(); for (std::uint32_t& value : target.maxWorkgroupSize) { value = reader.ReadU32(); } target.maxWorkgroupInvocations = reader.ReadU32(); target.maxWorkgroupSharedMemoryBytes = reader.ReadU32(); if (reader.ReadBool()) { result.mesh = readMeshTargetLimits(reader); target.mesh = result.mesh; } if (reader.ReadBool()) { result.tessellation = readTessellationTargetLimits(reader); target.tessellation = result.tessellation; } return target; } void writeBindingLayout(Writer& writer, const BindingLayout& layout) { writer.WriteU32(layout.descriptorSet); writer.WriteU32(layout.firstBinding); writer.WriteU32(layout.pushConstantOffsetBytes); writer.WriteU32(layout.pushConstantSizeBytes); } BindingLayout readBindingLayout(Reader& reader) { BindingLayout layout{}; layout.descriptorSet = reader.ReadU32(); layout.firstBinding = reader.ReadU32(); layout.pushConstantOffsetBytes = reader.ReadU32(); layout.pushConstantSizeBytes = reader.ReadU32(); return layout; } void writeGraphicsCompileContext(Writer& writer, const GraphicsCompileContext& graphics) { writer.WriteU32(graphics.firstUserSgpr); writer.WriteU64(graphics.linkedPrograms.size()); for (const auto& program : graphics.linkedPrograms) { writer.WriteU8(static_cast(program.role)); writeShaderBinary(writer, program.binary); writer.WriteU32(program.userDataBaseRegister); writer.WriteU32(program.firstUserSgpr); writer.WriteU32Span(program.userData); } writer.WriteBool(graphics.mesh.has_value()); if (graphics.mesh.has_value()) { writeMeshConfiguration(writer, *graphics.mesh); } writer.WriteBool(graphics.tessellation.has_value()); if (graphics.tessellation.has_value()) { writeTessellationConfiguration(writer, *graphics.tessellation); } writer.WriteU64(graphics.draw.indexAddress); writer.WriteU32(graphics.draw.indexCount); writer.WriteU32(graphics.draw.indexElementBytes); writer.WriteU32(graphics.draw.instanceCount); } GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGraphicsCompileContext& storage, std::uint32_t version) { GraphicsCompileContext graphics{}; graphics.firstUserSgpr = reader.ReadU32(); const auto programCount = reader.ReadU64(); reader.Require(programCount, 50u); storage.linkedProgramStorage.reserve(programCount); storage.linkedPrograms.reserve(programCount); for (std::uint64_t i = 0; i < programCount; i++) { LinkedProgram program{}; program.role = readProgramRole(reader); DeserializedLinkedProgram programStorage{}; program.binary = readShaderBinary(reader, programStorage.code, programStorage.header); program.userDataBaseRegister = reader.ReadU32(); program.firstUserSgpr = reader.ReadU32(); programStorage.userData = reader.ReadU32Vector(); storage.linkedProgramStorage.push_back(std::move(programStorage)); const auto& storedProgram = storage.linkedProgramStorage.back(); program.binary.code = storedProgram.code; program.binary.header = storedProgram.header; program.userData = storedProgram.userData; storage.linkedPrograms.push_back(program); } graphics.linkedPrograms = storage.linkedPrograms; if (reader.ReadBool()) { storage.mesh = readMeshConfiguration(reader, version); graphics.mesh = storage.mesh; } if (reader.ReadBool()) { storage.tessellation = readTessellationConfiguration(reader); graphics.tessellation = storage.tessellation; } storage.draw.indexAddress = reader.ReadU64(); storage.draw.indexCount = reader.ReadU32(); storage.draw.indexElementBytes = reader.ReadU32(); storage.draw.instanceCount = reader.ReadU32(); graphics.draw = storage.draw; return graphics; } } std::string RequestSerializer::Serialize(const RecompileRequest& request) const { std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); writer.WriteU32(4u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); writeBindingLayout(writer, request.layout); writer.WriteBool(request.graphics.has_value()); if (request.graphics.has_value()) { writeGraphicsCompileContext(writer, *request.graphics); } writer.WriteBool(request.useCache); if (request.context.compute.has_value()) { for (const std::uint32_t value : request.context.compute->partialThreads) { writer.WriteU32(value); } } return base64Encode(buffer); } DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const { const std::string decoded = base64Decode(text); Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); if (version < 1u || version > 4u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result); result.request.target = readSpirvTarget(reader, result); result.request.layout = readBindingLayout(reader); if (reader.ReadBool()) { result.graphicsStorage = std::make_unique(); result.request.graphics = readGraphicsCompileContext(reader, *result.graphicsStorage, version); } if (version >= 2u) result.request.useCache = reader.ReadBool(); if (version >= 3u && result.request.context.compute.has_value()) { for (std::uint32_t& value : result.request.context.compute->partialThreads) { value = reader.ReadU32(); } } return result; } }