#pragma once /** ****************************************************************************** * 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. * ****************************************************************************** * * @modified Tom Clay, 2026 - Adapted for ReXGlue runtime */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rex::graphics::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(); void InitializeShaderStorage(const std::filesystem::path& cache_root, uint32_t title_id, bool blocking); void ShutdownShaderStorage(); void EndSubmission(); 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, reg::RB_DEPTHCONTROL normalized_depth_control) 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, void** pipeline_handle_out = nullptr); bool IsCreatingPipelines() const; void GetPipelineAndLayoutByHandle(void* handle, VkPipeline& pipeline_out, const PipelineLayoutProvider*& pipeline_layout_out, bool* is_placeholder_out = nullptr) const; private: REXPACKEDSTRUCT(ShaderStoredHeader, { uint64_t ucode_data_hash; uint32_t ucode_dword_count : 31; xenos::ShaderType type : 1; static constexpr uint32_t kVersion = 0x20201219; }); 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! REXPACKEDSTRUCT(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 }); REXPACKEDSTRUCT(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 xenos::TessellationMode tessellation_mode : 2; // 8 // Rasterization. uint32_t depth_clamp_enable : 1; // 9 PipelinePolygonMode polygon_mode : 2; // 11 uint32_t cull_front : 1; // 12 uint32_t cull_back : 1; // 13 uint32_t front_face_clockwise : 1; // 14 uint32_t rasterizer_discard : 1; // 15 // Depth / stencil. uint32_t depth_write_enable : 1; // 16 xenos::CompareFunction depth_compare_op : 3; // 19 uint32_t stencil_test_enable : 1; // 20 xenos::StencilOp stencil_front_fail_op : 3; // 23 xenos::StencilOp stencil_front_pass_op : 3; // 26 xenos::StencilOp stencil_front_depth_fail_op : 3; // 29 xenos::CompareFunction stencil_front_compare_op : 3; // 32 xenos::StencilOp stencil_back_fail_op : 3; // 35 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 uint32_t sample_rate_shading : 1; // 10 // Filled only for the attachments present in the render pass object. PipelineRenderTarget render_targets[xenos::kMaxColorRenderTargets]; // Including all the padding, for a stable hash. static constexpr uint32_t kVersion = 0x20260228; 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()); } }; }); REXPACKEDSTRUCT(PipelineStoredDescription, { uint64_t description_hash; PipelineDescription description; }); struct Pipeline { std::atomic pipeline{VK_NULL_HANDLE}; // The layouts are owned by the VulkanCommandProcessor, and must not be // destroyed by it while the pipeline cache is active. std::atomic pipeline_layout{nullptr}; std::atomic is_placeholder{false}; Pipeline() = default; Pipeline(const PipelineLayoutProvider* pipeline_layout_provider) : pipeline_layout(pipeline_layout_provider) {} Pipeline(const Pipeline& other) : pipeline(other.pipeline.load(std::memory_order_acquire)), pipeline_layout(other.pipeline_layout.load(std::memory_order_acquire)), is_placeholder(other.is_placeholder.load(std::memory_order_acquire)) {} Pipeline& operator=(const Pipeline& other) { if (this == &other) { return *this; } pipeline.store(other.pipeline.load(std::memory_order_acquire), std::memory_order_release); pipeline_layout.store(other.pipeline_layout.load(std::memory_order_acquire), std::memory_order_release); is_placeholder.store(other.is_placeholder.load(std::memory_order_acquire), std::memory_order_release); return *this; } }; // Description that can be passed from the command processor thread to the // creation threads, with everything needed from caches pre-looked-up. struct PipelineCreationArguments { uint8_t priority = 0; std::pair* pipeline = nullptr; const PipelineLayoutProvider* pipeline_layout = nullptr; // Guest shader translation (VS or TES depending on host vertex type). const VulkanShader::VulkanTranslation* vertex_shader = nullptr; const VulkanShader::VulkanTranslation* pixel_shader = nullptr; // Non-guest stages for tessellation. VkShaderModule tessellation_vertex_shader = VK_NULL_HANDLE; VkShaderModule tessellation_control_shader = VK_NULL_HANDLE; uint32_t tessellation_patch_control_points = 0; VkShaderModule geometry_shader = VK_NULL_HANDLE; // VK_NULL_HANDLE when dynamic rendering is used. VkRenderPass render_pass = VK_NULL_HANDLE; }; struct PipelineCreationArgumentsPriorityComparator { bool operator()(const PipelineCreationArguments& a, const PipelineCreationArguments& b) const { return a.priority < b.priority; } }; union TessellationControlShaderKey { uint32_t key; struct { Shader::HostVertexShaderType host_vertex_shader_type : Shader::kHostVertexShaderTypeBitCount; xenos::TessellationMode tessellation_mode : 2; }; TessellationControlShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); } struct Hasher { size_t operator()(const TessellationControlShaderKey& key) const { return std::hash{}(key.key); } }; bool operator==(const TessellationControlShaderKey& other_key) const { return key == other_key.key; } }; 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; // PA_CL_CLIP_CNTL::ps_ucp_mode for point primitives. uint32_t point_ps_ucp_mode : 2; }; 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); } }; VulkanShader* LoadShader(xenos::ShaderType shader_type, const uint32_t* host_address, uint32_t dword_count, uint64_t data_hash); // 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); static uint32_t GetTessellationPatchControlPointCount( Shader::HostVertexShaderType host_vertex_shader_type, xenos::TessellationMode tessellation_mode); VkShaderModule GetTessellationVertexShader(bool adaptive); VkShaderModule GetTessellationControlShader(Shader::HostVertexShaderType host_vertex_shader_type, xenos::TessellationMode tessellation_mode); VkShaderModule GetGeometryShader(GeometryShaderKey key); bool TryGetPipelineCreationArgumentsForDescription( const PipelineDescription& description, std::pair* pipeline, PipelineCreationArguments& creation_arguments, bool for_placeholder = false); // Can be called from creation threads - all needed data must be fully set up // at the point of the call: shaders must be translated, and the pipeline // layout and render pass object (unless dynamic rendering is used) must be // available. bool EnsurePipelineCreated(const PipelineCreationArguments& creation_arguments, VkShaderModule fragment_shader_override = VK_NULL_HANDLE); void CreationThread(size_t thread_index); void CreateQueuedPipelinesOnProcessorThread(); void ProcessDeferredPipelineDestructions(bool force_all); 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. string::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_; // Fixed-function emulation shaders for tessellation. bool tessellation_indexed_vertex_shader_attempted_ = false; VkShaderModule tessellation_indexed_vertex_shader_ = VK_NULL_HANDLE; bool tessellation_adaptive_vertex_shader_attempted_ = false; VkShaderModule tessellation_adaptive_vertex_shader_ = VK_NULL_HANDLE; std::unordered_map tessellation_control_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; VkShaderModule placeholder_pixel_shader_ = VK_NULL_HANDLE; // Depth-only shaders for float24 emulation when no Xenos pixel shader is // provided in host render target mode. VkShaderModule depth_float24_truncate_fragment_shader_ = VK_NULL_HANDLE; VkShaderModule depth_float24_round_fragment_shader_ = VK_NULL_HANDLE; std::unordered_map pipelines_; // Previously used pipeline, to avoid lookups if the state wasn't changed. const std::pair* last_pipeline_ = nullptr; // . std::deque> deferred_destroy_pipelines_; std::mutex deferred_destroy_lock_; // Currently open shader storage path. std::filesystem::path shader_storage_cache_root_; uint32_t shader_storage_title_id_ = 0; // Shader storage output stream, for preload in the next emulator runs. FILE* shader_storage_file_ = nullptr; // For only writing shaders to the currently open storage once, incremented // when switching the storage. uint32_t shader_storage_index_ = 0; bool shader_storage_file_flush_needed_ = false; // Pipeline storage output stream, for preload in the next emulator runs. FILE* pipeline_storage_file_ = nullptr; bool pipeline_storage_file_flush_needed_ = false; // Thread for asynchronous writing to the storage streams. void StorageWriteThread(); std::mutex storage_write_request_lock_; std::condition_variable storage_write_request_cond_; // Storage thread input is protected with storage_write_request_lock_, and the // thread is notified about its change via storage_write_request_cond_. std::deque storage_write_shader_queue_; std::deque storage_write_pipeline_queue_; bool storage_write_flush_shaders_ = false; bool storage_write_flush_pipelines_ = false; bool storage_write_thread_shutdown_ = false; std::unique_ptr storage_write_thread_; mutable std::mutex creation_request_lock_; std::condition_variable creation_request_cond_; std::priority_queue, PipelineCreationArgumentsPriorityComparator> creation_queue_; size_t creation_threads_busy_ = 0; bool startup_loading_ = false; std::unique_ptr creation_completion_event_; bool creation_completion_set_event_ = false; size_t creation_threads_shutdown_from_ = SIZE_MAX; std::vector> creation_threads_; }; } // namespace rex::graphics::vulkan