mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-27 14:45:30 +03:00
Implement support for (de)serialising VkPipelineCaches to/from storage
Significantly improves launch times in games with many shader combinations, giving an 5x speedup in some cases.
This commit is contained in:
parent
db173083d7
commit
dd92cb1536
@ -401,12 +401,12 @@ namespace skyline::gpu {
|
||||
megaBufferAllocator(*this),
|
||||
descriptor(*this),
|
||||
helperShaders(*this, state.os->assetFileSystem),
|
||||
graphicsPipelineCache(*this),
|
||||
renderPassCache(*this),
|
||||
framebufferCache(*this) {}
|
||||
|
||||
void GPU::Initialise() {
|
||||
std::string titleId{state.loader->nacp->GetSaveDataOwnerId()};
|
||||
graphicsPipelineAssembler.emplace(*this, state.os->publicAppFilesPath + "vk_graphics_pipeline_cache/" + titleId);
|
||||
shader.emplace(state, *this,
|
||||
state.os->publicAppFilesPath + "shader_replacements/" + titleId,
|
||||
state.os->publicAppFilesPath + "shader_dumps/" + titleId);
|
||||
|
@ -14,8 +14,8 @@
|
||||
#include "gpu/descriptor_allocator.h"
|
||||
#include "gpu/shader_manager.h"
|
||||
#include "gpu/pipeline_cache_manager.h"
|
||||
#include "gpu/graphics_pipeline_assembler.h"
|
||||
#include "gpu/shaders/helper_shaders.h"
|
||||
#include "gpu/cache/graphics_pipeline_cache.h"
|
||||
#include "gpu/cache/renderpass_cache.h"
|
||||
#include "gpu/cache/framebuffer_cache.h"
|
||||
#include "gpu/interconnect/maxwell_3d/pipeline_manager.h"
|
||||
@ -59,7 +59,7 @@ namespace skyline::gpu {
|
||||
|
||||
HelperShaders helperShaders;
|
||||
|
||||
cache::GraphicsPipelineCache graphicsPipelineCache;
|
||||
std::optional<GraphicsPipelineAssembler> graphicsPipelineAssembler;
|
||||
cache::RenderPassCache renderPassCache;
|
||||
cache::FramebufferCache framebufferCache;
|
||||
|
||||
|
@ -1,406 +0,0 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <gpu.h>
|
||||
#include "graphics_pipeline_cache.h"
|
||||
|
||||
namespace skyline::gpu::cache {
|
||||
GraphicsPipelineCache::GraphicsPipelineCache(GPU &gpu)
|
||||
: gpu{gpu},
|
||||
vkPipelineCache{gpu.vkDevice, vk::PipelineCacheCreateInfo{}},
|
||||
pool{gpu.traits.quirks.brokenMultithreadedPipelineCompilation ? 1U : 0U} {}
|
||||
|
||||
#define VEC_CPY(pointer, size) state.pointer, state.pointer + state.size
|
||||
|
||||
GraphicsPipelineCache::PipelineCacheKey::PipelineCacheKey(const GraphicsPipelineCache::PipelineState &state)
|
||||
: shaderStages(state.shaderStages.begin(), state.shaderStages.end()),
|
||||
vertexState(state.vertexState),
|
||||
vertexBindings(VEC_CPY(VertexInputState().pVertexBindingDescriptions, VertexInputState().vertexBindingDescriptionCount)),
|
||||
vertexAttributes(VEC_CPY(VertexInputState().pVertexAttributeDescriptions, VertexInputState().vertexAttributeDescriptionCount)),
|
||||
vertexDivisors(VEC_CPY(VertexDivisorState().pVertexBindingDivisors, VertexDivisorState().vertexBindingDivisorCount)),
|
||||
inputAssemblyState(state.inputAssemblyState),
|
||||
tessellationState(state.tessellationState),
|
||||
viewportState(state.viewportState),
|
||||
viewports(VEC_CPY(viewportState.pViewports, viewportState.viewportCount)),
|
||||
scissors(VEC_CPY(viewportState.pScissors, viewportState.scissorCount)),
|
||||
rasterizationState(state.rasterizationState),
|
||||
multisampleState(state.multisampleState),
|
||||
depthStencilState(state.depthStencilState),
|
||||
colorBlendState(state.colorBlendState),
|
||||
dynamicStates(VEC_CPY(dynamicState.pDynamicStates, dynamicState.dynamicStateCount)),
|
||||
dynamicState(state.dynamicState),
|
||||
colorBlendAttachments(VEC_CPY(colorBlendState.pAttachments, colorBlendState.attachmentCount)) {
|
||||
auto &vertexInputState{vertexState.get<vk::PipelineVertexInputStateCreateInfo>()};
|
||||
vertexInputState.pVertexBindingDescriptions = vertexBindings.data();
|
||||
vertexInputState.pVertexAttributeDescriptions = vertexAttributes.data();
|
||||
vertexState.get<vk::PipelineVertexInputDivisorStateCreateInfoEXT>().pVertexBindingDivisors = vertexDivisors.data();
|
||||
|
||||
viewportState.pViewports = viewports.data();
|
||||
viewportState.pScissors = scissors.data();
|
||||
|
||||
colorBlendState.pAttachments = colorBlendAttachments.data();
|
||||
|
||||
dynamicState.pDynamicStates = dynamicStates.data();
|
||||
|
||||
for (auto &colorFormat : state.colorFormats)
|
||||
colorFormats.emplace_back(colorFormat);
|
||||
|
||||
depthStencilFormat = state.depthStencilFormat;
|
||||
sampleCount = state.sampleCount;
|
||||
}
|
||||
|
||||
#undef VEC_CPY
|
||||
|
||||
#define HASH(x) boost::hash_combine(hash, x)
|
||||
|
||||
template<typename T>
|
||||
size_t HashCommonPipelineState(const T &key, size_t hash = 0) {
|
||||
HASH(key.shaderStages.size());
|
||||
for (const auto &stage : key.shaderStages) {
|
||||
HASH(stage.stage);
|
||||
HASH(static_cast<VkShaderModule>(stage.module));
|
||||
}
|
||||
|
||||
auto &vertexInputState{key.VertexInputState()};
|
||||
HASH(vertexInputState.vertexBindingDescriptionCount);
|
||||
HASH(vertexInputState.vertexAttributeDescriptionCount);
|
||||
HASH(static_cast<VkFlags>(vertexInputState.flags));
|
||||
|
||||
for (size_t i{}; i < vertexInputState.vertexBindingDescriptionCount; i++) {
|
||||
const auto &descr{vertexInputState.pVertexBindingDescriptions[i]};
|
||||
HASH(descr.binding);
|
||||
HASH(descr.stride);
|
||||
HASH(static_cast<VkVertexInputRate>(descr.inputRate));
|
||||
}
|
||||
|
||||
for (size_t i{}; i < vertexInputState.vertexAttributeDescriptionCount; i++) {
|
||||
const auto &descr{vertexInputState.pVertexAttributeDescriptions[i]};
|
||||
HASH(descr.binding);
|
||||
HASH(descr.offset);
|
||||
HASH(descr.location);
|
||||
HASH(static_cast<VkFormat>(descr.format));
|
||||
}
|
||||
|
||||
if (key.vertexState.template isLinked<vk::PipelineVertexInputDivisorStateCreateInfoEXT>())
|
||||
HASH(key.VertexDivisorState().vertexBindingDivisorCount);
|
||||
|
||||
HASH(key.inputAssemblyState.topology);
|
||||
HASH(key.inputAssemblyState.primitiveRestartEnable);
|
||||
|
||||
HASH(key.tessellationState.patchControlPoints);
|
||||
|
||||
HASH(key.viewportState.viewportCount);
|
||||
HASH(key.viewportState.scissorCount);
|
||||
|
||||
for (size_t i{}; i < key.viewportState.viewportCount; i++) {
|
||||
const auto &viewport{key.viewportState.pViewports[i]};
|
||||
HASH(viewport.x);
|
||||
HASH(viewport.y);
|
||||
HASH(viewport.width);
|
||||
HASH(viewport.height);
|
||||
HASH(viewport.minDepth);
|
||||
HASH(viewport.maxDepth);
|
||||
}
|
||||
|
||||
for (size_t i{}; i < key.viewportState.scissorCount; i++) {
|
||||
const auto &scissor{key.viewportState.pScissors[i]};
|
||||
HASH(scissor.offset.x);
|
||||
HASH(scissor.offset.y);
|
||||
HASH(scissor.extent.width);
|
||||
HASH(scissor.extent.height);
|
||||
}
|
||||
|
||||
auto &rasterizationState{key.RasterizationState()};
|
||||
HASH(rasterizationState.depthClampEnable);
|
||||
HASH(rasterizationState.rasterizerDiscardEnable);
|
||||
HASH(rasterizationState.polygonMode);
|
||||
HASH(std::hash<vk::CullModeFlags>{}(rasterizationState.cullMode));
|
||||
HASH(rasterizationState.frontFace);
|
||||
HASH(rasterizationState.depthBiasEnable);
|
||||
HASH(rasterizationState.depthBiasConstantFactor);
|
||||
HASH(rasterizationState.depthBiasClamp);
|
||||
HASH(rasterizationState.depthBiasSlopeFactor);
|
||||
HASH(rasterizationState.lineWidth);
|
||||
|
||||
if (key.rasterizationState.template isLinked<vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT>())
|
||||
HASH(key.ProvokingVertexState().provokingVertexMode);
|
||||
|
||||
HASH(key.multisampleState.rasterizationSamples);
|
||||
HASH(key.multisampleState.sampleShadingEnable);
|
||||
HASH(key.multisampleState.minSampleShading);
|
||||
HASH(key.multisampleState.alphaToCoverageEnable);
|
||||
HASH(key.multisampleState.alphaToOneEnable);
|
||||
|
||||
HASH(key.depthStencilState.depthTestEnable);
|
||||
HASH(key.depthStencilState.depthWriteEnable);
|
||||
HASH(key.depthStencilState.depthCompareOp);
|
||||
HASH(key.depthStencilState.depthBoundsTestEnable);
|
||||
HASH(key.depthStencilState.stencilTestEnable);
|
||||
HASH(key.depthStencilState.front.compareOp);
|
||||
HASH(key.depthStencilState.front.failOp);
|
||||
HASH(key.depthStencilState.front.passOp);
|
||||
HASH(key.depthStencilState.front.depthFailOp);
|
||||
HASH(key.depthStencilState.front.compareMask);
|
||||
HASH(key.depthStencilState.front.writeMask);
|
||||
HASH(key.depthStencilState.front.reference);
|
||||
HASH(key.depthStencilState.back.compareOp);
|
||||
HASH(key.depthStencilState.back.failOp);
|
||||
HASH(key.depthStencilState.back.passOp);
|
||||
HASH(key.depthStencilState.back.depthFailOp);
|
||||
HASH(key.depthStencilState.back.compareMask);
|
||||
HASH(key.depthStencilState.back.writeMask);
|
||||
HASH(key.depthStencilState.back.reference);
|
||||
HASH(key.depthStencilState.minDepthBounds);
|
||||
HASH(key.depthStencilState.maxDepthBounds);
|
||||
|
||||
HASH(key.colorBlendState.logicOpEnable);
|
||||
HASH(key.colorBlendState.logicOp);
|
||||
HASH(key.colorBlendState.attachmentCount);
|
||||
|
||||
for (size_t i{}; i < key.colorBlendState.attachmentCount; i++) {
|
||||
const auto &attachment{key.colorBlendState.pAttachments[i]};
|
||||
HASH(static_cast<VkBool32>(attachment.blendEnable));
|
||||
|
||||
HASH(static_cast<VkBlendOp>(attachment.alphaBlendOp));
|
||||
HASH(static_cast<VkBlendOp>(attachment.colorBlendOp));
|
||||
|
||||
HASH(static_cast<VkBlendFactor>(attachment.dstAlphaBlendFactor));
|
||||
HASH(static_cast<VkBlendFactor>(attachment.dstColorBlendFactor));
|
||||
HASH(static_cast<VkBlendFactor>(attachment.srcAlphaBlendFactor));
|
||||
HASH(static_cast<VkBlendFactor>(attachment.srcColorBlendFactor));
|
||||
}
|
||||
|
||||
HASH(key.dynamicState.dynamicStateCount);
|
||||
|
||||
HASH(key.colorFormats.size());
|
||||
for (auto format : key.colorFormats)
|
||||
HASH(format);
|
||||
|
||||
HASH(key.depthStencilFormat);
|
||||
HASH(key.sampleCount);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
size_t GraphicsPipelineCache::PipelineStateHash::operator()(const GraphicsPipelineCache::PipelineState &key) const {
|
||||
return HashCommonPipelineState(key);
|
||||
}
|
||||
|
||||
size_t GraphicsPipelineCache::PipelineStateHash::operator()(const GraphicsPipelineCache::PipelineCacheKey &key) const {
|
||||
return HashCommonPipelineState(key);
|
||||
}
|
||||
|
||||
#undef HASH
|
||||
|
||||
bool GraphicsPipelineCache::PipelineCacheEqual::operator()(const GraphicsPipelineCache::PipelineCacheKey &lhs, const GraphicsPipelineCache::PipelineState &rhs) const {
|
||||
#define RETF(condition) if (condition) { return false; }
|
||||
#define KEYEQ(member) (lhs.member == rhs.member)
|
||||
#define KEYNEQ(member) (lhs.member != rhs.member)
|
||||
static constexpr auto NotEqual{[](auto pointer, auto size, auto pointer2, auto size2, auto equalFunction) -> bool {
|
||||
return
|
||||
size != size2 ||
|
||||
!std::equal(pointer, pointer + static_cast<ssize_t>(size), pointer2, equalFunction);
|
||||
}};
|
||||
#define CARREQ(pointer, size, func) NotEqual(lhs.pointer, lhs.size, rhs.pointer, rhs.size, [](decltype(*lhs.pointer) &lhs, decltype(*rhs.pointer) &rhs) { func }) // Note: typeof(*lhs/rhs.pointer) is required for clangd to resolve the parameter type correctly for autocomplete
|
||||
#define ARREQ(pointer, size) CARREQ(pointer, size, { return lhs == rhs; })
|
||||
|
||||
RETF(CARREQ(shaderStages.begin(), shaderStages.size(), {
|
||||
return KEYEQ(flags) && KEYEQ(stage) && KEYEQ(module) && std::strcmp(lhs.pName, rhs.pName) == 0;
|
||||
// Note: We intentionally ignore specialization constants here
|
||||
}))
|
||||
|
||||
RETF(KEYNEQ(VertexInputState().flags) ||
|
||||
ARREQ(VertexInputState().pVertexBindingDescriptions, VertexInputState().vertexBindingDescriptionCount) ||
|
||||
ARREQ(VertexInputState().pVertexAttributeDescriptions, VertexInputState().vertexAttributeDescriptionCount)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(vertexState.isLinked<vk::PipelineVertexInputDivisorStateCreateInfoEXT>()) ||
|
||||
(lhs.vertexState.isLinked<vk::PipelineVertexInputDivisorStateCreateInfoEXT>() &&
|
||||
ARREQ(VertexDivisorState().pVertexBindingDivisors, VertexDivisorState().vertexBindingDivisorCount)
|
||||
)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(tessellationState.flags) || KEYNEQ(tessellationState.patchControlPoints))
|
||||
|
||||
RETF(KEYNEQ(inputAssemblyState.flags) || KEYNEQ(inputAssemblyState.topology) || KEYNEQ(inputAssemblyState.primitiveRestartEnable))
|
||||
|
||||
RETF(KEYNEQ(viewportState.flags) ||
|
||||
ARREQ(viewportState.pViewports, viewportState.viewportCount) ||
|
||||
ARREQ(viewportState.pScissors, viewportState.scissorCount)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(RasterizationState().flags) ||
|
||||
KEYNEQ(RasterizationState().depthClampEnable) ||
|
||||
KEYNEQ(RasterizationState().rasterizerDiscardEnable) ||
|
||||
KEYNEQ(RasterizationState().polygonMode) ||
|
||||
KEYNEQ(RasterizationState().cullMode) ||
|
||||
KEYNEQ(RasterizationState().frontFace) ||
|
||||
KEYNEQ(RasterizationState().depthBiasEnable) ||
|
||||
KEYNEQ(RasterizationState().depthBiasConstantFactor) ||
|
||||
KEYNEQ(RasterizationState().depthBiasClamp) ||
|
||||
KEYNEQ(RasterizationState().depthBiasSlopeFactor) ||
|
||||
KEYNEQ(RasterizationState().lineWidth)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(rasterizationState.isLinked<vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT>()) ||
|
||||
(lhs.rasterizationState.isLinked<vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT>() &&
|
||||
KEYNEQ(ProvokingVertexState().provokingVertexMode)
|
||||
)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(multisampleState.flags) ||
|
||||
KEYNEQ(multisampleState.rasterizationSamples) ||
|
||||
KEYNEQ(multisampleState.sampleShadingEnable) ||
|
||||
KEYNEQ(multisampleState.minSampleShading) ||
|
||||
KEYNEQ(multisampleState.alphaToCoverageEnable) ||
|
||||
KEYNEQ(multisampleState.alphaToOneEnable)
|
||||
)
|
||||
|
||||
RETF(KEYNEQ(depthStencilState.flags) ||
|
||||
KEYNEQ(depthStencilState.depthTestEnable) ||
|
||||
KEYNEQ(depthStencilState.depthWriteEnable) ||
|
||||
KEYNEQ(depthStencilState.depthCompareOp) ||
|
||||
KEYNEQ(depthStencilState.depthBoundsTestEnable) ||
|
||||
KEYNEQ(depthStencilState.stencilTestEnable) ||
|
||||
KEYNEQ(depthStencilState.front) ||
|
||||
KEYNEQ(depthStencilState.back) ||
|
||||
KEYNEQ(depthStencilState.minDepthBounds) ||
|
||||
KEYNEQ(depthStencilState.maxDepthBounds)
|
||||
)
|
||||
|
||||
RETF(ARREQ(dynamicState.pDynamicStates, dynamicState.dynamicStateCount))
|
||||
|
||||
RETF(KEYNEQ(colorBlendState.flags) ||
|
||||
KEYNEQ(colorBlendState.logicOpEnable) ||
|
||||
KEYNEQ(colorBlendState.logicOp) ||
|
||||
ARREQ(colorBlendState.pAttachments, colorBlendState.attachmentCount) ||
|
||||
KEYNEQ(colorBlendState.blendConstants)
|
||||
)
|
||||
|
||||
RETF(CARREQ(colorFormats.begin(), colorFormats.size(), {
|
||||
return lhs == rhs;
|
||||
}))
|
||||
|
||||
RETF(lhs.depthStencilFormat == rhs.depthStencilFormat)
|
||||
RETF(lhs.sampleCount == rhs.sampleCount)
|
||||
|
||||
|
||||
#undef ARREQ
|
||||
#undef CARREQ
|
||||
#undef KEYNEQ
|
||||
#undef KEYEQ
|
||||
#undef RETF
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GraphicsPipelineCache::PipelineCacheEqual::operator()(const PipelineCacheKey &lhs, const PipelineCacheKey &rhs) const {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
GraphicsPipelineCache::PipelineCacheEntry::PipelineCacheEntry(vk::raii::DescriptorSetLayout &&descriptorSetLayout, vk::raii::PipelineLayout &&pipelineLayout) : descriptorSetLayout{std::move(descriptorSetLayout)}, pipelineLayout{std::move(pipelineLayout)} {}
|
||||
|
||||
vk::raii::Pipeline GraphicsPipelineCache::BuildPipeline(const PipelineCacheKey &key, vk::PipelineLayout pipelineLayout) {
|
||||
boost::container::small_vector<vk::AttachmentDescription, 8> attachmentDescriptions;
|
||||
boost::container::small_vector<vk::AttachmentReference, 8> attachmentReferences;
|
||||
|
||||
auto pushAttachment{[&](vk::Format format) {
|
||||
if (format != vk::Format::eUndefined) {
|
||||
attachmentDescriptions.push_back(vk::AttachmentDescription{
|
||||
.format = format,
|
||||
.samples = key.sampleCount,
|
||||
.loadOp = vk::AttachmentLoadOp::eLoad,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eLoad,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eStore,
|
||||
.initialLayout = vk::ImageLayout::eGeneral,
|
||||
.finalLayout = vk::ImageLayout::eGeneral,
|
||||
.flags = vk::AttachmentDescriptionFlagBits::eMayAlias
|
||||
});
|
||||
attachmentReferences.push_back(vk::AttachmentReference{
|
||||
.attachment = static_cast<u32>(attachmentDescriptions.size() - 1),
|
||||
.layout = vk::ImageLayout::eGeneral,
|
||||
});
|
||||
} else {
|
||||
attachmentReferences.push_back(vk::AttachmentReference{
|
||||
.attachment = VK_ATTACHMENT_UNUSED,
|
||||
.layout = vk::ImageLayout::eUndefined,
|
||||
});
|
||||
}
|
||||
}};
|
||||
|
||||
vk::SubpassDescription subpassDescription{
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
};
|
||||
|
||||
for (auto &colorAttachment : key.colorFormats)
|
||||
pushAttachment(colorAttachment);
|
||||
|
||||
if (key.depthStencilFormat != vk::Format::eUndefined) {
|
||||
pushAttachment(key.depthStencilFormat);
|
||||
|
||||
subpassDescription.pColorAttachments = attachmentReferences.data();
|
||||
subpassDescription.colorAttachmentCount = static_cast<u32>(attachmentReferences.size() - 1);
|
||||
subpassDescription.pDepthStencilAttachment = &attachmentReferences.back();
|
||||
} else {
|
||||
subpassDescription.pColorAttachments = attachmentReferences.data();
|
||||
subpassDescription.colorAttachmentCount = static_cast<u32>(attachmentReferences.size());
|
||||
}
|
||||
|
||||
vk::raii::RenderPass renderPass{gpu.vkDevice, vk::RenderPassCreateInfo{
|
||||
.attachmentCount = static_cast<u32>(attachmentDescriptions.size()),
|
||||
.pAttachments = attachmentDescriptions.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpassDescription,
|
||||
}};
|
||||
|
||||
return gpu.vkDevice.createGraphicsPipeline(vkPipelineCache, vk::GraphicsPipelineCreateInfo{
|
||||
.pStages = key.shaderStages.data(),
|
||||
.stageCount = static_cast<u32>(key.shaderStages.size()),
|
||||
.pVertexInputState = &key.vertexState.get<vk::PipelineVertexInputStateCreateInfo>(),
|
||||
.pInputAssemblyState = &key.inputAssemblyState,
|
||||
.pViewportState = &key.viewportState,
|
||||
.pRasterizationState = &key.rasterizationState.get<vk::PipelineRasterizationStateCreateInfo>(),
|
||||
.pMultisampleState = &key.multisampleState,
|
||||
.pDepthStencilState = &key.depthStencilState,
|
||||
.pColorBlendState = &key.colorBlendState,
|
||||
.pDynamicState = &key.dynamicState,
|
||||
.layout = pipelineLayout,
|
||||
.renderPass = *renderPass,
|
||||
.subpass = 0,
|
||||
});
|
||||
}
|
||||
|
||||
GraphicsPipelineCache::CompiledPipeline::CompiledPipeline(const PipelineCacheEntry &entry) : descriptorSetLayout{*entry.descriptorSetLayout}, pipelineLayout{*entry.pipelineLayout}, pipeline{*entry.pipeline} {}
|
||||
|
||||
GraphicsPipelineCache::CompiledPipeline GraphicsPipelineCache::GetCompiledPipeline(const PipelineState &state, span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges, bool noPushDescriptors) {
|
||||
std::unique_lock lock(mutex);
|
||||
|
||||
auto it{pipelineCache.find(state)};
|
||||
if (it != pipelineCache.end())
|
||||
return CompiledPipeline{it->second};
|
||||
|
||||
vk::raii::DescriptorSetLayout descriptorSetLayout{gpu.vkDevice, vk::DescriptorSetLayoutCreateInfo{
|
||||
.flags = vk::DescriptorSetLayoutCreateFlags{(!noPushDescriptors && gpu.traits.supportsPushDescriptors) ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR : vk::DescriptorSetLayoutCreateFlags{}},
|
||||
.pBindings = layoutBindings.data(),
|
||||
.bindingCount = static_cast<u32>(layoutBindings.size()),
|
||||
}};
|
||||
|
||||
vk::raii::PipelineLayout pipelineLayout{gpu.vkDevice, vk::PipelineLayoutCreateInfo{
|
||||
.pSetLayouts = &*descriptorSetLayout,
|
||||
.setLayoutCount = 1,
|
||||
.pPushConstantRanges = pushConstantRanges.data(),
|
||||
.pushConstantRangeCount = static_cast<u32>(pushConstantRanges.size()),
|
||||
}};
|
||||
|
||||
auto pipelineEntryIt{pipelineCache.try_emplace(PipelineCacheKey{state}, std::move(descriptorSetLayout), std::move(pipelineLayout))};
|
||||
auto pipelineFuture{pool.submit(&GraphicsPipelineCache::BuildPipeline, this, std::ref(pipelineEntryIt.first->first), std::ref(*pipelineEntryIt.first->second.pipelineLayout))};
|
||||
pipelineEntryIt.first->second.pipeline = pipelineFuture.share();
|
||||
return CompiledPipeline{pipelineEntryIt.first->second};
|
||||
}
|
||||
|
||||
void GraphicsPipelineCache::WaitIdle() {
|
||||
pool.wait_for_tasks();
|
||||
}
|
||||
}
|
254
app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp
Normal file
254
app/src/main/cpp/skyline/gpu/graphics_pipeline_assembler.cpp
Normal file
@ -0,0 +1,254 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <filesystem>
|
||||
#include <gpu.h>
|
||||
#include "graphics_pipeline_assembler.h"
|
||||
#include "trait_manager.h"
|
||||
|
||||
namespace skyline::gpu {
|
||||
/**
|
||||
* @brief Unique header serialized into the pipeline cache filename as a hexdump to identify a particular driver
|
||||
*/
|
||||
struct PipelineCacheFileNameHeader {
|
||||
u32 vendorId; //!< The driver reported vendor ID
|
||||
u32 deviceId; //!< The driver reported device ID
|
||||
u32 driverVersion; //!< The driver reported version
|
||||
std::array<u8, VK_UUID_SIZE> uuid; //!< The driver reported pipeline cache UUID
|
||||
|
||||
PipelineCacheFileNameHeader(const TraitManager &traits)
|
||||
: vendorId{traits.vendorId},
|
||||
deviceId{traits.deviceId},
|
||||
driverVersion{traits.driverVersion},
|
||||
uuid{traits.pipelineCacheUuid} {}
|
||||
|
||||
std::string HexDump() {
|
||||
return util::HexDump(span<u8>{reinterpret_cast<u8 *>(this), sizeof(PipelineCacheFileNameHeader)});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Header that precedes serialized pipeline cache data in the pipeline cache file
|
||||
*/
|
||||
struct PipelineCacheFileDataHeader {
|
||||
u64 size;
|
||||
u64 hash;
|
||||
u8 data[];
|
||||
};
|
||||
static_assert(sizeof(PipelineCacheFileDataHeader) == 0x10);
|
||||
|
||||
static vk::raii::PipelineCache DeserialisePipelineCache(GPU &gpu, std::string_view pipelineCacheDir) {
|
||||
std::filesystem::create_directories(pipelineCacheDir);
|
||||
PipelineCacheFileNameHeader expectedFilenameHeader{gpu.traits};
|
||||
std::filesystem::path path{std::filesystem::path{pipelineCacheDir} / expectedFilenameHeader.HexDump()};
|
||||
|
||||
if (!std::filesystem::exists(path))
|
||||
return {gpu.vkDevice, vk::PipelineCacheCreateInfo{}};
|
||||
|
||||
std::ifstream stream{path, std::ios::binary};
|
||||
if (stream.fail()) {
|
||||
Logger::Warn("Failed to open Vulkan pipeline cache!");
|
||||
return {gpu.vkDevice, vk::PipelineCacheCreateInfo{}};
|
||||
}
|
||||
|
||||
PipelineCacheFileDataHeader header{};
|
||||
stream.read(reinterpret_cast<char *>(&header), sizeof(PipelineCacheFileDataHeader));
|
||||
|
||||
std::vector<u8> readData(header.size);
|
||||
stream.read(reinterpret_cast<char *>(readData.data()), static_cast<std::streamsize>(header.size));
|
||||
|
||||
if (header.hash != XXH64(readData.data(), readData.size(), 0)) {
|
||||
Logger::Warn("Ignoring invalid pipeline cache file!");
|
||||
return {gpu.vkDevice, vk::PipelineCacheCreateInfo{}};
|
||||
}
|
||||
|
||||
return {gpu.vkDevice, vk::PipelineCacheCreateInfo{
|
||||
.initialDataSize = readData.size(),
|
||||
.pInitialData = readData.data(),
|
||||
}};
|
||||
}
|
||||
|
||||
static void SerialisePipelineCache(GPU &gpu, std::string_view pipelineCacheDir, span<u8> data) {
|
||||
PipelineCacheFileNameHeader expectedFilenameHeader{gpu.traits};
|
||||
std::filesystem::path path{std::filesystem::path{pipelineCacheDir} / expectedFilenameHeader.HexDump()};
|
||||
|
||||
PipelineCacheFileDataHeader header{
|
||||
.size = data.size(),
|
||||
.hash = XXH64(data.data(), data.size(), 0)
|
||||
};
|
||||
std::ofstream stream{path, std::ios::binary | std::ios::trunc};
|
||||
if (stream.fail()) {
|
||||
Logger::Warn("Failed to write Vulkan pipeline cache!");
|
||||
return;
|
||||
}
|
||||
|
||||
stream.write(reinterpret_cast<char *>(&header), sizeof(PipelineCacheFileDataHeader));
|
||||
stream.write(reinterpret_cast<char *>(data.data()), static_cast<std::streamsize>(data.size()));
|
||||
|
||||
Logger::Info("Wrote Vulkan pipeline cache to {} (size: 0x{:X} bytes)", path.string(), data.size());
|
||||
}
|
||||
|
||||
GraphicsPipelineAssembler::GraphicsPipelineAssembler(GPU &gpu, std::string_view pipelineCacheDir)
|
||||
: gpu{gpu},
|
||||
vkPipelineCache{DeserialisePipelineCache(gpu, pipelineCacheDir)},
|
||||
pool{gpu.traits.quirks.brokenMultithreadedPipelineCompilation ? 1U : 0U},
|
||||
pipelineCacheDir{pipelineCacheDir} {}
|
||||
|
||||
#define VEC_CPY(pointer, size) state.pointer, state.pointer + state.size
|
||||
|
||||
GraphicsPipelineAssembler::PipelineDescription::PipelineDescription(const GraphicsPipelineAssembler::PipelineState &state)
|
||||
: shaderStages(state.shaderStages.begin(), state.shaderStages.end()),
|
||||
vertexState(state.vertexState),
|
||||
vertexBindings(VEC_CPY(VertexInputState().pVertexBindingDescriptions, VertexInputState().vertexBindingDescriptionCount)),
|
||||
vertexAttributes(VEC_CPY(VertexInputState().pVertexAttributeDescriptions, VertexInputState().vertexAttributeDescriptionCount)),
|
||||
vertexDivisors(VEC_CPY(VertexDivisorState().pVertexBindingDivisors, VertexDivisorState().vertexBindingDivisorCount)),
|
||||
inputAssemblyState(state.inputAssemblyState),
|
||||
tessellationState(state.tessellationState),
|
||||
viewportState(state.viewportState),
|
||||
viewports(VEC_CPY(viewportState.pViewports, viewportState.viewportCount)),
|
||||
scissors(VEC_CPY(viewportState.pScissors, viewportState.scissorCount)),
|
||||
rasterizationState(state.rasterizationState),
|
||||
multisampleState(state.multisampleState),
|
||||
depthStencilState(state.depthStencilState),
|
||||
colorBlendState(state.colorBlendState),
|
||||
dynamicStates(VEC_CPY(dynamicState.pDynamicStates, dynamicState.dynamicStateCount)),
|
||||
dynamicState(state.dynamicState),
|
||||
colorBlendAttachments(VEC_CPY(colorBlendState.pAttachments, colorBlendState.attachmentCount)) {
|
||||
auto &vertexInputState{vertexState.get<vk::PipelineVertexInputStateCreateInfo>()};
|
||||
vertexInputState.pVertexBindingDescriptions = vertexBindings.data();
|
||||
vertexInputState.pVertexAttributeDescriptions = vertexAttributes.data();
|
||||
vertexState.get<vk::PipelineVertexInputDivisorStateCreateInfoEXT>().pVertexBindingDivisors = vertexDivisors.data();
|
||||
|
||||
viewportState.pViewports = viewports.data();
|
||||
viewportState.pScissors = scissors.data();
|
||||
|
||||
colorBlendState.pAttachments = colorBlendAttachments.data();
|
||||
|
||||
dynamicState.pDynamicStates = dynamicStates.data();
|
||||
|
||||
for (auto &colorFormat : state.colorFormats)
|
||||
colorFormats.emplace_back(colorFormat);
|
||||
|
||||
depthStencilFormat = state.depthStencilFormat;
|
||||
sampleCount = state.sampleCount;
|
||||
destroyShaderModules = state.destroyShaderModules;
|
||||
}
|
||||
|
||||
#undef VEC_CPY
|
||||
|
||||
vk::raii::Pipeline GraphicsPipelineAssembler::AssemblePipeline(std::list<PipelineDescription>::iterator pipelineDescIt, vk::PipelineLayout pipelineLayout) {
|
||||
boost::container::small_vector<vk::AttachmentDescription, 8> attachmentDescriptions;
|
||||
boost::container::small_vector<vk::AttachmentReference, 8> attachmentReferences;
|
||||
|
||||
auto pushAttachment{[&](vk::Format format) {
|
||||
if (format != vk::Format::eUndefined) {
|
||||
attachmentDescriptions.push_back(vk::AttachmentDescription{
|
||||
.format = format,
|
||||
.samples = pipelineDescIt->sampleCount,
|
||||
.loadOp = vk::AttachmentLoadOp::eLoad,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eLoad,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eStore,
|
||||
.initialLayout = vk::ImageLayout::eGeneral,
|
||||
.finalLayout = vk::ImageLayout::eGeneral,
|
||||
.flags = vk::AttachmentDescriptionFlagBits::eMayAlias
|
||||
});
|
||||
attachmentReferences.push_back(vk::AttachmentReference{
|
||||
.attachment = static_cast<u32>(attachmentDescriptions.size() - 1),
|
||||
.layout = vk::ImageLayout::eGeneral,
|
||||
});
|
||||
} else {
|
||||
attachmentReferences.push_back(vk::AttachmentReference{
|
||||
.attachment = VK_ATTACHMENT_UNUSED,
|
||||
.layout = vk::ImageLayout::eUndefined,
|
||||
});
|
||||
}
|
||||
}};
|
||||
|
||||
vk::SubpassDescription subpassDescription{
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
};
|
||||
|
||||
for (auto &colorAttachment : pipelineDescIt->colorFormats)
|
||||
pushAttachment(colorAttachment);
|
||||
|
||||
if (pipelineDescIt->depthStencilFormat != vk::Format::eUndefined) {
|
||||
pushAttachment(pipelineDescIt->depthStencilFormat);
|
||||
|
||||
subpassDescription.pColorAttachments = attachmentReferences.data();
|
||||
subpassDescription.colorAttachmentCount = static_cast<u32>(attachmentReferences.size() - 1);
|
||||
subpassDescription.pDepthStencilAttachment = &attachmentReferences.back();
|
||||
} else {
|
||||
subpassDescription.pColorAttachments = attachmentReferences.data();
|
||||
subpassDescription.colorAttachmentCount = static_cast<u32>(attachmentReferences.size());
|
||||
}
|
||||
|
||||
vk::raii::RenderPass renderPass{gpu.vkDevice, vk::RenderPassCreateInfo{
|
||||
.attachmentCount = static_cast<u32>(attachmentDescriptions.size()),
|
||||
.pAttachments = attachmentDescriptions.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpassDescription,
|
||||
}};
|
||||
|
||||
auto pipeline{gpu.vkDevice.createGraphicsPipeline(vkPipelineCache, vk::GraphicsPipelineCreateInfo{
|
||||
.pStages = pipelineDescIt->shaderStages.data(),
|
||||
.stageCount = static_cast<u32>(pipelineDescIt->shaderStages.size()),
|
||||
.pVertexInputState = &pipelineDescIt->vertexState.get<vk::PipelineVertexInputStateCreateInfo>(),
|
||||
.pInputAssemblyState = &pipelineDescIt->inputAssemblyState,
|
||||
.pViewportState = &pipelineDescIt->viewportState,
|
||||
.pRasterizationState = &pipelineDescIt->rasterizationState.get<vk::PipelineRasterizationStateCreateInfo>(),
|
||||
.pMultisampleState = &pipelineDescIt->multisampleState,
|
||||
.pDepthStencilState = &pipelineDescIt->depthStencilState,
|
||||
.pColorBlendState = &pipelineDescIt->colorBlendState,
|
||||
.pDynamicState = &pipelineDescIt->dynamicState,
|
||||
.layout = pipelineLayout,
|
||||
.renderPass = *renderPass,
|
||||
.subpass = 0,
|
||||
})};
|
||||
|
||||
if (pipelineDescIt->destroyShaderModules)
|
||||
for (auto &shaderStage : pipelineDescIt->shaderStages)
|
||||
(*gpu.vkDevice).destroyShaderModule(shaderStage.module, nullptr, *gpu.vkDevice.getDispatcher());
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
compilePendingDescs.erase(pipelineDescIt);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
|
||||
GraphicsPipelineAssembler::CompiledPipeline GraphicsPipelineAssembler::AssemblePipelineAsync(const PipelineState &state, span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges, bool noPushDescriptors) {
|
||||
vk::raii::DescriptorSetLayout descriptorSetLayout{gpu.vkDevice, vk::DescriptorSetLayoutCreateInfo{
|
||||
.flags = vk::DescriptorSetLayoutCreateFlags{(!noPushDescriptors && gpu.traits.supportsPushDescriptors) ? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR : vk::DescriptorSetLayoutCreateFlags{}},
|
||||
.pBindings = layoutBindings.data(),
|
||||
.bindingCount = static_cast<u32>(layoutBindings.size()),
|
||||
}};
|
||||
|
||||
vk::raii::PipelineLayout pipelineLayout{gpu.vkDevice, vk::PipelineLayoutCreateInfo{
|
||||
.pSetLayouts = &*descriptorSetLayout,
|
||||
.setLayoutCount = 1,
|
||||
.pPushConstantRanges = pushConstantRanges.data(),
|
||||
.pushConstantRangeCount = static_cast<u32>(pushConstantRanges.size()),
|
||||
}};
|
||||
|
||||
auto descIt{[this, &state]() {
|
||||
std::scoped_lock lock{mutex};
|
||||
compilePendingDescs.emplace_back(state);
|
||||
return std::prev(compilePendingDescs.end());
|
||||
}()};
|
||||
|
||||
auto pipelineFuture{pool.submit(&GraphicsPipelineAssembler::AssemblePipeline, this, descIt, *pipelineLayout)};
|
||||
return CompiledPipeline{std::move(descriptorSetLayout), std::move(pipelineLayout), std::move(pipelineFuture)};
|
||||
}
|
||||
|
||||
void GraphicsPipelineAssembler::WaitIdle() {
|
||||
pool.wait_for_tasks();
|
||||
}
|
||||
|
||||
void GraphicsPipelineAssembler::SavePipelineCache() {
|
||||
pool.submit([this] () {
|
||||
std::vector<u8> rawData{vkPipelineCache.getData()};
|
||||
SerialisePipelineCache(gpu, pipelineCacheDir, rawData);
|
||||
});
|
||||
}
|
||||
}
|
@ -11,12 +11,11 @@ namespace skyline::gpu {
|
||||
class TextureView;
|
||||
}
|
||||
|
||||
namespace skyline::gpu::cache {
|
||||
namespace skyline::gpu {
|
||||
/**
|
||||
* @brief A cache for all Vulkan graphics pipelines objects used by the GPU to avoid costly re-creation
|
||||
* @note The cache is **not** compliant with Vulkan specification's Render Pass Compatibility clause when used with multi-subpass Render Passes but certain drivers may support a more relaxed version of this clause in practice which may allow it to be used with multi-subpass Render Passes
|
||||
* @brief Wrapper for Vulkan pipelines to allow for asynchronous compilation
|
||||
*/
|
||||
class GraphicsPipelineCache {
|
||||
class GraphicsPipelineAssembler {
|
||||
public:
|
||||
/**
|
||||
* @brief All unique state required to compile a graphics pipeline as references
|
||||
@ -36,6 +35,7 @@ namespace skyline::gpu::cache {
|
||||
span<vk::Format> colorFormats; //!< All color attachment formats in the subpass of this pipeline
|
||||
vk::Format depthStencilFormat; //!< The depth attachment format in the subpass of this pipeline, 'Undefined' if there is no depth attachment
|
||||
vk::SampleCountFlagBits sampleCount; //!< The sample count of the subpass of this pipeline
|
||||
bool destroyShaderModules; //!< Whether the shader modules should be destroyed after the pipeline is compiled
|
||||
|
||||
constexpr const vk::PipelineVertexInputStateCreateInfo &VertexInputState() const {
|
||||
return vertexState.get<vk::PipelineVertexInputStateCreateInfo>();
|
||||
@ -56,8 +56,9 @@ namespace skyline::gpu::cache {
|
||||
|
||||
private:
|
||||
GPU &gpu;
|
||||
std::mutex mutex; //!< Synchronizes accesses to the pipeline cache
|
||||
vk::raii::PipelineCache vkPipelineCache; //!< A Vulkan Pipeline Cache which stores all unique graphics pipelines
|
||||
BS::thread_pool pool;
|
||||
std::string pipelineCacheDir;
|
||||
|
||||
/**
|
||||
* @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification
|
||||
@ -72,10 +73,7 @@ namespace skyline::gpu::cache {
|
||||
bool operator==(const AttachmentMetadata &rhs) const = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief All data in PipelineState in value form to allow cheap heterogenous lookups with reference types while still storing a value-based key in the map
|
||||
*/
|
||||
struct PipelineCacheKey {
|
||||
struct PipelineDescription {
|
||||
std::vector<vk::PipelineShaderStageCreateInfo> shaderStages;
|
||||
vk::StructureChain<vk::PipelineVertexInputStateCreateInfo, vk::PipelineVertexInputDivisorStateCreateInfoEXT> vertexState;
|
||||
std::vector<vk::VertexInputBindingDescription> vertexBindings;
|
||||
@ -97,10 +95,9 @@ namespace skyline::gpu::cache {
|
||||
std::vector<vk::Format> colorFormats;
|
||||
vk::Format depthStencilFormat;
|
||||
vk::SampleCountFlagBits sampleCount;
|
||||
bool destroyShaderModules;
|
||||
|
||||
PipelineCacheKey(const PipelineState& state);
|
||||
|
||||
bool operator==(const PipelineCacheKey& other) const = default;
|
||||
PipelineDescription(const PipelineState& state);
|
||||
|
||||
constexpr const vk::PipelineVertexInputStateCreateInfo &VertexInputState() const {
|
||||
return vertexState.get<vk::PipelineVertexInputStateCreateInfo>();
|
||||
@ -119,44 +116,30 @@ namespace skyline::gpu::cache {
|
||||
}
|
||||
};
|
||||
|
||||
struct PipelineStateHash {
|
||||
using is_transparent = std::true_type;
|
||||
std::mutex mutex; //!< Protects access to `compilePendingDescs`
|
||||
std::list<PipelineDescription> compilePendingDescs; //!< List of pipeline descriptions that are pending compilation
|
||||
|
||||
size_t operator()(const PipelineState &key) const;
|
||||
|
||||
size_t operator()(const PipelineCacheKey &key) const;
|
||||
};
|
||||
|
||||
struct PipelineCacheEqual {
|
||||
using is_transparent = std::true_type;
|
||||
|
||||
bool operator()(const PipelineCacheKey &lhs, const PipelineState &rhs) const;
|
||||
|
||||
bool operator()(const PipelineCacheKey &lhs, const PipelineCacheKey &rhs) const;
|
||||
};
|
||||
|
||||
struct PipelineCacheEntry {
|
||||
vk::raii::DescriptorSetLayout descriptorSetLayout;
|
||||
vk::raii::PipelineLayout pipelineLayout;
|
||||
std::optional<std::shared_future<vk::raii::Pipeline>> pipeline;
|
||||
|
||||
PipelineCacheEntry(vk::raii::DescriptorSetLayout&& descriptorSetLayout, vk::raii::PipelineLayout &&layout);
|
||||
};
|
||||
|
||||
BS::thread_pool pool;
|
||||
std::unordered_map<PipelineCacheKey, PipelineCacheEntry, PipelineStateHash, PipelineCacheEqual> pipelineCache;
|
||||
|
||||
vk::raii::Pipeline BuildPipeline(const PipelineCacheKey &key, vk::PipelineLayout pipelineLayout);
|
||||
/**
|
||||
* @brief Synchronously compiles a pipeline with the state from the given description
|
||||
*/
|
||||
vk::raii::Pipeline AssemblePipeline(std::list<PipelineDescription>::iterator pipelineDescIt, vk::PipelineLayout pipelineLayout);
|
||||
|
||||
public:
|
||||
GraphicsPipelineCache(GPU &gpu);
|
||||
GraphicsPipelineAssembler(GPU &gpu, std::string_view pipelineCacheDir);
|
||||
|
||||
struct CompiledPipeline {
|
||||
vk::DescriptorSetLayout descriptorSetLayout;
|
||||
vk::PipelineLayout pipelineLayout;
|
||||
vk::raii::DescriptorSetLayout descriptorSetLayout;
|
||||
vk::raii::PipelineLayout pipelineLayout;
|
||||
std::shared_future<vk::raii::Pipeline> pipeline;
|
||||
|
||||
CompiledPipeline(const PipelineCacheEntry &entry);
|
||||
CompiledPipeline() : descriptorSetLayout{nullptr}, pipelineLayout{nullptr} {};
|
||||
|
||||
CompiledPipeline(vk::raii::DescriptorSetLayout descriptorSetLayout,
|
||||
vk::raii::PipelineLayout pipelineLayout,
|
||||
std::shared_future<vk::raii::Pipeline> pipeline)
|
||||
: descriptorSetLayout{std::move(descriptorSetLayout)},
|
||||
pipelineLayout{std::move(pipelineLayout)},
|
||||
pipeline{std::move(pipeline)} {};
|
||||
};
|
||||
|
||||
/**
|
||||
@ -164,11 +147,16 @@ namespace skyline::gpu::cache {
|
||||
* @note Shader specializiation constants are **not** supported and will result in UB
|
||||
* @note Input/Resolve attachments are **not** supported and using them with the supplied pipeline will result in UB
|
||||
*/
|
||||
CompiledPipeline GetCompiledPipeline(const PipelineState& state, span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges = {}, bool noPushDescriptors = false);
|
||||
CompiledPipeline AssemblePipelineAsync(const PipelineState &state, span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges = {}, bool noPushDescriptors = false);
|
||||
|
||||
/**
|
||||
* @brief Waits until the pipeline compilation thread pool is idle and all pipelines have been compiled
|
||||
*/
|
||||
void WaitIdle();
|
||||
|
||||
/**
|
||||
* @brief Saves the current Vulkan pipeline cache to the filesystem
|
||||
*/
|
||||
void SavePipelineCache();
|
||||
};
|
||||
}
|
@ -92,7 +92,7 @@ namespace skyline::gpu::interconnect::kepler_compute {
|
||||
|
||||
|
||||
if (ctx.gpu.traits.quirks.brokenMultithreadedPipelineCompilation)
|
||||
ctx.gpu.graphicsPipelineCache.WaitIdle();
|
||||
ctx.gpu.graphicsPipelineAssembler->WaitIdle();
|
||||
|
||||
vk::raii::Pipeline pipeline{ctx.gpu.vkDevice, nullptr, pipelineInfo};
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <gpu/interconnect/command_executor.h>
|
||||
#include <gpu/interconnect/common/pipeline.inc>
|
||||
#include <gpu/interconnect/common/file_pipeline_state_accessor.h>
|
||||
#include <gpu/cache/graphics_pipeline_cache.h>
|
||||
#include <gpu/graphics_pipeline_assembler.h>
|
||||
#include <gpu/shader_manager.h>
|
||||
#include <gpu.h>
|
||||
#include "graphics_pipeline_state_accessor.h"
|
||||
@ -445,10 +445,10 @@ namespace skyline::gpu::interconnect::maxwell3d {
|
||||
}
|
||||
}
|
||||
|
||||
static cache::GraphicsPipelineCache::CompiledPipeline MakeCompiledPipeline(GPU &gpu,
|
||||
const PackedPipelineState &packedState,
|
||||
const std::array<Pipeline::ShaderStage, engine::ShaderStageCount> &shaderStages,
|
||||
span<vk::DescriptorSetLayoutBinding> layoutBindings) {
|
||||
static GraphicsPipelineAssembler::CompiledPipeline MakeCompiledPipeline(GPU &gpu,
|
||||
const PackedPipelineState &packedState,
|
||||
const std::array<ShaderStage, engine::ShaderStageCount> &shaderStages,
|
||||
span<vk::DescriptorSetLayoutBinding> layoutBindings) {
|
||||
boost::container::static_vector<vk::PipelineShaderStageCreateInfo, engine::ShaderStageCount> shaderStageInfos;
|
||||
for (const auto &stage : shaderStages)
|
||||
if (stage.module)
|
||||
@ -604,7 +604,7 @@ namespace skyline::gpu::interconnect::maxwell3d {
|
||||
|
||||
texture::Format depthStencilFormat{packedState.GetDepthRenderTargetFormat()};
|
||||
|
||||
return gpu.graphicsPipelineCache.GetCompiledPipeline(cache::GraphicsPipelineCache::PipelineState{
|
||||
return gpu.graphicsPipelineAssembler->AssemblePipelineAsync(GraphicsPipelineAssembler::PipelineState{
|
||||
.shaderStages = shaderStageInfos,
|
||||
.vertexState = vertexInputState,
|
||||
.inputAssemblyState = inputAssemblyState,
|
||||
@ -618,6 +618,7 @@ namespace skyline::gpu::interconnect::maxwell3d {
|
||||
.colorFormats = colorAttachmentFormats,
|
||||
.depthStencilFormat = depthStencilFormat ? depthStencilFormat->vkFormat : vk::Format::eUndefined,
|
||||
.sampleCount = vk::SampleCountFlagBits::e1, //TODO: fix after MSAA support
|
||||
.destroyShaderModules = true
|
||||
}, layoutBindings);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <shader_compiler/frontend/ir/program.h>
|
||||
#include <gpu/cache/graphics_pipeline_cache.h>
|
||||
#include <gpu/graphics_pipeline_assembler.h>
|
||||
#include <gpu/interconnect/common/samplers.h>
|
||||
#include <gpu/interconnect/common/textures.h>
|
||||
#include <gpu/interconnect/common/pipeline_state_accessor.h>
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <gpu.h>
|
||||
#include <gpu/descriptor_allocator.h>
|
||||
#include <gpu/texture/texture.h>
|
||||
#include <gpu/cache/graphics_pipeline_cache.h>
|
||||
#include <gpu/graphics_pipeline_assembler.h>
|
||||
#include <vfs/filesystem.h>
|
||||
#include "helper_shaders.h"
|
||||
|
||||
@ -39,9 +39,9 @@ namespace skyline::gpu {
|
||||
}}
|
||||
} {}
|
||||
|
||||
cache::GraphicsPipelineCache::CompiledPipeline SimpleSingleRtShader::GetPipeline(GPU &gpu,
|
||||
const PipelineState &state,
|
||||
span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges) {
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &SimpleSingleRtShader::GetPipeline(GPU &gpu,
|
||||
const PipelineState &state,
|
||||
span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges) {
|
||||
if (auto it{pipelineCache.find(state)}; it != pipelineCache.end())
|
||||
return it->second;
|
||||
|
||||
@ -137,7 +137,7 @@ namespace skyline::gpu {
|
||||
vertexState.unlink<vk::PipelineVertexInputDivisorStateCreateInfoEXT>();
|
||||
|
||||
std::array<vk::Format, 1> colorFormats{state.colorFormat};
|
||||
return pipelineCache.emplace(state, gpu.graphicsPipelineCache.GetCompiledPipeline(cache::GraphicsPipelineCache::PipelineState{
|
||||
return pipelineCache.emplace(state, gpu.graphicsPipelineAssembler->AssemblePipelineAsync(GraphicsPipelineAssembler::PipelineState{
|
||||
.shaderStages = shaderStages,
|
||||
.vertexState = vertexState,
|
||||
.inputAssemblyState = inputAssemblyState,
|
||||
@ -233,15 +233,15 @@ namespace skyline::gpu {
|
||||
blit::VertexPushConstantLayout vertexPushConstants;
|
||||
blit::FragmentPushConstantLayout fragmentPushConstants;
|
||||
DescriptorAllocator::ActiveDescriptorSet descriptorSet;
|
||||
cache::GraphicsPipelineCache::CompiledPipeline pipeline;
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &pipeline;
|
||||
vk::Extent2D imageDimensions;
|
||||
|
||||
DrawState(GPU &gpu,
|
||||
blit::VertexPushConstantLayout vertexPushConstants, blit::FragmentPushConstantLayout fragmentPushConstants,
|
||||
cache::GraphicsPipelineCache::CompiledPipeline pipeline,
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &pipeline,
|
||||
vk::Extent2D imageDimensions)
|
||||
: vertexPushConstants{vertexPushConstants}, fragmentPushConstants{fragmentPushConstants},
|
||||
descriptorSet{gpu.descriptor.AllocateSet(pipeline.descriptorSetLayout)},
|
||||
descriptorSet{gpu.descriptor.AllocateSet(*pipeline.descriptorSetLayout)},
|
||||
pipeline{pipeline},
|
||||
imageDimensions{imageDimensions} {}
|
||||
};
|
||||
@ -300,10 +300,10 @@ namespace skyline::gpu {
|
||||
commandBuffer.setScissor(0, {scissor});
|
||||
commandBuffer.setViewport(0, {viewport});
|
||||
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *drawState->pipeline.pipeline.get());
|
||||
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, drawState->pipeline.pipelineLayout, 0, *drawState->descriptorSet, nullptr);
|
||||
commandBuffer.pushConstants(drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eVertex, 0,
|
||||
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *drawState->pipeline.pipelineLayout, 0, *drawState->descriptorSet, nullptr);
|
||||
commandBuffer.pushConstants(*drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eVertex, 0,
|
||||
vk::ArrayProxy<const blit::VertexPushConstantLayout>{drawState->vertexPushConstants});
|
||||
commandBuffer.pushConstants(drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eFragment, sizeof(blit::VertexPushConstantLayout),
|
||||
commandBuffer.pushConstants(*drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eFragment, sizeof(blit::VertexPushConstantLayout),
|
||||
vk::ArrayProxy<const blit::FragmentPushConstantLayout>{drawState->fragmentPushConstants});
|
||||
commandBuffer.draw(6, 1, 0, 0);
|
||||
});
|
||||
@ -333,12 +333,12 @@ namespace skyline::gpu {
|
||||
std::function<void(std::function<void(vk::raii::CommandBuffer &, const std::shared_ptr<FenceCycle> &, GPU &, vk::RenderPass, u32)> &&)> &&recordCb) {
|
||||
struct DrawState {
|
||||
clear::FragmentPushConstantLayout fragmentPushConstants;
|
||||
cache::GraphicsPipelineCache::CompiledPipeline pipeline;
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &pipeline;
|
||||
vk::Extent2D imageDimensions;
|
||||
|
||||
DrawState(GPU &gpu,
|
||||
clear::FragmentPushConstantLayout fragmentPushConstants,
|
||||
cache::GraphicsPipelineCache::CompiledPipeline pipeline,
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &pipeline,
|
||||
vk::Extent2D imageDimensions)
|
||||
: fragmentPushConstants{fragmentPushConstants},
|
||||
pipeline{pipeline},
|
||||
@ -385,7 +385,7 @@ namespace skyline::gpu {
|
||||
commandBuffer.setScissor(0, {scissor});
|
||||
commandBuffer.setViewport(0, {viewport});
|
||||
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *drawState->pipeline.pipeline.get());
|
||||
commandBuffer.pushConstants(drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0,
|
||||
commandBuffer.pushConstants(*drawState->pipeline.pipelineLayout, vk::ShaderStageFlagBits::eFragment, 0,
|
||||
vk::ArrayProxy<const clear::FragmentPushConstantLayout>{drawState->fragmentPushConstants});
|
||||
commandBuffer.draw(6, 1, 0, 0);
|
||||
});
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <vulkan/vulkan_raii.hpp>
|
||||
#include <gpu/descriptor_allocator.h>
|
||||
#include <gpu/cache/graphics_pipeline_cache.h>
|
||||
#include <gpu/graphics_pipeline_assembler.h>
|
||||
|
||||
namespace skyline::vfs {
|
||||
class FileSystem;
|
||||
@ -36,7 +36,7 @@ namespace skyline::gpu {
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<PipelineState, cache::GraphicsPipelineCache::CompiledPipeline, util::ObjectHash<PipelineState>> pipelineCache;
|
||||
std::unordered_map<PipelineState, GraphicsPipelineAssembler::CompiledPipeline, util::ObjectHash<PipelineState>> pipelineCache;
|
||||
vk::raii::ShaderModule vertexShaderModule;
|
||||
vk::raii::ShaderModule fragmentShaderModule;
|
||||
|
||||
@ -47,9 +47,9 @@ namespace skyline::gpu {
|
||||
/**
|
||||
* @brief Returns a potentially cached pipeline built according to the supplied input state
|
||||
*/
|
||||
cache::GraphicsPipelineCache::CompiledPipeline GetPipeline(GPU &gpu,
|
||||
const PipelineState &state,
|
||||
span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges);
|
||||
const GraphicsPipelineAssembler::CompiledPipeline &GetPipeline(GPU &gpu,
|
||||
const PipelineState &state,
|
||||
span<const vk::DescriptorSetLayoutBinding> layoutBindings, span<const vk::PushConstantRange> pushConstantRanges);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -214,6 +214,11 @@ namespace skyline::gpu {
|
||||
|
||||
|
||||
minimumStorageBufferAlignment = static_cast<u32>(deviceProperties2.get().properties.limits.minStorageBufferOffsetAlignment);
|
||||
|
||||
vendorId = deviceProperties2.get().properties.vendorID;
|
||||
deviceId = deviceProperties2.get().properties.deviceID;
|
||||
driverVersion = deviceProperties2.get().properties.driverVersion;
|
||||
pipelineCacheUuid = deviceProperties2.get().properties.pipelineCacheUUID;
|
||||
}
|
||||
|
||||
std::string TraitManager::Summary() {
|
||||
|
@ -54,6 +54,11 @@ namespace skyline::gpu {
|
||||
u32 hostVisibleCoherentCachedMemoryType{std::numeric_limits<u32>::max()};
|
||||
u32 minimumStorageBufferAlignment{}; //!< Minimum alignment for storage buffers passed to shaders
|
||||
|
||||
u32 vendorId{}; //!< The `vendorID` Vulkan property
|
||||
u32 deviceId{}; //!< The `deviceID` Vulkan property
|
||||
u32 driverVersion{}; //!< The `driverVersion` Vulkan property
|
||||
std::array<u8, VK_UUID_SIZE> pipelineCacheUuid{}; //!< The `pipelineCacheUUID` Vulkan property
|
||||
|
||||
std::bitset<7> bcnSupport{}; //!< Bitmask of BCn texture formats supported, it is ordered as BC1, BC2, BC3, BC4, BC5, BC6H and BC7
|
||||
bool supportsAdrenoDirectMemoryImport{};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user