perf(agc): give the fragment stage its own descriptor set when modules are built at registration

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-11 14:50:51 +02:00
parent baf3437465
commit 4d0b5c7898
13 changed files with 164 additions and 53 deletions

View File

@ -19,6 +19,8 @@ struct PipelineLibraryKeys {
std::vector<std::byte> fragmentOutput;
std::vector<std::byte> renderPass;
std::vector<std::byte> layout;
std::vector<std::byte> preRasterizationLayout;
std::vector<std::byte> fragmentLayout;
};
std::span<const VkDynamicState> PipelineLibraryDynamicStates();

View File

@ -117,6 +117,8 @@ public:
std::vector<VkDescriptorSetLayoutBinding> LayoutBindings() const;
bool LayoutUpdateAfterBind() const;
std::span<const VkDescriptorBindingFlags> LayoutBindingFlags() const { return layoutBindingFlags; }
VkDescriptorSetLayout FragmentLayout() const { return fragment.layout; }
bool SplitSets() const { return splitSets; }
void Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const;
struct DrawBindings {
DrawBindings() = default;
@ -127,6 +129,7 @@ public:
std::shared_ptr<Buffer> buffer;
};
DescriptorCache* cache = nullptr;
DescriptorCache::SetAllocation fragmentAllocation;
DescriptorCache::SetAllocation allocation;
std::vector<Snapshot> snapshots;
~DrawBindings();
@ -165,6 +168,7 @@ public:
bool ReadsImage(const StorageTexture* image) const;
bool HasCachedStorageImages() const;
const std::vector<std::uint32_t>& LayoutKey() const { return layoutKey; }
const std::vector<std::uint32_t>& FragmentLayoutKey() const { return fragmentLayoutKey; }
// Debug aid: each bound guest resource with the fraction of sampled bytes that are nonzero.
std::string Describe() const;
@ -428,6 +432,17 @@ private:
VkDescriptorPool pool = VK_NULL_HANDLE;
// The cache pool the set was allocated from, freed back to it on release.
DescriptorCache::SetAllocation cacheAllocation;
struct SetObjects {
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
bool ownsLayout = false;
VkDescriptorSet set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
DescriptorCache::SetAllocation cacheAllocation;
};
bool splitSets = false;
std::vector<std::uint32_t> fragmentLayoutKey;
SetObjects fragment;
std::uint32_t setOf(const Binding& binding) const;
std::vector<Allocation> allocations;
std::vector<VkDescriptorBufferInfo> descriptorBuffers;
std::vector<VkDescriptorImageInfo> descriptorImages;
@ -479,6 +494,7 @@ private:
// the descriptor counts the set was sized for, and the compute stage of a deferred build.
std::vector<Binding> bindings;
std::vector<VkDescriptorBindingFlags> layoutBindingFlags;
std::vector<VkDescriptorBindingFlags> fragmentBindingFlags;
std::vector<std::uint32_t> refreshResourceKey;
struct DeferredImages {
const ShaderRecompiler::DescriptorBinding* binding;

View File

@ -1764,7 +1764,9 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK");
if (drawBindings != nullptr) {
const auto set = drawBindings->allocation.set;
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), 0, 1, &set, 0, nullptr);
const auto fragmentSet = drawBindings->fragmentAllocation.set;
if (set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), 0, 1, &set, 0, nullptr);
if (fragmentSet != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet, 1, &fragmentSet, 0, nullptr);
} else {
resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout());
}

View File

