diff --git a/src/xenia/gpu/spirv_shader.cc b/src/xenia/gpu/spirv_shader.cc new file mode 100644 index 000000000..db3ebd0da --- /dev/null +++ b/src/xenia/gpu/spirv_shader.cc @@ -0,0 +1,30 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/gpu/spirv_shader.h" + +#include + +namespace xe { +namespace gpu { + +SpirvShader::SpirvShader(xenos::ShaderType shader_type, + uint64_t ucode_data_hash, const uint32_t* ucode_dwords, + size_t ucode_dword_count, + std::endian ucode_source_endian) + : Shader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count, + ucode_source_endian) {} + +Shader::Translation* SpirvShader::CreateTranslationInstance( + uint64_t modification) { + return new SpirvTranslation(*this, modification); +} + +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/spirv_shader.h b/src/xenia/gpu/spirv_shader.h new file mode 100644 index 000000000..7eba372fa --- /dev/null +++ b/src/xenia/gpu/spirv_shader.h @@ -0,0 +1,81 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_SPIRV_SHADER_H_ +#define XENIA_GPU_SPIRV_SHADER_H_ + +#include +#include + +#include "xenia/gpu/shader.h" +#include "xenia/gpu/spirv_shader_translator.h" +#include "xenia/gpu/xenos.h" + +namespace xe { +namespace gpu { + +class SpirvShader : public Shader { + public: + class SpirvTranslation : public Translation { + public: + explicit SpirvTranslation(SpirvShader& shader, uint64_t modification) + : Translation(shader, modification) {} + }; + + explicit SpirvShader(xenos::ShaderType shader_type, uint64_t ucode_data_hash, + const uint32_t* ucode_dwords, size_t ucode_dword_count, + std::endian ucode_source_endian = std::endian::big); + + // Resource bindings are gathered after the successful translation of any + // modification for simplicity of translation (and they don't depend on + // modification bits). + + struct TextureBinding { + uint32_t fetch_constant : 5; + // Stacked and 3D are separate TextureBindings. + xenos::FetchOpDimension dimension : 2; + uint32_t is_signed : 1; + }; + // Safe to hash and compare with memcmp for layout hashing. + const std::vector& GetTextureBindingsAfterTranslation() + const { + return texture_bindings_; + } + const uint32_t GetUsedTextureMaskAfterTranslation() const { + return used_texture_mask_; + } + + struct SamplerBinding { + uint32_t fetch_constant : 5; + xenos::TextureFilter mag_filter : 2; + xenos::TextureFilter min_filter : 2; + xenos::TextureFilter mip_filter : 2; + xenos::AnisoFilter aniso_filter : 3; + }; + const std::vector& GetSamplerBindingsAfterTranslation() + const { + return sampler_bindings_; + } + + protected: + Translation* CreateTranslationInstance(uint64_t modification) override; + + private: + friend class SpirvShaderTranslator; + + std::atomic_flag bindings_setup_entered_ = ATOMIC_FLAG_INIT; + std::vector texture_bindings_; + std::vector sampler_bindings_; + uint32_t used_texture_mask_ = 0; +}; + +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_SPIRV_SHADER_H_ diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index 9cc6fec72..c05e4043c 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -20,6 +20,7 @@ #include "third_party/glslang/SPIRV/GLSL.std.450.h" #include "xenia/base/assert.h" #include "xenia/base/math.h" +#include "xenia/gpu/spirv_shader.h" namespace xe { namespace gpu { @@ -95,6 +96,9 @@ void SpirvShaderTranslator::Reset() { uniform_float_constants_ = spv::NoResult; + sampler_bindings_.clear(); + texture_bindings_.clear(); + main_interface_.clear(); var_main_registers_ = spv::NoResult; @@ -595,6 +599,41 @@ std::vector SpirvShaderTranslator::CompleteTranslation() { return module_bytes; } +void SpirvShaderTranslator::PostTranslation() { + Shader::Translation& translation = current_translation(); + if (!translation.is_valid()) { + return; + } + SpirvShader* spirv_shader = dynamic_cast(&translation.shader()); + if (spirv_shader && !spirv_shader->bindings_setup_entered_.test_and_set( + std::memory_order_relaxed)) { + spirv_shader->texture_bindings_.clear(); + spirv_shader->texture_bindings_.reserve(texture_bindings_.size()); + for (const TextureBinding& translator_binding : texture_bindings_) { + SpirvShader::TextureBinding& shader_binding = + spirv_shader->texture_bindings_.emplace_back(); + // For a stable hash. + std::memset(&shader_binding, 0, sizeof(shader_binding)); + shader_binding.fetch_constant = translator_binding.fetch_constant; + shader_binding.dimension = translator_binding.dimension; + shader_binding.is_signed = translator_binding.is_signed; + spirv_shader->used_texture_mask_ |= UINT32_C(1) + << translator_binding.fetch_constant; + } + spirv_shader->sampler_bindings_.clear(); + spirv_shader->sampler_bindings_.reserve(sampler_bindings_.size()); + for (const SamplerBinding& translator_binding : sampler_bindings_) { + SpirvShader::SamplerBinding& shader_binding = + spirv_shader->sampler_bindings_.emplace_back(); + shader_binding.fetch_constant = translator_binding.fetch_constant; + shader_binding.mag_filter = translator_binding.mag_filter; + shader_binding.min_filter = translator_binding.min_filter; + shader_binding.mip_filter = translator_binding.mip_filter; + shader_binding.aniso_filter = translator_binding.aniso_filter; + } + } +} + void SpirvShaderTranslator::ProcessLabel(uint32_t cf_index) { if (cf_index == 0) { // 0 already added in the beginning. diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index 429ab5fe0..abc3225a5 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -131,9 +131,11 @@ class SpirvShaderTranslator : public ShaderTranslator { kDescriptorSetMutableLayoutsStart, // Rarely used at all, but may be changed at an unpredictable rate when - // vertex textures are used, combined images and samplers. - kDescriptorSetTexturesVertex = kDescriptorSetMutableLayoutsStart, - // Per-material, combined images and samplers. + // vertex textures are used. + kDescriptorSetSamplersVertex = kDescriptorSetMutableLayoutsStart, + kDescriptorSetTexturesVertex, + // Per-material textures. + kDescriptorSetSamplersPixel, kDescriptorSetTexturesPixel, kDescriptorSetCount, }; @@ -217,6 +219,8 @@ class SpirvShaderTranslator : public ShaderTranslator { std::vector CompleteTranslation() override; + void PostTranslation() override; + void ProcessLabel(uint32_t cf_index) override; void ProcessExecInstructionBegin(const ParsedExecInstruction& instr) override; @@ -229,9 +233,31 @@ class SpirvShaderTranslator : public ShaderTranslator { void ProcessVertexFetchInstruction( const ParsedVertexFetchInstruction& instr) override; + void ProcessTextureFetchInstruction( + const ParsedTextureFetchInstruction& instr) override; void ProcessAluInstruction(const ParsedAluInstruction& instr) override; private: + struct TextureBinding { + uint32_t fetch_constant; + // Stacked and 3D are separate TextureBindings. + xenos::FetchOpDimension dimension; + bool is_signed; + + spv::Id type; + spv::Id variable; + }; + + struct SamplerBinding { + uint32_t fetch_constant; + xenos::TextureFilter mag_filter; + xenos::TextureFilter min_filter; + xenos::TextureFilter mip_filter; + xenos::AnisoFilter aniso_filter; + + spv::Id variable; + }; + // Builder helpers. void SpirvCreateSelectionMerge( spv::Id merge_block_id, spv::SelectionControlMask selection_control_mask = @@ -353,6 +379,15 @@ class SpirvShaderTranslator : public ShaderTranslator { spv::Id LoadUint32FromSharedMemory(spv::Id address_dwords_int); + size_t FindOrAddTextureBinding(uint32_t fetch_constant, + xenos::FetchOpDimension dimension, + bool is_signed); + size_t FindOrAddSamplerBinding(uint32_t fetch_constant, + xenos::TextureFilter mag_filter, + xenos::TextureFilter min_filter, + xenos::TextureFilter mip_filter, + xenos::AnisoFilter aniso_filter); + Features features_; std::unique_ptr builder_; @@ -446,6 +481,13 @@ class SpirvShaderTranslator : public ShaderTranslator { spv::Id buffers_shared_memory_; + // Not using combined images and samplers because + // maxPerStageDescriptorSamplers is often lower than + // maxPerStageDescriptorSampledImages, and for every fetch constant, there + // are, for regular fetches, two bindings (unsigned and signed). + std::vector texture_bindings_; + std::vector sampler_bindings_; + // VS as VS only - int. spv::Id input_vertex_index_; // VS as TES only - int. diff --git a/src/xenia/gpu/spirv_shader_translator_fetch.cc b/src/xenia/gpu/spirv_shader_translator_fetch.cc index 23dc33765..5ec982618 100644 --- a/src/xenia/gpu/spirv_shader_translator_fetch.cc +++ b/src/xenia/gpu/spirv_shader_translator_fetch.cc @@ -12,8 +12,10 @@ #include #include #include +#include #include +#include "third_party/fmt/include/fmt/format.h" #include "third_party/glslang/SPIRV/GLSL.std.450.h" #include "xenia/base/math.h" @@ -533,5 +535,145 @@ void SpirvShaderTranslator::ProcessVertexFetchInstruction( StoreResult(instr.result, result); } +void SpirvShaderTranslator::ProcessTextureFetchInstruction( + const ParsedTextureFetchInstruction& instr) { + UpdateInstructionPredication(instr.is_predicated, instr.predicate_condition); + + EnsureBuildPointAvailable(); + + // TODO(Triang3l): Fetch the texture. + if (instr.opcode == ucode::FetchOpcode::kTextureFetch) { + uint32_t fetch_constant_index = instr.operands[1].storage_index; + bool use_computed_lod = + instr.attributes.use_computed_lod && + (is_pixel_shader() || instr.attributes.use_register_gradients); + FindOrAddTextureBinding(fetch_constant_index, instr.dimension, false); + FindOrAddTextureBinding(fetch_constant_index, instr.dimension, true); + FindOrAddSamplerBinding(fetch_constant_index, instr.attributes.mag_filter, + instr.attributes.min_filter, + instr.attributes.mip_filter, + use_computed_lod ? instr.attributes.aniso_filter + : xenos::AnisoFilter::kDisabled); + } +} + +size_t SpirvShaderTranslator::FindOrAddTextureBinding( + uint32_t fetch_constant, xenos::FetchOpDimension dimension, + bool is_signed) { + // 1D and 2D textures (including stacked ones) are treated as 2D arrays for + // binding and coordinate simplicity. + if (dimension == xenos::FetchOpDimension::k1D) { + dimension = xenos::FetchOpDimension::k2D; + } + for (size_t i = 0; i < texture_bindings_.size(); ++i) { + const TextureBinding& texture_binding = texture_bindings_[i]; + if (texture_binding.fetch_constant == fetch_constant && + texture_binding.dimension == dimension && + texture_binding.is_signed == is_signed) { + return i; + } + } + // TODO(Triang3l): Limit the total count to that actually supported by the + // implementation. + size_t new_texture_binding_index = texture_bindings_.size(); + TextureBinding& new_texture_binding = texture_bindings_.emplace_back(); + new_texture_binding.fetch_constant = fetch_constant; + new_texture_binding.dimension = dimension; + new_texture_binding.is_signed = is_signed; + spv::Dim type_dimension; + bool is_array; + const char* dimension_name; + switch (dimension) { + case xenos::FetchOpDimension::k3DOrStacked: + type_dimension = spv::Dim3D; + is_array = false; + dimension_name = "3d"; + break; + case xenos::FetchOpDimension::kCube: + type_dimension = spv::DimCube; + is_array = false; + dimension_name = "cube"; + break; + default: + type_dimension = spv::Dim2D; + is_array = true; + dimension_name = "2d"; + } + new_texture_binding.type = + builder_->makeImageType(type_float_, type_dimension, false, is_array, + false, 1, spv::ImageFormatUnknown); + new_texture_binding.variable = builder_->createVariable( + spv::NoPrecision, spv::StorageClassUniformConstant, + new_texture_binding.type, + fmt::format("xe_texture{}_{}_{}", fetch_constant, dimension_name, + is_signed ? 's' : 'u') + .c_str()); + builder_->addDecoration( + new_texture_binding.variable, spv::DecorationDescriptorSet, + int(is_vertex_shader() ? kDescriptorSetTexturesVertex + : kDescriptorSetTexturesPixel)); + builder_->addDecoration(new_texture_binding.variable, spv::DecorationBinding, + int(new_texture_binding_index)); + if (features_.spirv_version >= spv::Spv_1_4) { + main_interface_.push_back(new_texture_binding.variable); + } + return new_texture_binding_index; +} + +size_t SpirvShaderTranslator::FindOrAddSamplerBinding( + uint32_t fetch_constant, xenos::TextureFilter mag_filter, + xenos::TextureFilter min_filter, xenos::TextureFilter mip_filter, + xenos::AnisoFilter aniso_filter) { + if (aniso_filter != xenos::AnisoFilter::kUseFetchConst) { + // TODO(Triang3l): Limit to what's actually supported by the implementation. + aniso_filter = std::min(aniso_filter, xenos::AnisoFilter::kMax_16_1); + } + for (size_t i = 0; i < sampler_bindings_.size(); ++i) { + const SamplerBinding& sampler_binding = sampler_bindings_[i]; + if (sampler_binding.fetch_constant == fetch_constant && + sampler_binding.mag_filter == mag_filter && + sampler_binding.min_filter == min_filter && + sampler_binding.mip_filter == mip_filter && + sampler_binding.aniso_filter == aniso_filter) { + return i; + } + } + // TODO(Triang3l): Limit the total count to that actually supported by the + // implementation. + size_t new_sampler_binding_index = sampler_bindings_.size(); + SamplerBinding& new_sampler_binding = sampler_bindings_.emplace_back(); + new_sampler_binding.fetch_constant = fetch_constant; + new_sampler_binding.mag_filter = mag_filter; + new_sampler_binding.min_filter = min_filter; + new_sampler_binding.mip_filter = mip_filter; + new_sampler_binding.aniso_filter = aniso_filter; + std::ostringstream name; + static const char kFilterSuffixes[] = {'p', 'l', 'b', 'f'}; + name << "xe_sampler" << fetch_constant << '_' + << kFilterSuffixes[uint32_t(mag_filter)] + << kFilterSuffixes[uint32_t(min_filter)] + << kFilterSuffixes[uint32_t(mip_filter)]; + if (aniso_filter != xenos::AnisoFilter::kUseFetchConst) { + if (aniso_filter == xenos::AnisoFilter::kDisabled) { + name << "_a0"; + } else { + name << "_a" << (UINT32_C(1) << (uint32_t(aniso_filter) - 1)); + } + } + new_sampler_binding.variable = builder_->createVariable( + spv::NoPrecision, spv::StorageClassUniformConstant, + builder_->makeSamplerType(), name.str().c_str()); + builder_->addDecoration( + new_sampler_binding.variable, spv::DecorationDescriptorSet, + int(is_vertex_shader() ? kDescriptorSetSamplersVertex + : kDescriptorSetSamplersPixel)); + builder_->addDecoration(new_sampler_binding.variable, spv::DecorationBinding, + int(new_sampler_binding_index)); + if (features_.spirv_version >= spv::Spv_1_4) { + main_interface_.push_back(new_sampler_binding.variable); + } + return new_sampler_binding_index; +} + } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index d10d40bcb..d9b81f9a8 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -44,10 +44,29 @@ namespace shaders { #include "xenia/gpu/shaders/bytecode/vulkan_spirv/uv_ps.h" } // namespace shaders +// No specific reason for 32768 descriptors, just the "too much" amount from +// Direct3D 12 PIX warnings. 2x descriptors for textures because of unsigned and +// signed bindings. VulkanCommandProcessor::VulkanCommandProcessor( VulkanGraphicsSystem* graphics_system, kernel::KernelState* kernel_state) : CommandProcessor(graphics_system, kernel_state), - deferred_command_buffer_(*this) {} + deferred_command_buffer_(*this), + transient_descriptor_allocator_uniform_buffer_( + *static_cast( + graphics_system->provider()), + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 32768, 32768), + transient_descriptor_allocator_storage_buffer_( + *static_cast( + graphics_system->provider()), + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 32768, 32768), + transient_descriptor_allocator_sampled_image_( + *static_cast( + graphics_system->provider()), + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 2 * 32768, 32768), + transient_descriptor_allocator_sampler_( + *static_cast( + graphics_system->provider()), + VK_DESCRIPTOR_TYPE_SAMPLER, 32768, 32768) {} VulkanCommandProcessor::~VulkanCommandProcessor() = default; @@ -70,6 +89,10 @@ bool VulkanCommandProcessor::SetupContext() { VkDevice device = provider.device(); const VkPhysicalDeviceFeatures& device_features = provider.device_features(); + // The unconditional inclusion of the vertex shader stage also covers the case + // of manual index / factor buffer fetch (the system constants and the shared + // memory are needed for that) in the tessellation vertex shader when + // fullDrawIndexUint32 is not supported. guest_shader_pipeline_stages_ = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; guest_shader_vertex_stages_ = VK_SHADER_STAGE_VERTEX_BIT; @@ -84,11 +107,6 @@ bool VulkanCommandProcessor::SetupContext() { guest_shader_vertex_stages_ |= VK_SHADER_STAGE_COMPUTE_BIT; } - // No specific reason for 32768, just the "too much" amount from Direct3D 12 - // PIX warnings. - transient_descriptor_pool_uniform_buffers_ = - std::make_unique( - provider, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 32768, 32768); // 16384 is bigger than any single uniform buffer that Xenia needs, but is the // minimum maxUniformBufferRange, thus the safe minimum amount. VkDeviceSize uniform_buffer_alignment = std::max( @@ -100,6 +118,10 @@ bool VulkanCommandProcessor::SetupContext() { size_t(16384)), size_t(uniform_buffer_alignment))); + // Descriptor set layouts. + VkShaderStageFlags guest_shader_stages = + guest_shader_vertex_stages_ | VK_SHADER_STAGE_FRAGMENT_BIT; + // Empty. VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info; descriptor_set_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; @@ -113,63 +135,11 @@ bool VulkanCommandProcessor::SetupContext() { XELOGE("Failed to create an empty Vulkan descriptor set layout"); return false; } - VkShaderStageFlags guest_shader_stages = - guest_shader_vertex_stages_ | VK_SHADER_STAGE_FRAGMENT_BIT; - VkDescriptorSetLayoutBinding descriptor_set_layout_binding_uniform_buffer; - descriptor_set_layout_binding_uniform_buffer.binding = 0; - descriptor_set_layout_binding_uniform_buffer.descriptorType = - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptor_set_layout_binding_uniform_buffer.descriptorCount = 1; - descriptor_set_layout_binding_uniform_buffer.stageFlags = guest_shader_stages; - descriptor_set_layout_binding_uniform_buffer.pImmutableSamplers = nullptr; - descriptor_set_layout_create_info.bindingCount = 1; - descriptor_set_layout_create_info.pBindings = - &descriptor_set_layout_binding_uniform_buffer; - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_fetch_bool_loop_constants_) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for the fetch, bool " - "and loop constants uniform buffer"); - return false; - } - descriptor_set_layout_binding_uniform_buffer.stageFlags = - guest_shader_vertex_stages_; - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_float_constants_vertex_) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for the vertex shader " - "float constants uniform buffer"); - return false; - } - descriptor_set_layout_binding_uniform_buffer.stageFlags = - VK_SHADER_STAGE_FRAGMENT_BIT; - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_float_constants_pixel_) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for the pixel shader " - "float constants uniform buffer"); - return false; - } - descriptor_set_layout_binding_uniform_buffer.stageFlags = guest_shader_stages; - if (device_features.tessellationShader) { - descriptor_set_layout_binding_uniform_buffer.stageFlags |= - VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; - } - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_system_constants_) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for the system " - "constants uniform buffer"); - return false; - } + // Shared memory and EDRAM. uint32_t shared_memory_binding_count_log2 = SpirvShaderTranslator::GetSharedMemoryStorageBufferCountLog2( provider.device_properties().limits.maxStorageBufferRange); - uint32_t shared_memory_binding_count = uint32_t(1) + uint32_t shared_memory_binding_count = UINT32_C(1) << shared_memory_binding_count_log2; VkDescriptorSetLayoutBinding descriptor_set_layout_bindings_shared_memory_and_edram[1]; @@ -178,15 +148,14 @@ bool VulkanCommandProcessor::SetupContext() { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; descriptor_set_layout_bindings_shared_memory_and_edram[0].descriptorCount = shared_memory_binding_count; - // TODO(Triang3l): When fullDrawIndexUint32 fallback is added, force host - // vertex shader access to the shared memory for the tessellation vertex - // shader (to retrieve tessellation factors). descriptor_set_layout_bindings_shared_memory_and_edram[0].stageFlags = guest_shader_stages; descriptor_set_layout_bindings_shared_memory_and_edram[0].pImmutableSamplers = nullptr; // TODO(Triang3l): EDRAM storage image binding for the fragment shader // interlocks case. + descriptor_set_layout_create_info.bindingCount = uint32_t( + xe::countof(descriptor_set_layout_bindings_shared_memory_and_edram)); descriptor_set_layout_create_info.pBindings = descriptor_set_layout_bindings_shared_memory_and_edram; if (dfn.vkCreateDescriptorSetLayout( @@ -197,6 +166,109 @@ bool VulkanCommandProcessor::SetupContext() { "and the EDRAM"); return false; } + // Transient: uniform buffer for the guest vertex shader stages. + VkDescriptorSetLayoutBinding descriptor_set_layout_binding_transient; + descriptor_set_layout_binding_transient.binding = 0; + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_set_layout_binding_transient.descriptorCount = 1; + descriptor_set_layout_binding_transient.stageFlags = + guest_shader_vertex_stages_; + descriptor_set_layout_binding_transient.pImmutableSamplers = nullptr; + descriptor_set_layout_create_info.bindingCount = 1; + descriptor_set_layout_create_info.pBindings = + &descriptor_set_layout_binding_transient; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kUniformBufferGuestVertex)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a uniform buffer " + "bound to the guest vertex shader stages"); + return false; + } + // Transient: uniform buffer for fragment shaders. + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_set_layout_binding_transient.stageFlags = + VK_SHADER_STAGE_FRAGMENT_BIT; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kUniformBufferFragment)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a uniform buffer " + "bound to the fragment shader"); + return false; + } + // Transient: uniform buffer for the guest shader stages. + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_set_layout_binding_transient.stageFlags = guest_shader_stages; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kUniformBufferGuestShader)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a uniform buffer " + "bound to the guest shader stages"); + return false; + } + // Transient: system constants. + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_set_layout_binding_transient.stageFlags = guest_shader_stages; + if (device_features.tessellationShader) { + descriptor_set_layout_binding_transient.stageFlags |= + VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + } + if (device_features.geometryShader) { + descriptor_set_layout_binding_transient.stageFlags |= + VK_SHADER_STAGE_GEOMETRY_BIT; + } + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout :: + kUniformBufferSystemConstants)]) != VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for the system " + "constants uniform buffer"); + return false; + } + // Transient: uniform buffer for compute shaders. + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptor_set_layout_binding_transient.stageFlags = + VK_SHADER_STAGE_COMPUTE_BIT; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kUniformBufferCompute)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a uniform buffer " + "bound to the compute shader"); + return false; + } + // Transient: storage buffer for compute shaders. + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptor_set_layout_binding_transient.stageFlags = + VK_SHADER_STAGE_COMPUTE_BIT; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kStorageBufferCompute)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a storage buffer " + "bound to the compute shader"); + return false; + } shared_memory_ = std::make_unique( *this, *memory_, trace_writer_, guest_shader_pipeline_stages_); @@ -556,6 +628,8 @@ void VulkanCommandProcessor::ShutdownContext() { shared_memory_.reset(); + ClearTransientDescriptorPools(); + for (const auto& pipeline_layout_pair : pipeline_layouts_) { dfn.vkDestroyPipelineLayout( device, pipeline_layout_pair.second.GetPipelineLayout(), nullptr); @@ -568,26 +642,19 @@ void VulkanCommandProcessor::ShutdownContext() { } descriptor_set_layouts_textures_.clear(); + for (VkDescriptorSetLayout& descriptor_set_layout_single_transient : + descriptor_set_layouts_single_transient_) { + ui::vulkan::util::DestroyAndNullHandle( + dfn.vkDestroyDescriptorSetLayout, device, + descriptor_set_layout_single_transient); + } ui::vulkan::util::DestroyAndNullHandle( dfn.vkDestroyDescriptorSetLayout, device, descriptor_set_layout_shared_memory_and_edram_); - ui::vulkan::util::DestroyAndNullHandle( - dfn.vkDestroyDescriptorSetLayout, device, - descriptor_set_layout_system_constants_); - ui::vulkan::util::DestroyAndNullHandle( - dfn.vkDestroyDescriptorSetLayout, device, - descriptor_set_layout_float_constants_pixel_); - ui::vulkan::util::DestroyAndNullHandle( - dfn.vkDestroyDescriptorSetLayout, device, - descriptor_set_layout_float_constants_vertex_); - ui::vulkan::util::DestroyAndNullHandle( - dfn.vkDestroyDescriptorSetLayout, device, - descriptor_set_layout_fetch_bool_loop_constants_); ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, device, descriptor_set_layout_empty_); uniform_buffer_pool_.reset(); - transient_descriptor_pool_uniform_buffers_.reset(); sparse_bind_wait_stage_mask_ = 0; sparse_buffer_binds_.clear(); @@ -651,14 +718,14 @@ void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { if (current_float_constant_map_pixel_[float_constant_index >> 6] & (1ull << (float_constant_index & 63))) { current_graphics_descriptor_set_values_up_to_date_ &= - ~(uint32_t(1) + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel); } } else { if (current_float_constant_map_vertex_[float_constant_index >> 6] & (1ull << (float_constant_index & 63))) { current_graphics_descriptor_set_values_up_to_date_ &= - ~(uint32_t(1) + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex); } } @@ -666,11 +733,11 @@ void VulkanCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { } else if (index >= XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031 && index <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31) { current_graphics_descriptor_set_values_up_to_date_ &= ~( - uint32_t(1) << SpirvShaderTranslator::kDescriptorSetBoolLoopConstants); + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetBoolLoopConstants); } else if (index >= XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 && index <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5) { current_graphics_descriptor_set_values_up_to_date_ &= - ~(uint32_t(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants); + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants); } } @@ -1102,12 +1169,103 @@ void VulkanCommandProcessor::EndRenderPass() { current_framebuffer_ = nullptr; } +VkDescriptorSet VulkanCommandProcessor::AllocateSingleTransientDescriptor( + SingleTransientDescriptorLayout transient_descriptor_layout) { + assert_true(frame_open_); + VkDescriptorSet descriptor_set; + std::vector& transient_descriptors_free = + single_transient_descriptors_free_[size_t(transient_descriptor_layout)]; + if (!transient_descriptors_free.empty()) { + descriptor_set = transient_descriptors_free.back(); + transient_descriptors_free.pop_back(); + } else { + const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + ui::vulkan::SingleTypeDescriptorSetAllocator& + transfer_descriptor_allocator = + transient_descriptor_layout == + SingleTransientDescriptorLayout::kStorageBufferCompute + ? transient_descriptor_allocator_storage_buffer_ + : transient_descriptor_allocator_uniform_buffer_; + descriptor_set = transfer_descriptor_allocator.Allocate( + GetSingleTransientDescriptorLayout(transient_descriptor_layout), 1); + if (descriptor_set == VK_NULL_HANDLE) { + return VK_NULL_HANDLE; + } + } + UsedSingleTransientDescriptor used_descriptor; + used_descriptor.frame = frame_current_; + used_descriptor.layout = transient_descriptor_layout; + used_descriptor.set = descriptor_set; + single_transient_descriptors_used_.emplace_back(used_descriptor); + return descriptor_set; +} + +VkDescriptorSetLayout VulkanCommandProcessor::GetTextureDescriptorSetLayout( + bool is_samplers, bool is_vertex, size_t binding_count) { + if (!binding_count) { + return descriptor_set_layout_empty_; + } + + TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key; + texture_descriptor_set_layout_key.is_samplers = uint32_t(is_samplers); + texture_descriptor_set_layout_key.is_vertex = uint32_t(is_vertex); + texture_descriptor_set_layout_key.binding_count = uint32_t(binding_count); + auto it_existing = + descriptor_set_layouts_textures_.find(texture_descriptor_set_layout_key); + if (it_existing != descriptor_set_layouts_textures_.end()) { + return it_existing->second; + } + + const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + + descriptor_set_layout_bindings_.clear(); + descriptor_set_layout_bindings_.reserve(binding_count); + VkDescriptorType descriptor_type = is_samplers + ? VK_DESCRIPTOR_TYPE_SAMPLER + : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + VkShaderStageFlags stage_flags = + is_vertex ? guest_shader_vertex_stages_ : VK_SHADER_STAGE_FRAGMENT_BIT; + for (size_t i = 0; i < binding_count; ++i) { + VkDescriptorSetLayoutBinding& descriptor_set_layout_binding = + descriptor_set_layout_bindings_.emplace_back(); + descriptor_set_layout_binding.binding = uint32_t(i); + descriptor_set_layout_binding.descriptorType = descriptor_type; + descriptor_set_layout_binding.descriptorCount = 1; + descriptor_set_layout_binding.stageFlags = stage_flags; + } + VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info; + descriptor_set_layout_create_info.sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + descriptor_set_layout_create_info.pNext = nullptr; + descriptor_set_layout_create_info.flags = 0; + descriptor_set_layout_create_info.bindingCount = uint32_t(binding_count); + descriptor_set_layout_create_info.pBindings = + descriptor_set_layout_bindings_.data(); + VkDescriptorSetLayout texture_descriptor_set_layout; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &texture_descriptor_set_layout) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + descriptor_set_layouts_textures_.emplace(texture_descriptor_set_layout_key, + texture_descriptor_set_layout); + return texture_descriptor_set_layout; +} + const VulkanPipelineCache::PipelineLayoutProvider* -VulkanCommandProcessor::GetPipelineLayout(uint32_t texture_count_pixel, - uint32_t texture_count_vertex) { +VulkanCommandProcessor::GetPipelineLayout(size_t texture_count_pixel, + size_t sampler_count_pixel, + size_t texture_count_vertex, + size_t sampler_count_vertex) { PipelineLayoutKey pipeline_layout_key; - pipeline_layout_key.texture_count_pixel = texture_count_pixel; - pipeline_layout_key.texture_count_vertex = texture_count_vertex; + pipeline_layout_key.texture_count_pixel = uint16_t(texture_count_pixel); + pipeline_layout_key.sampler_count_pixel = uint16_t(sampler_count_pixel); + pipeline_layout_key.texture_count_vertex = uint16_t(texture_count_vertex); + pipeline_layout_key.sampler_count_vertex = uint16_t(sampler_count_vertex); { auto it = pipeline_layouts_.find(pipeline_layout_key); if (it != pipeline_layouts_.end()) { @@ -1115,92 +1273,41 @@ VulkanCommandProcessor::GetPipelineLayout(uint32_t texture_count_pixel, } } - const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); - const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); - VkDevice device = provider.device(); - - VkDescriptorSetLayout descriptor_set_layout_textures_pixel; - if (texture_count_pixel) { - TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key; - texture_descriptor_set_layout_key.is_vertex = 0; - texture_descriptor_set_layout_key.texture_count = texture_count_pixel; - auto it = descriptor_set_layouts_textures_.find( - texture_descriptor_set_layout_key); - if (it != descriptor_set_layouts_textures_.end()) { - descriptor_set_layout_textures_pixel = it->second; - } else { - VkDescriptorSetLayoutBinding descriptor_set_layout_binding; - descriptor_set_layout_binding.binding = 0; - descriptor_set_layout_binding.descriptorType = - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptor_set_layout_binding.descriptorCount = texture_count_pixel; - descriptor_set_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - descriptor_set_layout_binding.pImmutableSamplers = nullptr; - VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info; - descriptor_set_layout_create_info.sType = - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptor_set_layout_create_info.pNext = nullptr; - descriptor_set_layout_create_info.flags = 0; - descriptor_set_layout_create_info.bindingCount = 1; - descriptor_set_layout_create_info.pBindings = - &descriptor_set_layout_binding; - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_textures_pixel) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for {} combined " - "images and samplers for guest pixel shaders", - texture_count_pixel); - return nullptr; - } - descriptor_set_layouts_textures_.emplace( - texture_descriptor_set_layout_key, - descriptor_set_layout_textures_pixel); - } - } else { - descriptor_set_layout_textures_pixel = descriptor_set_layout_empty_; + VkDescriptorSetLayout descriptor_set_layout_textures_pixel = + GetTextureDescriptorSetLayout(false, false, texture_count_pixel); + if (descriptor_set_layout_textures_pixel == VK_NULL_HANDLE) { + XELOGE( + "Failed to obtain a Vulkan descriptor set layout for {} sampled images " + "for guest pixel shaders", + texture_count_pixel); + return nullptr; } - - VkDescriptorSetLayout descriptor_set_layout_textures_vertex; - if (texture_count_vertex) { - TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key; - texture_descriptor_set_layout_key.is_vertex = 0; - texture_descriptor_set_layout_key.texture_count = texture_count_vertex; - auto it = descriptor_set_layouts_textures_.find( - texture_descriptor_set_layout_key); - if (it != descriptor_set_layouts_textures_.end()) { - descriptor_set_layout_textures_vertex = it->second; - } else { - VkDescriptorSetLayoutBinding descriptor_set_layout_binding; - descriptor_set_layout_binding.binding = 0; - descriptor_set_layout_binding.descriptorType = - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - descriptor_set_layout_binding.descriptorCount = texture_count_vertex; - descriptor_set_layout_binding.stageFlags = guest_shader_vertex_stages_; - descriptor_set_layout_binding.pImmutableSamplers = nullptr; - VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info; - descriptor_set_layout_create_info.sType = - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptor_set_layout_create_info.pNext = nullptr; - descriptor_set_layout_create_info.flags = 0; - descriptor_set_layout_create_info.bindingCount = 1; - descriptor_set_layout_create_info.pBindings = - &descriptor_set_layout_binding; - if (dfn.vkCreateDescriptorSetLayout( - device, &descriptor_set_layout_create_info, nullptr, - &descriptor_set_layout_textures_vertex) != VK_SUCCESS) { - XELOGE( - "Failed to create a Vulkan descriptor set layout for {} combined " - "images and samplers for guest vertex shaders", - texture_count_vertex); - return nullptr; - } - descriptor_set_layouts_textures_.emplace( - texture_descriptor_set_layout_key, - descriptor_set_layout_textures_vertex); - } - } else { - descriptor_set_layout_textures_vertex = descriptor_set_layout_empty_; + VkDescriptorSetLayout descriptor_set_layout_samplers_pixel = + GetTextureDescriptorSetLayout(true, false, sampler_count_pixel); + if (descriptor_set_layout_samplers_pixel == VK_NULL_HANDLE) { + XELOGE( + "Failed to obtain a Vulkan descriptor set layout for {} samplers for " + "guest pixel shaders", + sampler_count_pixel); + return nullptr; + } + VkDescriptorSetLayout descriptor_set_layout_textures_vertex = + GetTextureDescriptorSetLayout(false, true, texture_count_vertex); + if (descriptor_set_layout_textures_vertex == VK_NULL_HANDLE) { + XELOGE( + "Failed to obtain a Vulkan descriptor set layout for {} sampled images " + "for guest vertex shaders", + texture_count_vertex); + return nullptr; + } + VkDescriptorSetLayout descriptor_set_layout_samplers_vertex = + GetTextureDescriptorSetLayout(true, true, sampler_count_vertex); + if (descriptor_set_layout_samplers_vertex == VK_NULL_HANDLE) { + XELOGE( + "Failed to obtain a Vulkan descriptor set layout for {} samplers for " + "guest vertex shaders", + sampler_count_vertex); + return nullptr; } VkDescriptorSetLayout @@ -1211,23 +1318,36 @@ VulkanCommandProcessor::GetPipelineLayout(uint32_t texture_count_pixel, descriptor_set_layout_shared_memory_and_edram_; descriptor_set_layouts [SpirvShaderTranslator::kDescriptorSetBoolLoopConstants] = - descriptor_set_layout_fetch_bool_loop_constants_; + GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout::kUniformBufferGuestShader); descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetSystemConstants] = - descriptor_set_layout_system_constants_; + GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout::kUniformBufferSystemConstants); descriptor_set_layouts [SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel] = - descriptor_set_layout_float_constants_pixel_; + GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout::kUniformBufferFragment); descriptor_set_layouts [SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex] = - descriptor_set_layout_float_constants_vertex_; + GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout::kUniformBufferGuestVertex); descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetFetchConstants] = - descriptor_set_layout_fetch_bool_loop_constants_; + GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout::kUniformBufferGuestShader); // Mutable layouts. + descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetSamplersVertex] = + descriptor_set_layout_samplers_vertex; descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetTexturesVertex] = descriptor_set_layout_textures_vertex; + descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetSamplersPixel] = + descriptor_set_layout_samplers_pixel; descriptor_set_layouts[SpirvShaderTranslator::kDescriptorSetTexturesPixel] = descriptor_set_layout_textures_pixel; + const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + VkPipelineLayoutCreateInfo pipeline_layout_create_info; pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; @@ -1251,7 +1371,9 @@ VulkanCommandProcessor::GetPipelineLayout(uint32_t texture_count_pixel, std::piecewise_construct, std::forward_as_tuple(pipeline_layout_key), std::forward_as_tuple(pipeline_layout, descriptor_set_layout_textures_vertex, - descriptor_set_layout_textures_pixel)); + descriptor_set_layout_samplers_vertex, + descriptor_set_layout_textures_pixel, + descriptor_set_layout_samplers_pixel)); // unordered_map insertion doesn't invalidate element references. return &emplaced_pair.first->second; } @@ -1472,6 +1594,13 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, // set N if set layouts 0 through N are compatible). uint32_t descriptor_sets_kept = uint32_t(SpirvShaderTranslator::kDescriptorSetCount); + if (current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_samplers_vertex_ref() != + pipeline_layout->descriptor_set_layout_samplers_vertex_ref()) { + descriptor_sets_kept = std::min( + descriptor_sets_kept, + uint32_t(SpirvShaderTranslator::kDescriptorSetSamplersVertex)); + } if (current_guest_graphics_pipeline_layout_ ->descriptor_set_layout_textures_vertex_ref() != pipeline_layout->descriptor_set_layout_textures_vertex_ref()) { @@ -1479,6 +1608,13 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, descriptor_sets_kept, uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesVertex)); } + if (current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_samplers_pixel_ref() != + pipeline_layout->descriptor_set_layout_samplers_pixel_ref()) { + descriptor_sets_kept = std::min( + descriptor_sets_kept, + uint32_t(SpirvShaderTranslator::kDescriptorSetSamplersPixel)); + } if (current_guest_graphics_pipeline_layout_ ->descriptor_set_layout_textures_pixel_ref() != pipeline_layout->descriptor_set_layout_textures_pixel_ref()) { @@ -1860,13 +1996,45 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) { [SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram] = shared_memory_and_edram_descriptor_set_; current_graphics_descriptor_set_values_up_to_date_ = - uint32_t(1) + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram; // Reclaim pool pages - no need to do this every small submission since some // may be reused. - transient_descriptor_pool_uniform_buffers_->Reclaim(frame_completed_); + // FIXME(Triang3l): This will result in a memory leak if the guest is not + // presenting. uniform_buffer_pool_->Reclaim(frame_completed_); + while (!single_transient_descriptors_used_.empty()) { + const UsedSingleTransientDescriptor& used_transient_descriptor = + single_transient_descriptors_used_.front(); + if (used_transient_descriptor.frame > frame_completed_) { + break; + } + single_transient_descriptors_free_[size_t( + used_transient_descriptor.layout)] + .push_back(used_transient_descriptor.set); + single_transient_descriptors_used_.pop_front(); + } + while (!texture_transient_descriptor_sets_used_.empty()) { + const UsedTextureTransientDescriptorSet& used_transient_descriptor_set = + texture_transient_descriptor_sets_used_.front(); + if (used_transient_descriptor_set.frame > frame_completed_) { + break; + } + auto it = texture_transient_descriptor_sets_free_.find( + used_transient_descriptor_set.layout); + if (it == texture_transient_descriptor_sets_free_.end()) { + it = + texture_transient_descriptor_sets_free_ + .emplace( + std::piecewise_construct, + std::forward_as_tuple(used_transient_descriptor_set.layout), + std::forward_as_tuple()) + .first; + } + it->second.push_back(used_transient_descriptor_set.set); + texture_transient_descriptor_sets_used_.pop_front(); + } primitive_processor_->BeginFrame(); @@ -2107,8 +2275,9 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) { } command_buffers_writable_.clear(); + ClearTransientDescriptorPools(); + uniform_buffer_pool_->ClearCache(); - transient_descriptor_pool_uniform_buffers_->ClearCache(); texture_cache_->ClearCache(); @@ -2140,6 +2309,21 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) { return true; } +void VulkanCommandProcessor::ClearTransientDescriptorPools() { + texture_transient_descriptor_sets_free_.clear(); + texture_transient_descriptor_sets_used_.clear(); + transient_descriptor_allocator_sampler_.Reset(); + transient_descriptor_allocator_sampled_image_.Reset(); + + for (std::vector& transient_descriptors_free : + single_transient_descriptors_free_) { + transient_descriptors_free.clear(); + } + single_transient_descriptors_used_.clear(); + transient_descriptor_allocator_storage_buffer_.Reset(); + transient_descriptor_allocator_uniform_buffer_.Reset(); +} + void VulkanCommandProcessor::SplitPendingBarrier() { size_t pending_buffer_memory_barrier_count = pending_barriers_buffer_memory_barriers_.size(); @@ -2431,7 +2615,7 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( if (dirty) { current_graphics_descriptor_set_values_up_to_date_ &= - ~(uint32_t(1) << SpirvShaderTranslator::kDescriptorSetSystemConstants); + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSystemConstants); } } @@ -2443,7 +2627,13 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, const RegisterFile& regs = *register_file_; + const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + // Invalidate descriptors for changed data. + + // Float constants. // These are the constant base addresses/ranges for shaders. // We have these hardcoded right now cause nothing seems to differ on the Xbox // 360 (however, OpenGL ES on Adreno 200 on Android has different ranges). @@ -2465,7 +2655,7 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, if (float_constant_count_vertex) { current_graphics_descriptor_set_values_up_to_date_ &= ~( - uint32_t(1) + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex); } } @@ -2482,7 +2672,7 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, float_constant_map_pixel.float_bitmap[i]; if (float_constant_count_pixel) { current_graphics_descriptor_set_values_up_to_date_ &= - ~(uint32_t(1) + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel); } } @@ -2492,29 +2682,133 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, sizeof(current_float_constant_map_pixel_)); } + // Textures and samplers. + const std::vector& samplers_vertex = + vertex_shader->GetSamplerBindingsAfterTranslation(); + const std::vector& textures_vertex = + vertex_shader->GetTextureBindingsAfterTranslation(); + uint32_t sampler_count_vertex = uint32_t(samplers_vertex.size()); + uint32_t texture_count_vertex = uint32_t(textures_vertex.size()); + const std::vector* samplers_pixel; + const std::vector* textures_pixel; + uint32_t sampler_count_pixel, texture_count_pixel; + if (pixel_shader) { + samplers_pixel = &pixel_shader->GetSamplerBindingsAfterTranslation(); + textures_pixel = &pixel_shader->GetTextureBindingsAfterTranslation(); + sampler_count_pixel = uint32_t(samplers_pixel->size()); + texture_count_pixel = uint32_t(textures_pixel->size()); + } else { + samplers_pixel = nullptr; + textures_pixel = nullptr; + sampler_count_pixel = 0; + texture_count_pixel = 0; + } + // TODO(Triang3l): Reuse texture and sampler bindings if not changed. + current_graphics_descriptor_set_values_up_to_date_ &= + ~((UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersVertex) | + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex) | + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersPixel) | + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel)); + // Make sure new descriptor sets are bound to the command buffer. + current_graphics_descriptor_sets_bound_up_to_date_ &= current_graphics_descriptor_set_values_up_to_date_; + // Fill the texture and sampler write image infos. + + bool write_vertex_samplers = + sampler_count_vertex && + !(current_graphics_descriptor_set_values_up_to_date_ & + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersVertex)); + bool write_vertex_textures = + texture_count_vertex && + !(current_graphics_descriptor_set_values_up_to_date_ & + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex)); + bool write_pixel_samplers = + sampler_count_pixel && + !(current_graphics_descriptor_set_values_up_to_date_ & + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersPixel)); + bool write_pixel_textures = + texture_count_pixel && + !(current_graphics_descriptor_set_values_up_to_date_ & + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel)); + descriptor_write_image_info_.clear(); + descriptor_write_image_info_.reserve( + (write_vertex_samplers ? sampler_count_vertex : 0) + + (write_vertex_textures ? texture_count_vertex : 0) + + (write_pixel_samplers ? sampler_count_pixel : 0) + + (write_pixel_textures ? texture_count_pixel : 0)); + size_t vertex_sampler_image_info_offset = descriptor_write_image_info_.size(); + if (write_vertex_samplers) { + // TODO(Triang3l): Real samplers. + for (const VulkanShader::SamplerBinding& sampler_binding : + samplers_vertex) { + VkDescriptorImageInfo& descriptor_image_info = + descriptor_write_image_info_.emplace_back(); + descriptor_image_info.sampler = provider.GetHostSampler( + ui::vulkan::VulkanProvider::HostSampler::kNearestClamp); + } + } + size_t vertex_texture_image_info_offset = descriptor_write_image_info_.size(); + if (write_vertex_textures) { + // TODO(Triang3l): Real textures. + for (const VulkanShader::TextureBinding& texture_binding : + textures_vertex) { + VkDescriptorImageInfo& descriptor_image_info = + descriptor_write_image_info_.emplace_back(); + descriptor_image_info.imageView = + texture_cache_->GetNullImageView(texture_binding.dimension); + descriptor_image_info.imageLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + } + size_t pixel_sampler_image_info_offset = descriptor_write_image_info_.size(); + if (write_pixel_samplers) { + // TODO(Triang3l): Real samplers. + for (const VulkanShader::SamplerBinding& sampler_binding : + *samplers_pixel) { + VkDescriptorImageInfo& descriptor_image_info = + descriptor_write_image_info_.emplace_back(); + descriptor_image_info.sampler = provider.GetHostSampler( + ui::vulkan::VulkanProvider::HostSampler::kNearestClamp); + } + } + size_t pixel_texture_image_info_offset = descriptor_write_image_info_.size(); + if (write_pixel_textures) { + // TODO(Triang3l): Real textures. + for (const VulkanShader::TextureBinding& texture_binding : + *textures_pixel) { + VkDescriptorImageInfo& descriptor_image_info = + descriptor_write_image_info_.emplace_back(); + descriptor_image_info.imageView = + texture_cache_->GetNullImageView(texture_binding.dimension); + descriptor_image_info.imageLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + } + // Write the new descriptor sets. + VkWriteDescriptorSet write_descriptor_sets[SpirvShaderTranslator::kDescriptorSetCount]; uint32_t write_descriptor_set_count = 0; uint32_t write_descriptor_set_bits = 0; assert_not_zero( current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSharedMemoryAndEdram)); + // Bool and loop constants. VkDescriptorBufferInfo buffer_info_bool_loop_constants; if (!(current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetBoolLoopConstants))) { VkWriteDescriptorSet& write_bool_loop_constants = write_descriptor_sets[write_descriptor_set_count++]; constexpr size_t kBoolLoopConstantsSize = sizeof(uint32_t) * (8 + 32); - uint8_t* mapping_bool_loop_constants = WriteUniformBufferBinding( + uint8_t* mapping_bool_loop_constants = WriteTransientUniformBufferBinding( kBoolLoopConstantsSize, - descriptor_set_layout_fetch_bool_loop_constants_, + SingleTransientDescriptorLayout::kUniformBufferGuestShader, buffer_info_bool_loop_constants, write_bool_loop_constants); if (!mapping_bool_loop_constants) { return false; @@ -2523,35 +2817,37 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, ®s[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031].u32, kBoolLoopConstantsSize); write_descriptor_set_bits |= - uint32_t(1) << SpirvShaderTranslator::kDescriptorSetBoolLoopConstants; + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetBoolLoopConstants; current_graphics_descriptor_sets_ [SpirvShaderTranslator::kDescriptorSetBoolLoopConstants] = write_bool_loop_constants.dstSet; } + // System constants. VkDescriptorBufferInfo buffer_info_system_constants; if (!(current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSystemConstants))) { VkWriteDescriptorSet& write_system_constants = write_descriptor_sets[write_descriptor_set_count++]; - uint8_t* mapping_system_constants = WriteUniformBufferBinding( + uint8_t* mapping_system_constants = WriteTransientUniformBufferBinding( sizeof(SpirvShaderTranslator::SystemConstants), - descriptor_set_layout_system_constants_, buffer_info_system_constants, - write_system_constants); + SingleTransientDescriptorLayout::kUniformBufferSystemConstants, + buffer_info_system_constants, write_system_constants); if (!mapping_system_constants) { return false; } std::memcpy(mapping_system_constants, &system_constants_, sizeof(SpirvShaderTranslator::SystemConstants)); write_descriptor_set_bits |= - uint32_t(1) << SpirvShaderTranslator::kDescriptorSetSystemConstants; + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSystemConstants; current_graphics_descriptor_sets_ [SpirvShaderTranslator::kDescriptorSetSystemConstants] = write_system_constants.dstSet; } + // Pixel shader float constants. VkDescriptorBufferInfo buffer_info_float_constant_pixel; if (!(current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel))) { // Even if the shader doesn't need any float constants, a valid binding must // still be provided (the pipeline layout always has float constants, for @@ -2560,9 +2856,9 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, // buffer. VkWriteDescriptorSet& write_float_constants_pixel = write_descriptor_sets[write_descriptor_set_count++]; - uint8_t* mapping_float_constants_pixel = WriteUniformBufferBinding( - sizeof(float) * 4 * std::max(float_constant_count_pixel, uint32_t(1)), - descriptor_set_layout_float_constants_pixel_, + uint8_t* mapping_float_constants_pixel = WriteTransientUniformBufferBinding( + sizeof(float) * 4 * std::max(float_constant_count_pixel, UINT32_C(1)), + SingleTransientDescriptorLayout::kUniformBufferFragment, buffer_info_float_constant_pixel, write_float_constants_pixel); if (!mapping_float_constants_pixel) { return false; @@ -2582,21 +2878,24 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, } } write_descriptor_set_bits |= - uint32_t(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel; + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel; current_graphics_descriptor_sets_ [SpirvShaderTranslator::kDescriptorSetFloatConstantsPixel] = write_float_constants_pixel.dstSet; } + // Vertex shader float constants. VkDescriptorBufferInfo buffer_info_float_constant_vertex; if (!(current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex))) { VkWriteDescriptorSet& write_float_constants_vertex = write_descriptor_sets[write_descriptor_set_count++]; - uint8_t* mapping_float_constants_vertex = WriteUniformBufferBinding( - sizeof(float) * 4 * std::max(float_constant_count_vertex, uint32_t(1)), - descriptor_set_layout_float_constants_vertex_, - buffer_info_float_constant_vertex, write_float_constants_vertex); + uint8_t* mapping_float_constants_vertex = + WriteTransientUniformBufferBinding( + sizeof(float) * 4 * + std::max(float_constant_count_vertex, UINT32_C(1)), + SingleTransientDescriptorLayout::kUniformBufferGuestVertex, + buffer_info_float_constant_vertex, write_float_constants_vertex); if (!mapping_float_constants_vertex) { return false; } @@ -2615,20 +2914,22 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, } } write_descriptor_set_bits |= - uint32_t(1) + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex; current_graphics_descriptor_sets_ [SpirvShaderTranslator::kDescriptorSetFloatConstantsVertex] = write_float_constants_vertex.dstSet; } + // Fetch constants. VkDescriptorBufferInfo buffer_info_fetch_constants; if (!(current_graphics_descriptor_set_values_up_to_date_ & - (uint32_t(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants))) { + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants))) { VkWriteDescriptorSet& write_fetch_constants = write_descriptor_sets[write_descriptor_set_count++]; constexpr size_t kFetchConstantsSize = sizeof(uint32_t) * 6 * 32; - uint8_t* mapping_fetch_constants = WriteUniformBufferBinding( - kFetchConstantsSize, descriptor_set_layout_fetch_bool_loop_constants_, + uint8_t* mapping_fetch_constants = WriteTransientUniformBufferBinding( + kFetchConstantsSize, + SingleTransientDescriptorLayout::kUniformBufferGuestShader, buffer_info_fetch_constants, write_fetch_constants); if (!mapping_fetch_constants) { return false; @@ -2637,36 +2938,115 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, ®s[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0].u32, kFetchConstantsSize); write_descriptor_set_bits |= - uint32_t(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants; + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetFetchConstants; current_graphics_descriptor_sets_ [SpirvShaderTranslator::kDescriptorSetFetchConstants] = write_fetch_constants.dstSet; } + // Vertex shader samplers. + if (write_vertex_samplers) { + VkWriteDescriptorSet& write_samplers = + write_descriptor_sets[write_descriptor_set_count++]; + if (!WriteTransientTextureBindings( + true, true, sampler_count_vertex, + current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_samplers_vertex_ref(), + descriptor_write_image_info_.data() + + vertex_sampler_image_info_offset, + write_samplers)) { + return false; + } + write_descriptor_set_bits |= + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersVertex; + current_graphics_descriptor_sets_ + [SpirvShaderTranslator::kDescriptorSetSamplersVertex] = + write_samplers.dstSet; + } + // Vertex shader textures. + if (write_vertex_textures) { + VkWriteDescriptorSet& write_textures = + write_descriptor_sets[write_descriptor_set_count++]; + if (!WriteTransientTextureBindings( + false, true, texture_count_vertex, + current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_textures_vertex_ref(), + descriptor_write_image_info_.data() + + vertex_texture_image_info_offset, + write_textures)) { + return false; + } + write_descriptor_set_bits |= + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex; + current_graphics_descriptor_sets_ + [SpirvShaderTranslator::kDescriptorSetTexturesVertex] = + write_textures.dstSet; + } + // Pixel shader samplers. + if (write_pixel_samplers) { + VkWriteDescriptorSet& write_samplers = + write_descriptor_sets[write_descriptor_set_count++]; + if (!WriteTransientTextureBindings( + true, false, sampler_count_pixel, + current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_samplers_pixel_ref(), + descriptor_write_image_info_.data() + + pixel_sampler_image_info_offset, + write_samplers)) { + return false; + } + write_descriptor_set_bits |= + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersPixel; + current_graphics_descriptor_sets_ + [SpirvShaderTranslator::kDescriptorSetSamplersPixel] = + write_samplers.dstSet; + } + // Pixel shader textures. + if (write_pixel_textures) { + VkWriteDescriptorSet& write_textures = + write_descriptor_sets[write_descriptor_set_count++]; + if (!WriteTransientTextureBindings( + false, false, texture_count_pixel, + current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_textures_pixel_ref(), + descriptor_write_image_info_.data() + + pixel_texture_image_info_offset, + write_textures)) { + return false; + } + write_descriptor_set_bits |= + UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel; + current_graphics_descriptor_sets_ + [SpirvShaderTranslator::kDescriptorSetTexturesPixel] = + write_textures.dstSet; + } + // Write. if (write_descriptor_set_count) { - const ui::vulkan::VulkanProvider& provider = GetVulkanProvider(); - const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); - VkDevice device = provider.device(); dfn.vkUpdateDescriptorSets(device, write_descriptor_set_count, write_descriptor_sets, 0, nullptr); } - // Only make valid if written successfully. + // Only make valid if all descriptor sets have been allocated and written + // successfully. current_graphics_descriptor_set_values_up_to_date_ |= write_descriptor_set_bits; // Bind the new descriptor sets. uint32_t descriptor_sets_needed = - (uint32_t(1) << SpirvShaderTranslator::kDescriptorSetCount) - 1; - if (current_guest_graphics_pipeline_layout_ - ->descriptor_set_layout_textures_vertex_ref() == - descriptor_set_layout_empty_) { + (UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetCount) - 1; + if (!sampler_count_vertex) { descriptor_sets_needed &= - ~(uint32_t(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex); + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersVertex); } - if (current_guest_graphics_pipeline_layout_ - ->descriptor_set_layout_textures_pixel_ref() == - descriptor_set_layout_empty_) { + if (!texture_count_vertex) { descriptor_sets_needed &= - ~(uint32_t(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel); + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesVertex); + } + if (!sampler_count_pixel) { + descriptor_sets_needed &= + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetSamplersPixel); + } + if (!texture_count_pixel) { + descriptor_sets_needed &= + ~(UINT32_C(1) << SpirvShaderTranslator::kDescriptorSetTexturesPixel); } uint32_t descriptor_sets_remaining = descriptor_sets_needed & @@ -2676,9 +3056,9 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, xe::bit_scan_forward(descriptor_sets_remaining, &descriptor_set_index)) { uint32_t descriptor_set_mask_tzcnt = xe::tzcnt(~(descriptor_sets_remaining | - ((uint32_t(1) << descriptor_set_index) - 1))); - // TODO(Triang3l): Bind to compute for rectangle list emulation without - // geometry shaders. + ((UINT32_C(1) << descriptor_set_index) - 1))); + // TODO(Triang3l): Bind to compute for memexport emulation without vertex + // shader memory stores. deferred_command_buffer_.CmdVkBindDescriptorSets( VK_PIPELINE_BIND_POINT_GRAPHICS, current_guest_graphics_pipeline_layout_->GetPipelineLayout(), @@ -2688,20 +3068,20 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, break; } descriptor_sets_remaining &= - ~((uint32_t(1) << descriptor_set_mask_tzcnt) - 1); + ~((UINT32_C(1) << descriptor_set_mask_tzcnt) - 1); } current_graphics_descriptor_sets_bound_up_to_date_ |= descriptor_sets_needed; return true; } -uint8_t* VulkanCommandProcessor::WriteUniformBufferBinding( - size_t size, VkDescriptorSetLayout descriptor_set_layout, +uint8_t* VulkanCommandProcessor::WriteTransientUniformBufferBinding( + size_t size, SingleTransientDescriptorLayout transient_descriptor_layout, VkDescriptorBufferInfo& descriptor_buffer_info_out, VkWriteDescriptorSet& write_descriptor_set_out) { + assert_true(frame_open_); VkDescriptorSet descriptor_set = - transient_descriptor_pool_uniform_buffers_->Request( - frame_current_, descriptor_set_layout, 1); + AllocateSingleTransientDescriptor(transient_descriptor_layout); if (descriptor_set == VK_NULL_HANDLE) { return nullptr; } @@ -2728,6 +3108,53 @@ uint8_t* VulkanCommandProcessor::WriteUniformBufferBinding( return mapping; } +bool VulkanCommandProcessor::WriteTransientTextureBindings( + bool is_samplers, bool is_vertex, uint32_t binding_count, + VkDescriptorSetLayout descriptor_set_layout, + const VkDescriptorImageInfo* image_info, + VkWriteDescriptorSet& write_descriptor_set_out) { + assert_not_zero(binding_count); + assert_true(frame_open_); + TextureDescriptorSetLayoutKey texture_descriptor_set_layout_key; + texture_descriptor_set_layout_key.is_samplers = uint32_t(is_samplers); + texture_descriptor_set_layout_key.is_vertex = uint32_t(is_vertex); + texture_descriptor_set_layout_key.binding_count = binding_count; + VkDescriptorSet texture_descriptor_set; + auto textures_free_it = texture_transient_descriptor_sets_free_.find( + texture_descriptor_set_layout_key); + if (textures_free_it != texture_transient_descriptor_sets_free_.end() && + !textures_free_it->second.empty()) { + texture_descriptor_set = textures_free_it->second.back(); + textures_free_it->second.pop_back(); + } else { + texture_descriptor_set = + (is_samplers ? transient_descriptor_allocator_sampler_ + : transient_descriptor_allocator_sampled_image_) + .Allocate(descriptor_set_layout, binding_count); + if (texture_descriptor_set == VK_NULL_HANDLE) { + return false; + } + } + UsedTextureTransientDescriptorSet& used_texture_descriptor_set = + texture_transient_descriptor_sets_used_.emplace_back(); + used_texture_descriptor_set.frame = frame_current_; + used_texture_descriptor_set.layout = texture_descriptor_set_layout_key; + used_texture_descriptor_set.set = texture_descriptor_set; + write_descriptor_set_out.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor_set_out.pNext = nullptr; + write_descriptor_set_out.dstSet = texture_descriptor_set; + write_descriptor_set_out.dstBinding = 0; + write_descriptor_set_out.dstArrayElement = 0; + write_descriptor_set_out.descriptorCount = binding_count; + write_descriptor_set_out.descriptorType = + is_samplers ? VK_DESCRIPTOR_TYPE_SAMPLER + : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write_descriptor_set_out.pImageInfo = image_info; + write_descriptor_set_out.pBufferInfo = nullptr; + write_descriptor_set_out.pTexelBufferView = nullptr; + return true; +} + } // namespace vulkan } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.h b/src/xenia/gpu/vulkan/vulkan_command_processor.h index 2f90ca614..3158db0b6 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.h +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.h @@ -36,7 +36,7 @@ #include "xenia/gpu/vulkan/vulkan_texture_cache.h" #include "xenia/gpu/xenos.h" #include "xenia/kernel/kernel_state.h" -#include "xenia/ui/vulkan/transient_descriptor_pool.h" +#include "xenia/ui/vulkan/single_type_descriptor_set_allocator.h" #include "xenia/ui/vulkan/vulkan_presenter.h" #include "xenia/ui/vulkan/vulkan_provider.h" #include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h" @@ -47,6 +47,17 @@ namespace vulkan { class VulkanCommandProcessor : public CommandProcessor { public: + // Single-descriptor layouts for use within a single frame. + enum class SingleTransientDescriptorLayout { + kUniformBufferGuestVertex, + kUniformBufferFragment, + kUniformBufferGuestShader, + kUniformBufferSystemConstants, + kUniformBufferCompute, + kStorageBufferCompute, + kCount, + }; + VulkanCommandProcessor(VulkanGraphicsSystem* graphics_system, kernel::KernelState* kernel_state); ~VulkanCommandProcessor(); @@ -119,9 +130,23 @@ class VulkanCommandProcessor : public CommandProcessor { // scope. Submission must be open. void EndRenderPass(); + VkDescriptorSetLayout GetSingleTransientDescriptorLayout( + SingleTransientDescriptorLayout transient_descriptor_layout) const { + return descriptor_set_layouts_single_transient_[size_t( + transient_descriptor_layout)]; + } + // A frame must be open. + VkDescriptorSet AllocateSingleTransientDescriptor( + SingleTransientDescriptorLayout transient_descriptor_layout); + + // The returned reference is valid until a cache clear. + VkDescriptorSetLayout GetTextureDescriptorSetLayout(bool is_samplers, + bool is_vertex, + size_t binding_count); // The returned reference is valid until a cache clear. const VulkanPipelineCache::PipelineLayoutProvider* GetPipelineLayout( - uint32_t texture_count_pixel, uint32_t texture_count_vertex); + size_t texture_count_pixel, size_t sampler_count_pixel, + size_t texture_count_vertex, size_t sampler_count_vertex); // Binds a graphics pipeline for host-specific purposes, invalidating the // affected state. keep_dynamic_* must be false (to invalidate the dynamic @@ -172,10 +197,12 @@ class VulkanCommandProcessor : public CommandProcessor { union TextureDescriptorSetLayoutKey { uint32_t key; struct { + // 0 - sampled image descriptors, 1 - sampler descriptors. + uint32_t is_samplers : 1; uint32_t is_vertex : 1; // For 0, use descriptor_set_layout_empty_ instead as these are owning // references. - uint32_t texture_count : 31; + uint32_t binding_count : 30; }; TextureDescriptorSetLayoutKey() : key(0) { @@ -196,12 +223,14 @@ class VulkanCommandProcessor : public CommandProcessor { }; union PipelineLayoutKey { - uint32_t key; + uint64_t key; struct { // Pixel textures in the low bits since those are varied much more // commonly. - uint32_t texture_count_pixel : 16; - uint32_t texture_count_vertex : 16; + uint16_t texture_count_pixel; + uint16_t sampler_count_pixel; + uint16_t texture_count_vertex; + uint16_t sampler_count_vertex; }; PipelineLayoutKey() : key(0) { static_assert_size(*this, sizeof(key)); } @@ -221,29 +250,55 @@ class VulkanCommandProcessor : public CommandProcessor { class PipelineLayout : public VulkanPipelineCache::PipelineLayoutProvider { public: - PipelineLayout( + explicit PipelineLayout( VkPipelineLayout pipeline_layout, VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref, - VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref) + VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref, + VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref, + VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref) : pipeline_layout_(pipeline_layout), descriptor_set_layout_textures_vertex_ref_( descriptor_set_layout_textures_vertex_ref), + descriptor_set_layout_samplers_vertex_ref_( + descriptor_set_layout_samplers_vertex_ref), descriptor_set_layout_textures_pixel_ref_( - descriptor_set_layout_textures_pixel_ref) {} + descriptor_set_layout_textures_pixel_ref), + descriptor_set_layout_samplers_pixel_ref_( + descriptor_set_layout_samplers_pixel_ref) {} VkPipelineLayout GetPipelineLayout() const override { return pipeline_layout_; } VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref() const { return descriptor_set_layout_textures_vertex_ref_; } + VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref() const { + return descriptor_set_layout_samplers_vertex_ref_; + } VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref() const { return descriptor_set_layout_textures_pixel_ref_; } + VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref() const { + return descriptor_set_layout_samplers_pixel_ref_; + } private: VkPipelineLayout pipeline_layout_; VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref_; + VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref_; VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref_; + VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref_; + }; + + struct UsedSingleTransientDescriptor { + uint64_t frame; + SingleTransientDescriptorLayout layout; + VkDescriptorSet set; + }; + + struct UsedTextureTransientDescriptorSet { + uint64_t frame; + TextureDescriptorSetLayoutKey layout; + VkDescriptorSet set; }; // BeginSubmission and EndSubmission may be called at any time. If there's an @@ -272,6 +327,8 @@ class VulkanCommandProcessor : public CommandProcessor { return !submission_open_ && submissions_in_flight_fences_.empty(); } + void ClearTransientDescriptorPools(); + void SplitPendingBarrier(); void UpdateDynamicState(const draw_util::ViewportInfo& viewport_info, @@ -284,10 +341,20 @@ class VulkanCommandProcessor : public CommandProcessor { // Allocates a descriptor, space in the uniform buffer pool, and fills the // VkWriteDescriptorSet structure and VkDescriptorBufferInfo referenced by it. // Returns null in case of failure. - uint8_t* WriteUniformBufferBinding( - size_t size, VkDescriptorSetLayout descriptor_set_layout, + uint8_t* WriteTransientUniformBufferBinding( + size_t size, SingleTransientDescriptorLayout transient_descriptor_layout, VkDescriptorBufferInfo& descriptor_buffer_info_out, VkWriteDescriptorSet& write_descriptor_set_out); + // Allocates a descriptor set and fills the VkWriteDescriptorSet structure. + // The descriptor set layout must be the one for the given is_samplers, + // is_vertex, binding_count (from GetTextureDescriptorSetLayout - may be + // already available at the moment of the call, no need to locate it again). + // Returns whether the allocation was successful. + bool WriteTransientTextureBindings( + bool is_samplers, bool is_vertex, uint32_t binding_count, + VkDescriptorSetLayout descriptor_set_layout, + const VkDescriptorImageInfo* image_info, + VkWriteDescriptorSet& write_descriptor_set_out); bool device_lost_ = false; @@ -333,22 +400,21 @@ class VulkanCommandProcessor : public CommandProcessor { std::vector sparse_buffer_bind_infos_temp_; VkPipelineStageFlags sparse_bind_wait_stage_mask_ = 0; - std::unique_ptr - transient_descriptor_pool_uniform_buffers_; + // Temporary storage with reusable memory for creating descriptor set layouts. + std::vector descriptor_set_layout_bindings_; + // Temporary storage with reusable memory for writing image and sampler + // descriptors. + std::vector descriptor_write_image_info_; + std::unique_ptr uniform_buffer_pool_; // Descriptor set layouts used by different shaders. VkDescriptorSetLayout descriptor_set_layout_empty_ = VK_NULL_HANDLE; - VkDescriptorSetLayout descriptor_set_layout_fetch_bool_loop_constants_ = - VK_NULL_HANDLE; - VkDescriptorSetLayout descriptor_set_layout_float_constants_vertex_ = - VK_NULL_HANDLE; - VkDescriptorSetLayout descriptor_set_layout_float_constants_pixel_ = - VK_NULL_HANDLE; - VkDescriptorSetLayout descriptor_set_layout_system_constants_ = - VK_NULL_HANDLE; VkDescriptorSetLayout descriptor_set_layout_shared_memory_and_edram_ = VK_NULL_HANDLE; + std::array + descriptor_set_layouts_single_transient_{}; // Descriptor set layouts are referenced by pipeline_layouts_. std::unordered_map pipeline_layouts_; + ui::vulkan::SingleTypeDescriptorSetAllocator + transient_descriptor_allocator_uniform_buffer_; + ui::vulkan::SingleTypeDescriptorSetAllocator + transient_descriptor_allocator_storage_buffer_; + std::deque single_transient_descriptors_used_; + std::array, + size_t(SingleTransientDescriptorLayout::kCount)> + single_transient_descriptors_free_; + + ui::vulkan::SingleTypeDescriptorSetAllocator + transient_descriptor_allocator_sampled_image_; + ui::vulkan::SingleTypeDescriptorSetAllocator + transient_descriptor_allocator_sampler_; + std::deque + texture_transient_descriptor_sets_used_; + std::unordered_map, + TextureDescriptorSetLayoutKey::Hasher> + texture_transient_descriptor_sets_free_; + std::unique_ptr shared_memory_; std::unique_ptr primitive_processor_; diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index b6e74a648..433c42aeb 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -82,6 +82,7 @@ void VulkanPipelineCache::ClearCache() { const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn(); VkDevice device = provider.device(); + // Destroy all pipelines. last_pipeline_ = nullptr; for (const auto& pipeline_pair : pipelines_) { if (pipeline_pair.second.pipeline != VK_NULL_HANDLE) { @@ -90,10 +91,13 @@ void VulkanPipelineCache::ClearCache() { } pipelines_.clear(); + // Destroy all shaders. for (auto it : shaders_) { delete it.second; } shaders_.clear(); + texture_binding_layout_map_.clear(); + texture_binding_layouts_.clear(); } VulkanShader* VulkanPipelineCache::LoadShader(xenos::ShaderType shader_type, @@ -241,7 +245,23 @@ bool VulkanPipelineCache::ConfigurePipeline( // Create the pipeline if not the latest and not already existing. const PipelineLayoutProvider* pipeline_layout = - command_processor_.GetPipelineLayout(0, 0); + command_processor_.GetPipelineLayout( + pixel_shader + ? static_cast(pixel_shader->shader()) + .GetTextureBindingsAfterTranslation() + .size() + : 0, + pixel_shader + ? static_cast(pixel_shader->shader()) + .GetSamplerBindingsAfterTranslation() + .size() + : 0, + static_cast(vertex_shader->shader()) + .GetTextureBindingsAfterTranslation() + .size(), + static_cast(vertex_shader->shader()) + .GetSamplerBindingsAfterTranslation() + .size()); if (!pipeline_layout) { return false; } @@ -277,14 +297,80 @@ bool VulkanPipelineCache::ConfigurePipeline( bool VulkanPipelineCache::TranslateAnalyzedShader( SpirvShaderTranslator& translator, VulkanShader::VulkanTranslation& translation) { + VulkanShader& shader = static_cast(translation.shader()); + // Perform translation. // If this fails the shader will be marked as invalid and ignored later. if (!translator.TranslateAnalyzedShader(translation)) { XELOGE("Shader {:016X} translation failed; marking as ignored", - translation.shader().ucode_data_hash()); + shader.ucode_data_hash()); return false; } - return translation.GetOrCreateShaderModule() != VK_NULL_HANDLE; + if (translation.GetOrCreateShaderModule() == VK_NULL_HANDLE) { + return false; + } + + // TODO(Triang3l): Log that the shader has been successfully translated in + // common code. + + // Set up the texture binding layout. + if (shader.EnterBindingLayoutUserUIDSetup()) { + // Obtain the unique IDs of the binding layout if there are any texture + // bindings, for invalidation in the command processor. + size_t texture_binding_layout_uid = kLayoutUIDEmpty; + const std::vector& texture_bindings = + shader.GetTextureBindingsAfterTranslation(); + size_t texture_binding_count = texture_bindings.size(); + if (texture_binding_count) { + size_t texture_binding_layout_bytes = + texture_binding_count * sizeof(*texture_bindings.data()); + uint64_t texture_binding_layout_hash = + XXH3_64bits(texture_bindings.data(), texture_binding_layout_bytes); + auto found_range = + texture_binding_layout_map_.equal_range(texture_binding_layout_hash); + for (auto it = found_range.first; it != found_range.second; ++it) { + if (it->second.vector_span_length == texture_binding_count && + !std::memcmp( + texture_binding_layouts_.data() + it->second.vector_span_offset, + texture_bindings.data(), texture_binding_layout_bytes)) { + texture_binding_layout_uid = it->second.uid; + break; + } + } + if (texture_binding_layout_uid == kLayoutUIDEmpty) { + static_assert( + kLayoutUIDEmpty == 0, + "Layout UID is size + 1 because it's assumed that 0 is the UID for " + "an empty layout"); + texture_binding_layout_uid = texture_binding_layout_map_.size() + 1; + LayoutUID new_uid; + new_uid.uid = texture_binding_layout_uid; + new_uid.vector_span_offset = texture_binding_layouts_.size(); + new_uid.vector_span_length = texture_binding_count; + texture_binding_layouts_.resize(new_uid.vector_span_offset + + texture_binding_count); + std::memcpy( + texture_binding_layouts_.data() + new_uid.vector_span_offset, + texture_bindings.data(), texture_binding_layout_bytes); + texture_binding_layout_map_.emplace(texture_binding_layout_hash, + new_uid); + } + } + shader.SetTextureBindingLayoutUserUID(texture_binding_layout_uid); + + // Use the sampler count for samplers because it's the only thing that must + // be the same for layouts to be compatible in this case + // (instruction-specified parameters are used as overrides for creating + // actual samplers). + static_assert( + kLayoutUIDEmpty == 0, + "Empty layout UID is assumed to be 0 because for bindful samplers, the " + "UID is their count"); + shader.SetSamplerBindingLayoutUserUID( + shader.GetSamplerBindingsAfterTranslation().size()); + } + + return true; } void VulkanPipelineCache::WritePipelineRenderTargetDescription( diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h index 6be73d43c..58f53cff4 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h @@ -39,6 +39,8 @@ class VulkanCommandProcessor; // implementations. class VulkanPipelineCache { public: + static constexpr size_t kLayoutUIDEmpty = 0; + class PipelineLayoutProvider { public: virtual ~PipelineLayoutProvider() {} @@ -278,6 +280,21 @@ class VulkanPipelineCache { // Reusable shader translator on the command processor thread. std::unique_ptr shader_translator_; + struct LayoutUID { + size_t uid; + size_t vector_span_offset; + size_t vector_span_length; + }; + std::mutex layouts_mutex_; + // Texture binding layouts of different shaders, for obtaining layout UIDs. + std::vector texture_binding_layouts_; + // Map of texture binding layouts used by shaders, for obtaining UIDs. Keys + // are XXH3 hashes of layouts, values need manual collision resolution using + // layout_vector_offset:layout_length of texture_binding_layouts_. + std::unordered_multimap> + texture_binding_layout_map_; + // Ucode hash -> shader. std::unordered_map> diff --git a/src/xenia/gpu/vulkan/vulkan_shader.cc b/src/xenia/gpu/vulkan/vulkan_shader.cc index 9ebfc41a8..1ff7734ff 100644 --- a/src/xenia/gpu/vulkan/vulkan_shader.cc +++ b/src/xenia/gpu/vulkan/vulkan_shader.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -11,6 +11,7 @@ #include +#include "xenia/base/logging.h" #include "xenia/ui/vulkan/vulkan_provider.h" namespace xe { @@ -45,6 +46,10 @@ VkShaderModule VulkanShader::VulkanTranslation::GetOrCreateShaderModule() { if (provider.dfn().vkCreateShaderModule(provider.device(), &shader_module_create_info, nullptr, &shader_module_) != VK_SUCCESS) { + XELOGE( + "VulkanShader::VulkanTranslation: Failed to create a Vulkan shader " + "module for shader {:016X} modification {:016X}", + shader().ucode_data_hash(), modification()); MakeInvalid(); return VK_NULL_HANDLE; } @@ -57,8 +62,8 @@ VulkanShader::VulkanShader(const ui::vulkan::VulkanProvider& provider, const uint32_t* ucode_dwords, size_t ucode_dword_count, std::endian ucode_source_endian) - : Shader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count, - ucode_source_endian), + : SpirvShader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count, + ucode_source_endian), provider_(provider) {} Shader::Translation* VulkanShader::CreateTranslationInstance( diff --git a/src/xenia/gpu/vulkan/vulkan_shader.h b/src/xenia/gpu/vulkan/vulkan_shader.h index b8e2a55f2..7e78ac3b6 100644 --- a/src/xenia/gpu/vulkan/vulkan_shader.h +++ b/src/xenia/gpu/vulkan/vulkan_shader.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -12,7 +12,7 @@ #include -#include "xenia/gpu/shader.h" +#include "xenia/gpu/spirv_shader.h" #include "xenia/gpu/xenos.h" #include "xenia/ui/vulkan/vulkan_provider.h" @@ -20,12 +20,12 @@ namespace xe { namespace gpu { namespace vulkan { -class VulkanShader : public Shader { +class VulkanShader : public SpirvShader { public: - class VulkanTranslation : public Translation { + class VulkanTranslation : public SpirvTranslation { public: - VulkanTranslation(VulkanShader& shader, uint64_t modification) - : Translation(shader, modification) {} + explicit VulkanTranslation(VulkanShader& shader, uint64_t modification) + : SpirvTranslation(shader, modification) {} ~VulkanTranslation() override; VkShaderModule GetOrCreateShaderModule(); @@ -35,16 +35,43 @@ class VulkanShader : public Shader { VkShaderModule shader_module_ = VK_NULL_HANDLE; }; - VulkanShader(const ui::vulkan::VulkanProvider& provider, - xenos::ShaderType shader_type, uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, size_t ucode_dword_count, - std::endian ucode_source_endian = std::endian::big); + explicit VulkanShader(const ui::vulkan::VulkanProvider& provider, + xenos::ShaderType shader_type, uint64_t ucode_data_hash, + const uint32_t* ucode_dwords, size_t ucode_dword_count, + std::endian ucode_source_endian = std::endian::big); + + // For owning subsystem like the pipeline cache, accessors for unique + // identifiers (used instead of hashes to make sure collisions can't happen) + // of binding layouts used by the shader, for invalidation if a shader with an + // incompatible layout has been bound. + size_t GetTextureBindingLayoutUserUID() const { + return texture_binding_layout_user_uid_; + } + size_t GetSamplerBindingLayoutUserUID() const { + return sampler_binding_layout_user_uid_; + } + // Modifications of the same shader can be translated on different threads. + // The "set" function must only be called if "enter" returned true - these are + // set up only once. + bool EnterBindingLayoutUserUIDSetup() { + return !binding_layout_user_uids_set_up_.test_and_set(); + } + void SetTextureBindingLayoutUserUID(size_t uid) { + texture_binding_layout_user_uid_ = uid; + } + void SetSamplerBindingLayoutUserUID(size_t uid) { + sampler_binding_layout_user_uid_ = uid; + } protected: Translation* CreateTranslationInstance(uint64_t modification) override; private: const ui::vulkan::VulkanProvider& provider_; + + std::atomic_flag binding_layout_user_uids_set_up_ = ATOMIC_FLAG_INIT; + size_t texture_binding_layout_user_uid_ = 0; + size_t sampler_binding_layout_user_uid_ = 0; }; } // namespace vulkan diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.h b/src/xenia/gpu/vulkan/vulkan_texture_cache.h index 69f3965d5..ee4f8b9ee 100644 --- a/src/xenia/gpu/vulkan/vulkan_texture_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.h @@ -45,6 +45,17 @@ class VulkanTextureCache final : public TextureCache { void BeginSubmission(uint64_t new_submission_index) override; + VkImageView GetNullImageView(xenos::FetchOpDimension dimension) const { + switch (dimension) { + case xenos::FetchOpDimension::k3DOrStacked: + return null_image_view_3d_; + case xenos::FetchOpDimension::kCube: + return null_image_view_cube_; + default: + return null_image_view_2d_array_; + } + } + protected: uint32_t GetHostFormatSwizzle(TextureKey key) const override; diff --git a/src/xenia/ui/vulkan/single_layout_descriptor_set_pool.cc b/src/xenia/ui/vulkan/single_layout_descriptor_set_pool.cc index 8dfff2a3f..5b07c0673 100644 --- a/src/xenia/ui/vulkan/single_layout_descriptor_set_pool.cc +++ b/src/xenia/ui/vulkan/single_layout_descriptor_set_pool.cc @@ -94,8 +94,7 @@ size_t SingleLayoutDescriptorSetPool::Allocate() { if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, &descriptor_set) != VK_SUCCESS) { XELOGE( - "SingleLayoutDescriptorSetPool: Failed to allocate a descriptor " - "layout"); + "SingleLayoutDescriptorSetPool: Failed to allocate a descriptor set"); if (current_pool_sets_remaining_ >= pool_set_count_) { // Failed to allocate in a new pool - something completely wrong, don't // store empty pools as full. diff --git a/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.cc b/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.cc new file mode 100644 index 000000000..62621bb49 --- /dev/null +++ b/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.cc @@ -0,0 +1,216 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/vulkan/single_type_descriptor_set_allocator.h" + +#include "xenia/base/logging.h" +#include "xenia/ui/vulkan/vulkan_util.h" + +namespace xe { +namespace ui { +namespace vulkan { + +void SingleTypeDescriptorSetAllocator::Reset() { + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn(); + VkDevice device = provider_.device(); + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device, + page_usable_latest_.pool); + for (const std::pair& page_pair : pages_usable_) { + dfn.vkDestroyDescriptorPool(device, page_pair.second.pool, nullptr); + } + pages_usable_.clear(); + for (VkDescriptorPool pool : pages_full_) { + dfn.vkDestroyDescriptorPool(device, pool, nullptr); + } + pages_full_.clear(); +} + +VkDescriptorSet SingleTypeDescriptorSetAllocator::Allocate( + VkDescriptorSetLayout descriptor_set_layout, uint32_t descriptor_count) { + assert_not_zero(descriptor_count); + if (descriptor_count == 0) { + return VK_NULL_HANDLE; + } + + const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn(); + VkDevice device = provider_.device(); + + VkDescriptorSetAllocateInfo descriptor_set_allocate_info; + descriptor_set_allocate_info.sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descriptor_set_allocate_info.pNext = nullptr; + descriptor_set_allocate_info.descriptorSetCount = 1; + descriptor_set_allocate_info.pSetLayouts = &descriptor_set_layout; + VkDescriptorSet descriptor_set; + + if (descriptor_count > descriptor_pool_size_.descriptorCount) { + // Can't allocate in the pool, need a dedicated allocation. + VkDescriptorPoolSize dedicated_descriptor_pool_size; + dedicated_descriptor_pool_size.type = descriptor_pool_size_.type; + dedicated_descriptor_pool_size.descriptorCount = descriptor_count; + VkDescriptorPoolCreateInfo dedicated_descriptor_pool_create_info; + dedicated_descriptor_pool_create_info.sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + dedicated_descriptor_pool_create_info.pNext = nullptr; + dedicated_descriptor_pool_create_info.flags = 0; + dedicated_descriptor_pool_create_info.maxSets = 1; + dedicated_descriptor_pool_create_info.poolSizeCount = 1; + dedicated_descriptor_pool_create_info.pPoolSizes = + &dedicated_descriptor_pool_size; + VkDescriptorPool dedicated_descriptor_pool; + if (dfn.vkCreateDescriptorPool( + device, &dedicated_descriptor_pool_create_info, nullptr, + &dedicated_descriptor_pool) != VK_SUCCESS) { + XELOGE( + "SingleTypeDescriptorSetAllocator: Failed to create a dedicated pool " + "for {} descriptors", + dedicated_descriptor_pool_size.descriptorCount); + return VK_NULL_HANDLE; + } + descriptor_set_allocate_info.descriptorPool = dedicated_descriptor_pool; + if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, + &descriptor_set) != VK_SUCCESS) { + XELOGE( + "SingleTypeDescriptorSetAllocator: Failed to allocate {} descriptors " + "in a dedicated pool", + descriptor_count); + dfn.vkDestroyDescriptorPool(device, dedicated_descriptor_pool, nullptr); + return VK_NULL_HANDLE; + } + pages_full_.push_back(dedicated_descriptor_pool); + return descriptor_set; + } + + // Try allocating from the latest page an allocation has happened from, to + // avoid detaching from the map and re-attaching for every allocation. + if (page_usable_latest_.pool != VK_NULL_HANDLE) { + assert_not_zero(page_usable_latest_.descriptors_remaining); + assert_not_zero(page_usable_latest_.descriptor_sets_remaining); + if (page_usable_latest_.descriptors_remaining >= descriptor_count) { + descriptor_set_allocate_info.descriptorPool = page_usable_latest_.pool; + if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, + &descriptor_set) == VK_SUCCESS) { + page_usable_latest_.descriptors_remaining -= descriptor_count; + --page_usable_latest_.descriptor_sets_remaining; + if (!page_usable_latest_.descriptors_remaining || + !page_usable_latest_.descriptor_sets_remaining) { + pages_full_.push_back(page_usable_latest_.pool); + page_usable_latest_.pool = VK_NULL_HANDLE; + } + return descriptor_set; + } + // Failed to allocate internally even though there should be enough space, + // don't try to allocate from this pool again at all. + pages_full_.push_back(page_usable_latest_.pool); + page_usable_latest_.pool = VK_NULL_HANDLE; + } + } + + // If allocating from the latest pool wasn't possible, pick any that has free + // space. Prefer filling pages that have the most free space as they can more + // likely be used for more allocations later. + while (!pages_usable_.empty()) { + auto page_usable_last_it = std::prev(pages_usable_.cend()); + if (page_usable_last_it->second.descriptors_remaining < descriptor_count) { + // All other pages_usable_ entries have fewer free descriptors too (the + // remaining count is the map key). + break; + } + // Remove the page from the map unconditionally - in case of a successful + // allocation, it will have a different number of free descriptors, thus a + // new map key (but it will also become page_usable_latest_ instead even), + // or will become full, and in case of a failure to allocate internally even + // though there still should be enough space, it should never be allocated + // from again. + Page map_page = pages_usable_.crend()->second; + pages_usable_.erase(page_usable_last_it); + descriptor_set_allocate_info.descriptorPool = map_page.pool; + if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, + &descriptor_set) != VK_SUCCESS) { + pages_full_.push_back(map_page.pool); + continue; + } + map_page.descriptors_remaining -= descriptor_count; + --map_page.descriptor_sets_remaining; + if (!map_page.descriptors_remaining || + !map_page.descriptor_sets_remaining) { + pages_full_.push_back(map_page.pool); + } else { + if (page_usable_latest_.pool != VK_NULL_HANDLE) { + // Make the page with more free descriptors the next to allocate from. + if (map_page.descriptors_remaining > + page_usable_latest_.descriptors_remaining) { + pages_usable_.emplace(page_usable_latest_.descriptors_remaining, + page_usable_latest_); + page_usable_latest_ = map_page; + } else { + pages_usable_.emplace(map_page.descriptors_remaining, map_page); + } + } else { + page_usable_latest_ = map_page; + } + } + return descriptor_set; + } + + // Try allocating from a new page. + VkDescriptorPoolCreateInfo new_descriptor_pool_create_info; + new_descriptor_pool_create_info.sType = + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + new_descriptor_pool_create_info.pNext = nullptr; + new_descriptor_pool_create_info.flags = 0; + new_descriptor_pool_create_info.maxSets = descriptor_sets_per_page_; + new_descriptor_pool_create_info.poolSizeCount = 1; + new_descriptor_pool_create_info.pPoolSizes = &descriptor_pool_size_; + VkDescriptorPool new_descriptor_pool; + if (dfn.vkCreateDescriptorPool(device, &new_descriptor_pool_create_info, + nullptr, &new_descriptor_pool) != VK_SUCCESS) { + XELOGE( + "SingleTypeDescriptorSetAllocator: Failed to create a pool for {} sets " + "with {} descriptors", + descriptor_sets_per_page_, descriptor_pool_size_.descriptorCount); + return VK_NULL_HANDLE; + } + descriptor_set_allocate_info.descriptorPool = new_descriptor_pool; + if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, + &descriptor_set) != VK_SUCCESS) { + XELOGE( + "SingleTypeDescriptorSetAllocator: Failed to allocate {} descriptors", + descriptor_count); + dfn.vkDestroyDescriptorPool(device, new_descriptor_pool, nullptr); + return VK_NULL_HANDLE; + } + Page new_page; + new_page.pool = new_descriptor_pool; + new_page.descriptors_remaining = + descriptor_pool_size_.descriptorCount - descriptor_count; + new_page.descriptor_sets_remaining = descriptor_sets_per_page_ - 1; + if (!new_page.descriptors_remaining || !new_page.descriptor_sets_remaining) { + pages_full_.push_back(new_page.pool); + } else { + if (page_usable_latest_.pool != VK_NULL_HANDLE) { + // Make the page with more free descriptors the next to allocate from. + if (new_page.descriptors_remaining > + page_usable_latest_.descriptors_remaining) { + pages_usable_.emplace(page_usable_latest_.descriptors_remaining, + page_usable_latest_); + page_usable_latest_ = new_page; + } else { + pages_usable_.emplace(new_page.descriptors_remaining, new_page); + } + } else { + page_usable_latest_ = new_page; + } + } + return descriptor_set; +} + +} // namespace vulkan +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.h b/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.h new file mode 100644 index 000000000..7a21f6f35 --- /dev/null +++ b/src/xenia/ui/vulkan/single_type_descriptor_set_allocator.h @@ -0,0 +1,84 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_ +#define XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_ + +#include +#include +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/ui/vulkan/vulkan_provider.h" + +namespace xe { +namespace ui { +namespace vulkan { + +// Allocates multiple descriptors of a single type in descriptor set layouts +// consisting of descriptors of only that type. There's no way to free these +// descriptors within the SingleTypeDescriptorSetAllocator, per-layout free +// lists should be used externally. +class SingleTypeDescriptorSetAllocator { + public: + explicit SingleTypeDescriptorSetAllocator( + const ui::vulkan::VulkanProvider& provider, + VkDescriptorType descriptor_type, uint32_t descriptors_per_page, + uint32_t descriptor_sets_per_page) + : provider_(provider), + descriptor_sets_per_page_(descriptor_sets_per_page) { + assert_not_zero(descriptor_sets_per_page_); + descriptor_pool_size_.type = descriptor_type; + // Not allocating sets with 0 descriptors using the allocator - pointless to + // have the descriptor count below the set count. + descriptor_pool_size_.descriptorCount = + std::max(descriptors_per_page, descriptor_sets_per_page); + } + SingleTypeDescriptorSetAllocator( + const SingleTypeDescriptorSetAllocator& allocator) = delete; + SingleTypeDescriptorSetAllocator& operator=( + const SingleTypeDescriptorSetAllocator& allocator) = delete; + ~SingleTypeDescriptorSetAllocator() { Reset(); } + + void Reset(); + + VkDescriptorSet Allocate(VkDescriptorSetLayout descriptor_set_layout, + uint32_t descriptor_count); + + private: + struct Page { + VkDescriptorPool pool; + uint32_t descriptors_remaining; + uint32_t descriptor_sets_remaining; + }; + + const ui::vulkan::VulkanProvider& provider_; + + VkDescriptorPoolSize descriptor_pool_size_; + uint32_t descriptor_sets_per_page_; + + std::vector pages_full_; + // Because allocations must be contiguous, overflow may happen even if a page + // still has free descriptors, so multiple pages may have free space. + // To avoid removing and re-adding the page to the map that keeps them sorted + // (the key is the number of free descriptors remaining, and it changes at + // every allocation from a page), instead of always looking for a free space + // in the map, maintaining one page outside the map, and allocation attempts + // will be made from that page first. + std::multimap pages_usable_; + // Doesn't exist if page_usable_latest_.pool == VK_NULL_HANDLE. + Page page_usable_latest_ = {}; +}; + +} // namespace vulkan +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_VULKAN_SINGLE_TYPE_DESCRIPTOR_SET_ALLOCATOR_H_ diff --git a/src/xenia/ui/vulkan/transient_descriptor_pool.cc b/src/xenia/ui/vulkan/transient_descriptor_pool.cc deleted file mode 100644 index 963738d80..000000000 --- a/src/xenia/ui/vulkan/transient_descriptor_pool.cc +++ /dev/null @@ -1,162 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/ui/vulkan/transient_descriptor_pool.h" - -#include - -#include "xenia/base/assert.h" -#include "xenia/base/logging.h" - -namespace xe { -namespace ui { -namespace vulkan { - -TransientDescriptorPool::TransientDescriptorPool( - const VulkanProvider& provider, VkDescriptorType descriptor_type, - uint32_t page_descriptor_set_count, uint32_t page_descriptor_count) - : provider_(provider), - descriptor_type_(descriptor_type), - page_descriptor_set_count_(page_descriptor_set_count), - page_descriptor_count_(page_descriptor_count) { - assert_not_zero(page_descriptor_set_count); - assert_true(page_descriptor_set_count <= page_descriptor_count); -} - -TransientDescriptorPool::~TransientDescriptorPool() { ClearCache(); } - -void TransientDescriptorPool::Reclaim(uint64_t completed_submission_index) { - const VulkanProvider::DeviceFunctions& dfn = provider_.dfn(); - VkDevice device = provider_.device(); - while (!pages_submitted_.empty()) { - const auto& descriptor_pool_pair = pages_submitted_.front(); - if (descriptor_pool_pair.second > completed_submission_index) { - break; - } - dfn.vkResetDescriptorPool(device, descriptor_pool_pair.first, 0); - pages_writable_.push_back(descriptor_pool_pair.first); - pages_submitted_.pop_front(); - } -} - -void TransientDescriptorPool::ClearCache() { - const VulkanProvider::DeviceFunctions& dfn = provider_.dfn(); - VkDevice device = provider_.device(); - for (const auto& descriptor_pool_pair : pages_submitted_) { - dfn.vkDestroyDescriptorPool(device, descriptor_pool_pair.first, nullptr); - } - pages_submitted_.clear(); - page_current_descriptors_used_ = 0; - page_current_descriptor_sets_used_ = 0; - page_current_last_submission_ = 0; - for (VkDescriptorPool descriptor_pool : pages_writable_) { - dfn.vkDestroyDescriptorPool(device, descriptor_pool, nullptr); - } - pages_writable_.clear(); -} - -VkDescriptorSet TransientDescriptorPool::Request( - uint64_t submission_index, VkDescriptorSetLayout layout, - uint32_t layout_descriptor_count) { - assert_true(submission_index >= page_current_last_submission_); - assert_not_zero(layout_descriptor_count); - assert_true(layout_descriptor_count <= page_descriptor_count_); - - const VulkanProvider::DeviceFunctions& dfn = provider_.dfn(); - VkDevice device = provider_.device(); - - VkDescriptorSetAllocateInfo descriptor_set_allocate_info; - descriptor_set_allocate_info.sType = - VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - descriptor_set_allocate_info.pNext = nullptr; - descriptor_set_allocate_info.descriptorSetCount = 1; - descriptor_set_allocate_info.pSetLayouts = &layout; - VkDescriptorSet descriptor_set; - - // Try to allocate as normal. - // TODO(Triang3l): Investigate the possibility of reuse of descriptor sets, as - // vkAllocateDescriptorSets may be implemented suboptimally. - if (!pages_writable_.empty()) { - if (page_current_descriptor_sets_used_ < page_descriptor_set_count_ && - page_current_descriptors_used_ + layout_descriptor_count <= - page_descriptor_count_) { - descriptor_set_allocate_info.descriptorPool = pages_writable_.front(); - switch (dfn.vkAllocateDescriptorSets( - device, &descriptor_set_allocate_info, &descriptor_set)) { - case VK_SUCCESS: - page_current_last_submission_ = submission_index; - ++page_current_descriptor_sets_used_; - page_current_descriptors_used_ += layout_descriptor_count; - return descriptor_set; - case VK_ERROR_FRAGMENTED_POOL: - case VK_ERROR_OUT_OF_POOL_MEMORY: - // Need to create a new pool. - break; - default: - XELOGE( - "Failed to allocate a transient Vulkan descriptor set with {} " - "descriptors of type {}", - layout_descriptor_count, uint32_t(descriptor_type_)); - return VK_NULL_HANDLE; - } - } - - // Overflow - go to the next pool. - pages_submitted_.emplace_back(pages_writable_.front(), - page_current_last_submission_); - pages_writable_.front() = pages_writable_.back(); - pages_writable_.pop_back(); - page_current_descriptor_sets_used_ = 0; - page_current_descriptors_used_ = 0; - } - - if (pages_writable_.empty()) { - VkDescriptorPoolSize descriptor_pool_size; - descriptor_pool_size.type = descriptor_type_; - descriptor_pool_size.descriptorCount = page_descriptor_count_; - VkDescriptorPoolCreateInfo descriptor_pool_create_info; - descriptor_pool_create_info.sType = - VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - descriptor_pool_create_info.pNext = nullptr; - descriptor_pool_create_info.flags = 0; - descriptor_pool_create_info.maxSets = page_descriptor_set_count_; - descriptor_pool_create_info.poolSizeCount = 1; - descriptor_pool_create_info.pPoolSizes = &descriptor_pool_size; - VkDescriptorPool descriptor_pool; - if (dfn.vkCreateDescriptorPool(device, &descriptor_pool_create_info, - nullptr, &descriptor_pool) != VK_SUCCESS) { - XELOGE( - "Failed to create a transient Vulkan descriptor pool for {} sets of " - "up to {} descriptors of type {}", - page_descriptor_set_count_, page_descriptor_count_, - uint32_t(descriptor_type_)); - return VK_NULL_HANDLE; - } - pages_writable_.push_back(descriptor_pool); - } - - // Try to allocate after handling overflow. - descriptor_set_allocate_info.descriptorPool = pages_writable_.front(); - if (dfn.vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, - &descriptor_set) != VK_SUCCESS) { - XELOGE( - "Failed to allocate a transient Vulkan descriptor set with {} " - "descriptors of type {}", - layout_descriptor_count, uint32_t(descriptor_type_)); - return VK_NULL_HANDLE; - } - page_current_last_submission_ = submission_index; - ++page_current_descriptor_sets_used_; - page_current_descriptors_used_ += layout_descriptor_count; - return descriptor_set; -} - -} // namespace vulkan -} // namespace ui -} // namespace xe diff --git a/src/xenia/ui/vulkan/transient_descriptor_pool.h b/src/xenia/ui/vulkan/transient_descriptor_pool.h deleted file mode 100644 index 07760aff0..000000000 --- a/src/xenia/ui/vulkan/transient_descriptor_pool.h +++ /dev/null @@ -1,61 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_ -#define XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_ - -#include -#include -#include -#include - -#include "xenia/ui/vulkan/vulkan_provider.h" - -namespace xe { -namespace ui { -namespace vulkan { - -// A pool of descriptor pools for single-submission use. For simplicity of -// tracking when overflow happens, only allocating descriptors for sets -// containing descriptors of a single type. -class TransientDescriptorPool { - public: - TransientDescriptorPool(const VulkanProvider& provider, - VkDescriptorType descriptor_type, - uint32_t page_descriptor_set_count, - uint32_t page_descriptor_count); - ~TransientDescriptorPool(); - - void Reclaim(uint64_t completed_submission_index); - void ClearCache(); - - // Returns the allocated set, or VK_NULL_HANDLE if failed to allocate. - VkDescriptorSet Request(uint64_t submission_index, - VkDescriptorSetLayout layout, - uint32_t layout_descriptor_count); - - private: - const VulkanProvider& provider_; - - VkDescriptorType descriptor_type_; - uint32_t page_descriptor_set_count_; - uint32_t page_descriptor_count_; - - std::vector pages_writable_; - uint64_t page_current_last_submission_ = 0; - uint32_t page_current_descriptor_sets_used_ = 0; - uint32_t page_current_descriptors_used_ = 0; - std::deque> pages_submitted_; -}; - -} // namespace vulkan -} // namespace ui -} // namespace xe - -#endif // XENIA_UI_VULKAN_TRANSIENT_DESCRIPTOR_POOL_H_