perf(agc): render with dynamic rendering when pipelines come from libraries
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
be403084c6
commit
0c20cab656
|
|
@ -1034,18 +1034,22 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
nonSeamlessCubeFeatures.nonSeamlessCubeMap = VK_TRUE;
|
||||
VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT libraryFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT};
|
||||
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicStateFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT};
|
||||
if (std::getenv("APS5_NO_GPL") == nullptr && hasExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME) && hasExtension(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME) && hasExtension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceDynamicRenderingFeaturesKHR renderingFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR};
|
||||
constexpr std::array<const char*, 6> libraryExtensions{VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME};
|
||||
if (std::getenv("APS5_NO_GPL") == nullptr && std::all_of(libraryExtensions.begin(), libraryExtensions.end(), hasExtension)) {
|
||||
dynamicStateFeatures.pNext = &renderingFeatures;
|
||||
libraryFeatures.pNext = &dynamicStateFeatures;
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &libraryFeatures};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
|
||||
VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT libraryProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &libraryProperties};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &properties);
|
||||
state->graphicsPipelineLibrary = libraryFeatures.graphicsPipelineLibrary == VK_TRUE && dynamicStateFeatures.extendedDynamicState == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE;
|
||||
if (state->graphicsPipelineLibrary) deviceExtensions.insert(deviceExtensions.end(), {VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME});
|
||||
state->graphicsPipelineLibrary = libraryFeatures.graphicsPipelineLibrary == VK_TRUE && dynamicStateFeatures.extendedDynamicState == VK_TRUE && renderingFeatures.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE;
|
||||
if (state->graphicsPipelineLibrary) deviceExtensions.insert(deviceExtensions.end(), libraryExtensions.begin(), libraryExtensions.end());
|
||||
}
|
||||
renderingFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, nullptr, VK_TRUE};
|
||||
libraryFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, &dynamicStateFeatures, VK_TRUE};
|
||||
dynamicStateFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, nullptr, VK_TRUE};
|
||||
dynamicStateFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, &renderingFeatures, VK_TRUE};
|
||||
if (state->meshShader) {
|
||||
deviceExtensions.insert(deviceExtensions.end(), meshExtensions.begin(), meshExtensions.end());
|
||||
state->capabilities.push_back(5283);
|
||||
|
|
@ -1154,7 +1158,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
|
|||
deviceInfo.pNext = &depthClipFeatures;
|
||||
}
|
||||
if (state->graphicsPipelineLibrary) {
|
||||
dynamicStateFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
|
||||
renderingFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
|
||||
deviceInfo.pNext = &libraryFeatures;
|
||||
}
|
||||
if (state->primitiveListRestart) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ struct DeviceFunctions {
|
|||
PFN_vkCmdDispatchIndirect cmdDispatchIndirect = nullptr;
|
||||
PFN_vkCmdBeginRenderPass cmdBeginRenderPass = nullptr;
|
||||
PFN_vkCmdEndRenderPass cmdEndRenderPass = nullptr;
|
||||
PFN_vkCmdBeginRenderingKHR cmdBeginRendering = nullptr;
|
||||
PFN_vkCmdEndRenderingKHR cmdEndRendering = nullptr;
|
||||
PFN_vkCmdSetViewport cmdSetViewport = nullptr;
|
||||
PFN_vkCmdSetScissor cmdSetScissor = nullptr;
|
||||
PFN_vkCmdSetDepthBounds cmdSetDepthBounds = nullptr;
|
||||
|
|
@ -188,6 +190,11 @@ struct Context {
|
|||
}
|
||||
};
|
||||
|
||||
inline void EndRenderPass(const Context& context, VkCommandBuffer commands) {
|
||||
if (context.graphicsPipelineLibrary) context.Resolved(&DeviceFunctions::cmdEndRendering, "vkCmdEndRenderingKHR")(commands);
|
||||
else context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(commands);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ public:
|
|||
Framebuffer(const Framebuffer&) = delete;
|
||||
Framebuffer& operator=(const Framebuffer&) = delete;
|
||||
VkFramebuffer Handle() const { return framebuffer; }
|
||||
std::span<const VkImageView> Views() const { return views; }
|
||||
// Forgets the handle without destroying it (the device it belongs to is already gone).
|
||||
void Abandon() noexcept;
|
||||
|
||||
private:
|
||||
Context context;
|
||||
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||
std::vector<VkImageView> views;
|
||||
};
|
||||
|
||||
// The shader modules, layout, render pass and VkPipeline of one draw configuration. Viewport,
|
||||
|
|
@ -74,6 +76,10 @@ private:
|
|||
bool depthBounds = false;
|
||||
bool depthBias = false;
|
||||
bool libraries = false;
|
||||
bool dynamicRendering = false;
|
||||
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
||||
VkImageLayout attachmentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
std::vector<std::uint32_t> colorSlots;
|
||||
std::vector<CachedFramebuffer> framebuffers;
|
||||
};
|
||||
|
||||
|
|
@ -88,6 +94,7 @@ std::shared_ptr<Pipeline> CachedPipeline(const Context& context, const State& st
|
|||
// entries of a gone device are recognised by their buffer pool (made and reset with the device, so
|
||||
// it tells device instances apart when the loader reuses a VkDevice handle) and forgotten unused.
|
||||
void ClearCachedPipelines(VkDevice device);
|
||||
bool HasStencil(VkFormat format);
|
||||
// Device limit checks of the viewport, which is dynamic state and so no longer checked by Pipeline.
|
||||
void ValidateViewport(const Context& context, const VkViewport& viewport);
|
||||
void ValidateDepthBounds(const Context& context, const State& state);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ struct PipelineLibraryKeys {
|
|||
|
||||
std::span<const VkDynamicState> PipelineLibraryDynamicStates();
|
||||
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkRenderPassCreateInfo& pass, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys);
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys);
|
||||
|
||||
void ClearPipelineLibraries(VkDevice device);
|
||||
|
||||
|
|
|
|||
|
|
@ -2151,7 +2151,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
|||
auto checkRecords = indirectRecordCheck(args != nullptr ? &indirect : nullptr);
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Draw recorded");
|
||||
if (recorded) recorder->EndPassSamples();
|
||||
context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(commands);
|
||||
EndRenderPass(context, commands);
|
||||
APS5_LOG_CHARS_OUT_DEBUG("Render pass ended");
|
||||
for (auto& binding : targets) {
|
||||
if (binding.proxied) {
|
||||
|
|
|
|||
|
|
@ -64,10 +64,16 @@ void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipel
|
|||
}
|
||||
}
|
||||
|
||||
bool HasStencil(VkFormat format) {
|
||||
return format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_S8_UINT;
|
||||
}
|
||||
|
||||
Framebuffer::Framebuffer(const Context& context, VkRenderPass renderPass, std::span<const VkImageView> targets, VkExtent2D extent) : context(context) {
|
||||
// Cached objects outlive their device's teardown; they must not keep its buffer pool alive past it.
|
||||
this->context.bufferPool.reset();
|
||||
Require(extent.width != 0 && extent.height != 0 && extent.width <= context.limits.maxFramebufferWidth && extent.height <= context.limits.maxFramebufferHeight, "framebuffer extent exceeds device limits");
|
||||
views.assign(targets.begin(), targets.end());
|
||||
if (renderPass == VK_NULL_HANDLE) return;
|
||||
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebufferInfo.renderPass = renderPass;
|
||||
framebufferInfo.attachmentCount = static_cast<std::uint32_t>(targets.size());
|
||||
|
|
@ -118,7 +124,7 @@ void appendKey(std::vector<std::byte>& key, const TValue& value) {
|
|||
key.insert(key.end(), bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& state, const VertexInputLayout& input, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) {
|
||||
bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& state, const VertexInputLayout& input, const ShaderResources& resources, std::span<const CompiledShader> shaders) {
|
||||
using Stage = ShaderRecompiler::ShaderStage;
|
||||
if (!context.graphicsPipelineLibrary || context.pipelineExecutableInfo || state.depthBiasPerFace || state.rectList) return false;
|
||||
keys = {};
|
||||
|
|
@ -150,10 +156,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
|
|||
appendKey(keys.preRasterization, state.depth.has_value() && state.depthBias);
|
||||
appendKey(keys.preRasterization, state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) appendKey(keys.preRasterization, state.stages.tessellation->inputControlPoints);
|
||||
appendKey(keys.fragmentShader, state.depth.has_value());
|
||||
for (const auto& blend : state.blends) appendKey(keys.fragmentOutput, blend);
|
||||
for (const auto value : state.blendConstants) appendKey(keys.fragmentOutput, value);
|
||||
appendKey(keys.renderPass, attachmentLayout);
|
||||
appendKey(keys.renderPass, state.blends.size());
|
||||
for (const auto& color : state.colors) {
|
||||
appendKey(keys.renderPass, color.format);
|
||||
|
|
@ -168,7 +172,7 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State&
|
|||
|
||||
}
|
||||
|
||||
Pipeline::Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) : context(context), _modules(shaders.size()), attachments(state.colors.size() + (state.depth ? 1u : 0u)), colorAttachments(state.colors.size()), depthBounds(state.depth.has_value() && state.depthBoundsTest), depthBias(state.depth.has_value() && state.depthBias) {
|
||||
Pipeline::Pipeline(const Context& context, const State& state, const VertexInputLayout& vertexInput, const ShaderResources& resources, std::span<const CompiledShader> shaders, VkImageLayout attachmentLayout) : context(context), _modules(shaders.size()), attachments(state.colors.size() + (state.depth ? 1u : 0u)), colorAttachments(state.colors.size()), depthBounds(state.depth.has_value() && state.depthBoundsTest), depthBias(state.depth.has_value() && state.depthBias), dynamicRendering(context.graphicsPipelineLibrary) {
|
||||
PerformanceTimer timing("Vulkan.GraphicsPipeline");
|
||||
// A cached pipeline may outlive its device's teardown (see ClearCachedPipelines); it must not keep
|
||||
// the buffer pool, which is reset with the device, alive past it.
|
||||
|
|
@ -258,12 +262,27 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
colors.push_back(depth);
|
||||
subpass.pDepthStencilAttachment = &depthReference;
|
||||
}
|
||||
std::vector<VkFormat> renderingColors(state.blends.size(), VK_FORMAT_UNDEFINED);
|
||||
for (const auto& color : state.colors) renderingColors.at(color.exportIndex) = color.format;
|
||||
VkPipelineRenderingCreateInfoKHR rendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
rendering.colorAttachmentCount = static_cast<std::uint32_t>(renderingColors.size());
|
||||
rendering.pColorAttachmentFormats = renderingColors.empty() ? nullptr : renderingColors.data();
|
||||
if (state.depth) {
|
||||
rendering.depthAttachmentFormat = state.depth->format;
|
||||
if (HasStencil(state.depth->format)) rendering.stencilAttachmentFormat = state.depth->format;
|
||||
}
|
||||
if (dynamicRendering) {
|
||||
depthFormat = state.depth ? state.depth->format : VK_FORMAT_UNDEFINED;
|
||||
colorSlots.assign(state.blends.size(), ~0u);
|
||||
for (std::uint32_t index = 0; index < state.colors.size(); ++index) colorSlots.at(state.colors[index].exportIndex) = index;
|
||||
this->attachmentLayout = attachmentLayout;
|
||||
}
|
||||
VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
passInfo.attachmentCount = static_cast<std::uint32_t>(colors.size());
|
||||
passInfo.pAttachments = colors.empty() ? nullptr : colors.data();
|
||||
passInfo.subpassCount = 1;
|
||||
passInfo.pSubpasses = &subpass;
|
||||
Check(context.Function<PFN_vkCreateRenderPass>("vkCreateRenderPass")(context.device, &passInfo, nullptr, &renderPass), "vkCreateRenderPass");
|
||||
if (!dynamicRendering) Check(context.Function<PFN_vkCreateRenderPass>("vkCreateRenderPass")(context.device, &passInfo, nullptr, &renderPass), "vkCreateRenderPass");
|
||||
VkPipelineVertexInputStateCreateInfo input{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
||||
input.vertexBindingDescriptionCount = static_cast<std::uint32_t>(vertexInput.bindings.size());
|
||||
input.pVertexBindingDescriptions = vertexInput.bindings.data();
|
||||
|
|
@ -282,7 +301,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
viewports.viewportCount = 1;
|
||||
viewports.scissorCount = 1;
|
||||
PipelineLibraryKeys keys;
|
||||
libraries = libraryKeys(keys, context, state, vertexInput, resources, shaders, attachmentLayout);
|
||||
libraries = libraryKeys(keys, context, state, vertexInput, resources, shaders);
|
||||
std::vector<VkDynamicState> dynamicStates{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
||||
if (depthBounds) dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS);
|
||||
if (depthBias) dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
|
||||
|
|
@ -340,8 +359,9 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput
|
|||
pipelineInfo.pDynamicState = &dynamic;
|
||||
pipelineInfo.layout = layout;
|
||||
pipelineInfo.renderPass = renderPass;
|
||||
if (dynamicRendering) pipelineInfo.pNext = &rendering;
|
||||
timing.Mark("modules_and_state");
|
||||
if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, passInfo, layoutInfo, keys);
|
||||
if (libraries) pipeline = LinkPipelineFromLibraries(context, pipelineInfo, rendering, layoutInfo, keys);
|
||||
else Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
|
||||
if (state.depthBiasPerFace) {
|
||||
raster.cullMode = VK_CULL_MODE_FRONT_BIT;
|
||||
|
|
@ -434,6 +454,35 @@ std::shared_ptr<Framebuffer> Pipeline::AcquireFramebuffer(std::span<const VkImag
|
|||
}
|
||||
|
||||
void Pipeline::Begin(VkCommandBuffer commands, const Framebuffer& framebuffer, VkExtent2D extent, const State& state) const {
|
||||
if (dynamicRendering) {
|
||||
const auto views = framebuffer.Views();
|
||||
Require(views.size() == attachments, "render targets do not match the pipeline's attachments");
|
||||
std::vector<VkRenderingAttachmentInfoKHR> colors(colorSlots.size(), VkRenderingAttachmentInfoKHR{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR});
|
||||
for (std::size_t slot = 0; slot < colorSlots.size(); ++slot) {
|
||||
if (colorSlots[slot] == ~0u) continue;
|
||||
colors[slot].imageView = views[colorSlots[slot]];
|
||||
colors[slot].imageLayout = attachmentLayout;
|
||||
colors[slot].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
colors[slot].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
}
|
||||
VkRenderingAttachmentInfoKHR depth{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR};
|
||||
if (depthFormat != VK_FORMAT_UNDEFINED) {
|
||||
depth.imageView = views.back();
|
||||
depth.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
depth.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
depth.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
}
|
||||
VkRenderingInfoKHR rendering{VK_STRUCTURE_TYPE_RENDERING_INFO_KHR};
|
||||
rendering.renderArea = {{0, 0}, extent};
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = static_cast<std::uint32_t>(colors.size());
|
||||
rendering.pColorAttachments = colors.empty() ? nullptr : colors.data();
|
||||
if (depthFormat != VK_FORMAT_UNDEFINED) rendering.pDepthAttachment = &depth;
|
||||
if (depthFormat != VK_FORMAT_UNDEFINED && HasStencil(depthFormat)) rendering.pStencilAttachment = &depth;
|
||||
context.Resolved(&DeviceFunctions::cmdBeginRendering, "vkCmdBeginRenderingKHR")(commands, &rendering);
|
||||
Continue(commands, state);
|
||||
return;
|
||||
}
|
||||
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
begin.renderPass = renderPass;
|
||||
begin.framebuffer = framebuffer.Handle();
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ using Key = std::vector<std::byte>;
|
|||
struct DeviceLibraries {
|
||||
PFN_vkDestroyPipeline destroyPipeline = nullptr;
|
||||
PFN_vkDestroyPipelineLayout destroyLayout = nullptr;
|
||||
PFN_vkDestroyRenderPass destroyRenderPass = nullptr;
|
||||
std::map<Key, VkRenderPass> renderPasses;
|
||||
std::map<Key, VkPipelineLayout> layouts;
|
||||
std::array<std::map<Key, VkPipeline>, 4> parts;
|
||||
PipelineLibraryCounters counters;
|
||||
|
|
@ -49,7 +47,7 @@ LibraryStore& Libraries() {
|
|||
return *store;
|
||||
}
|
||||
|
||||
Key Combined(const Key& part, const Key& renderPass, const Key& layout) {
|
||||
Key Combined(const Key& part, const Key& context) {
|
||||
Key key;
|
||||
const auto append = [&](const Key& value) {
|
||||
const auto size = value.size();
|
||||
|
|
@ -58,8 +56,7 @@ Key Combined(const Key& part, const Key& renderPass, const Key& layout) {
|
|||
key.insert(key.end(), value.begin(), value.end());
|
||||
};
|
||||
append(part);
|
||||
append(renderPass);
|
||||
append(layout);
|
||||
append(context);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
|
@ -69,21 +66,14 @@ std::span<const VkDynamicState> PipelineLibraryDynamicStates() {
|
|||
return dynamicStates;
|
||||
}
|
||||
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkRenderPassCreateInfo& pass, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys) {
|
||||
Require(info.pNext == nullptr && info.renderPass != VK_NULL_HANDLE && info.pDynamicState != nullptr, "pipeline library parts need the render pass path and the library dynamic states");
|
||||
VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPipelineCreateInfo& info, const VkPipelineRenderingCreateInfoKHR& rendering, const VkPipelineLayoutCreateInfo& layout, const PipelineLibraryKeys& keys) {
|
||||
Require(info.renderPass == VK_NULL_HANDLE && info.pDynamicState != nullptr, "pipeline library parts need dynamic rendering and the library dynamic states");
|
||||
auto& store = Libraries();
|
||||
std::lock_guard lock(store.mutex);
|
||||
auto& device = store.devices[context.device];
|
||||
if (device.destroyPipeline == nullptr) {
|
||||
device.destroyPipeline = context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline");
|
||||
device.destroyLayout = context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout");
|
||||
device.destroyRenderPass = context.Function<PFN_vkDestroyRenderPass>("vkDestroyRenderPass");
|
||||
}
|
||||
auto renderPass = device.renderPasses.find(keys.renderPass);
|
||||
if (renderPass == device.renderPasses.end()) {
|
||||
VkRenderPass handle = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateRenderPass>("vkCreateRenderPass")(context.device, &pass, nullptr, &handle), "vkCreateRenderPass library");
|
||||
renderPass = device.renderPasses.emplace(keys.renderPass, handle).first;
|
||||
}
|
||||
auto pipelineLayout = device.layouts.find(keys.layout);
|
||||
if (pipelineLayout == device.layouts.end()) {
|
||||
|
|
@ -108,30 +98,31 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
|
|||
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT,
|
||||
};
|
||||
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 VkPipelineRenderingCreateInfoKHR shaderRendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
||||
VkPipelineDepthStencilStateCreateInfo dynamicDepth{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
std::vector<VkPipeline> libraries;
|
||||
for (std::size_t part = 0; part < flags.size(); ++part) {
|
||||
if (part == 0 && mesh) continue;
|
||||
const auto key = Combined(*partKeys[part], keys.renderPass, keys.layout);
|
||||
const auto key = Combined(*partKeys[part], *contextKeys[part]);
|
||||
auto& cache = device.parts[part];
|
||||
if (const auto found = cache.find(key); found != cache.end()) {
|
||||
libraries.push_back(found->second);
|
||||
continue;
|
||||
}
|
||||
PerformanceTimer timing("Vulkan.GraphicsPipelineLibrary");
|
||||
VkGraphicsPipelineLibraryCreateInfoEXT library{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT};
|
||||
VkGraphicsPipelineLibraryCreateInfoEXT library{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, flags[part] == VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT ? &rendering : &shaderRendering};
|
||||
library.flags = flags[part];
|
||||
VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library};
|
||||
create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
|
||||
create.pDynamicState = info.pDynamicState;
|
||||
create.layout = pipelineLayout->second;
|
||||
create.renderPass = renderPass->second;
|
||||
create.subpass = info.subpass;
|
||||
switch (flags[part]) {
|
||||
case VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT:
|
||||
create.pVertexInputState = info.pVertexInputState;
|
||||
create.pInputAssemblyState = info.pInputAssemblyState;
|
||||
create.layout = VK_NULL_HANDLE;
|
||||
create.renderPass = VK_NULL_HANDLE;
|
||||
break;
|
||||
case VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT:
|
||||
create.stageCount = static_cast<std::uint32_t>(preRasterization.size());
|
||||
|
|
@ -144,11 +135,12 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip
|
|||
create.stageCount = 1;
|
||||
create.pStages = fragment;
|
||||
create.pMultisampleState = info.pMultisampleState;
|
||||
create.pDepthStencilState = info.pDepthStencilState;
|
||||
create.pDepthStencilState = info.pDepthStencilState != nullptr ? info.pDepthStencilState : &dynamicDepth;
|
||||
break;
|
||||
default:
|
||||
create.pMultisampleState = info.pMultisampleState;
|
||||
create.pColorBlendState = info.pColorBlendState;
|
||||
create.layout = VK_NULL_HANDLE;
|
||||
break;
|
||||
}
|
||||
VkPipeline handle = VK_NULL_HANDLE;
|
||||
|
|
@ -179,7 +171,6 @@ void ClearPipelineLibraries(VkDevice device) {
|
|||
for (const auto& [key, handle] : part) libraries.destroyPipeline(device, handle, nullptr);
|
||||
}
|
||||
for (const auto& [key, handle] : libraries.layouts) libraries.destroyLayout(device, handle, nullptr);
|
||||
for (const auto& [key, handle] : libraries.renderPasses) libraries.destroyRenderPass(device, handle, nullptr);
|
||||
store.devices.erase(found);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1395,7 +1395,7 @@ void Recorder::LeaveRenderPassOpen(std::uint64_t key, std::uint32_t timing, bool
|
|||
void Recorder::endOpenRenderPass() {
|
||||
auto& pass = open->renderPass;
|
||||
if (open->segmentActive) endSegment(*open);
|
||||
context.Resolved(&DeviceFunctions::cmdEndRenderPass, "vkCmdEndRenderPass")(open->commands);
|
||||
EndRenderPass(context, open->commands);
|
||||
// The pass's attachment and shader writes are visible to everything recorded after it (the
|
||||
// host sees them at the batch's fence).
|
||||
recordBarrier(open->commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
|
|
|
|||
|
|
@ -167,6 +167,8 @@ void FillDeviceFunctions(const Context& context, DeviceFunctions& functions) {
|
|||
functions.cmdSetDepthBounds = context.Function<PFN_vkCmdSetDepthBounds>("vkCmdSetDepthBounds");
|
||||
functions.cmdSetDepthBias = context.Function<PFN_vkCmdSetDepthBias>("vkCmdSetDepthBias");
|
||||
if (context.graphicsPipelineLibrary) {
|
||||
functions.cmdBeginRendering = context.Function<PFN_vkCmdBeginRenderingKHR>("vkCmdBeginRenderingKHR");
|
||||
functions.cmdEndRendering = context.Function<PFN_vkCmdEndRenderingKHR>("vkCmdEndRenderingKHR");
|
||||
functions.cmdSetCullMode = context.Function<PFN_vkCmdSetCullModeEXT>("vkCmdSetCullModeEXT");
|
||||
functions.cmdSetFrontFace = context.Function<PFN_vkCmdSetFrontFaceEXT>("vkCmdSetFrontFaceEXT");
|
||||
functions.cmdSetDepthTestEnable = context.Function<PFN_vkCmdSetDepthTestEnableEXT>("vkCmdSetDepthTestEnableEXT");
|
||||
|
|
|
|||
|
|
@ -195,20 +195,23 @@ public:
|
|||
minLod.pNext = address.pNext;
|
||||
address.pNext = &minLod;
|
||||
}
|
||||
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicState{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT};
|
||||
VkPhysicalDeviceDynamicRenderingFeaturesKHR rendering{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR};
|
||||
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicState{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, &rendering};
|
||||
VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT library{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, &dynamicState};
|
||||
if (std::getenv("APS5_NO_GPL") == nullptr && hasExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME) && hasExtension(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME) && hasExtension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||
const std::array<const char*, 6> libraryExtensions{VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME};
|
||||
if (std::getenv("APS5_NO_GPL") == nullptr && std::all_of(libraryExtensions.begin(), libraryExtensions.end(), hasExtension)) {
|
||||
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &library};
|
||||
function<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(context.physical, &features);
|
||||
VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT libraryProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT};
|
||||
VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &libraryProperties};
|
||||
function<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(context.physical, &properties);
|
||||
context.graphicsPipelineLibrary = library.graphicsPipelineLibrary == VK_TRUE && dynamicState.extendedDynamicState == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE;
|
||||
context.graphicsPipelineLibrary = library.graphicsPipelineLibrary == VK_TRUE && dynamicState.extendedDynamicState == VK_TRUE && rendering.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE;
|
||||
}
|
||||
rendering = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, address.pNext, VK_TRUE};
|
||||
library = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, &dynamicState, VK_TRUE};
|
||||
dynamicState = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, address.pNext, VK_TRUE};
|
||||
dynamicState = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, &rendering, VK_TRUE};
|
||||
if (context.graphicsPipelineLibrary) {
|
||||
extensionsEnabled.insert(extensionsEnabled.end(), {VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME});
|
||||
extensionsEnabled.insert(extensionsEnabled.end(), libraryExtensions.begin(), libraryExtensions.end());
|
||||
address.pNext = &library;
|
||||
}
|
||||
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &address};
|
||||
|
|
@ -3815,6 +3818,9 @@ void pipelineLibraryTests(const Device& device) {
|
|||
fragment.variantId = 13;
|
||||
static_cast<void>(lookup());
|
||||
Require(counters().built == std::array<std::uint64_t, 4>{2, 1, 2, 2}, "a new pixel shader rebuilt more than the fragment shader library");
|
||||
state.colors.front().format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
static_cast<void>(lookup());
|
||||
Require(counters().built == std::array<std::uint64_t, 4>{2, 1, 2, 3}, "another target format rebuilt more than the fragment output library");
|
||||
ClearCachedPipelines(context.device);
|
||||
Require(counters().linked == 0, "clearing the pipelines kept the device's libraries");
|
||||
std::cout << "Pipeline library reuse tests passed\n";
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ flowchart LR
|
|||
- A compute shader whose LDS plus its lock dword exceeds the host's `maxComputeSharedMemorySize` is not given a `Workgroup` array. `Recompile` sets `ShaderInfo::sharedMemoryBytes` for it and the SPIR-V backend addresses the LDS in a coherent storage buffer at descriptor set 1 (`WorkgroupMemoryDescriptorSet`), with a slice of `RecompileResult::workgroupMemoryDwords` per workgroup, indexed by `WorkgroupID` and `NumWorkGroups`. `VulkanDevice` keeps one such buffer, sized from the dispatch dimensions, and binds it after the resource set; programs that fit keep using shared memory.
|
||||
- `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks).
|
||||
- The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead.
|
||||
- A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking and `VK_EXT_extended_dynamic_state`, the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend or vertex layout builds only that part, and cull mode, front face, depth and stencil state are dynamic, so changing them only links. Pipelines with per-face depth bias, rect lists, geometry shaders or a stage without a variant id, and devices without the extensions (or `APS5_NO_GPL=1`), are created whole.
|
||||
- A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. Pipelines with per-face depth bias, rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines.
|
||||
|
||||
### Shader MODE register access
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- On Linux, [direct memory](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again.
|
||||
- [Per-face depth bias](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order.
|
||||
- [Compute LDS beyond the device's shared-memory limit](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (libSceAgcDriver) lives in a coherent storage buffer with one slice of `LDS bytes / 4 + 1` dwords per workgroup (the last dword is the atomic lock), shared by all such dispatches and grown in 1 MiB steps up to `maxStorageBufferRange`; a dispatch whose slices do not fit throws. It is slower than shared memory and its cost was not measured. An indirect dispatch of such a program always reads its group counts on the CPU. Its results are pinned against the in-limit semantics in `agc_driver_lds_beyond_device_limit`, not measured on hardware.
|
||||
- [Graphics pipeline libraries](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp) (libSceAgcDriver) are fast-linked only, never relinked with `VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT`. A vertex stage then keeps the outputs its fragment stage does not read: on an RTX 4090 (NVIDIA 615.71) 13 of 15 PPSA36082 vertex shaders paired with a pixel shader that reads none of them used up to 68 registers instead of 38 and a binary up to 21 % larger, while pairs that read every output compiled to the same code as a whole pipeline (#2242). The libraries are created against a render pass of the draw's attachment formats, so a shader drawn to targets of other formats builds its libraries again
|
||||
- [Graphics pipeline libraries](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp) (libSceAgcDriver) are fast-linked only, never relinked with `VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT`. A vertex stage then keeps the outputs its fragment stage does not read: on an RTX 4090 (NVIDIA 615.71) 13 of 15 PPSA36082 vertex shaders paired with a pixel shader that reads none of them used up to 68 registers instead of 38 and a binary up to 21 % larger, while pairs that read every output compiled to the same code as a whole pipeline (#2242).
|
||||
- [sceAvPlayerGetVideoData](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) in sync mode `SCE_AVPLAYER_AV_SYNC_MODE_NONE` skips queued frames that the audio clock has already passed while the title pulls audio, so a title that requests video slower than the film's frame rate keeps the picture with the sound. Whether the console hands out every frame in this mode is unknown. PPSA12544 requests 7-19 frames per second for a 30 fps cinematic
|
||||
- [libSceHttp2 asynchronous requests](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) never reach the network: `sceHttp2SendRequestAsync` accepts the request, queues a completion whose result is the library's network error and triggers the user event named by the kqueue option (equeue at offset 0, event id at 8, user data at 16, layout inferred from PPSA17221); `sceHttp2WaitAsync` returns that completion. The completion's `event_type` value is unknown and left 0. PPSA17221 completes its request object only after `sceHttp2SendRequestAsync` returns, and its handler for a synchronous failure calls `shared_from_this` before that, so a send must not fail synchronously. A kqueue option or user event that is missing, an extra option, and a wait with no completed operation throw
|
||||
- [system](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified
|
||||
|
|
|
|||
Loading…
Reference in New Issue