Merge pull request #4456 from Mouhand-Kaddo/fix/agc-library-used-descriptor-sets

fix(agc): retain used descriptor sets in pipeline libraries
This commit is contained in:
Dean 2026-10-13 10:41:32 +13:00 committed by GitHub
commit 8a67eb2e68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 95 additions and 9 deletions

View File

@ -21,6 +21,8 @@ struct PipelineLibraryKeys {
std::vector<std::byte> layout;
std::vector<std::byte> preRasterizationLayout;
std::vector<std::byte> fragmentLayout;
std::uint32_t preRasterizationSets = 0;
std::uint32_t fragmentSets = 0;
};
std::span<const VkDynamicState> PipelineLibraryDynamicStates();

View File

@ -182,6 +182,13 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
appendKey(key, shader.stage);
appendKey(key, shader.program->PipelineVariantId());
appendKey(key, shader.pushConstantOffset);
if (resources.split) {
auto& sets = shader.stage == Stage::Fragment ? keys.fragmentSets : keys.preRasterizationSets;
for (const auto& binding : shader.program->bindings) {
Require(binding.descriptorSet < 2u, "split pipeline library descriptor set exceeds two slots");
sets |= 1u << binding.descriptorSet;
}
}
}
for (const auto& binding : input.bindings) {
appendKey(keys.vertexInput, binding.binding);
@ -246,8 +253,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
}
};
splitKey(keys.layout, 3u);
splitKey(keys.preRasterizationLayout, 1u);
splitKey(keys.fragmentLayout, 2u);
splitKey(keys.preRasterizationLayout, keys.preRasterizationSets);
splitKey(keys.fragmentLayout, keys.fragmentSets);
return true;
}

View File

@ -104,6 +104,8 @@ std::span<const VkDynamicState> PipelineLibraryDynamicStates() {
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys, std::shared_ptr<OptimizedPipeline>* optimized, bool partsOnly, std::span<VkShaderModule> modules) {
Require(info.renderPass == VK_NULL_HANDLE && info.pDynamicState != nullptr, "pipeline library parts need dynamic rendering and the library dynamic states");
const bool split = (layout.flags & VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT) != 0;
Require(!split || (layout.setLayoutCount == 2u && layout.pSetLayouts != nullptr && ((keys.preRasterizationSets | keys.fragmentSets) & ~3u) == 0u), "split pipeline library layout requires two descriptor set slots and a valid mask");
auto& store = Libraries();
std::unique_lock lock(store.mutex);
auto& device = store.devices[context.device];
@ -112,22 +114,23 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
device.destroyLayout = context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout");
device.destroyModule = context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule");
}
const auto layoutFor = [&](const Key& key, std::optional<std::uint32_t> set) {
const auto layoutFor = [&](const Key& key, std::optional<std::uint32_t> mask) {
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];
if (mask.has_value()) {
for (std::uint32_t set = 0; set < sets.size(); ++set) {
if ((*mask & (1u << set)) != 0u) sets[set] = layout.pSetLayouts[set];
}
info.pSetLayouts = sets.data();
}
VkPipelineLayout handle = VK_NULL_HANDLE;
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};
const std::array<VkPipelineLayout, 4> partLayouts{VK_NULL_HANDLE, split ? layoutFor(keys.preRasterizationLayout, keys.preRasterizationSets) : fullLayout, split ? layoutFor(keys.fragmentLayout, keys.fragmentSets) : fullLayout, VK_NULL_HANDLE};
std::vector<VkPipelineShaderStageCreateInfo> preRasterization;
const VkPipelineShaderStageCreateInfo* fragment = nullptr;
bool mesh = false;

View File

