#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 #include #include #include #include #include namespace rex::graphics::nrhi { class Device; } namespace rex::graphics::vulkan { class VulkanCommandProcessor : public CommandProcessor { public: // Single-descriptor layouts for use within a single frame. enum class SingleTransientDescriptorLayout { kStorageBufferCompute, kStorageBufferPairCompute, kCount, }; class ScratchBufferAcquisition { public: explicit ScratchBufferAcquisition() = default; explicit ScratchBufferAcquisition(VulkanCommandProcessor& command_processor, VkBuffer buffer, VkPipelineStageFlags stage_mask, VkAccessFlags access_mask) : command_processor_(&command_processor), buffer_(buffer), stage_mask_(stage_mask), access_mask_(access_mask) {} ScratchBufferAcquisition(const ScratchBufferAcquisition& acquisition) = delete; ScratchBufferAcquisition& operator=(const ScratchBufferAcquisition& acquisition) = delete; ScratchBufferAcquisition(ScratchBufferAcquisition&& acquisition) { command_processor_ = acquisition.command_processor_; buffer_ = acquisition.buffer_; stage_mask_ = acquisition.stage_mask_; access_mask_ = acquisition.access_mask_; acquisition.command_processor_ = nullptr; acquisition.buffer_ = VK_NULL_HANDLE; acquisition.stage_mask_ = 0; acquisition.access_mask_ = 0; } ScratchBufferAcquisition& operator=(ScratchBufferAcquisition&& acquisition) { if (this == &acquisition) { return *this; } command_processor_ = acquisition.command_processor_; buffer_ = acquisition.buffer_; stage_mask_ = acquisition.stage_mask_; access_mask_ = acquisition.access_mask_; acquisition.command_processor_ = nullptr; acquisition.buffer_ = VK_NULL_HANDLE; acquisition.stage_mask_ = 0; acquisition.access_mask_ = 0; return *this; } ~ScratchBufferAcquisition() { if (buffer_ != VK_NULL_HANDLE) { assert_true(command_processor_->scratch_buffer_used_); assert_true(command_processor_->scratch_buffer_ == buffer_); command_processor_->scratch_buffer_last_stage_mask_ = stage_mask_; command_processor_->scratch_buffer_last_access_mask_ = access_mask_; command_processor_->scratch_buffer_last_usage_submission_ = command_processor_->GetCurrentSubmission(); command_processor_->scratch_buffer_used_ = false; } } // VK_NULL_HANDLE if failed to acquire or if moved. VkBuffer buffer() const { return buffer_; } VkPipelineStageFlags GetStageMask() const { return stage_mask_; } VkPipelineStageFlags SetStageMask(VkPipelineStageFlags new_stage_mask) { VkPipelineStageFlags old_stage_mask = stage_mask_; stage_mask_ = new_stage_mask; return old_stage_mask; } VkAccessFlags GetAccessMask() const { return access_mask_; } VkAccessFlags SetAccessMask(VkAccessFlags new_access_mask) { VkAccessFlags old_access_mask = access_mask_; access_mask_ = new_access_mask; return old_access_mask; } private: VulkanCommandProcessor* command_processor_ = nullptr; VkBuffer buffer_ = VK_NULL_HANDLE; VkPipelineStageFlags stage_mask_ = 0; VkAccessFlags access_mask_ = 0; }; VulkanCommandProcessor(VulkanGraphicsSystem* graphics_system, system::KernelState* kernel_state); ~VulkanCommandProcessor(); void ClearCaches() override; void InvalidateGpuMemory() override; void InitializeShaderStorage(const std::filesystem::path& cache_root, uint32_t title_id, bool blocking) override; void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override; void RestoreEdramSnapshot(const void* snapshot) override; ui::vulkan::VulkanDevice* GetVulkanDevice() const { return static_cast(graphics_system_->provider()) ->vulkan_device(); } bool CompileGlslToSpirv(VkShaderStageFlagBits stage, std::string_view source, std::vector& spirv_out, std::string& error_out) const; // Returns the deferred drawing command list for the currently open // submission. DeferredCommandBuffer& deferred_command_buffer() { assert_true(submission_open_); return deferred_command_buffer_; } bool submission_open() const { return submission_open_; } uint64_t GetCurrentSubmission() const { return submission_completed_ + uint64_t(submissions_in_flight_fences_.size()) + 1; } uint64_t GetCompletedSubmission() const { return submission_completed_; } // Sparse binds are: // - In a single submission, all submitted in one vkQueueBindSparse. // - Sent to the queue without waiting for a semaphore. // Thus, multiple sparse binds between the completed and the current // submission, and within one submission, must not touch any overlapping // memory regions. void SparseBindBuffer(VkBuffer buffer, uint32_t bind_count, const VkSparseMemoryBind* binds, VkPipelineStageFlags wait_stage_mask); uint64_t GetCurrentFrame() const { return frame_current_; } uint64_t GetCompletedFrame() const { return frame_completed_; } // Submission must be open to insert barriers. If no pipeline stages access // the resource in a synchronization scope, the stage masks should be 0 (top / // bottom of pipe should be specified only if explicitly needed). Returning // true if the barrier has actually been inserted and not dropped. bool PushBufferMemoryBarrier(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, uint32_t src_queue_family_index = VK_QUEUE_FAMILY_IGNORED, uint32_t dst_queue_family_index = VK_QUEUE_FAMILY_IGNORED, bool skip_if_equal = true); bool PushImageMemoryBarrier(VkImage image, const VkImageSubresourceRange& subresource_range, VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask, VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, VkImageLayout old_layout, VkImageLayout new_layout, uint32_t src_queue_family_index = VK_QUEUE_FAMILY_IGNORED, uint32_t dst_queue_family_index = VK_QUEUE_FAMILY_IGNORED, bool skip_if_equal = true); // Returns whether any barriers have been submitted - if true is returned, the // render pass will also be closed. bool SubmitBarriers(bool force_end_render_pass); // If not started yet, begins a render pass from the render target cache. // Submission must be open. void SubmitBarriersAndEnterRenderTargetCacheRenderPass( VkRenderPass render_pass, const VulkanRenderTargetCache::Framebuffer* framebuffer); // Overload for transfer operations with dynamic rendering. void SubmitBarriersAndEnterRenderTargetCacheRenderPass( VkRenderPass render_pass, const VulkanRenderTargetCache::Framebuffer* framebuffer, VkImageView transfer_dest_view, bool transfer_dest_is_depth); // Must be called before doing anything outside the render pass scope, // including adding pipeline barriers that are not a part of the render pass // 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_vertex, size_t texture_count, size_t sampler_count); // The returned reference is valid until a cache clear. const VulkanPipelineCache::PipelineLayoutProvider* GetPipelineLayout(size_t texture_count_pixel, size_t sampler_count_pixel, size_t texture_count_vertex, size_t sampler_count_vertex); // Returns a single temporary GPU-side buffer within a submission for tasks // like texture untiling and resolving. May push a buffer memory barrier into // the initial usage. Submission must be open. ScratchBufferAcquisition AcquireScratchGpuBuffer(VkDeviceSize size, VkPipelineStageFlags initial_stage_mask, VkAccessFlags initial_access_mask); // Binds a graphics pipeline for host-specific purposes, invalidating the // affected state. keep_dynamic_* must be false (to invalidate the dynamic // state after binding the pipeline with the same state being static, or if // the caller changes the dynamic state bypassing the VulkanCommandProcessor) // unless the caller has these state variables as dynamic and uses the // tracking in VulkanCommandProcessor to modify them. void BindExternalGraphicsPipeline(VkPipeline pipeline, bool keep_dynamic_depth_bias = false, bool keep_dynamic_blend_constants = false, bool keep_dynamic_stencil_mask_ref = false); void BindExternalComputePipeline(VkPipeline pipeline); uint32_t BeginGpuTimestampedRegion(rex::perf::DrawBucket bucket) { return BeginGpuTimestampedDraw(bucket); } void SetViewport(const VkViewport& viewport); void SetScissor(const VkRect2D& scissor); // Returns the text to display in the GPU backend name in the window title. std::string GetWindowTitleText() const; protected: bool SetupContext() override; void ShutdownContext() override; void WriteRegister(uint32_t index, uint32_t value) override; void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers) override; bool ExecutePacketType3_EVENT_WRITE_ZPD(memory::RingBuffer* reader, uint32_t packet, uint32_t count) override; void OnGammaRamp256EntryTableValueWritten() override; void OnGammaRampPWLValueWritten() override; void OnPrimaryBufferEnd() override; void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width, uint32_t frontbuffer_height) override; Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address, const uint32_t* host_address, uint32_t dword_count) override; bool IssueDraw(xenos::PrimitiveType prim_type, uint32_t index_count, IndexBufferInfo* index_buffer_info, bool major_mode_explicit) override; bool IssueCopy() override; void InitializeTrace() override; private: friend class VulkanTextureCache; struct CommandBuffer { VkCommandPool pool; VkCommandBuffer buffer; }; struct SparseBufferBind { VkBuffer buffer; size_t bind_offset; uint32_t bind_count; }; union TextureDescriptorSetLayoutKey { uint32_t key; struct { // If texture and sampler counts are both 0, use // descriptor_set_layout_empty_ instead as these are owning references. uint32_t texture_count : 16; uint32_t sampler_count : 15; uint32_t is_vertex : 1; }; TextureDescriptorSetLayoutKey() : key(0) { static_assert_size(*this, sizeof(key)); } struct Hasher { size_t operator()(const TextureDescriptorSetLayoutKey& key) const { return std::hash{}(key.key); } }; bool operator==(const TextureDescriptorSetLayoutKey& other_key) const { return key == other_key.key; } bool operator!=(const TextureDescriptorSetLayoutKey& other_key) const { return !(*this == other_key); } }; union PipelineLayoutKey { uint64_t key; struct { // Pixel textures in the low bits since those are varied much more // commonly. 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)); } struct Hasher { size_t operator()(const PipelineLayoutKey& key) const { return std::hash{}(key.key); } }; bool operator==(const PipelineLayoutKey& other_key) const { return key == other_key.key; } bool operator!=(const PipelineLayoutKey& other_key) const { return !(*this == other_key); } }; class PipelineLayout : public VulkanPipelineCache::PipelineLayoutProvider { public: explicit PipelineLayout(VkPipelineLayout pipeline_layout, VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref, VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref) : pipeline_layout_(pipeline_layout), descriptor_set_layout_textures_vertex_ref_(descriptor_set_layout_textures_vertex_ref), descriptor_set_layout_textures_pixel_ref_(descriptor_set_layout_textures_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_textures_pixel_ref() const { return descriptor_set_layout_textures_pixel_ref_; } private: VkPipelineLayout pipeline_layout_; VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref_; VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref_; }; struct UsedSingleTransientDescriptor { uint64_t frame; SingleTransientDescriptorLayout layout; VkDescriptorSet set; }; struct UsedTextureTransientDescriptorSet { uint64_t frame; TextureDescriptorSetLayoutKey layout; VkDescriptorSet set; }; enum SwapApplyGammaDescriptorSet : uint32_t { kSwapApplyGammaDescriptorSetRamp, kSwapApplyGammaDescriptorSetSource, kSwapApplyGammaDescriptorSetCount, }; enum SwapApplyGammaComputeDescriptorSet : uint32_t { kSwapApplyGammaComputeDescriptorSetRamp, kSwapApplyGammaComputeDescriptorSetSource, kSwapApplyGammaComputeDescriptorSetDestination, kSwapApplyGammaComputeDescriptorSetCount, }; struct SwapApplyGammaConstants { uint32_t size[2]; }; enum SwapFxaaDescriptorSet : uint32_t { kSwapFxaaDescriptorSetSource, kSwapFxaaDescriptorSetDestination, kSwapFxaaDescriptorSetCount, }; struct SwapFxaaConstants { uint32_t size[2]; float size_inv[2]; }; // Framebuffer for the current presenter's guest output image revision, and // its usage tracking. struct SwapFramebuffer { VkFramebuffer framebuffer = VK_NULL_HANDLE; uint64_t version = UINT64_MAX; uint64_t last_submission = 0; }; // BeginSubmission and EndSubmission may be called at any time. If there's an // open non-frame submission, BeginSubmission(true) will promote it to a // frame. EndSubmission(true) will close the frame no matter whether the // submission has already been closed. // Unlike on Direct3D 12, submission boundaries do not imply any memory // barriers aside from an incoming host write (but not outgoing host read) // dependency. // Rechecks submission number and reclaims per-submission resources. Pass 0 as // the submission to await to simply check status, or pass // GetCurrentSubmission() to wait for all queue operations to be completed. void CheckSubmissionFenceAndDeviceLoss(uint64_t await_submission); // If is_guest_command is true, a new full frame - with full cleanup of // resources and, if needed, starting capturing - is opened if pending (as // opposed to simply resuming after mid-frame synchronization). Returns // whether a submission is open currently and the device is not lost. bool BeginSubmission(bool is_guest_command); // If is_swap is true, a full frame is closed - with, if needed, cache // clearing and stopping capturing. Returns whether the submission was done // successfully, if it has failed, leaves it open. bool EndSubmission(bool is_swap); bool AwaitAllQueueOperationsCompletion() { CheckSubmissionFenceAndDeviceLoss(GetCurrentSubmission()); return !submission_open_ && submissions_in_flight_fences_.empty(); } // Keep primary-buffer-end submit behavior aligned with D3D12: only submit // when immediate submission is safe. bool CanEndSubmissionImmediately() const; void DebugRecordResolve(uint32_t address, uint32_t length, bool scaled); void ClearTransientDescriptorPools(); bool IssueCopy_ReadbackResolvePath(); bool DownscaleScaledResolveToSharedMemory(uint32_t start_unscaled, uint32_t length_unscaled, uint32_t pixel_size_log2); bool IssueDraw_MemexportReadbackFullPath(uint32_t total_size); bool IssueDraw_MemexportReadbackFastPath(uint32_t total_size); void SplitPendingBarrier(); void DestroyScratchBuffer(); bool InitializeOcclusionQueryResources(); void ShutdownOcclusionQueryResources(); bool BeginGuestOcclusionQuery(uint32_t sample_count_address); bool EndGuestOcclusionQuery(uint32_t sample_count_address); bool AcquireOcclusionQueryIndex(uint32_t& host_index_out); void DisableHostOcclusionQueries(); uint64_t NormalizeOcclusionSamples(uint64_t samples) const; void WriteGuestOcclusionResult(uint32_t sample_count_address, uint64_t samples); bool InitializeGpuTimestampResources(); void ShutdownGpuTimestampResources(); void BeginGpuTimestampFrame(); void EndGpuTimestampFrame(); void ResolveGpuTimestampFrame(VkCommandBuffer command_buffer); void ProcessGpuTimestampResults(); uint32_t BeginGpuTimestampedDraw(rex::perf::DrawBucket bucket); void EndGpuTimestampedDraw(uint32_t start_query); void InvalidateAllVertexBufferResidency(); void InvalidateVertexBufferResidency(uint32_t vfetch_index); void InvalidateVertexBufferResidencyRange(uint32_t first_vfetch, uint32_t last_vfetch); struct ReadbackBuffer { VkBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE}; VkDeviceMemory memories[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE}; void* mapped_data[2] = {nullptr, nullptr}; uint32_t sizes[2] = {0, 0}; uint64_t submission_written[2] = {0, 0}; uint32_t written_size[2] = {0, 0}; uint32_t current_index = 0; uint64_t last_used_frame = 0; }; void EvictOldReadbackBuffers(std::unordered_map& buffer_map); static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024; static constexpr size_t kMaxReadbackBuffers = 256; static constexpr uint64_t kReadbackBufferEvictionAgeFrames = 60; static inline uint32_t AlignReadbackBufferSize(uint32_t size) { if (size < 1 * 1024 * 1024) { return rex::align(size, 256u * 1024u); } if (size < 4 * 1024 * 1024) { return rex::align(size, 1u * 1024u * 1024u); } return rex::align(size, kReadbackBufferSizeIncrement); } static inline uint64_t MakeReadbackResolveKey(uint32_t address, uint32_t length) { return (uint64_t(address) << 32) | uint64_t(length); } static inline uint64_t MakeMemexportReadbackKey(uint32_t first_base_address_dwords, uint32_t total_size) { return (uint64_t(first_base_address_dwords) << 32) | uint64_t(total_size); } struct ResolveDownscaleConstants { uint32_t scale_x; uint32_t scale_y; uint32_t pixel_size_log2; uint32_t tile_count; uint32_t source_offset_bytes; uint32_t half_pixel_offset; }; bool EnsureSwapFxaaSourceImage(uint32_t width, uint32_t height); void DestroySwapFxaaSourceImage(); void UpdateDynamicState(const draw_util::ViewportInfo& viewport_info, bool primitive_polygonal, reg::RB_DEPTHCONTROL normalized_depth_control); void UpdateSystemConstantValues( bool primitive_polygonal, const PrimitiveProcessor::ProcessingResult& primitive_processing_result, bool shader_32bit_index_dma, uint32_t compute_memexport_vertex_count, const draw_util::ViewportInfo& viewport_info, uint32_t used_texture_mask, uint64_t vertex_shader_hash, uint64_t pixel_shader_hash, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask); bool UpdateBindings(const VulkanShader* vertex_shader, const VulkanShader* pixel_shader); // Allocates a descriptor set and fills one or two VkWriteDescriptorSet // structure instances (for images and samplers). // The descriptor set layout must be the one for the given is_vertex, // texture_count, sampler_count (from GetTextureDescriptorSetLayout - may be // already available at the moment of the call, no need to locate it again). // Returns how many VkWriteDescriptorSet structure instances have been // written, or 0 if there was a failure to allocate the descriptor set or no // bindings were requested. uint32_t WriteTransientTextureBindings(bool is_vertex, uint32_t texture_count, uint32_t sampler_count, VkDescriptorSetLayout descriptor_set_layout, const VkDescriptorImageInfo* texture_image_info, const VkDescriptorImageInfo* sampler_image_info, VkWriteDescriptorSet* descriptor_set_writes_out); bool device_lost_ = false; bool cache_clear_requested_ = false; // Host shader types that guest shaders can be translated into - they can // access the shared memory (via vertex fetch, memory export, or manual index // buffer reading) and textures. VkPipelineStageFlags guest_shader_pipeline_stages_ = 0; VkShaderStageFlags guest_shader_vertex_stages_ = 0; std::vector fences_free_; std::vector semaphores_free_; bool submission_open_ = false; uint64_t submission_completed_ = 0; // In case vkQueueSubmit fails after something like a successful // vkQueueBindSparse, to wait correctly on the next attempt. std::vector current_submission_wait_semaphores_; std::vector current_submission_wait_stage_masks_; std::vector submissions_in_flight_fences_; std::deque> submissions_in_flight_semaphores_; static constexpr uint32_t kMaxFramesInFlight = 3; bool frame_open_ = false; // Tracks whether any draw in the current frame used an async placeholder // graphics pipeline and may have produced incomplete output. bool frame_used_async_placeholder_pipeline_ = false; uint32_t async_placeholder_consecutive_frame_skips_ = 0; bool async_placeholder_skip_fallback_logged_ = false; uint32_t debug_frame_draws_ = 0; uint32_t debug_frame_copy_resolves_ = 0; uint32_t debug_frame_raster_draws_ = 0; uint32_t debug_frame_color_draws_ = 0; uint32_t debug_frame_depth_stencil_draws_ = 0; uint32_t debug_frame_texture_draws_ = 0; uint32_t debug_frame_placeholder_draws_ = 0; uint32_t debug_frame_no_effect_draws_ = 0; uint32_t debug_frame_memexport_draws_ = 0; uint32_t debug_frame_resolve_ranges_ = 0; uint32_t debug_frame_scaled_resolve_ranges_ = 0; uint64_t debug_frame_resolve_bytes_ = 0; static constexpr uint32_t kDebugFrameResolveRangeSamples = 6; std::array debug_frame_resolve_addresses_ = {}; std::array debug_frame_resolve_lengths_ = {}; std::array debug_frame_resolve_scaled_ = {}; // Guest frame index, since some transient resources can be reused across // submissions. Values updated in the beginning of a frame. uint64_t frame_current_ = 1; uint64_t frame_completed_ = 0; // Submission indices of frames that have already been submitted. uint64_t closed_frame_submissions_[kMaxFramesInFlight] = {}; // , sorted by the submission number. std::deque> destroy_memory_; std::deque> destroy_buffers_; std::deque> destroy_image_views_; std::deque> destroy_images_; std::deque> destroy_framebuffers_; std::vector command_buffers_writable_; std::deque> command_buffers_submitted_; DeferredCommandBuffer deferred_command_buffer_; std::vector sparse_memory_binds_; std::vector sparse_buffer_binds_; // SparseBufferBind converted to VkSparseBufferMemoryBindInfo to this buffer // on submission (because pBinds should point to a place in std::vector, but // it may be reallocated). std::vector sparse_buffer_bind_infos_temp_; VkPipelineStageFlags sparse_bind_wait_stage_mask_ = 0; // Temporary storage with reusable memory for creating descriptor set layouts. std::vector descriptor_set_layout_bindings_; // Temporary storage with reusable memory for writing image and sampler // descriptors. std::vector descriptor_write_image_info_; std::unique_ptr uniform_buffer_pool_; // Native-render RHI device (rex/graphics/native_rhi.h), created lazily on // the first swap with a native guest-output renderer registered. nrhi::Device* native_rhi_device_ = nullptr; // Descriptor set layouts used by different shaders. VkDescriptorSetLayout descriptor_set_layout_empty_ = VK_NULL_HANDLE; VkDescriptorSetLayout descriptor_set_layout_constants_ = VK_NULL_HANDLE; // Whether the guest constant buffer descriptors are pushed via // VK_KHR_push_descriptor instead of being written to transient descriptor // sets (vulkan_push_constants_descriptors and driver support, applied at // setup; descriptor_set_layout_constants_ is created with the push // descriptor flag and must not be used with vkAllocateDescriptorSets then). bool constants_push_descriptors_used_ = false; std::array descriptor_set_layouts_single_transient_{}; VkDescriptorSetLayout descriptor_set_layout_shared_memory_and_edram_ = VK_NULL_HANDLE; // Descriptor set layouts are referenced by pipeline_layouts_. std::unordered_map descriptor_set_layouts_textures_; // Pipeline layouts are referenced by VulkanPipelineCache. std::unordered_map pipeline_layouts_; // No specific reason for 32768, just the "too much" descriptor count from // Direct3D 12 PIX warnings. static constexpr uint32_t kLinkedTypeDescriptorPoolSetCount = 32768; static const VkDescriptorPoolSize kDescriptorPoolSizeUniformBuffer; static const VkDescriptorPoolSize kDescriptorPoolSizeStorageBuffer; static const VkDescriptorPoolSize kDescriptorPoolSizeTextures[2]; ui::vulkan::LinkedTypeDescriptorSetAllocator transient_descriptor_allocator_uniform_buffer_; ui::vulkan::LinkedTypeDescriptorSetAllocator transient_descriptor_allocator_storage_buffer_; std::deque single_transient_descriptors_used_; std::array, size_t(SingleTransientDescriptorLayout::kCount)> single_transient_descriptors_free_; // . std::deque> constants_transient_descriptors_used_; std::vector constants_transient_descriptors_free_; ui::vulkan::LinkedTypeDescriptorSetAllocator transient_descriptor_allocator_textures_; std::deque texture_transient_descriptor_sets_used_; std::unordered_map, TextureDescriptorSetLayoutKey::Hasher> texture_transient_descriptor_sets_free_; // Frame-local reuse of transient texture/sampler descriptor sets: XXH3 of // the binding contents (stage, counts, image views, samplers) -> a set // already written with those contents this frame. Transient sets are // recycled only after their frame completes on the GPU, so within a frame // their contents are immutable and the same set can simply be rebound // instead of allocating and writing a new one for every draw. Cleared at // frame open. std::unordered_map> texture_transient_descriptor_set_reuse_cache_; // Content hashes of the texture descriptor sets currently recorded in // current_graphics_descriptor_sets_, indexed [0] = vertex, [1] = pixel. The // valid flag is false when the recorded set's contents are unknown (frame // open, write failure, or reuse disabled). bool current_texture_descriptor_set_hash_valid_[2] = {}; uint64_t current_texture_descriptor_set_hashes_[2] = {}; std::unique_ptr shared_memory_; std::unique_ptr primitive_processor_; std::unique_ptr render_target_cache_; std::unique_ptr pipeline_cache_; std::unique_ptr texture_cache_; VkDescriptorPool shared_memory_and_edram_descriptor_pool_ = VK_NULL_HANDLE; VkDescriptorSet shared_memory_and_edram_descriptor_set_; // Bytes 0x0...0x3FF - 256-entry gamma ramp table with B10G10R10X2 data (read // as R10G10B10X2 with swizzle). // Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta, // low 6 bits of each are zero, 3 elements per entry). // kMaxFramesInFlight pairs of gamma ramps if in host-visible memory and // uploaded directly, one otherwise. VkDeviceMemory gamma_ramp_buffer_memory_ = VK_NULL_HANDLE; VkBuffer gamma_ramp_buffer_ = VK_NULL_HANDLE; // kMaxFramesInFlight pairs, only when the gamma ramp buffer is not // host-visible. VkDeviceMemory gamma_ramp_upload_buffer_memory_ = VK_NULL_HANDLE; VkBuffer gamma_ramp_upload_buffer_ = VK_NULL_HANDLE; VkDeviceSize gamma_ramp_upload_memory_size_; uint32_t gamma_ramp_upload_memory_type_; // Mapping of either gamma_ramp_buffer_memory_ (if it's host-visible) or // gamma_ramp_upload_buffer_memory_ (otherwise). void* gamma_ramp_upload_mapping_; std::array gamma_ramp_buffer_views_{}; // UINT32_MAX if outdated. uint32_t gamma_ramp_256_entry_table_current_frame_ = UINT32_MAX; uint32_t gamma_ramp_pwl_current_frame_ = UINT32_MAX; VkDescriptorSetLayout swap_descriptor_set_layout_sampled_image_ = VK_NULL_HANDLE; VkDescriptorSetLayout swap_descriptor_set_layout_combined_image_sampler_ = VK_NULL_HANDLE; VkDescriptorSetLayout swap_descriptor_set_layout_storage_image_ = VK_NULL_HANDLE; VkDescriptorSetLayout swap_descriptor_set_layout_uniform_texel_buffer_ = VK_NULL_HANDLE; // Descriptor pool for allocating descriptors needed for presentation, such as // the destination images and the gamma ramps. VkDescriptorPool swap_descriptor_pool_ = VK_NULL_HANDLE; // Interleaved 256-entry table and PWL texel buffer descriptors. // kMaxFramesInFlight pairs of gamma ramps if in host-visible memory and // uploaded directly, one otherwise. std::array swap_descriptors_gamma_ramp_; // Sampled images. std::array swap_descriptors_source_; // Combined image sampler descriptors for FXAA. std::array swap_descriptors_fxaa_source_; // Storage image descriptors for the apply-gamma compute pass destination. std::array swap_descriptors_destination_storage_; // Separate storage image descriptors for the FXAA compute destination. std::array swap_descriptors_fxaa_destination_storage_; VkSampler swap_sampler_linear_clamp_ = VK_NULL_HANDLE; VkPipelineLayout swap_apply_gamma_pipeline_layout_ = VK_NULL_HANDLE; VkPipelineLayout swap_apply_gamma_compute_pipeline_layout_ = VK_NULL_HANDLE; VkPipelineLayout swap_fxaa_pipeline_layout_ = VK_NULL_HANDLE; // Has no dependencies on specific pipeline stages on both ends to simplify // use in different scenarios with different pipelines - use explicit barriers // for synchronization. VkRenderPass swap_apply_gamma_render_pass_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_256_entry_table_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_pwl_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_256_entry_table_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_pwl_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_256_entry_table_fxaa_luma_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_pwl_fxaa_luma_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_256_entry_table_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_pwl_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_256_entry_table_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_pwl_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_256_entry_table_fxaa_luma_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_pwl_fxaa_luma_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_256_entry_table_fxaa_luma_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_apply_gamma_compute_pwl_fxaa_luma_rb_swap_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_fxaa_pipeline_ = VK_NULL_HANDLE; VkPipeline swap_fxaa_extreme_pipeline_ = VK_NULL_HANDLE; VkPipelineLayout resolve_downscale_pipeline_layout_ = VK_NULL_HANDLE; VkPipeline resolve_downscale_pipeline_ = VK_NULL_HANDLE; VkBuffer resolve_downscale_buffer_ = VK_NULL_HANDLE; VkDeviceMemory resolve_downscale_buffer_memory_ = VK_NULL_HANDLE; uint32_t resolve_downscale_buffer_size_ = 0; VkImage swap_fxaa_source_image_ = VK_NULL_HANDLE; VkDeviceMemory swap_fxaa_source_image_memory_ = VK_NULL_HANDLE; VkImageView swap_fxaa_source_image_view_ = VK_NULL_HANDLE; uint32_t swap_fxaa_source_image_width_ = 0; uint32_t swap_fxaa_source_image_height_ = 0; uint64_t swap_fxaa_source_image_submission_ = 0; VkPipelineStageFlags swap_fxaa_source_stage_mask_ = 0; VkAccessFlags swap_fxaa_source_access_mask_ = 0; VkImageLayout swap_fxaa_source_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; std::array swap_framebuffers_; // Pending pipeline barriers. std::vector pending_barriers_buffer_memory_barriers_; std::vector pending_barriers_image_memory_barriers_; struct PendingBarrier { VkPipelineStageFlags src_stage_mask = 0; VkPipelineStageFlags dst_stage_mask = 0; size_t buffer_memory_barriers_offset = 0; size_t image_memory_barriers_offset = 0; }; std::vector pending_barriers_; PendingBarrier current_pending_barrier_; // GPU-local scratch buffer. static constexpr VkDeviceSize kScratchBufferSizeIncrement = 16 * 1024 * 1024; VkDeviceMemory scratch_buffer_memory_ = VK_NULL_HANDLE; VkBuffer scratch_buffer_ = VK_NULL_HANDLE; VkDeviceSize scratch_buffer_size_ = 0; VkPipelineStageFlags scratch_buffer_last_stage_mask_ = 0; VkAccessFlags scratch_buffer_last_access_mask_ = 0; uint64_t scratch_buffer_last_usage_submission_ = 0; bool scratch_buffer_used_ = false; static constexpr uint32_t kMaxOcclusionQueries = 8192; VkQueryPool occlusion_query_pool_ = VK_NULL_HANDLE; VkBuffer occlusion_query_readback_buffer_ = VK_NULL_HANDLE; VkDeviceMemory occlusion_query_readback_memory_ = VK_NULL_HANDLE; uint32_t occlusion_query_readback_memory_type_ = UINT32_MAX; VkDeviceSize occlusion_query_readback_memory_size_ = 0; uint8_t* occlusion_query_readback_mapping_ = nullptr; uint32_t occlusion_query_cursor_ = 0; bool occlusion_query_resources_available_ = false; struct ActiveOcclusionQuery { uint32_t sample_count_address = 0; uint32_t host_index = UINT32_MAX; bool valid = false; } active_occlusion_query_; // MoltenVK maps timestamp queries to Metal counter sample buffers with a // small per-pool limit, so keep the per-frame query budget modest there. #if REX_PLATFORM_MAC static constexpr uint32_t kMaxGpuTimestampQueriesPerFrame = 1024; #else static constexpr uint32_t kMaxGpuTimestampQueriesPerFrame = 4096; #endif VkQueryPool gpu_timestamp_query_pool_ = VK_NULL_HANDLE; VkBuffer gpu_timestamp_readback_buffer_ = VK_NULL_HANDLE; VkDeviceMemory gpu_timestamp_readback_memory_ = VK_NULL_HANDLE; uint32_t gpu_timestamp_readback_memory_type_ = UINT32_MAX; VkDeviceSize gpu_timestamp_readback_memory_size_ = 0; uint64_t* gpu_timestamp_readback_mapping_ = nullptr; bool gpu_timestamp_resources_available_ = false; bool gpu_timestamp_draws_enabled_ = false; struct GpuTimestampFrame { uint64_t frame = 0; uint64_t submission = 0; // Submission index when the frame began - for counting the queue // submissions the frame spans in the profile log. uint64_t first_submission = 0; uint32_t query_base = 0; uint32_t query_count = 0; uint32_t frame_start_query = UINT32_MAX; uint32_t frame_end_query = UINT32_MAX; bool pending = false; bool active_bucket_valid = false; rex::perf::DrawBucket active_bucket = rex::perf::DrawBucket::kMainColorDepth; uint32_t active_bucket_index = UINT32_MAX; std::vector buckets; std::vector bucket_start_queries; std::vector bucket_end_queries; }; std::array gpu_timestamp_frames_; // Averaged full-frame GPU time attribution, logged periodically when bucket // timestamps are enabled. static constexpr uint32_t kGpuProfileLogFrameInterval = 120; uint64_t gpu_profile_bucket_us_[size_t(rex::perf::DrawBucket::kCount)] = {}; // Interval counts per bucket over the same logging window (distinguishes // "more intervals" from "same intervals, slower"). uint64_t gpu_profile_bucket_n_[size_t(rex::perf::DrawBucket::kCount)] = {}; uint64_t gpu_profile_span_us_ = 0; uint64_t gpu_profile_submissions_ = 0; uint32_t gpu_profile_frames_ = 0; struct VertexBufferState { uint32_t address = UINT32_MAX; uint32_t size = UINT32_MAX; }; std::array vertex_buffer_states_{}; uint64_t vertex_buffers_in_sync_[2] = {}; std::unordered_map readback_buffers_; std::unordered_map memexport_readback_buffers_; // The current dynamic state of the graphics pipeline bind point. Note that // binding any pipeline to the bind point with static state (even if it's // unused, like depth bias being disabled, but the values themselves still not // declared as dynamic in the pipeline) invalidates such dynamic state. VkViewport dynamic_viewport_; VkRect2D dynamic_scissor_; // Dynamic fixed-function depth bias, blend constants, stencil state are // applicable only to the render target implementations where they are // actually involved. float dynamic_depth_bias_constant_factor_; float dynamic_depth_bias_slope_factor_; float dynamic_blend_constants_[4]; // The stencil values are pre-initialized (to D3D11_DEFAULT_STENCIL_*, and the // initial values for front and back are the same for portability subset // safety) because they're updated conditionally to avoid changing the back // face values when stencil is disabled and the primitive type is changed // between polygonal and non-polygonal. uint32_t dynamic_stencil_compare_mask_front_ = UINT8_MAX; uint32_t dynamic_stencil_compare_mask_back_ = UINT8_MAX; uint32_t dynamic_stencil_write_mask_front_ = UINT8_MAX; uint32_t dynamic_stencil_write_mask_back_ = UINT8_MAX; uint32_t dynamic_stencil_reference_front_ = 0; uint32_t dynamic_stencil_reference_back_ = 0; bool dynamic_viewport_update_needed_; bool dynamic_scissor_update_needed_; bool dynamic_depth_bias_update_needed_; bool dynamic_blend_constants_update_needed_; bool dynamic_stencil_compare_mask_front_update_needed_; bool dynamic_stencil_compare_mask_back_update_needed_; bool dynamic_stencil_write_mask_front_update_needed_; bool dynamic_stencil_write_mask_back_update_needed_; bool dynamic_stencil_reference_front_update_needed_; bool dynamic_stencil_reference_back_update_needed_; // Currently used samplers. std::vector> current_samplers_vertex_; std::vector> current_samplers_pixel_; // State the per-draw binding fast path compares against // (vulkan_reuse_unchanged_draw_bindings): when the previous draw in the same // submission used the same shaders, identical sampler parameters and the // texture cache's binding views are unchanged, the sampler cache walks and // the texture descriptor gather/hash in UpdateBindings are skipped. bool draw_binding_state_valid_ = false; uint64_t draw_binding_submission_ = 0; const VulkanShader* draw_binding_vertex_shader_ = nullptr; const VulkanShader* draw_binding_pixel_shader_ = nullptr; uint64_t draw_binding_texture_generation_ = 0; // Set by IssueDraw for the draw currently being issued, consumed by // UpdateBindings. bool texture_bindings_unchanged_this_draw_ = false; // Cache render pass currently started in the command buffer with the // framebuffer. For dynamic rendering, current_render_pass_ is VK_NULL_HANDLE // but in_render_pass_ is true. VkRenderPass current_render_pass_; const VulkanRenderTargetCache::Framebuffer* current_framebuffer_; bool in_render_pass_ = false; // Currently bound graphics pipeline, either from the pipeline cache (with // potentially deferred creation - current_external_graphics_pipeline_ is // VK_NULL_HANDLE in this case) or a non-Xenos one // (current_guest_graphics_pipeline_ is VK_NULL_HANDLE in this case). // TODO(Triang3l): Change to a deferred compilation handle. VkPipeline current_guest_graphics_pipeline_; VkPipeline current_external_graphics_pipeline_; VkPipeline current_external_compute_pipeline_; // Pipeline layout of the current guest graphics pipeline. const PipelineLayout* current_guest_graphics_pipeline_layout_; VkDescriptorBufferInfo current_constant_buffer_infos_[SpirvShaderTranslator::kConstantBufferCount]; // Whether up-to-date data has been written to constant (uniform) buffers, and // the buffer infos in current_constant_buffer_infos_ point to them. uint32_t current_constant_buffers_up_to_date_; VkDescriptorSet current_graphics_descriptor_sets_[SpirvShaderTranslator::kDescriptorSetCount]; // Whether descriptor sets in current_graphics_descriptor_sets_ point to // up-to-date data. uint32_t current_graphics_descriptor_set_values_up_to_date_; // Whether the descriptor sets currently bound to the command buffer - only // low bits for the descriptor set layouts that remained the same are kept // when changing the pipeline layout. May be out of sync with // current_graphics_descriptor_set_values_up_to_date_, but should be ensured // to be a subset of it at some point when it becomes important; bits for // non-existent descriptor set layouts may also be set, but need to be ignored // when they start to matter. uint32_t current_graphics_descriptor_sets_bound_up_to_date_; static_assert(SpirvShaderTranslator::kDescriptorSetCount <= sizeof(current_graphics_descriptor_set_values_up_to_date_) * CHAR_BIT, "Bit fields storing descriptor set validity must be large enough"); static_assert(SpirvShaderTranslator::kDescriptorSetCount <= sizeof(current_graphics_descriptor_sets_bound_up_to_date_) * CHAR_BIT, "Bit fields storing descriptor set validity must be large enough"); // Float constant usage masks of the last draw call. uint64_t current_float_constant_map_vertex_[4]; uint64_t current_float_constant_map_pixel_[4]; // System shader constants. SpirvShaderTranslator::SystemConstants system_constants_; // Temporary storage for memexport stream constants used in the draw. std::vector memexport_ranges_; }; } // namespace rex::graphics::vulkan