@ -217,8 +217,28 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
}
appendKey(keys.renderPass, state.depth.has_value());
if (state.depth) appendKey(keys.renderPass, state.depth->format);
appendKey(keys.layout, pushStagesOf(context));
for (const auto word : resources.LayoutKey()) appendKey(keys.layout, word);
if (!resources.SplitSets()) {
appendKey(keys.layout, pushStagesOf(context));
for (const auto word : resources.LayoutKey()) appendKey(keys.layout, word);
keys.preRasterizationLayout = keys.fragmentLayout = keys.layout;
return true;
}
const auto splitKey = [&](std::vector<std::byte>& key, std::uint32_t sets) {
appendKey(key, ~0u);
appendKey(key, sets);
appendKey(key, pushStagesOf(context));
if ((sets & 1u) != 0u) {
appendKey(key, resources.LayoutKey().size());
for (const auto word : resources.LayoutKey()) appendKey(key, word);
}
if ((sets & 2u) != 0u) {
appendKey(key, resources.FragmentLayoutKey().size());
for (const auto word : resources.FragmentLayoutKey()) appendKey(key, word);
}
};
splitKey(keys.layout, 3u);
splitKey(keys.preRasterizationLayout, 1u);
splitKey(keys.fragmentLayout, 2u);
return true;
}
@ -280,12 +300,13 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
}
// A descriptor set layout with the same bindings as this one is compatible with the pipeline
// layout, so later draws bind their own ShaderResources' set under it.
const auto setLayout = resources.Layout();
const std::array<VkDescriptorSetLayout, 2> setLayouts{resources.Layout(), resources.FragmentLayout()};
const VkPushConstantRange push{pushStages, 0, PipelinePushSlotBytes};
const std::array<VkPushConstantRange, 2> slots{{{pushStagesOf(context), 0, PipelinePushSlotBytes}, {VK_SHADER_STAGE_FRAGMENT_BIT, PipelinePushSlotBytes, PipelinePushSlotBytes}}};
VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
layoutInfo.setLayoutCount = 1;
layoutInfo.pSetLayouts = &setLayout;
layoutInfo.flags = resources.SplitSets() ? VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT : 0;
layoutInfo.setLayoutCount = resources.SplitSets() ? 2u : 1u;
layoutInfo.pSetLayouts = setLayouts.data();
layoutInfo.pushConstantRangeCount = dynamicRendering ? static_cast<std::uint32_t>(slots.size()) : pushStages != 0 ? 1 : 0;
layoutInfo.pPushConstantRanges = dynamicRendering ? slots.data() : pushStages != 0 ? &push : nullptr;
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &layout), "vkCreatePipelineLayout graphics");
@ -709,6 +730,8 @@ void pipelineKey(std::vector<std::byte>& key, const Context& context, const Stat
}
append(key, resources.LayoutKey().size());
for (const auto word : resources.LayoutKey()) append(key, word);
append(key, resources.FragmentLayoutKey().size());
for (const auto word : resources.FragmentLayoutKey()) append(key, word);
append(key, state.hasColorTarget);
append(key, state.rectList);
append(key, state.topology);

View File

