[Vulkan] Basic texture descriptor set allocation/binding

This commit is contained in:
Triang3l
2022-05-17 22:42:28 +03:00
parent 3381d679b4
commit 46202dd27a
17 changed files with 1561 additions and 492 deletions

View File

@@ -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 <cstring>
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

View File

@@ -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 <atomic>
#include <vector>
#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<TextureBinding>& 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<SamplerBinding>& 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<TextureBinding> texture_bindings_;
std::vector<SamplerBinding> sampler_bindings_;
uint32_t used_texture_mask_ = 0;
};
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_SPIRV_SHADER_H_

View File

@@ -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<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
return module_bytes;
}
void SpirvShaderTranslator::PostTranslation() {
Shader::Translation& translation = current_translation();
if (!translation.is_valid()) {
return;
}
SpirvShader* spirv_shader = dynamic_cast<SpirvShader*>(&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.

View File

@@ -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<uint8_t> 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<spv::Builder> 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<TextureBinding> texture_bindings_;
std::vector<SamplerBinding> sampler_bindings_;
// VS as VS only - int.
spv::Id input_vertex_index_;
// VS as TES only - int.

View File

@@ -12,8 +12,10 @@
#include <climits>
#include <cmath>
#include <memory>
#include <sstream>
#include <utility>
#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

File diff suppressed because it is too large Load Diff

View File

@@ -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<VkSparseBufferMemoryBindInfo> sparse_buffer_bind_infos_temp_;
VkPipelineStageFlags sparse_bind_wait_stage_mask_ = 0;
std::unique_ptr<ui::vulkan::TransientDescriptorPool>
transient_descriptor_pool_uniform_buffers_;
// Temporary storage with reusable memory for creating descriptor set layouts.
std::vector<VkDescriptorSetLayoutBinding> descriptor_set_layout_bindings_;
// Temporary storage with reusable memory for writing image and sampler
// descriptors.
std::vector<VkDescriptorImageInfo> descriptor_write_image_info_;
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> 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<VkDescriptorSetLayout,
size_t(SingleTransientDescriptorLayout::kCount)>
descriptor_set_layouts_single_transient_{};
// Descriptor set layouts are referenced by pipeline_layouts_.
std::unordered_map<TextureDescriptorSetLayoutKey, VkDescriptorSetLayout,
@@ -359,6 +425,26 @@ class VulkanCommandProcessor : public CommandProcessor {
PipelineLayoutKey::Hasher>
pipeline_layouts_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_uniform_buffer_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_storage_buffer_;
std::deque<UsedSingleTransientDescriptor> single_transient_descriptors_used_;
std::array<std::vector<VkDescriptorSet>,
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<UsedTextureTransientDescriptorSet>
texture_transient_descriptor_sets_used_;
std::unordered_map<TextureDescriptorSetLayoutKey,
std::vector<VkDescriptorSet>,
TextureDescriptorSetLayoutKey::Hasher>
texture_transient_descriptor_sets_free_;
std::unique_ptr<VulkanSharedMemory> shared_memory_;
std::unique_ptr<VulkanPrimitiveProcessor> primitive_processor_;

View File

@@ -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<const VulkanShader&>(pixel_shader->shader())
.GetTextureBindingsAfterTranslation()
.size()
: 0,
pixel_shader
? static_cast<const VulkanShader&>(pixel_shader->shader())
.GetSamplerBindingsAfterTranslation()
.size()
: 0,
static_cast<const VulkanShader&>(vertex_shader->shader())
.GetTextureBindingsAfterTranslation()
.size(),
static_cast<const VulkanShader&>(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<VulkanShader&>(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<VulkanShader::TextureBinding>& 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(

View File

@@ -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<SpirvShaderTranslator> 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<VulkanShader::TextureBinding> 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<uint64_t, LayoutUID,
xe::hash::IdentityHasher<uint64_t>>
texture_binding_layout_map_;
// Ucode hash -> shader.
std::unordered_map<uint64_t, VulkanShader*,
xe::hash::IdentityHasher<uint64_t>>

View File

@@ -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 <cstdint>
#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(

View File

@@ -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 <cstdint>
#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

View File

@@ -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;