/** ****************************************************************************** * 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 #include #include #include #include #include #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_provider.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) const; SpirvShaderTranslator::Modification GetCurrentPixelShaderModification( const Shader& shader, uint32_t normalized_color_mask) 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, 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* 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{}(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, 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 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> shaders_; // Geometry shaders for Xenos primitive types not supported by Vulkan. // Stores VK_NULL_HANDLE if failed to create. std::unordered_map geometry_shaders_; std::unordered_map pipelines_; // Previously used pipeline, to avoid lookups if the state wasn't changed. const std::pair* last_pipeline_ = nullptr; }; } // namespace vulkan } // namespace gpu } // namespace xe #endif // XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_