@ -8,6 +8,7 @@
#include <thread>
#include <map>
#include <mutex>
#include <optional>
#include <string_view>
namespace AgcDriver::Graphics {
@ -111,12 +112,22 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
device.destroyLayout = context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout");
device.destroyModule = context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule");
}
auto pipelineLayout = device.layouts.find(keys.layout);
if (pipelineLayout == device.layouts.end()) {
const auto layoutFor = [&](const Key& key, std::optional<std::uint32_t> set) {
auto found = device.layouts.find(key);
if (found != device.layouts.end()) return found->second;
auto info = layout;
std::array<VkDescriptorSetLayout, 2> sets{VK_NULL_HANDLE, VK_NULL_HANDLE};
if (set) {
sets.at(*set) = layout.pSetLayouts[*set];
info.pSetLayouts = sets.data();
}
VkPipelineLayout handle = VK_NULL_HANDLE;
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &layout, nullptr, &handle), "vkCreatePipelineLayout library");
pipelineLayout = device.layouts.emplace(keys.layout, handle).first;
}
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &info, nullptr, &handle), "vkCreatePipelineLayout library");
return device.layouts.emplace(key, handle).first->second;
};
const bool split = (layout.flags & VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT) != 0;
const auto fullLayout = layoutFor(keys.layout, std::nullopt);
const std::array<VkPipelineLayout, 4> partLayouts{VK_NULL_HANDLE, split ? layoutFor(keys.preRasterizationLayout, 0u) : fullLayout, split ? layoutFor(keys.fragmentLayout, 1u) : fullLayout, VK_NULL_HANDLE};
std::vector<VkPipelineShaderStageCreateInfo> preRasterization;
const VkPipelineShaderStageCreateInfo* fragment = nullptr;
bool mesh = false;
@ -135,7 +146,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
};
const std::array<const Key*, 4> partKeys{&keys.vertexInput, &keys.preRasterization, &keys.fragmentShader, &keys.fragmentOutput};
const Key none;
const std::array<const Key*, 4> contextKeys{&none, &keys.layout, &keys.layout, &keys.renderPass};
const std::array<const Key*, 4> contextKeys{&none, &keys.preRasterizationLayout, &keys.fragmentLayout, &keys.renderPass};
const VkPipelineRenderingCreateInfoKHR shaderRendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
VkPipelineDepthStencilStateCreateInfo dynamicDepth{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
static const bool backgroundLinking = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr;
@ -155,7 +166,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library};
create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT;
create.pDynamicState = info.pDynamicState;
create.layout = pipelineLayout->second;
create.layout = partLayouts[part];
switch (flags[part]) {
case VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT:
create.pVertexInputState = info.pVertexInputState;
@ -212,7 +223,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
*optimized = slot;
const auto createPipelines = context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines");
const auto cache = context.pipelineCache;
const auto libraryLayout = pipelineLayout->second;
const auto libraryLayout = fullLayout;
enqueue(store, context.device, [slot, createPipelines, cache, libraryLayout, libraries, &store, devicePointer = &device] {
if (slot->released.load()) return;
VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR};

View File

@ -1166,6 +1166,14 @@ VkDescriptorSetLayout CreateSetLayout(const Context& context, std::span<const Vk
return layout;
}
std::vector<VkDescriptorPoolSize> PoolSizes(std::span<const VkDescriptorSetLayoutBinding> bindings) {
std::map<VkDescriptorType, std::uint32_t> counts;
for (const auto& binding : bindings) counts[binding.descriptorType] += binding.descriptorCount;
std::vector<VkDescriptorPoolSize> sizes;
for (const auto& [type, count] : counts) sizes.push_back({type, count});
return sizes;
}
VkDescriptorPool CreateSetPool(const Context& context, std::span<const VkDescriptorPoolSize> sizes, bool updateAfterBind) {
VkDescriptorPoolCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
info.flags = updateAfterBind ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0;
@ -1339,7 +1347,7 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
updateAfterBind = true;
};
for (const auto& binding : shader.program->bindings) {
Require(binding.descriptorSet == 0, "unexpected descriptor set: every shader resource must use descriptor set zero");
Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shader.stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "unexpected descriptor set: a shader resource must use its stage's descriptor set");
Require(occupied.insert(binding.binding).second, "duplicate shader binding");
const bool addressRole = binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer;
const bool bufferRole = addressRole || binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers || binding.role == ShaderRecompiler::DescriptorRole::ShaderData || binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt || binding.role == ShaderRecompiler::DescriptorRole::Gds;
@ -1416,23 +1424,62 @@ void ShaderResources::buildPrepare(std::span<const CompiledShader> shaders, cons
if (drawBuild && !usesBda && std::all_of(allocations.begin(), allocations.end(), [&](const Allocation& allocation) { return !allocation.guest || allocation.pushByte >= 0 || (allocation.dataAllocation >= 0 && allocation.dataByte < allocations[static_cast<std::size_t>(allocation.dataAllocation)].size); })) guestMemory.AllowAdjustedRegions();
guestMemory.UploadPrepare(usesBda);
timing.uploadMs = phase(BuildPhase::Upload);
std::vector<VkDescriptorSetLayoutBinding> description;
splitSets = std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.program != nullptr && std::any_of(shader.program->bindings.begin(), shader.program->bindings.end(), [](const auto& binding) { return binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet; }); });
std::array<std::vector<VkDescriptorSetLayoutBinding>, 2> descriptions;
for (const auto& binding : bindings) {
description.push_back(binding.layout);
layoutBindingFlags.push_back(updateAfterBind && binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0);
layoutKey.insert(layoutKey.end(), {binding.layout.binding, static_cast<std::uint32_t>(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags});
const auto set = setOf(binding);
descriptions[set].push_back(binding.layout);
(set == 0 ? layoutBindingFlags : fragmentBindingFlags).push_back(updateAfterBind && binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0);
auto& key = set == 0 ? layoutKey : fragmentLayoutKey;
key.insert(key.end(), {binding.layout.binding, static_cast<std::uint32_t>(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags});
}
if (updateAfterBind) {
layoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
if (splitSets) fragmentLayoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
}
if (updateAfterBind) layoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
static const bool noLayoutCache = std::getenv("APS5_NO_LAYOUT_CACHE") != nullptr;
static const bool noPoolCache = std::getenv("APS5_NO_POOL_CACHE") != nullptr;
if (context.descriptorCache != nullptr && !noLayoutCache) {
_layout = context.descriptorCache->Layout(layoutKey, description, updateAfterBind, layoutBindingFlags);
} else {
const auto createLayout = [&](const std::vector<std::uint32_t>& key, const std::vector<VkDescriptorSetLayoutBinding>& description, const std::vector<VkDescriptorBindingFlags>& flags, bool& owns) {
if (context.descriptorCache != nullptr && !noLayoutCache) return context.descriptorCache->Layout(key, description, updateAfterBind, flags);
ValidateDescriptorLayout(context, description, updateAfterBind);
_layout = CreateSetLayout(context, description, updateAfterBind, layoutBindingFlags);
ownsLayout = true;
owns = true;
return CreateSetLayout(context, description, updateAfterBind, flags);
};
_layout = createLayout(layoutKey, descriptions[0], layoutBindingFlags, ownsLayout);
if (splitSets) {
fragment.layout = createLayout(fragmentLayoutKey, descriptions[1], fragmentBindingFlags, fragment.ownsLayout);
if (!descriptions[1].empty()) {
const auto sizes = PoolSizes(descriptions[1]);
if (context.descriptorCache != nullptr && !noPoolCache) {
fragment.cacheAllocation = context.descriptorCache->Allocate(fragment.layout, sizes, updateAfterBind);
fragment.set = fragment.cacheAllocation.set;
}
if (fragment.set == VK_NULL_HANDLE) {
fragment.pool = CreateSetPool(context, sizes, updateAfterBind);
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocation.descriptorPool = fragment.pool;
allocation.descriptorSetCount = 1;
allocation.pSetLayouts = &fragment.layout;
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &fragment.set), "vkAllocateDescriptorSets");
}
}
}
if (!bindings.empty()) {
if (splitSets && !descriptions[0].empty()) {
const auto sizes = PoolSizes(descriptions[0]);
if (context.descriptorCache != nullptr && !noPoolCache) {
cacheAllocation = context.descriptorCache->Allocate(_layout, sizes, updateAfterBind);
_set = cacheAllocation.set;
}
if (_set == VK_NULL_HANDLE) {
pool = CreateSetPool(context, sizes, updateAfterBind);
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocation.descriptorPool = pool;
allocation.descriptorSetCount = 1;
allocation.pSetLayouts = &_layout;
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &_set), "vkAllocateDescriptorSets");
}
}
if (!splitSets && !bindings.empty()) {
// The set is sized from the plan: every image element becomes exactly one descriptor
// when stage B looks it up.
std::vector<VkDescriptorPoolSize> sizes;
@ -1492,7 +1539,7 @@ void ShaderResources::buildComplete() {
if (usesBda) bda = std::make_unique<BdaResources>(context, guestMemory);
else if (usesFaultBuffer) bda = std::make_unique<BdaResources>(context);
phase(BuildPhase::Bda);
if (_set != VK_NULL_HANDLE) {
if (_set != VK_NULL_HANDLE || fragment.set != VK_NULL_HANDLE) {
// One update call for the whole set: the info arrays are sized up front so every write's
// pointer into them stays valid until the call.
std::size_t bufferCount = 0;
@ -1514,7 +1561,7 @@ void ShaderResources::buildComplete() {
};
for (const auto& binding : bindings) {
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = _set;
write.dstSet = setOf(binding) == 0 ? _set : fragment.set;
write.dstBinding = binding.layout.binding;
write.descriptorCount = binding.layout.descriptorCount;
write.descriptorType = binding.layout.descriptorType;
@ -3334,10 +3381,19 @@ void ShaderResources::release() noexcept {
if (cacheAllocation.pool && context.descriptorCache != nullptr) context.descriptorCache->Free(cacheAllocation);
if (pool) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
if (_layout && ownsLayout) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, _layout, nullptr);
if (fragment.cacheAllocation.pool && context.descriptorCache != nullptr) context.descriptorCache->Free(fragment.cacheAllocation);
if (fragment.pool) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, fragment.pool, nullptr);
if (fragment.layout && fragment.ownsLayout) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, fragment.layout, nullptr);
cacheAllocation = {};
pool = VK_NULL_HANDLE;
_set = VK_NULL_HANDLE;
_layout = VK_NULL_HANDLE;
fragment = {};
}
std::uint32_t ShaderResources::setOf(const Binding& binding) const {
constexpr auto count = static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Binding::Count);
return splitSets && binding.layout.binding / count == static_cast<std::uint32_t>(ShaderRecompiler::RuntimeAbi::Stage::Fragment) ? 1u : 0u;
}
VkDescriptorSetLayout ShaderResources::Layout() const {
@ -3356,11 +3412,12 @@ bool ShaderResources::LayoutUpdateAfterBind() const {
ShaderResources::DrawBindings::~DrawBindings() {
if (cache != nullptr && allocation.set != VK_NULL_HANDLE) cache->Free(allocation);
if (cache != nullptr && fragmentAllocation.set != VK_NULL_HANDLE) cache->Free(fragmentAllocation);
}
std::optional<std::vector<ShaderResources::MovedBuffer>> ShaderResources::MovedReadOnlyBuffers(std::span<const CompiledShader> shaders, Recorder& recorder) const {
std::vector<MovedBuffer> moved;
if (_set == VK_NULL_HANDLE || usesBda) return moved;
if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return moved;
for (const auto& shader : shaders) {
if (shader.program == nullptr) return std::nullopt;
for (const auto& binding : shader.program->bindings) {
@ -3413,7 +3470,7 @@ std::size_t ShaderResources::MaxDrawSnapshotBytes() {
}
std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span<const MovedBuffer> moved) const {
if (_set == VK_NULL_HANDLE || usesBda) return {};
if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return {};
const auto reads = guestMemory.InPlaceReads();
const auto maxSnapshotBytes = MaxDrawSnapshotBytes();
std::size_t totalSnapshotBytes = 0;
@ -3460,13 +3517,17 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
}
if (selected.empty()) return {};
Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache");
std::map<VkDescriptorType, std::uint32_t> counts;
for (const auto& binding : bindings) counts[binding.layout.descriptorType] += binding.layout.descriptorCount;
std::vector<VkDescriptorPoolSize> sizes;
for (const auto& [type, count] : counts) sizes.push_back({type, count});
std::array<std::vector<VkDescriptorSetLayoutBinding>, 2> descriptions;
for (const auto& binding : bindings) descriptions[setOf(binding)].push_back(binding.layout);
result->cache = context.descriptorCache;
result->allocation = result->cache->Allocate(_layout, sizes);
Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed");
if (!descriptions[0].empty()) {
result->allocation = result->cache->Allocate(_layout, PoolSizes(descriptions[0]));
Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed");
}
if (!descriptions[1].empty()) {
result->fragmentAllocation = result->cache->Allocate(fragment.layout, PoolSizes(descriptions[1]));
Require(result->fragmentAllocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed");
}
auto buffers = descriptorBuffers;
auto writes = descriptorWrites;
std::vector<std::size_t> snapshotIndices(allocations.size(), selected.size());
@ -3474,7 +3535,7 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
for (std::size_t index = 0; index < bindings.size(); ++index) {
const auto& binding = bindings[index];
auto& write = writes[index];
write.dstSet = result->allocation.set;
write.dstSet = setOf(binding) == 0 ? result->allocation.set : result->fragmentAllocation.set;
if (binding.allocations.empty()) continue;
const auto offset = static_cast<std::size_t>(write.pBufferInfo - descriptorBuffers.data());
write.pBufferInfo = buffers.data() + offset;
@ -3492,8 +3553,8 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
}
void ShaderResources::Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const {
if (_set == VK_NULL_HANDLE) return;
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr);
if (_set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr);
if (fragment.set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet, 1, &fragment.set, 0, nullptr);
}
namespace {

View File

@ -574,7 +574,7 @@ std::set<std::uint32_t> ValidateShaders(std::span<const CompiledShader> shaders,
const auto expected = state.rectList ? (i == 0 ? Stage::Vertex : tessStages[i]) : tessellation ? tessStages[i] : geometry && i == 1 ? Stage::Geometry : i == shaders.size() - 1 ? Stage::Fragment : state.stages.path == ShaderPath::Geometry ? Stage::Mesh : Stage::Vertex;
Require(shaders[i].program != nullptr, "missing compiled shader");
Require(shaders[i].stage == expected, "graphics stage order disagrees");
for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == 0, "graphics resource uses a descriptor set other than zero");
for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shaders[i].stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "graphics resource uses a descriptor set other than its stage's");
const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance);
if (i != 0) {
for (const auto& [location, signature] : current.inputs) {

View File

@ -4388,7 +4388,7 @@ void validationTests() {
{
auto vertex = pushed({.push = true, .bufferArray = 2});
vertex.bindings.front().descriptorSet = 1;
expectFailure([&] { validate(vertex, 8); }, "descriptor set other than zero");
expectFailure([&] { validate(vertex, 8); }, "descriptor set other than its stage's");
}
{
auto vertex = pushed({.push = true, .bufferArray = 2});

View File

@ -103,7 +103,7 @@ BindingAllocationResult BindingAllocator::Allocate(IrProgram& program, const Bin
if (info.buffers.size() > RuntimeAbi::BufferCapacity || info.images.size() > RuntimeAbi::ImageCapacity || info.samplers.size() > RuntimeAbi::SamplerHeapCapacity) fail("shader binding layout exceeds runtime metadata capacity");
if (std::ranges::any_of(next.userDataRegisters, [](auto reg) { return reg >= RuntimeAbi::UserDataCapacity; })) fail("shader user data exceeds runtime ABI capacity");
if (registrationModules || std::ranges::any_of(info.images, [](const auto& image) { return image.indirectRoot != ImageResource::NoIndirectImage; })) next.runtimeImageCount = static_cast<std::uint32_t>(info.images.size());
if (registrationModules && (program.Resources().stage == IrShaderStage::Pixel || std::ranges::any_of(info.samplers, [](const auto& sampler) { return FoldsTexelOffsets(sampler); }))) next.runtimeStateDwords = RuntimeAbi::StateDwords;
if (registrationModules && program.Resources().stage == IrShaderStage::Pixel) next.runtimeStateDwords = RuntimeAbi::StateDwords;
const auto pushSize = layout.pushConstantSizeBytes / 4u;
next.pushDataStartDword = next.runtimeImageCount == 0u && next.ShaderDataDwords() != 0u && next.ShaderDataDwords() <= pushSize ? layout.pushConstantOffsetBytes / 4u : PushData::NoStart;

View File

@ -257,11 +257,8 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity");
DescriptorBindingPlan plan;
const auto unnormalized = ProveUnnormalized(info, snapshot);
std::uint32_t unnormalizedSamplers = 0u;
for (std::uint32_t index = 0; index < info.samplers.size(); ++index) {
if (!FoldsTexelOffsets(info.samplers[index])) continue;
if (!registrationModules) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u});
else if (unnormalized.samplers[index]) unnormalizedSamplers |= 1u << index;
if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u});
}
std::vector<std::uint32_t> compareStates(info.images.size());
for (std::uint32_t index = 0; index < info.images.size(); ++index) {
@ -382,7 +379,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
if ((imageHeap || samplerHeap) && compact.resources.empty()) continue;
PreparedDescriptorBinding entry{};
auto& physical = entry.descriptor;
physical.descriptorSet = RuntimeAbi::DescriptorSet;
physical.descriptorSet = registrationModules && stage == IrShaderStage::Pixel ? RuntimeAbi::FragmentDescriptorSet : RuntimeAbi::DescriptorSet;
physical.binding = NativeBinding(stage, compact.kind);
physical.count = compact.resources.empty() ? 1u : static_cast<std::uint32_t>(compact.resources.size());
physical.kind = PhysicalKindFor(compact.kind);
@ -459,7 +456,6 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l
}
if (layout.runtimeStateDwords != 0u) {
plan.runtimeState.assign(layout.runtimeStateDwords, 0u);
plan.runtimeState.at(RuntimeAbi::StateUnnormalizedSamplers) = unnormalizedSamplers;
for (std::uint32_t target = 0; target < exportMappings.size(); ++target) plan.runtimeState.at(RuntimeAbi::StateExportMappings + target / 4u) |= static_cast<std::uint32_t>(exportMappings[target]) << (target % 4u * 8u);
}
std::vector<std::uint32_t> samplerMasks(info.samplers.size());

View File

@ -11,6 +11,7 @@ namespace ShaderRecompiler::RuntimeAbi {
inline constexpr std::uint32_t Version = 11u;
inline constexpr std::uint32_t DescriptorSet = 0u;
inline constexpr std::uint32_t FragmentDescriptorSet = 1u;
inline constexpr std::uint32_t StageCount = 4u;
inline constexpr std::uint32_t PushConstantDwords = 32u;
inline constexpr std::uint32_t FirstImageBinding = 1u;
@ -39,9 +40,8 @@ inline constexpr std::uint32_t BindlessTableSlots = 16u;
inline constexpr std::uint32_t StorageMipSlots = 4u;
inline constexpr std::uint32_t StorageHeapCapacity = 16u;
inline constexpr std::uint32_t SamplerHeapCapacity = 32u;
inline constexpr std::uint32_t StateUnnormalizedSamplers = 0u;
inline constexpr std::uint32_t StateExportMappings = 1u;
inline constexpr std::uint32_t StateDwords = 3u;
inline constexpr std::uint32_t StateExportMappings = 0u;
inline constexpr std::uint32_t StateDwords = 2u;
inline constexpr std::uint32_t ImageModeMask = 0xffu;
inline constexpr std::uint32_t CompareStateShift = 9u;
inline constexpr std::uint32_t CompareStateMask = 0x1fffu;

View File

@ -1494,7 +1494,7 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con
} else if (!offsetOperand) {
sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands);
} else {
const auto selector = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.RuntimeStateDword() + RuntimeAbi::StateUnnormalizedSamplers), ConstantU32(state, mem.sampler)), 1u) : state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u);
const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u);
const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u));
const auto foldLabel = state.module.AllocateId();
const auto operandLabel = state.module.AllocateId();

View File

@ -391,7 +391,7 @@ void DefineDescriptors(SpirvEmitterState& state) {
const auto Define = [&](std::uint32_t type, const char* name, std::uint32_t storage = spv::StorageClassStorageBuffer) {
const auto variable = state.module.DefineGlobalVariable(TypePointer(state, storage, type), storage);
state.module.AddName(variable, name);
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0u);
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, state.registrationModules && stage == IrShaderStage::Pixel ? RuntimeAbi::FragmentDescriptorSet : RuntimeAbi::DescriptorSet);
state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding, NativeBinding(stage, binding.kind));
return variable;
};