@ -2257,6 +2257,10 @@ struct MockVulkan {
std::uint32_t boundFirst = 0;
VkPipelineBindPoint boundPoint = VK_PIPELINE_BIND_POINT_MAX_ENUM;
std::map<VkPipelineLayout, VkDeviceSize> pipelineLayoutPushConstantSize;
std::map<VkPipelineLayout, std::vector<VkDescriptorSetLayout>> pipelineLayoutSets;
std::map<VkPipelineLayout, VkPipelineLayoutCreateFlags> pipelineLayoutFlags;
std::map<VkPipelineLayout, std::vector<VkPushConstantRange>> pipelineLayoutPushRanges;
std::map<VkGraphicsPipelineLibraryFlagsEXT, VkPipelineLayout> libraryLayouts;
std::uint32_t pipelineCreateCount = 0;
std::vector<std::array<std::uint32_t, 3>> pipelineSpecializations;
VkPipeline boundPipeline = VK_NULL_HANDLE;
@ -2415,6 +2419,19 @@ VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const
VKAPI_ATTR VkResult VKAPI_CALL mockCreatePipelineLayout(VkDevice, const VkPipelineLayoutCreateInfo* info, const VkAllocationCallbacks*, VkPipelineLayout* layout) {
*layout = makeHandle<VkPipelineLayout>();
mock.pipelineLayoutPushConstantSize[*layout] = info->pushConstantRangeCount > 0 ? info->pPushConstantRanges[0].size : 0;
if (info->setLayoutCount != 0) mock.pipelineLayoutSets[*layout].assign(info->pSetLayouts, info->pSetLayouts + info->setLayoutCount);
mock.pipelineLayoutFlags[*layout] = info->flags;
if (info->pushConstantRangeCount != 0) mock.pipelineLayoutPushRanges[*layout].assign(info->pPushConstantRanges, info->pPushConstantRanges + info->pushConstantRangeCount);
++mock.live;
return VK_SUCCESS;
}
VKAPI_ATTR VkResult VKAPI_CALL mockCreateGraphicsPipelines(VkDevice, VkPipelineCache, std::uint32_t count, const VkGraphicsPipelineCreateInfo* infos, const VkAllocationCallbacks*, VkPipeline* pipelines) {
Require(count == 1 && (infos[0].flags & VK_PIPELINE_CREATE_LIBRARY_BIT_KHR) != 0, "mock expects a graphics pipeline library");
const auto* library = static_cast<const VkGraphicsPipelineLibraryCreateInfoEXT*>(infos[0].pNext);
Require(library != nullptr && library->sType == VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, "missing graphics pipeline library description");
mock.libraryLayouts[library->flags] = infos[0].layout;
*pipelines = makeHandle<VkPipeline>();
++mock.live;
return VK_SUCCESS;
}
@ -2429,8 +2446,8 @@ VKAPI_ATTR VkResult VKAPI_CALL mockCreateShaderModule(VkDevice, const VkShaderMo
return VK_SUCCESS;
}
VKAPI_ATTR void VKAPI_CALL mockDestroyShaderModule(VkDevice, VkShaderModule, const VkAllocationCallbacks*) {
--mock.live;
VKAPI_ATTR void VKAPI_CALL mockDestroyShaderModule(VkDevice, VkShaderModule module, const VkAllocationCallbacks*) {
if (module != VK_NULL_HANDLE) --mock.live;
}
VKAPI_ATTR VkResult VKAPI_CALL mockCreateComputePipelines(VkDevice, VkPipelineCache, std::uint32_t count, const VkComputePipelineCreateInfo* infos, const VkAllocationCallbacks*, VkPipeline* pipelines) {
@ -2528,6 +2545,7 @@ PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
{"vkResetDescriptorPool", reinterpret_cast<PFN_vkVoidFunction>(mockResetDescriptorPool)},
{"vkUpdateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockUpdateDescriptorSets)},
{"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockCmdBindDescriptorSets)},
{"vkCreateGraphicsPipelines", reinterpret_cast<PFN_vkVoidFunction>(mockCreateGraphicsPipelines)},
{"vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(mockCreatePipelineLayout)},
{"vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(mockDestroyPipelineLayout)},
{"vkCreateShaderModule", reinterpret_cast<PFN_vkVoidFunction>(mockCreateShaderModule)},
@ -3384,6 +3402,61 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
return words;
}
void pipelineLibrarySetTests() {
using namespace AgcDriver::Graphics;
for (const auto mask : {0u, 1u, 2u, 3u}) {
mock = MockVulkan{};
auto context = mockContext();
context.graphicsPipelineLibrary = true;
context.limits.framebufferNoAttachmentsSampleCounts = VK_SAMPLE_COUNT_1_BIT;
State state{};
ShaderRecompiler::RecompileResult vertex, fragment;
vertex.spirv = makeModule({});
fragment.spirv = makeModule({.fragment = true});
vertex.variantId = 1;
fragment.variantId = 2;
for (std::uint32_t set = 0; set < 2; ++set) {
ShaderRecompiler::DescriptorBinding binding{};
binding.descriptorSet = set;
if ((mask & (1u << set)) != 0u) fragment.bindings.push_back(binding);
else vertex.bindings.push_back(binding);
}
const std::array<CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}};
std::array<std::uint32_t, 1> mainKey{11}, fragmentKey{22};
PipelineSetLayouts resources{makeHandle<VkDescriptorSetLayout>(), makeHandle<VkDescriptorSetLayout>(), true, mainKey, fragmentKey};
const auto prebuild = [&] { Require(Pipeline::Prebuild(context, state, VertexInputLayout{}, resources, shaders), "graphics pipeline library prebuild was skipped"); };
prebuild();
for (const auto part : {VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT, VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT}) {
const auto layout = mock.libraryLayouts.at(part);
const auto expectedMask = part == VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT ? mask : mask ^ 3u;
const std::vector<VkDescriptorSetLayout> expected{(expectedMask & 1u) != 0u ? resources.main : VK_NULL_HANDLE, (expectedMask & 2u) != 0u ? resources.fragment : VK_NULL_HANDLE};
Require(mock.pipelineLayoutSets.at(layout) == expected, "shader library lost a used set or retained an unused set");
Require(mock.pipelineLayoutFlags.at(layout) == VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT, "shader library lost independent sets");
const auto& pushes = mock.pipelineLayoutPushRanges.at(layout);
Require(pushes.size() == 2 && pushes[0].offset == 0 && pushes[0].size == PipelinePushSlotBytes && pushes[0].stageFlags == VK_SHADER_STAGE_VERTEX_BIT && pushes[1].offset == PipelinePushSlotBytes && pushes[1].size == PipelinePushSlotBytes && pushes[1].stageFlags == VK_SHADER_STAGE_FRAGMENT_BIT, "shader library changed push constant slots");
}
auto before = PipelineLibraryCountersOf(context.device).built;
prebuild();
Require(PipelineLibraryCountersOf(context.device).built == before, "identical shader libraries were rebuilt");
for (std::uint32_t set = 0; set < 2; ++set) {
if (set == 0) { ++mainKey[0]; resources.main = makeHandle<VkDescriptorSetLayout>(); }
else { ++fragmentKey[0]; resources.fragment = makeHandle<VkDescriptorSetLayout>(); }
prebuild();
const auto after = PipelineLibraryCountersOf(context.device).built;
Require(after[0] == before[0] && after[3] == before[3], "descriptor layout change rebuilt interface libraries");
Require(after[1] == before[1] + ((mask & (1u << set)) == 0u ? 1u : 0u), "pre-rasterization library reuse ignored descriptor set usage");
Require(after[2] == before[2] + ((mask & (1u << set)) != 0u ? 1u : 0u), "fragment library reuse ignored descriptor set usage");
before = after;
}
ShaderRecompiler::DescriptorBinding invalid{};
invalid.descriptorSet = 32;
fragment.bindings.push_back(invalid);
expectFailure(prebuild, "descriptor set exceeds two slots");
ClearPipelineLibraries(context.device);
Require(mock.live == 0, "shader library tests leaked Vulkan objects");
}
}
void barycentricEmulationTests() {
using namespace ShaderRecompiler;
using namespace AgcDriver::Graphics;
@ -4983,6 +5056,7 @@ int main(int argc, char** argv) {
}
RunGuestLeaseWaitTests();
stateTests();
pipelineLibrarySetTests();
hdrComponentSwapTests();
depthMaintenanceTests();
hardwareScreenOffsetTests();