refactor(shader): remove resource specialization from artifact key

This commit is contained in:
boykopovar committed 2026-10-08 17:26:38 +03:00
1 parent ab0f9985fa
commit b2a4813307
22 files changed
+177 -333

No files matched your search

@@ -1,4 +1,5 @@
#include "ShaderDiskCache.hpp"
#include "CacheKey.hpp"
#include "ShaderCacheDirectory.hpp"
#include <algorithm>
#include <array>
@@ -217,7 +218,6 @@ struct SampleRequest {
std::array<std::string_view, 1> extensions{"SPV_KHR_storage_buffer_storage_class"};
std::array<std::byte, 16> header{};
RecompileRequest request{};
ResourceSpecialization specialization;
std::uint32_t hostSubgroupSize = 32;
SampleRequest() {
@@ -236,8 +236,6 @@ struct SampleRequest {
request.target.maxWorkgroupInvocations = 1024;
request.target.maxWorkgroupSharedMemoryBytes = 49152;
request.layout = {0, 0, 0, 128};
specialization.images = {ResourceSpecialization::Image{}};
specialization.boundDescriptors = {0, 0};
}
std::vector<std::byte> Key() {
@@ -245,7 +243,7 @@ struct SampleRequest {
request.context.userData = userData;
request.target.supportedCapabilities = capabilities;
std::vector<std::byte> key;
ShaderDiskCache::BuildKey(request, hostSubgroupSize, specialization, key);
ShaderDiskCache::BuildKey(request, hostSubgroupSize, key);
return key;
}
};
@@ -377,19 +375,6 @@ void verifyKeySensitivity() {
changes("the first binding", [](SampleRequest& sample) { sample.request.layout.firstBinding = 1; });
changes("the push constant offset", [](SampleRequest& sample) { sample.request.layout.pushConstantOffsetBytes = 16; });
changes("the push constant size", [](SampleRequest& sample) { sample.request.layout.pushConstantSizeBytes = 64; });
changes("an image class", [](SampleRequest& sample) { sample.specialization.images[0].numericClass = IrTextureNumericClass::Float; });
changes("an image dimension", [](SampleRequest& sample) { sample.specialization.images[0].dimension = static_cast<RdnaImageDimension>(1); });
changes("an image mip count", [](SampleRequest& sample) { sample.specialization.images[0].mipCount = 2; });
changes("an image conversion", [](SampleRequest& sample) { sample.specialization.images[0].conversionFormat = static_cast<IrBufferFormat>(1); });
changes("an image swizzle", [](SampleRequest& sample) { sample.specialization.images[0].shaderSwizzle = 0; });
changes("an image indirect root", [](SampleRequest& sample) { sample.specialization.images[0].indirectRoot = 0; });
changes("an image mapping offset", [](SampleRequest& sample) { sample.specialization.images[0].indirectMappingOffset = 4; });
changes("an image search depth", [](SampleRequest& sample) { sample.specialization.images[0].indirectSearchIterations = 2; });
changes("an image cube flag", [](SampleRequest& sample) { sample.specialization.images[0].cube = true; });
changes("an image FMASK flag", [](SampleRequest& sample) { sample.specialization.images[0].fmask = true; });
changes("an image sRGB decode", [](SampleRequest& sample) { sample.specialization.images[0].srgbDecode = true; });
changes("the image count", [](SampleRequest& sample) { sample.specialization.images.emplace_back(); });
changes("the bound descriptors", [](SampleRequest& sample) { sample.specialization.boundDescriptors.push_back(1); });
SampleRequest moved;
moved.userData[0] ^= 0x10000u;
@@ -397,6 +382,33 @@ void verifyKeySensitivity() {
moved.request.shader.headerAddress += 0x1000000u;
moved.header[3] = std::byte{1};
require(moved.Key() == key, "the key depends on the user data values or the addresses");
std::array<std::byte, 32> descriptors{};
const std::array regions{MemoryRegion{0x100000u, descriptors}};
moved.request.context.memory = regions;
std::fill(moved.userData.begin(), moved.userData.end(), 0xffffffffu);
require(moved.Key() == key, "runtime descriptors changed the artifact key");
SampleRequest vertex;
vertex.request.shader.stage = ShaderStage::Vertex;
vertex.request.context.compute.reset();
vertex.request.context.vertex.emplace();
auto& input = *vertex.request.context.vertex;
input.resourcesNum = 1u;
input.resources[0].fields = {0x10000u, 16u << 16u, 64u, (22u << 12u) | 0xfacu};
input.resourcesDst[0] = {0u, 4u, 0u, 0u};
const auto vertexKey = vertex.Key();
const auto contextKey = RecompileCacheKey::ContextHash(vertex.request);
input.resources[0].fields = {0x20000u, 32u << 16u, 128u, (77u << 12u) | 0xfa9u};
require(vertex.Key() == vertexKey && RecompileCacheKey::ContextHash(vertex.request) == contextKey, "runtime vertex format, stride, swizzle or address changed the static interface key");
input.resources[0].fields[3] = (20u << 12u) | 0xfacu;
require(vertex.Key() != vertexKey && RecompileCacheKey::ContextHash(vertex.request) != contextKey, "integer vertex input reused a float interface");
input.resources[0].fields[3] = (77u << 12u) | 0xfa9u;
input.resourcesDst[0].registersNum = 2u;
require(vertex.Key() != vertexKey, "vertex component interface did not change the artifact key");
input.fetchEmbedded = true;
const auto embeddedKey = vertex.Key();
input.resources[0].fields = {};
input.resourcesNum = 0u;
require(vertex.Key() == embeddedKey, "runtime vertex fetch descriptors changed the artifact key");
}
void verifyStore() {
@@ -482,7 +494,8 @@ void verifyAcrossProcesses(const char* self) {
struct ClockRequest {
std::vector<std::uint32_t> code{0xf4900100u, 0x00000000u, 0xbf8cc07fu, 0x7e020204u, 0xe0700000u, 0x80000100u, 0xf800180fu, 0x01010101u, 0xbf810000u};
std::array<std::uint32_t, 4> userData{0x10000000u, 0x00000000u, 0x40u, 0x00027facu};
std::array<std::uint32_t, 1> capabilities{1u};
std::array<std::uint32_t, 4> capabilities{1u, 11u, 5347u, 4448u};
std::array<std::string_view, 2> extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"};
RecompileRequest request{};
explicit ClockRequest(std::uint32_t stride = 0) {
@@ -501,13 +514,15 @@ struct ClockRequest {
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 32;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.bdaAbiVersion = 1u;
request.layout.pushConstantSizeBytes = 128;
}
};
std::string emissionFailure(const RecompileRequest& request) {
try {
static_cast<void>(Recompile(request));
static_cast<void>(PrepareShader(request));
} catch (const std::runtime_error& error) {
const std::string message = error.what();
require(message.find("reads the shader clock, which needs the device's VK_KHR_shader_clock") != std::string::npos, "unexpected failure: " + message.substr(0, message.find("RecompileRequest:")));
@@ -534,12 +549,12 @@ void verifyEmissionFailureMemo() {
require(diskMisses() == before.misses + 2, "another binding layout reused the failure");
ClockRequest stride(16);
static_cast<void>(emissionFailure(stride.request));
require(diskMisses() == before.misses + 3, "another buffer specialization reused the failure");
require(diskMisses() == before.misses + 2, "a runtime buffer stride change repeated static preparation");
ClockRequest relocated;
relocated.request.shader.codeAddress += 0x1000u;
static_cast<void>(emissionFailure(relocated.request));
require(diskMisses() == before.misses + 4, "a relocated copy reused the failure");
require(emissionFailure(clock.request) == first && diskMisses() == before.misses + 4, "the other requests replaced the failure");
require(diskMisses() == before.misses + 3, "a relocated copy reused the failure");
require(emissionFailure(clock.request) == first && diskMisses() == before.misses + 3, "the other requests replaced the failure");
ClockRequest concurrent;
concurrent.request.layout.pushConstantSizeBytes = 64;
@@ -548,7 +563,7 @@ void verifyEmissionFailureMemo() {
std::vector<std::string> messages;
for (auto& future : failures) messages.push_back(future.get());
require(std::all_of(messages.begin(), messages.end(), [&](const std::string& message) { return message == messages.front(); }), "concurrent requests failed with different messages");
require(diskMisses() == before.misses + 5, "concurrent requests emitted the program " + std::to_string(diskMisses() - before.misses - 4) + " times");
require(diskMisses() == before.misses + 4, "concurrent requests emitted the program " + std::to_string(diskMisses() - before.misses - 3) + " times");
ShaderDiskCache::Flush();
require(ShaderDiskCache::Totals().writes == before.writes, "an emission failure was stored in the disk cache");
@@ -277,14 +277,12 @@ void verifyBindlessTable() {
return std::vector<std::uint32_t>(snapshot.flattenedSrt.end() - static_cast<std::ptrdiff_t>(1u + 2u * slots), snapshot.flattenedSrt.end());
};
const auto tableRoot = [&](const ResourceCapture& capture, std::uint32_t direct) {
require(capture.specialization.images.size() == direct + slots - 1u, "bindless: the specialization does not hold the table slots");
require(capture.snapshot.images.size() == direct + slots - 1u, "bindless: the snapshot does not hold the table slots");
std::uint32_t root = ImageResource::NoIndirectImage;
for (std::uint32_t i = 0; i < direct; i++) {
if (capture.specialization.images[i].indirectRoot == i) root = i;
if (capture.plan->descriptorSources.at(capture.plan->info.images.at(i).source).indirectImage.has_value()) root = i;
}
require(root != ImageResource::NoIndirectImage, "bindless: no table root");
for (std::uint32_t i = direct; i < capture.specialization.images.size(); i++) require(capture.specialization.images[i].indirectRoot == root, "bindless: an extra image is not the root's slot");
return root;
};
@@ -300,6 +298,16 @@ void verifyBindlessTable() {
require(tables == 1, "bindless: the table source was not planned");
for (const auto& image : plan->info.images) require(image.indirectSearchIterations == 0u, "bindless: the plan carries a search depth");
const auto direct = static_cast<std::uint32_t>(plan->info.images.size());
auto staticRequest = request;
const std::array<std::uint32_t, 2> absentResources{};
staticRequest.context.userData = absentResources;
staticRequest.context.memory = {};
auto staticProgram = PrepareResourceProgram(staticRequest);
ResourceMaterializer{}.ApplyStaticInterface(staticProgram);
const auto& staticImages = staticProgram.Resources().info.images;
require(staticImages.size() == direct + slots - 1u, "bindless: the static interface needs runtime descriptors");
const auto staticRoot = std::ranges::find_if(staticImages, [](const ImageResource& image) { return image.indirectSearchIterations != 0u; });
require(staticRoot != staticImages.end() && staticRoot->indirectResources.size() == slots && staticRoot->indirectMappingOffset == plan->srtReads.size(), "bindless: the static table interface is incomplete");
AgcDriver::ShaderMemory memory({});
const auto capture = memory.Capture(request);
@@ -308,7 +316,7 @@ void verifyBindlessTable() {
for (std::uint32_t i = direct + 2u; i < capture->snapshot.images.size(); i++) require(capture->snapshot.images[i].dwords == heap[2], "bindless: a pad slot is not null");
const auto mapping = mappingOf(capture->snapshot);
require(std::vector<std::uint32_t>(mapping.begin(), mapping.begin() + 7) == std::vector<std::uint32_t>{3u, 0u, 0u, 1u, 1u, 3u, 2u}, "bindless: the (key, slot) mapping is wrong");
require(capture->specialization.images[root].indirectMappingOffset + mapping.size() == capture->snapshot.flattenedSrt.size(), "bindless: the mapping offset does not name the block");
require(capture->plan->srtReads.size() + mapping.size() == capture->snapshot.flattenedSrt.size(), "bindless: the mapping offset does not name the block");
auto regions = memory.Regions();
for (const auto& material : materials) require(covered(regions, &material[1], sizeof(std::uint32_t)), "bindless: a material key was not captured");
for (const auto entry : {0u, 1u, 3u}) require(covered(regions, heap[entry].data(), 32u), "bindless: a table entry was not captured");
+2 -2
View File
@@ -2,6 +2,7 @@
#define CORE_SHADER_RECOMPILER_CACHEKEY_HPP
#include "Recompiler.hpp"
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <cstdlib>
#include <stdexcept>
@@ -164,8 +165,7 @@ private:
if (value.fetchEmbedded) return;
append(key, value.resourcesNum);
for (std::uint32_t i = 0; i < value.resourcesNum; ++i) {
append(key, value.resources[i].fields[1] & 0xffff0000u);
append(key, value.resources[i].fields[3]);
append(key, VertexInputNumericClass(static_cast<IrBufferFormat>((value.resources[i].fields[3] >> 12u) & 0x7fu)));
append(key, value.resourcesDst[i]);
}
}
@@ -4,12 +4,10 @@
#include "Recompiler.hpp"
#include "IntermediateRepresentation/IrMetadata/CompiledShaderInfo.hpp"
#include "Optimization/include/Optimization/BindingAllocator.hpp"
#include "Optimization/include/Optimization/ResourceMaterializer.hpp"
namespace ShaderRecompiler {
struct CompiledVariant {
ResourceSpecialization specialization;
BindingLayout layout;
CompiledShaderInfo info;
CompiledBindingLayout bindings;
@@ -8,46 +8,18 @@
namespace ShaderRecompiler {
struct ResourceSpecialization {
struct Image {
IrTextureNumericClass numericClass = IrTextureNumericClass::Unsupported;
RdnaImageDimension dimension = RdnaImageDimension::Unknown;
std::uint32_t mipCount = 1;
IrBufferFormat conversionFormat = IrBufferFormat::Invalid;
std::uint32_t shaderSwizzle = ShaderImageIdentitySwizzle;
std::uint32_t indirectRoot = ImageResource::NoIndirectImage;
std::uint32_t indirectMappingOffset = 0;
std::uint32_t indirectSearchIterations = 0;
bool cube = false;
bool fmask = false;
bool depthBits = false;
bool depthUnorm16 = false;
IrBufferFormat packedFormat = IrBufferFormat::Invalid;
std::uint32_t emulatedCompare = 0;
bool srgbDecode = false;
bool operator==(const Image& other) const;
};
std::vector<Image> images;
bool operator==(const ResourceSpecialization& other) const;
std::vector<std::uint32_t> boundDescriptors;
};
// Why a bindless image table (a T# loaded from a table buffer at a runtime key) was not bound;
// counted on the [bindless] line (APS5_PROFILE_DRAW).
enum class BindlessRejection { Capacity, MaterialScan, NoEntry, Storage, NonUniform, ImageSlots, Count };
class ResourceMaterializer {
public:
void Apply(IrProgram& program, const ResourceSpecialization& specialization) const;
static std::uint32_t EmulatedCompareState(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint32_t index);
void ApplyStaticInterface(IrProgram& program) const;
static std::vector<ImageResource> RuntimeImageModes(const ImageResource& image);
static std::uint32_t RuntimeImageMode(const ImageResource& image, const DescriptorValue& descriptor);
[[nodiscard]] IrResourcePlan ExtractPlan(const IrProgram& program) const;
void Materialize(const IrResourcePlan& program, const SrtRuntime& runtime, ResourceSnapshot& snapshot, ResourceSpecialization& specialization) const;
// APS5_PROFILE_DRAW: the time Materialize spent building specializations, over every call.
void Materialize(const IrResourcePlan& program, const SrtRuntime& runtime, ResourceSnapshot& snapshot) const;
static std::uint64_t SpecializationNanoseconds();
static std::uint32_t BindlessSlots();
static void CountBindlessRejection(BindlessRejection reason);
@@ -3,7 +3,7 @@
#include "Recompiler.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ResourceMaterializer.hpp"
#include "Optimization/SrtWalker.hpp"
#include <cstdint>
#include <memory>
@@ -12,13 +12,10 @@ namespace ShaderRecompiler {
[[nodiscard]] IrProgram PrepareResourceProgram(const RecompileRequest& request);
[[nodiscard]] std::shared_ptr<const IrResourcePlan> GetResourcePlan(const RecompileRequest& request);
// What a driver's capture of a request produces: the plan and the materialization of the words the
// runtime read through it. Recompile(request, capture) compiles from these without a second walk.
struct SourceEntry;
struct ResourceCapture {
std::shared_ptr<const IrResourcePlan> plan;
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
// The cache entry the plan belongs to; null when the request bypasses the cache.
std::shared_ptr<SourceEntry> source;
// The walk's read addresses when the plan has pure flat slots (the leaf of each pure slot,
@@ -491,8 +491,8 @@ std::uint32_t colorCompareReference(IrBufferFormat format) {
}
}
std::uint32_t emulatedCompareState(const IrResourcePlan& plan, const ResourceSnapshot& snapshot, std::uint32_t index) {
const auto& image = plan.info.images[index];
std::uint32_t emulatedCompareState(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint32_t index) {
const auto& image = info.images[index];
const auto& descriptor = snapshot.images[index];
if (!image.depthCompare || descriptor.dwordCount != 8u || nullImageDescriptor(descriptor)) return 0u;
const auto format = rawImageFormat(descriptor);
@@ -502,7 +502,7 @@ std::uint32_t emulatedCompareState(const IrResourcePlan& plan, const ResourceSna
if (type != ImageType::Color2D && type != ImageType::Color2DArray) throw std::runtime_error("comparison sampling of a color texture is implemented only for 2D and 2D array views");
if ((descriptorImageSwizzle(descriptor) & 0x7u) != 4u) throw std::runtime_error("comparison sampling of a color texture is implemented only when the view's X channel is red");
std::optional<std::uint32_t> samplerState;
for (const auto& pair : plan.info.sampledPairs) {
for (const auto& pair : info.sampledPairs) {
if (pair.image != index) continue;
if (pair.sampler >= snapshot.samplers.size() || snapshot.samplers[pair.sampler].dwordCount != 4u) throw std::runtime_error("comparison sampling of a color texture has no sampler descriptor");
const auto& words = snapshot.samplers[pair.sampler].dwords;
@@ -536,77 +536,33 @@ std::uint32_t emulatedCompareState(const IrResourcePlan& plan, const ResourceSna
return *samplerState | (reference << EmulatedCompare::ReferenceShift) | (singleLevel ? EmulatedCompare::SingleLevel : 0u);
}
void buildResourceSpecialization(const IrResourcePlan& plan, ResourceSnapshot& snapshot, const std::vector<TableResolution>& tables, ResourceSpecialization& specialization) {
ResourceSpecialization result;
for (std::uint32_t i = 0; i < plan.info.buffers.size(); ++i) {
void materializeTables(const IrResourcePlan& plan, ResourceSnapshot& snapshot, const std::vector<TableResolution>& tables) {
for (std::uint32_t i = 0u; i < plan.info.buffers.size(); ++i) {
const auto decoded = decodeBufferDescriptor(snapshot.buffers.at(i));
if (decoded.Type() != 0u) throw std::runtime_error("buffer descriptor uses an unsupported type");
if (plan.stage != IrShaderStage::Compute && decoded.AddTid()) throw std::runtime_error("buffer ADD_TID is only valid for compute shaders");
}
result.images.reserve(plan.info.images.size());
for (std::uint32_t i = 0; i < plan.info.images.size(); i++) {
if (snapshot.flattenedSrt.size() != plan.srtReads.size()) throw std::runtime_error("runtime SRT size differs from the static interface");
for (std::uint32_t i = 0u; i < plan.info.images.size(); ++i) {
const auto& image = plan.info.images[i];
const DecodedImage decoded = decodeImageDescriptor(snapshot.images[i], image, plan.srgbDecodeFormats);
if (decoded.fmask && std::any_of(plan.info.sampledPairs.begin(), plan.info.sampledPairs.end(), [i](const SampledResourcePair& pair) { return pair.image == i; })) {
throw std::runtime_error("FMASK requires a direct image load");
}
ResourceSpecialization::Image entry;
entry.numericClass = image.atomic ? IrTextureNumericClass::Uint : IrTextureNumericClass::Float;
entry.dimension = image.dimension;
entry.mipCount = image.mipMode == ImageMipMode::DynamicStorage ? RuntimeAbi::StorageHeapCapacity : 1u;
entry.conversionFormat = IrBufferFormat::Invalid;
entry.shaderSwizzle = ShaderImageIdentitySwizzle;
entry.indirectRoot = ImageResource::NoIndirectImage;
entry.indirectMappingOffset = 0u;
entry.indirectSearchIterations = 0u;
entry.cube = false;
entry.fmask = false;
entry.depthBits = false;
entry.depthUnorm16 = false;
entry.packedFormat = IrBufferFormat::Invalid;
entry.emulatedCompare = emulatedCompareState(plan, snapshot, i);
entry.srgbDecode = decoded.srgbDecode;
result.images.push_back(entry);
}
// A table root is followed by its slots 1..C-1 as extra images of the root's shape; the
// (key, slot) mapping the SPIR-V selector searches is appended to the flattened SRT in a
// block of fixed size, so the offsets (part of the specialization) never depend on the keys.
for (std::uint32_t i = 0; i < plan.info.images.size(); i++) {
const auto& table = tables[i];
if (table.slots.empty()) {
static_cast<void>(ResourceMaterializer::RuntimeImageMode(image, snapshot.images.at(i)));
const auto& table = tables.at(i);
if (!plan.descriptorSources.at(image.source).indirectImage.has_value()) {
if (!table.slots.empty()) throw std::runtime_error("direct image has runtime table slots");
continue;
}
const auto mappingOffset = static_cast<std::uint32_t>(snapshot.flattenedSrt.size());
const auto slots = ResourceMaterializer::BindlessSlots();
if (table.slots.size() != slots) throw std::runtime_error("runtime table size differs from the static interface");
const auto mappingOffset = snapshot.flattenedSrt.size();
snapshot.flattenedSrt.push_back(static_cast<std::uint32_t>(table.mapping.size()));
for (const auto& [key, slot] : table.mapping) {
if (slot >= slots) throw std::runtime_error("runtime table slot exceeds its static capacity");
snapshot.flattenedSrt.push_back(key);
snapshot.flattenedSrt.push_back(slot == 0u ? i : static_cast<std::uint32_t>(snapshot.images.size()) + slot - 1u);
}
snapshot.flattenedSrt.resize(mappingOffset + 1u + 2u * table.slots.size(), 0u);
auto root = result.images[i];
root.indirectRoot = i;
root.indirectMappingOffset = mappingOffset;
root.indirectSearchIterations = static_cast<std::uint32_t>(std::bit_width(table.slots.size()));
result.images[i] = root;
root.indirectMappingOffset = 0u;
root.indirectSearchIterations = 0u;
for (std::uint32_t slot = 1; slot < table.slots.size(); slot++) {
result.images.push_back(root);
snapshot.images.push_back(table.slots[slot]);
}
snapshot.flattenedSrt.resize(mappingOffset + 1u + 2u * slots, 0u);
snapshot.images.insert(snapshot.images.end(), table.slots.begin() + 1u, table.slots.end());
}
result.boundDescriptors.clear();
result.boundDescriptors.reserve(plan.info.buffers.size() + result.images.size());
for (std::uint32_t index = 0; index < plan.info.buffers.size(); index++) {
result.boundDescriptors.push_back(index);
}
for (std::uint32_t index = 0; index < result.images.size(); index++) {
result.boundDescriptors.push_back(index);
}
specialization = std::move(result);
}
}
@@ -694,60 +650,44 @@ std::uint32_t ResourceMaterializer::RuntimeImageMode(const ImageResource& image,
throw std::runtime_error("image descriptor is incompatible with the static runtime image interface");
}
void ResourceMaterializer::Apply(IrProgram& program, const ResourceSpecialization& specialization) const {
IrResourcePlan& resources = program.Resources();
if (!resources.resourceTrackingComplete) {
throw std::runtime_error("ResourceMaterializer::Apply requires a completed resource plan");
}
if (resources.info.images.size() > specialization.images.size()) {
throw std::runtime_error("ResourceMaterializer::Apply image count mismatch");
}
std::uint32_t ResourceMaterializer::EmulatedCompareState(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint32_t index) {
return emulatedCompareState(info, snapshot, index);
}
void ResourceMaterializer::ApplyStaticInterface(IrProgram& program) const {
auto& resources = program.Resources();
if (!resources.resourceTrackingComplete || !resources.srtPlanComplete) throw std::runtime_error("static resource interface requires a completed resource plan");
auto images = resources.info.images;
images.reserve(specialization.images.size());
for (std::uint32_t index = 0; index < specialization.images.size(); index++) {
const auto& source = specialization.images[index];
if (index >= images.size()) {
if (source.indirectRoot >= resources.info.images.size()) {
throw std::runtime_error("ResourceMaterializer::Apply indirect image root is out of range");
const auto directCount = static_cast<std::uint32_t>(images.size());
auto mappingOffset = static_cast<std::uint32_t>(resources.srtReads.size());
const auto slots = BindlessSlots();
for (std::uint32_t index = 0u; index < directCount; ++index) {
auto image = images[index];
if (image.indirectRoot != ImageResource::NoIndirectImage) throw std::runtime_error("static image interface was already expanded");
image.numericClass = image.atomic ? IrTextureNumericClass::Uint : IrTextureNumericClass::Float;
image.mipCount = image.mipMode == ImageMipMode::DynamicStorage ? RuntimeAbi::StorageHeapCapacity : 1u;
if (resources.descriptorSources.at(image.source).indirectImage.has_value()) {
if (images.size() + slots - 1u > ShaderInfo::MaxImages) throw std::runtime_error("static bindless image capacity exceeded");
image.indirectRoot = index;
image.indirectResources.push_back(index);
for (std::uint32_t slot = 1u; slot < slots; ++slot) {
auto entry = image;
entry.indirectResources.clear();
image.indirectResources.push_back(static_cast<std::uint32_t>(images.size()));
images.push_back(std::move(entry));
}
images.push_back(resources.info.images[source.indirectRoot]);
image.indirectMappingOffset = mappingOffset;
image.indirectSearchIterations = static_cast<std::uint32_t>(std::bit_width(slots));
mappingOffset += 1u + 2u * slots;
}
auto& image = images[index];
image.numericClass = source.numericClass;
image.dimension = source.dimension;
image.mipCount = source.mipCount;
image.conversionFormat = source.conversionFormat;
image.shaderSwizzle = source.shaderSwizzle;
image.indirectRoot = source.indirectRoot;
image.indirectMappingOffset = source.indirectMappingOffset;
image.indirectSearchIterations = source.indirectSearchIterations;
image.cube = source.cube;
image.depthBits = source.depthBits;
image.depthUnorm16 = source.depthUnorm16;
image.packedFormat = source.packedFormat;
image.emulatedCompare = source.emulatedCompare;
image.srgbDecode = source.srgbDecode;
if ((source.emulatedCompare & EmulatedCompare::Enabled) != 0u) image.depthCompare = false;
image.indirectResources.clear();
images[index] = std::move(image);
}
for (std::uint32_t index = 0; index < images.size(); index++) {
const auto root = images[index].indirectRoot;
if (root != ImageResource::NoIndirectImage) {
if (root >= images.size()) {
throw std::runtime_error("ResourceMaterializer::Apply indirect image root is out of range");
}
images[root].indirectResources.push_back(index);
}
}
auto samplers = resources.info.samplers;
for (const auto& pair : resources.info.sampledPairs) {
if (pair.image >= images.size() || pair.sampler >= samplers.size()) throw std::runtime_error("runtime sampled pair is out of range");
samplers[pair.sampler].depthCompare = samplers[pair.sampler].depthCompare || images[pair.image].depthCompare;
if (pair.image >= images.size() || pair.sampler >= resources.info.samplers.size()) throw std::runtime_error("static sampled pair is out of range");
auto& sampler = resources.info.samplers[pair.sampler];
sampler.depthCompare = sampler.depthCompare || images[pair.image].depthCompare;
}
resources.info.images = std::move(images);
resources.info.samplers = std::move(samplers);
}
namespace {
@@ -856,7 +796,7 @@ IrResourcePlan ResourceMaterializer::ExtractPlan(const IrProgram& program) const
return plan;
}
void ResourceMaterializer::Materialize(const IrResourcePlan& program, const SrtRuntime& runtime, ResourceSnapshot& snapshot, ResourceSpecialization& specialization) const {
void ResourceMaterializer::Materialize(const IrResourcePlan& program, const SrtRuntime& runtime, ResourceSnapshot& snapshot) const {
const IrResourcePlan& plan = program;
if (!plan.resourceTrackingComplete) {
throw std::runtime_error("ResourceMaterializer::Materialize requires a completed resource plan");
@@ -873,12 +813,10 @@ void ResourceMaterializer::Materialize(const IrResourcePlan& program, const SrtR
reportBindless();
throw;
}
ResourceSpecialization nextSpecialization;
const auto started = MaterializeProfiled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
buildResourceSpecialization(plan, nextSnapshot, tables, nextSpecialization);
materializeTables(plan, nextSnapshot, tables);
if (MaterializeProfiled()) specializationNanoseconds.fetch_add(static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - started).count()), std::memory_order_relaxed);
snapshot = std::move(nextSnapshot);
specialization = std::move(nextSpecialization);
reportBindless();
}
@@ -894,12 +832,4 @@ void ResourceMaterializer::CountBindlessRejection(BindlessRejection reason) {
if (reason < BindlessRejection::Count) bindlessCounters().rejected[static_cast<std::size_t>(reason)].fetch_add(1, std::memory_order_relaxed);
}
bool ResourceSpecialization::Image::operator==(const Image& other) const {
return numericClass == other.numericClass && dimension == other.dimension && mipCount == other.mipCount && conversionFormat == other.conversionFormat && shaderSwizzle == other.shaderSwizzle && indirectRoot == other.indirectRoot && indirectMappingOffset == other.indirectMappingOffset && indirectSearchIterations == other.indirectSearchIterations && cube == other.cube && fmask == other.fmask && depthBits == other.depthBits && depthUnorm16 == other.depthUnorm16 && packedFormat == other.packedFormat && emulatedCompare == other.emulatedCompare && srgbDecode == other.srgbDecode;
}
bool ResourceSpecialization::operator==(const ResourceSpecialization& other) const {
return images == other.images;
}
}
@@ -17,6 +17,7 @@ enum class ImageType : std::uint32_t {
Color2DMsaaArray = 15
};
[[nodiscard]] IrTextureNumericClass VertexInputNumericClass(IrBufferFormat format);
[[nodiscard]] bool IsFmaskTextureFormat(IrBufferFormat format);
[[nodiscard]] IrTextureNumericClass SampledTextureNumericClass(IrBufferFormat format);
[[nodiscard]] IrBufferFormat RemapTextureFormat(IrBufferFormat format);
@@ -137,4 +137,30 @@ bool IsDepthBitsTexture(std::uint32_t word1, std::uint32_t word3) {
return DepthBitsTextureWidth(word1, word3) != 0u;
}
IrTextureNumericClass VertexInputNumericClass(IrBufferFormat format) {
switch (format) {
case IrBufferFormat::Format8UInt:
case IrBufferFormat::Format16UInt:
case IrBufferFormat::Format8_8UInt:
case IrBufferFormat::Format32UInt:
case IrBufferFormat::Format16_16UInt:
case IrBufferFormat::Format8_8_8_8UInt:
case IrBufferFormat::Format32_32UInt:
case IrBufferFormat::Format16_16_16_16UInt:
case IrBufferFormat::Format32_32_32UInt:
case IrBufferFormat::Format32_32_32_32UInt: return IrTextureNumericClass::Uint;
case IrBufferFormat::Format8SInt:
case IrBufferFormat::Format16SInt:
case IrBufferFormat::Format8_8SInt:
case IrBufferFormat::Format32SInt:
case IrBufferFormat::Format16_16SInt:
case IrBufferFormat::Format8_8_8_8SInt:
case IrBufferFormat::Format32_32SInt:
case IrBufferFormat::Format16_16_16_16SInt:
case IrBufferFormat::Format32_32_32SInt:
case IrBufferFormat::Format32_32_32_32SInt: return IrTextureNumericClass::Sint;
default: return IrTextureNumericClass::Float;
}
}
}
+23 -29
View File
@@ -181,7 +181,6 @@ struct ResultMemoEntry {
struct EmissionFailure {
std::uint64_t codeAddress;
BindingLayout layout;
ResourceSpecialization specialization;
std::exception_ptr failure;
};
@@ -240,6 +239,7 @@ std::shared_ptr<SourceEntry> getSource(const RecompileRequest& request) {
struct SourceKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, SourceKeyStorage>();
RecompileCacheKey::Build(request, key);
key.push_back(HostSubgroupSize(request));
const auto find = [&]() -> std::shared_ptr<SourceEntry> {
const auto found = sources.find(key);
if (found == sources.end()) return nullptr;
@@ -297,11 +297,11 @@ std::uint64_t nextVariantId() {
return variants.fetch_add(1, std::memory_order_relaxed) + 1;
}
CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, const ResourceSnapshot& resourceSnapshot, const ResourceSpecialization& resourceSpecialization, std::exception_ptr* emissionFailure = nullptr) {
CompiledVariant compileVariant(const RecompileRequest& request, IrProgram program, std::exception_ptr* emissionFailure = nullptr) {
const auto inputInfo = RequestInputInfo(request);
constexpr DeadCodeEliminator deadCodeEliminator;
constexpr ResourceMaterializer resourceMaterializer;
resourceMaterializer.Apply(program, resourceSpecialization);
resourceMaterializer.ApplyStaticInterface(program);
deadCodeEliminator.RemoveIdentities(program);
deadCodeEliminator.Eliminate(program);
@@ -312,9 +312,6 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra
constexpr BindingAllocator bindingAllocator;
auto bindings = bindingAllocator.Allocate(program, request.layout);
constexpr DescriptorBindingBuilder descriptorBindingBuilder;
descriptorBindingBuilder.Populate(bindings, program, resourceSnapshot, partialThreads(request));
SpirvTargetOptions targetOptions {};
targetOptions.vulkanVersion = request.target.vulkanVersion;
targetOptions.spirvVersion = request.target.spirvVersion;
@@ -362,7 +359,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra
}
}
return {resourceSpecialization, request.layout, std::move(program).TakeCompiledInfo(), std::move(static_cast<CompiledBindingLayout&>(bindings)), std::move(result)};
return {request.layout, std::move(program).TakeCompiledInfo(), std::move(static_cast<CompiledBindingLayout&>(bindings)), std::move(result)};
}
RecompileResult materializeResult(const CompiledVariant& variant, const RecompileRequest& request, const ResourceSnapshot& snapshot) {
@@ -387,25 +384,24 @@ bool sameLayout(const BindingLayout& left, const BindingLayout& right) {
return left.descriptorSet == right.descriptorSet && left.firstBinding == right.firstBinding && left.pushConstantOffsetBytes == right.pushConstantOffsetBytes && left.pushConstantSizeBytes == right.pushConstantSizeBytes;
}
std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source, const RecompileRequest& request, const ResourceSnapshot& snapshot, const ResourceSpecialization& specialization, bool& cacheHit) {
std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source, const RecompileRequest& request, bool& cacheHit) {
for (const auto& candidate : source.variants) {
if (sameLayout(candidate->layout, request.layout) && candidate->specialization == specialization) {
if (sameLayout(candidate->layout, request.layout)) {
cacheHit = true;
return candidate;
}
}
for (const auto& failure : source.emissionFailures) {
if (failure.codeAddress == request.shader.codeAddress && sameLayout(failure.layout, request.layout) && failure.specialization == specialization) std::rethrow_exception(failure.failure);
if (failure.codeAddress == request.shader.codeAddress && sameLayout(failure.layout, request.layout)) std::rethrow_exception(failure.failure);
}
cacheHit = false;
const bool disk = ShaderDiskCache::Enabled() && !DebugProbeActive();
std::vector<std::byte> diskKey;
std::shared_ptr<const CompiledVariant> variant;
if (disk) {
ShaderDiskCache::BuildKey(request, HostSubgroupSize(request), specialization, diskKey);
ShaderDiskCache::BuildKey(request, HostSubgroupSize(request), diskKey);
CompiledVariant loaded;
if (ShaderDiskCache::Load(diskKey, loaded)) {
loaded.specialization = specialization;
loaded.layout = request.layout;
loaded.artifact.variantId = nextVariantId();
variant = std::make_shared<const CompiledVariant>(std::move(loaded));
@@ -416,9 +412,9 @@ std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source,
source.program.reset();
std::exception_ptr emissionFailure;
try {
variant = std::make_shared<const CompiledVariant>(compileVariant(request, std::move(program), snapshot, specialization, &emissionFailure));
variant = std::make_shared<const CompiledVariant>(compileVariant(request, std::move(program), &emissionFailure));
} catch (...) {
if (emissionFailure != nullptr && FailureMemo()) source.emissionFailures.push_back({request.shader.codeAddress, request.layout, specialization, emissionFailure});
if (emissionFailure != nullptr && FailureMemo()) source.emissionFailures.push_back({request.shader.codeAddress, request.layout, emissionFailure});
throw;
}
if (disk) ShaderDiskCache::Store(std::move(diskKey), variant);
@@ -428,13 +424,12 @@ std::shared_ptr<const CompiledVariant> findOrCompileVariant(SourceEntry& source,
return variant;
}
// The cached variant of `source` for the specialization, compiled on first use.
RecompileResult materializeVariant(SourceEntry& source, const RecompileRequest& request, const ResourceSnapshot& snapshot, const ResourceSpecialization& specialization) {
RecompileResult materializeVariant(SourceEntry& source, const RecompileRequest& request, const ResourceSnapshot& snapshot) {
std::shared_ptr<const CompiledVariant> variant;
bool cacheHit = false;
{
std::lock_guard lock(source.mutex);
variant = findOrCompileVariant(source, request, snapshot, specialization, cacheHit);
variant = findOrCompileVariant(source, request, cacheHit);
}
auto result = materializeResult(*variant, request, snapshot);
result.cacheHit = cacheHit;
@@ -446,18 +441,17 @@ RecompileResult RecompileImpl(const RecompileRequest& request) {
RequestMemoryView memory(request.context.memory);
const auto runtime = memory.MakeRuntime(request.context.userData, request.shader.codeAddress);
ResourceSnapshot snapshot;
ResourceSpecialization specialization;
constexpr ResourceMaterializer materializer;
if (!request.useCache) {
auto program = PrepareResourceProgram(request);
const auto plan = materializer.ExtractPlan(program);
materializer.Materialize(plan, runtime, snapshot, specialization);
const auto variant = compileVariant(request, std::move(program), snapshot, specialization);
materializer.Materialize(plan, runtime, snapshot);
const auto variant = compileVariant(request, std::move(program));
return materializeResult(variant, request, snapshot);
}
const auto source = getSource(request);
materializer.Materialize(*source->plan, runtime, snapshot, specialization);
return materializeVariant(*source, request, snapshot, specialization);
materializer.Materialize(*source->plan, runtime, snapshot);
return materializeVariant(*source, request, snapshot);
}
// APS5_NO_RESULT_MEMO=1: every Recompile(request, capture) materializes its own result as before.
@@ -543,7 +537,7 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh
// The memo'd result of `source`'s variant for the snapshot (design13 R5): a hit returns the shared
// object, a miss materializes outside the source mutex and inserts (a concurrent miss's object is
// as good). `memoHit` reports the hit.
std::shared_ptr<const RecompileResult> materializeMemoized(SourceEntry& source, const RecompileRequest& request, const ResourceSnapshot& snapshot, const ResourceSpecialization& specialization, bool* memoHit) {
std::shared_ptr<const RecompileResult> materializeMemoized(SourceEntry& source, const RecompileRequest& request, const ResourceSnapshot& snapshot, bool* memoHit) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
std::shared_ptr<const CompiledVariant> variant;
bool cacheHit = false;
@@ -552,7 +546,7 @@ std::shared_ptr<const RecompileResult> materializeMemoized(SourceEntry& source,
auto& counters = resultMemoCounters();
{
std::lock_guard lock(source.mutex);
variant = findOrCompileVariant(source, request, snapshot, specialization, cacheHit);
variant = findOrCompileVariant(source, request, cacheHit);
index = (variant->artifact.variantId * 0x9e3779b97f4a7c15ull) ^ hash;
const auto found = source.memoIndex.find(index);
if (found != source.memoIndex.end() && found->second->variantId == variant->artifact.variantId && found->second->hash == hash) {
@@ -601,11 +595,11 @@ std::shared_ptr<const RecompileResult> materializeMemoized(SourceEntry& source,
std::shared_ptr<const RecompileResult> RecompileImpl(const RecompileRequest& request, const ResourceCapture& capture, bool* memoHit) {
if (!request.useCache || capture.source == nullptr) {
auto program = PrepareResourceProgram(request);
const auto variant = compileVariant(request, std::move(program), capture.snapshot, capture.specialization);
const auto variant = compileVariant(request, std::move(program));
return std::make_shared<const RecompileResult>(materializeResult(variant, request, capture.snapshot));
}
if (!ResultMemo()) return std::make_shared<const RecompileResult>(materializeVariant(*capture.source, request, capture.snapshot, capture.specialization));
return materializeMemoized(*capture.source, request, capture.snapshot, capture.specialization, memoHit);
if (!ResultMemo()) return std::make_shared<const RecompileResult>(materializeVariant(*capture.source, request, capture.snapshot));
return materializeMemoized(*capture.source, request, capture.snapshot, memoHit);
}
template <typename Impl>
@@ -636,12 +630,12 @@ namespace {
void materializeCapture(ResourceCapture& capture, const SrtRuntime& runtime) {
const auto& plan = *capture.plan;
if (std::none_of(plan.pureFlatSlots.begin(), plan.pureFlatSlots.end(), [](std::uint8_t pure) { return pure != 0u; })) {
ResourceMaterializer{}.Materialize(plan, runtime, capture.snapshot, capture.specialization);
ResourceMaterializer{}.Materialize(plan, runtime, capture.snapshot);
return;
}
SrtRuntime traced = runtime;
traced.readTrace = &capture.readTrace;
ResourceMaterializer{}.Materialize(plan, traced, capture.snapshot, capture.specialization);
ResourceMaterializer{}.Materialize(plan, traced, capture.snapshot);
auto& other = capture.readTrace.otherReads;
std::sort(other.begin(), other.end());
other.erase(std::unique(other.begin(), other.end()), other.end());
-6
View File
@@ -438,12 +438,6 @@ struct RecompileResult : CompiledShaderArtifact, ShaderInvocation {
[[nodiscard]] RecompileResult Recompile(const RecompileRequest& request);
// The resource plan, snapshot and specialization a driver captured for the request (see
// CaptureResources in Optimization/ResourceProgram.hpp): this overload reuses them instead of
// materializing the request's memory regions again, and is otherwise Recompile(request). The
// result is immutable and shared: a capture that reproduces a snapshot the source's variant was
// materialized over before receives the same object (`memoHit`), so the descriptor population runs
// once per distinct snapshot. APS5_NO_RESULT_MEMO=1 materializes every call.
struct ResourceCapture;
[[nodiscard]] std::shared_ptr<const RecompileResult> Recompile(const RecompileRequest& request, const ResourceCapture& capture, bool* memoHit = nullptr);
+1 -21
View File
@@ -82,8 +82,6 @@ static_assert(sizeof(IrBindingLayout) == 64, "IrBindingLayout changed: update th
static_assert(sizeof(IrDescriptorBinding) == 32, "IrDescriptorBinding changed: update the layout encoder");
static_assert(sizeof(BindingAllocationResult) == 120, "BindingAllocationResult changed: update the allocation encoder");
static_assert(sizeof(CompiledBindingLayout) == 72, "CompiledBindingLayout changed: update the allocation encoder");
static_assert(sizeof(ResourceSpecialization) == 48, "ResourceSpecialization changed: update BuildKey");
static_assert(sizeof(ResourceSpecialization::Image) == 48, "ResourceSpecialization::Image changed: update BuildKey");
static_assert(sizeof(BindingLayout) == 16, "BindingLayout changed: update BuildKey");
#endif
@@ -802,7 +800,7 @@ std::uint64_t SourceVersion() {
return Generated::SourceVersion;
}
void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, const ResourceSpecialization& specialization, std::vector<std::byte>& key) {
void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, std::vector<std::byte>& key) {
key.clear();
Writer writer(key);
writer.Value(FileMagic);
@@ -818,24 +816,6 @@ void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, c
writer.Value(request.layout.firstBinding);
writer.Value(request.layout.pushConstantOffsetBytes);
writer.Value(request.layout.pushConstantSizeBytes);
writer.List(specialization.images, [](Writer& out, const ResourceSpecialization::Image& image) {
out.Value(image.numericClass);
out.Value(image.dimension);
out.Value(image.mipCount);
out.Value(image.conversionFormat);
out.Value(image.shaderSwizzle);
out.Value(image.indirectRoot);
out.Value(image.indirectMappingOffset);
out.Value(image.indirectSearchIterations);
out.Value(image.cube);
out.Value(image.fmask);
out.Value(image.depthBits);
out.Value(image.depthUnorm16);
out.Value(image.packedFormat);
out.Value(image.emulatedCompare);
out.Value(image.srgbDecode);
});
writer.Values(std::span<const std::uint32_t>(specialization.boundDescriptors));
const auto& switches = switchKey();
key.insert(key.end(), switches.begin(), switches.end());
}
+2 -2
View File
@@ -12,7 +12,7 @@
namespace ShaderRecompiler::ShaderDiskCache {
inline constexpr std::uint32_t FormatVersion = 10;
inline constexpr std::uint32_t FormatVersion = 11;
enum class LoadStatus {
Loaded,
@@ -33,7 +33,7 @@ struct Counters {
[[nodiscard]] std::uint64_t SourceVersion();
void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, const ResourceSpecialization& specialization, std::vector<std::byte>& key);
void BuildKey(const RecompileRequest& request, std::uint32_t hostSubgroupSize, std::vector<std::byte>& key);
[[nodiscard]] std::string EntryName(std::span<const std::byte> key);
@@ -24,8 +24,8 @@ struct SpirvTargetOptions {
class SpirvEmitter {
public:
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const BindingAllocationResult& bindings, const SpirvTargetOptions& target) const;
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const BindingAllocationResult& bindings, const SpirvTargetOptions& target) const;
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const;
[[nodiscard]] std::vector<std::uint32_t> Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const;
};
@@ -71,7 +71,7 @@ std::uint32_t ConstantBool(SpirvEmitterState& state, bool value);
std::uint32_t ConstantU64(SpirvEmitterState& state, std::uint64_t value);
std::uint32_t ConstantU32CompositeZero(SpirvEmitterState& state, std::uint32_t components);
std::uint32_t DefineInterfaceVariable(SpirvEmitterState& state, std::uint32_t type, std::uint32_t storage, const char* name);
void CheckBindings(const IrProgram& program, const BindingAllocationResult& bindings);
void CheckBindings(const IrProgram& program, const CompiledBindingLayout& bindings);
void EmitBaseHeader(SpirvModule& module, const IrProgram& program);
void DefineInputs(SpirvEmitterState& state);
void DefineOutputs(SpirvEmitterState& state);
@@ -8,8 +8,8 @@
namespace ShaderRecompiler {
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const CompiledBindingLayout& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const CompiledBindingLayout& bindings);
void DefineModule(SpirvEmitterState& state);
std::uint32_t DefineInterfaceVariable(SpirvEmitterState& state, std::uint32_t type, std::uint32_t storage, const char* name);
std::uint32_t ExecutionModelForStage(IrShaderStage stage);
@@ -9,8 +9,8 @@
namespace ShaderRecompiler {
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const CompiledBindingLayout& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const CompiledBindingLayout& bindings);
void DefineModule(SpirvEmitterState& state);
std::uint32_t ExecutionModelForStage(IrShaderStage stage);
std::uint32_t DefineInterfaceVariable(SpirvEmitterState& state, std::uint32_t type, std::uint32_t storage, const char* name);
@@ -9,9 +9,9 @@
namespace ShaderRecompiler {
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const CompiledBindingLayout& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const BindingAllocationResult& bindings);
void EmitModuleHeader(SpirvEmitterState& state, const CompiledBindingLayout& bindings);
}
@@ -229,11 +229,11 @@ std::uint32_t SpirvValueEmitContext::Label(const IrBlock* block) const {
throw std::runtime_error("SPIR-V emission failed: hash=0x" + std::to_string(state.program.Resources().shaderHash) + " stage=" + std::to_string(static_cast<unsigned>(state.program.Resources().stage)) + " opcode=" + std::string(IrOpcodeName(inst.Opcode())) + " reason=" + reason);
}
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const BindingAllocationResult& bindings, const SpirvTargetOptions& target) const {
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const {
return Emit(program, ShaderStageInputInfo {}, bindings, target);
}
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const BindingAllocationResult& bindings, const SpirvTargetOptions& target) const {
std::vector<std::uint32_t> SpirvEmitter::Emit(const IrProgram& program, const ShaderStageInputInfo& inputInfo, const CompiledBindingLayout& bindings, const SpirvTargetOptions& target) const {
if (program.Resources().stage != IrShaderStage::Compute && program.Resources().stage != IrShaderStage::Vertex && program.Resources().stage != IrShaderStage::Pixel && program.Resources().stage != IrShaderStage::Mesh && program.Resources().stage != IrShaderStage::Local && program.Resources().stage != IrShaderStage::TessellationControl && program.Resources().stage != IrShaderStage::TessellationEvaluation) {
FailProgram(program, "binary SPIR-V emitter received an unsupported shader stage");
}
@@ -125,7 +125,7 @@ std::uint32_t PushConstantBlockType(SpirvEmitterState& state) {
}
void CheckBindings(const IrProgram& program, const BindingAllocationResult& bindings) {
void CheckBindings(const IrProgram& program, const CompiledBindingLayout& bindings) {
const IrProgramMetadata& metadata = program.Metadata();
if (!metadata.bindingLayoutComplete) {
FailEmit("shader binding layout has not been allocated for this program");
@@ -133,59 +133,6 @@ void CheckBindings(const IrProgram& program, const BindingAllocationResult& bind
if (!(bindings.layout == metadata.bindings)) {
FailEmit("binding allocation result does not match the program's committed binding layout");
}
if (bindings.bindings.size() != bindings.layout.descriptors.size()) {
FailEmit("binding allocation result has a different descriptor count than the binding layout");
}
const IrShaderStage stage = program.Resources().stage;
for (std::size_t index = 0; index < bindings.layout.descriptors.size(); index++) {
const IrDescriptorBinding& logical = bindings.layout.descriptors[index];
const DescriptorBinding& physical = bindings.bindings[index];
const std::uint32_t expectedCount = logical.resources.empty() ? 1u : static_cast<std::uint32_t>(logical.resources.size());
if (physical.count != expectedCount) {
FailEmit("descriptor binding " + std::to_string(index) + " has an incorrect descriptor count");
}
if (physical.descriptorSet != 0u) {
FailEmit("descriptor binding " + std::to_string(index) + " is bound to the wrong descriptor set");
}
if (physical.binding != NativeBinding(stage, logical.kind)) {
FailEmit("descriptor binding " + std::to_string(index) + " is bound to the wrong native binding slot");
}
DescriptorKind expectedKind = DescriptorKind::StorageBuffer;
DescriptorRole expectedRole = DescriptorRole::GuestBuffers;
if (logical.kind == DescriptorBindingKind::Samplers) {
expectedKind = DescriptorKind::Sampler;
expectedRole = DescriptorRole::GuestSamplers;
} else if (logical.kind == DescriptorBindingKind::Gds) {
expectedRole = DescriptorRole::Gds;
} else if (logical.kind == DescriptorBindingKind::BdaPagetable) {
expectedRole = DescriptorRole::BdaPagetable;
} else if (logical.kind == DescriptorBindingKind::FaultBuffer) {
expectedRole = DescriptorRole::FaultBuffer;
} else if (logical.kind == DescriptorBindingKind::FlattenedSrt) {
expectedRole = DescriptorRole::FlattenedSrt;
} else if (logical.kind == DescriptorBindingKind::ShaderData) {
expectedRole = DescriptorRole::ShaderData;
} else {
const auto imageClass = ImageBindingResourceClass(logical.kind);
if (imageClass == ImageResourceClass::Sampled) {
expectedKind = DescriptorKind::SampledImage;
expectedRole = DescriptorRole::GuestImages;
} else if (imageClass == ImageResourceClass::Storage) {
expectedKind = DescriptorKind::StorageImage;
expectedRole = DescriptorRole::GuestImages;
} else if (logical.kind == DescriptorBindingKind::Buffers) {
expectedRole = DescriptorRole::GuestBuffers;
} else {
FailEmit("descriptor binding " + std::to_string(index) + " has an unmapped binding kind");
}
}
if (physical.kind != expectedKind) {
FailEmit("descriptor binding " + std::to_string(index) + " has an incorrect descriptor kind");
}
if (physical.role != expectedRole) {
FailEmit("descriptor binding " + std::to_string(index) + " has an incorrect descriptor role");
}
}
}
void EmitBaseHeader(SpirvModule& module, const IrProgram& program) {
@@ -1,3 +1,4 @@
#include "RdnaDecoder/RdnaDescriptorFormat.hpp"
#include "SpirvBackend/SpirvMemory/SpirvInputOutput.hpp"
#include "SpirvBackend/SpirvEmitterHelpers.hpp"
#include "SpirvBackend/SpirvMemory/SpirvTypes.hpp"
@@ -88,29 +89,10 @@ namespace ShaderRecompiler
if (location >= static_cast<std::uint32_t>(ShaderVertexInputInfo::MaxResources) || location >= static_cast<std::uint32_t>(vertex.resourcesNum)) {
return VertexInputScalarKind::Float;
}
switch (vertex.resources[location].Format()) {
case IrBufferFormat::Format8UInt:
case IrBufferFormat::Format16UInt:
case IrBufferFormat::Format8_8UInt:
case IrBufferFormat::Format32UInt:
case IrBufferFormat::Format16_16UInt:
case IrBufferFormat::Format8_8_8_8UInt:
case IrBufferFormat::Format32_32UInt:
case IrBufferFormat::Format16_16_16_16UInt:
case IrBufferFormat::Format32_32_32UInt:
case IrBufferFormat::Format32_32_32_32UInt: return VertexInputScalarKind::Uint;
case IrBufferFormat::Format8SInt:
case IrBufferFormat::Format16SInt:
case IrBufferFormat::Format8_8SInt:
case IrBufferFormat::Format32SInt:
case IrBufferFormat::Format16_16SInt:
case IrBufferFormat::Format8_8_8_8SInt:
case IrBufferFormat::Format32_32SInt:
case IrBufferFormat::Format16_16_16_16SInt:
case IrBufferFormat::Format32_32_32SInt:
case IrBufferFormat::Format32_32_32_32SInt: return VertexInputScalarKind::Sint;
default: return VertexInputScalarKind::Float;
}
const auto numeric = VertexInputNumericClass(vertex.resources[location].Format());
if (numeric == IrTextureNumericClass::Uint) return VertexInputScalarKind::Uint;
if (numeric == IrTextureNumericClass::Sint) return VertexInputScalarKind::Sint;
return VertexInputScalarKind::Float;
}
std::uint32_t VertexParameterComponentCount(const SpirvInputBinding& input) {
@@ -11,12 +11,12 @@
namespace ShaderRecompiler
{
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const BindingAllocationResult& bindings) {
void EmitModuleHeader(SpirvModule& module, const IrProgram& program, const CompiledBindingLayout& bindings) {
CheckBindings(program, bindings);
EmitBaseHeader(module, program);
}
void EmitModuleHeader(SpirvEmitterState& state, const BindingAllocationResult& bindings) {
void EmitModuleHeader(SpirvEmitterState& state, const CompiledBindingLayout& bindings) {
CheckBindings(state.program, bindings);
DefineModule(state);
}