Enable portability subset physical device enumeration. Don't use Vulkan 1.1+ logical devices on Vulkan 1.0 instances due to the VkApplicationInfo::apiVersion specification. Make sure all extension dependencies are enabled when creating a device. Prefer exposing feature support over extension support via the device interface to avoid causing confusion with regard to promoted extensions (especially those that required some features as extensions, but had those features made optional when they were promoted). Allow creating presentation-only devices, not demanding any optional features beyond the basic Vulkan 1.0, for use cases such as internal tools or CPU rendering. Require the independentBlend feature for GPU emulation as working around is complicated, while support is almost ubiquitous. Move the graphics system initialization fatal error message to xenia_main after attempting to initialize all implementations, for automatic fallback to other implementations in the future. Log Vulkan driver info. Improve Vulkan debug message logging, enabled by default. Refactor code, with simplified logic for enabling extensions and layers.
334 lines
12 KiB
C++
334 lines
12 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
|
#define XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|
|
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include "xenia/base/hash.h"
|
|
#include "xenia/base/platform.h"
|
|
#include "xenia/base/xxhash.h"
|
|
#include "xenia/gpu/primitive_processor.h"
|
|
#include "xenia/gpu/register_file.h"
|
|
#include "xenia/gpu/registers.h"
|
|
#include "xenia/gpu/spirv_shader_translator.h"
|
|
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
|
|
#include "xenia/gpu/vulkan/vulkan_shader.h"
|
|
#include "xenia/gpu/xenos.h"
|
|
#include "xenia/ui/vulkan/vulkan_api.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace vulkan {
|
|
|
|
class VulkanCommandProcessor;
|
|
|
|
// TODO(Triang3l): Create a common base for both the Vulkan and the Direct3D
|
|
// implementations.
|
|
class VulkanPipelineCache {
|
|
public:
|
|
static constexpr size_t kLayoutUIDEmpty = 0;
|
|
|
|
class PipelineLayoutProvider {
|
|
public:
|
|
virtual ~PipelineLayoutProvider() {}
|
|
virtual VkPipelineLayout GetPipelineLayout() const = 0;
|
|
|
|
protected:
|
|
PipelineLayoutProvider() = default;
|
|
};
|
|
|
|
VulkanPipelineCache(VulkanCommandProcessor& command_processor,
|
|
const RegisterFile& register_file,
|
|
VulkanRenderTargetCache& render_target_cache,
|
|
VkShaderStageFlags guest_shader_vertex_stages);
|
|
~VulkanPipelineCache();
|
|
|
|
bool Initialize();
|
|
void Shutdown();
|
|
|
|
VulkanShader* LoadShader(xenos::ShaderType shader_type,
|
|
const uint32_t* host_address, uint32_t dword_count);
|
|
// Analyze shader microcode on the translator thread.
|
|
void AnalyzeShaderUcode(Shader& shader) {
|
|
shader.AnalyzeUcode(ucode_disasm_buffer_);
|
|
}
|
|
|
|
// Retrieves the shader modification for the current state. The shader must
|
|
// have microcode analyzed.
|
|
SpirvShaderTranslator::Modification GetCurrentVertexShaderModification(
|
|
const Shader& shader,
|
|
Shader::HostVertexShaderType host_vertex_shader_type,
|
|
uint32_t interpolator_mask, bool ps_param_gen_used) const;
|
|
SpirvShaderTranslator::Modification GetCurrentPixelShaderModification(
|
|
const Shader& shader, uint32_t interpolator_mask,
|
|
uint32_t param_gen_pos) const;
|
|
|
|
bool EnsureShadersTranslated(VulkanShader::VulkanTranslation* vertex_shader,
|
|
VulkanShader::VulkanTranslation* pixel_shader);
|
|
// TODO(Triang3l): Return a deferred creation handle.
|
|
bool ConfigurePipeline(
|
|
VulkanShader::VulkanTranslation* vertex_shader,
|
|
VulkanShader::VulkanTranslation* pixel_shader,
|
|
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t normalized_color_mask,
|
|
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
|
VkPipeline& pipeline_out,
|
|
const PipelineLayoutProvider*& pipeline_layout_out);
|
|
|
|
private:
|
|
enum class PipelineGeometryShader : uint32_t {
|
|
kNone,
|
|
kPointList,
|
|
kRectangleList,
|
|
kQuadList,
|
|
};
|
|
|
|
enum class PipelinePrimitiveTopology : uint32_t {
|
|
kPointList,
|
|
kLineList,
|
|
kLineStrip,
|
|
kTriangleList,
|
|
kTriangleStrip,
|
|
kTriangleFan,
|
|
kLineListWithAdjacency,
|
|
kPatchList,
|
|
};
|
|
|
|
enum class PipelinePolygonMode : uint32_t {
|
|
kFill,
|
|
kLine,
|
|
kPoint,
|
|
};
|
|
|
|
enum class PipelineBlendFactor : uint32_t {
|
|
kZero,
|
|
kOne,
|
|
kSrcColor,
|
|
kOneMinusSrcColor,
|
|
kDstColor,
|
|
kOneMinusDstColor,
|
|
kSrcAlpha,
|
|
kOneMinusSrcAlpha,
|
|
kDstAlpha,
|
|
kOneMinusDstAlpha,
|
|
kConstantColor,
|
|
kOneMinusConstantColor,
|
|
kConstantAlpha,
|
|
kOneMinusConstantAlpha,
|
|
kSrcAlphaSaturate,
|
|
};
|
|
|
|
// Update PipelineDescription::kVersion if anything is changed!
|
|
XEPACKEDSTRUCT(PipelineRenderTarget, {
|
|
PipelineBlendFactor src_color_blend_factor : 4; // 4
|
|
PipelineBlendFactor dst_color_blend_factor : 4; // 8
|
|
xenos::BlendOp color_blend_op : 3; // 11
|
|
PipelineBlendFactor src_alpha_blend_factor : 4; // 15
|
|
PipelineBlendFactor dst_alpha_blend_factor : 4; // 19
|
|
xenos::BlendOp alpha_blend_op : 3; // 22
|
|
uint32_t color_write_mask : 4; // 26
|
|
});
|
|
|
|
XEPACKEDSTRUCT(PipelineDescription, {
|
|
uint64_t vertex_shader_hash;
|
|
uint64_t vertex_shader_modification;
|
|
// 0 if no pixel shader.
|
|
uint64_t pixel_shader_hash;
|
|
uint64_t pixel_shader_modification;
|
|
VulkanRenderTargetCache::RenderPassKey render_pass_key;
|
|
|
|
// Shader stages.
|
|
PipelineGeometryShader geometry_shader : 2; // 2
|
|
// Input assembly.
|
|
PipelinePrimitiveTopology primitive_topology : 3; // 5
|
|
uint32_t primitive_restart : 1; // 6
|
|
// Rasterization.
|
|
uint32_t depth_clamp_enable : 1; // 7
|
|
PipelinePolygonMode polygon_mode : 2; // 9
|
|
uint32_t cull_front : 1; // 10
|
|
uint32_t cull_back : 1; // 11
|
|
uint32_t front_face_clockwise : 1; // 12
|
|
// Depth / stencil.
|
|
uint32_t depth_write_enable : 1; // 13
|
|
xenos::CompareFunction depth_compare_op : 3; // 15
|
|
uint32_t stencil_test_enable : 1; // 17
|
|
xenos::StencilOp stencil_front_fail_op : 3; // 20
|
|
xenos::StencilOp stencil_front_pass_op : 3; // 23
|
|
xenos::StencilOp stencil_front_depth_fail_op : 3; // 26
|
|
xenos::CompareFunction stencil_front_compare_op : 3; // 29
|
|
xenos::StencilOp stencil_back_fail_op : 3; // 32
|
|
|
|
xenos::StencilOp stencil_back_pass_op : 3; // 3
|
|
xenos::StencilOp stencil_back_depth_fail_op : 3; // 6
|
|
xenos::CompareFunction stencil_back_compare_op : 3; // 9
|
|
|
|
// Filled only for the attachments present in the render pass object.
|
|
PipelineRenderTarget render_targets[xenos::kMaxColorRenderTargets];
|
|
|
|
// Including all the padding, for a stable hash.
|
|
PipelineDescription() { Reset(); }
|
|
PipelineDescription(const PipelineDescription& description) {
|
|
std::memcpy(this, &description, sizeof(*this));
|
|
}
|
|
PipelineDescription& operator=(const PipelineDescription& description) {
|
|
std::memcpy(this, &description, sizeof(*this));
|
|
return *this;
|
|
}
|
|
bool operator==(const PipelineDescription& description) const {
|
|
return std::memcmp(this, &description, sizeof(*this)) == 0;
|
|
}
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
uint64_t GetHash() const { return XXH3_64bits(this, sizeof(*this)); }
|
|
struct Hasher {
|
|
size_t operator()(const PipelineDescription& description) const {
|
|
return size_t(description.GetHash());
|
|
}
|
|
};
|
|
});
|
|
|
|
struct Pipeline {
|
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
// The layouts are owned by the VulkanCommandProcessor, and must not be
|
|
// destroyed by it while the pipeline cache is active.
|
|
const PipelineLayoutProvider* pipeline_layout;
|
|
Pipeline(const PipelineLayoutProvider* pipeline_layout_provider)
|
|
: pipeline_layout(pipeline_layout_provider) {}
|
|
};
|
|
|
|
// Description that can be passed from the command processor thread to the
|
|
// creation threads, with everything needed from caches pre-looked-up.
|
|
struct PipelineCreationArguments {
|
|
std::pair<const PipelineDescription, Pipeline>* pipeline;
|
|
const VulkanShader::VulkanTranslation* vertex_shader;
|
|
const VulkanShader::VulkanTranslation* pixel_shader;
|
|
VkShaderModule geometry_shader;
|
|
VkRenderPass render_pass;
|
|
};
|
|
|
|
union GeometryShaderKey {
|
|
uint32_t key;
|
|
struct {
|
|
PipelineGeometryShader type : 2;
|
|
uint32_t interpolator_count : 5;
|
|
uint32_t user_clip_plane_count : 3;
|
|
uint32_t user_clip_plane_cull : 1;
|
|
uint32_t has_vertex_kill_and : 1;
|
|
uint32_t has_point_size : 1;
|
|
uint32_t has_point_coordinates : 1;
|
|
};
|
|
|
|
GeometryShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
|
|
|
|
struct Hasher {
|
|
size_t operator()(const GeometryShaderKey& key) const {
|
|
return std::hash<uint32_t>{}(key.key);
|
|
}
|
|
};
|
|
bool operator==(const GeometryShaderKey& other_key) const {
|
|
return key == other_key.key;
|
|
}
|
|
bool operator!=(const GeometryShaderKey& other_key) const {
|
|
return !(*this == other_key);
|
|
}
|
|
};
|
|
|
|
// Can be called from multiple threads.
|
|
bool TranslateAnalyzedShader(SpirvShaderTranslator& translator,
|
|
VulkanShader::VulkanTranslation& translation);
|
|
|
|
void WritePipelineRenderTargetDescription(
|
|
reg::RB_BLENDCONTROL blend_control, uint32_t write_mask,
|
|
PipelineRenderTarget& render_target_out) const;
|
|
bool GetCurrentStateDescription(
|
|
const VulkanShader::VulkanTranslation* vertex_shader,
|
|
const VulkanShader::VulkanTranslation* pixel_shader,
|
|
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
|
|
reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t normalized_color_mask,
|
|
VulkanRenderTargetCache::RenderPassKey render_pass_key,
|
|
PipelineDescription& description_out) const;
|
|
|
|
// Whether the pipeline for the given description is supported by the device.
|
|
bool ArePipelineRequirementsMet(const PipelineDescription& description) const;
|
|
|
|
static bool GetGeometryShaderKey(
|
|
PipelineGeometryShader geometry_shader_type,
|
|
SpirvShaderTranslator::Modification vertex_shader_modification,
|
|
SpirvShaderTranslator::Modification pixel_shader_modification,
|
|
GeometryShaderKey& key_out);
|
|
VkShaderModule GetGeometryShader(GeometryShaderKey key);
|
|
|
|
// Can be called from creation threads - all needed data must be fully set up
|
|
// at the point of the call: shaders must be translated, pipeline layout and
|
|
// render pass objects must be available.
|
|
bool EnsurePipelineCreated(
|
|
const PipelineCreationArguments& creation_arguments);
|
|
|
|
VulkanCommandProcessor& command_processor_;
|
|
const RegisterFile& register_file_;
|
|
VulkanRenderTargetCache& render_target_cache_;
|
|
VkShaderStageFlags guest_shader_vertex_stages_;
|
|
|
|
// Temporary storage for AnalyzeUcode calls on the processor thread.
|
|
StringBuffer ucode_disasm_buffer_;
|
|
// 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>>
|
|
shaders_;
|
|
|
|
// Geometry shaders for Xenos primitive types not supported by Vulkan.
|
|
// Stores VK_NULL_HANDLE if failed to create.
|
|
std::unordered_map<GeometryShaderKey, VkShaderModule,
|
|
GeometryShaderKey::Hasher>
|
|
geometry_shaders_;
|
|
|
|
// Empty depth-only pixel shader for writing to depth buffer using fragment
|
|
// shader interlock when no Xenos pixel shader provided.
|
|
VkShaderModule depth_only_fragment_shader_ = VK_NULL_HANDLE;
|
|
|
|
std::unordered_map<PipelineDescription, Pipeline, PipelineDescription::Hasher>
|
|
pipelines_;
|
|
|
|
// Previously used pipeline, to avoid lookups if the state wasn't changed.
|
|
const std::pair<const PipelineDescription, Pipeline>* last_pipeline_ =
|
|
nullptr;
|
|
};
|
|
|
|
} // namespace vulkan
|
|
} // namespace gpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
|