// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" #include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" #include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace VideoCore { class ShaderNotify; } namespace Vulkan { struct GraphicsPipelineCacheKey { std::array unique_hashes; FixedPipelineState state; size_t Hash() const noexcept; bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept; bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { return !operator==(rhs); } size_t Size() const noexcept { return sizeof(unique_hashes) + state.Size(); } }; static_assert(std::has_unique_object_representations_v); static_assert(std::is_trivially_copyable_v); static_assert(std::is_trivially_constructible_v); } // namespace Vulkan namespace std { template <> struct hash { size_t operator()(const Vulkan::GraphicsPipelineCacheKey& k) const noexcept { return k.Hash(); } }; } // namespace std namespace Vulkan { class Device; class PipelineStatistics; class RenderPassCache; class RescalingPushConstant; class RenderAreaPushConstant; class Scheduler; class GraphicsPipeline { static constexpr size_t NUM_STAGES = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage; public: explicit GraphicsPipeline( Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache, vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify, const Device& device, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, std::array stages, const std::array& infos); bool HasDynamicVertexInput() const noexcept { return key.state.dynamic_vertex_input; } bool SupportsAlphaToCoverage() const noexcept { return fragment_has_color0_output; } bool SupportsAlphaToOne() const noexcept { return fragment_has_color0_output; } bool UsesExtendedDynamicState() const noexcept { return key.state.extended_dynamic_state != 0; } /// Switch 2 task/mesh pipeline: drawn with vkCmdDrawMeshTasksEXT. [[nodiscard]] bool IsMeshPipeline() const noexcept { return stage_infos[0].is_task || stage_infos[0].is_mesh; } GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete; GraphicsPipeline(GraphicsPipeline&&) noexcept = delete; GraphicsPipeline& operator=(const GraphicsPipeline&) = delete; GraphicsPipeline(const GraphicsPipeline&) = delete; void AddTransition(GraphicsPipeline* transition); bool Configure(bool is_indexed) { return configure_func(this, is_indexed); } [[nodiscard]] GraphicsPipeline* Next(const GraphicsPipelineCacheKey& current_key) noexcept { if (key == current_key) { return this; } const auto it{std::find(transition_keys.begin(), transition_keys.end(), current_key)}; return it != transition_keys.end() ? transitions[std::distance(transition_keys.begin(), it)] : nullptr; } /// The frame the pipeline cache last handed this pipeline out (evicts long unused ones). void MarkUsed(u64 frame) noexcept { last_use_frame = frame; } [[nodiscard]] u64 LastUse() const noexcept { return last_use_frame; } /// Forgets the transitions, which may point to pipelines the cache evicted. void ClearTransitions() noexcept { transition_keys.clear(); transitions.clear(); } [[nodiscard]] bool IsBuilt() const noexcept { return is_built.load(std::memory_order::relaxed); } template static auto MakeConfigureSpecFunc() { return [](GraphicsPipeline* pl, bool is_indexed) { return pl->ConfigureImpl(is_indexed); }; } void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) { maxwell3d = maxwell3d_; gpu_memory = gpu_memory_; } private: template bool ConfigureImpl(bool is_indexed); bool ConfigureDraw(const RescalingPushConstant& rescaling, const RenderAreaPushConstant& render_are); void MakePipeline(VkRenderPass render_pass); void Validate(); const GraphicsPipelineCacheKey key; Tegra::Engines::Maxwell3D* maxwell3d; Tegra::MemoryManager* gpu_memory; const Device& device; TextureCache& texture_cache; BufferCache& buffer_cache; vk::PipelineCache& pipeline_cache; Scheduler& scheduler; GuestDescriptorQueue& guest_descriptor_queue; DescriptorBufferRing& descriptor_buffer_ring; bool (*configure_func)(GraphicsPipeline*, bool){}; std::vector transition_keys; std::vector transitions; u64 last_use_frame{}; std::array spv_modules; std::array stage_infos; std::array enabled_uniform_buffer_masks{}; VideoCommon::UniformBufferSizes uniform_buffer_sizes{}; u32 num_descriptor_entries{}; size_t num_image_elements{}; u32 num_textures{}; bool fragment_has_color0_output{}; vk::DescriptorSetLayout descriptor_set_layout; DescriptorAllocator descriptor_allocator; vk::PipelineLayout pipeline_layout; vk::DescriptorUpdateTemplate descriptor_update_template; vk::Pipeline pipeline; DescriptorBufferLayout descriptor_buffer_layout; std::vector last_descriptor_payload; VkDeviceSize last_descriptor_buffer_offset{}; u32 last_descriptor_buffer_chunk{}; u64 last_descriptor_buffer_generation{}; std::condition_variable build_condvar; std::mutex build_mutex; std::atomic_bool is_built{false}; bool uses_push_descriptor{false}; bool uses_descriptor_buffer{false}; }; } // namespace Vulkan