rexglue-skate3/include/rex/graphics/vulkan/command_processor.h
2026-06-14 05:22:30 +01:00

962 lines
46 KiB
C++

#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 <array>
#include <climits>
#include <cstdint>
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include <rex/assert.h>
#include <rex/graphics/command_processor.h>
#include <rex/graphics/pipeline/shader/spirv_translator.h>
#include <rex/graphics/registers.h>
#include <rex/graphics/util/draw.h>
#include <rex/graphics/vulkan/deferred_command_buffer.h>
#include <rex/graphics/vulkan/graphics_system.h>
#include <rex/graphics/vulkan/pipeline_cache.h>
#include <rex/graphics/vulkan/primitive_processor.h>
#include <rex/graphics/vulkan/render_target_cache.h>
#include <rex/graphics/vulkan/shader.h>
#include <rex/graphics/vulkan/shared_memory.h>
#include <rex/graphics/vulkan/texture_cache.h>
#include <rex/graphics/xenos.h>
#include <rex/hash.h>
#include <rex/perf/counter.h>
#include <rex/system/kernel_state.h>
#include <rex/ui/vulkan/linked_type_descriptor_set_allocator.h>
#include <rex/ui/vulkan/presenter.h>
#include <rex/ui/vulkan/provider.h>
#include <rex/ui/vulkan/upload_buffer_pool.h>
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<const ui::vulkan::VulkanProvider*>(graphics_system_->provider())
->vulkan_device();
}
bool CompileGlslToSpirv(VkShaderStageFlagBits stage, std::string_view source,
std::vector<uint32_t>& 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<decltype(key.key)>{}(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<decltype(key.key)>{}(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<uint64_t, ReadbackBuffer>& 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<VkFence> fences_free_;
std::vector<VkSemaphore> 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<VkSemaphore> current_submission_wait_semaphores_;
std::vector<VkPipelineStageFlags> current_submission_wait_stage_masks_;
std::vector<VkFence> submissions_in_flight_fences_;
std::deque<std::pair<uint64_t, VkSemaphore>> 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<uint32_t, kDebugFrameResolveRangeSamples> debug_frame_resolve_addresses_ = {};
std::array<uint32_t, kDebugFrameResolveRangeSamples> debug_frame_resolve_lengths_ = {};
std::array<bool, kDebugFrameResolveRangeSamples> 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] = {};
// <Submission where last used, resource>, sorted by the submission number.
std::deque<std::pair<uint64_t, VkDeviceMemory>> destroy_memory_;
std::deque<std::pair<uint64_t, VkBuffer>> destroy_buffers_;
std::deque<std::pair<uint64_t, VkImageView>> destroy_image_views_;
std::deque<std::pair<uint64_t, VkImage>> destroy_images_;
std::deque<std::pair<uint64_t, VkFramebuffer>> destroy_framebuffers_;
std::vector<CommandBuffer> command_buffers_writable_;
std::deque<std::pair<uint64_t, CommandBuffer>> command_buffers_submitted_;
DeferredCommandBuffer deferred_command_buffer_;
std::vector<VkSparseMemoryBind> sparse_memory_binds_;
std::vector<SparseBufferBind> 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<VkSparseBufferMemoryBindInfo> sparse_buffer_bind_infos_temp_;
VkPipelineStageFlags sparse_bind_wait_stage_mask_ = 0;
// Temporary storage with reusable memory for creating descriptor set layouts.
std::vector<VkDescriptorSetLayoutBinding> descriptor_set_layout_bindings_;
// Temporary storage with reusable memory for writing image and sampler
// descriptors.
std::vector<VkDescriptorImageInfo> descriptor_write_image_info_;
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> uniform_buffer_pool_;
// 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<VkDescriptorSetLayout, size_t(SingleTransientDescriptorLayout::kCount)>
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<TextureDescriptorSetLayoutKey, VkDescriptorSetLayout,
TextureDescriptorSetLayoutKey::Hasher>
descriptor_set_layouts_textures_;
// Pipeline layouts are referenced by VulkanPipelineCache.
std::unordered_map<PipelineLayoutKey, PipelineLayout, PipelineLayoutKey::Hasher>
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<UsedSingleTransientDescriptor> single_transient_descriptors_used_;
std::array<std::vector<VkDescriptorSet>, size_t(SingleTransientDescriptorLayout::kCount)>
single_transient_descriptors_free_;
// <Usage frame, set>.
std::deque<std::pair<uint64_t, VkDescriptorSet>> constants_transient_descriptors_used_;
std::vector<VkDescriptorSet> constants_transient_descriptors_free_;
ui::vulkan::LinkedTypeDescriptorSetAllocator transient_descriptor_allocator_textures_;
std::deque<UsedTextureTransientDescriptorSet> texture_transient_descriptor_sets_used_;
std::unordered_map<TextureDescriptorSetLayoutKey, std::vector<VkDescriptorSet>,
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<uint64_t, VkDescriptorSet, rex::IdentityHasher<uint64_t>>
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<VulkanSharedMemory> shared_memory_;
std::unique_ptr<VulkanPrimitiveProcessor> primitive_processor_;
std::unique_ptr<VulkanRenderTargetCache> render_target_cache_;
std::unique_ptr<VulkanPipelineCache> pipeline_cache_;
std::unique_ptr<VulkanTextureCache> 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<VkBufferView, 2 * kMaxFramesInFlight> 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<VkDescriptorSet, 2 * kMaxFramesInFlight> swap_descriptors_gamma_ramp_;
// Sampled images.
std::array<VkDescriptorSet, kMaxFramesInFlight> swap_descriptors_source_;
// Combined image sampler descriptors for FXAA.
std::array<VkDescriptorSet, kMaxFramesInFlight> swap_descriptors_fxaa_source_;
// Storage image descriptors for the apply-gamma compute pass destination.
std::array<VkDescriptorSet, kMaxFramesInFlight> swap_descriptors_destination_storage_;
// Separate storage image descriptors for the FXAA compute destination.
std::array<VkDescriptorSet, kMaxFramesInFlight> 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<SwapFramebuffer, ui::vulkan::VulkanPresenter::kMaxActiveGuestOutputImageVersions>
swap_framebuffers_;
// Pending pipeline barriers.
std::vector<VkBufferMemoryBarrier> pending_barriers_buffer_memory_barriers_;
std::vector<VkImageMemoryBarrier> 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<PendingBarrier> 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<rex::perf::DrawBucket> buckets;
std::vector<uint32_t> bucket_start_queries;
std::vector<uint32_t> bucket_end_queries;
};
std::array<GpuTimestampFrame, kMaxFramesInFlight> 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)] = {};
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<VertexBufferState, 96> vertex_buffer_states_{};
uint64_t vertex_buffers_in_sync_[2] = {};
std::unordered_map<uint64_t, ReadbackBuffer> readback_buffers_;
std::unordered_map<uint64_t, ReadbackBuffer> 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<std::pair<VulkanTextureCache::SamplerParameters, VkSampler>> current_samplers_vertex_;
std::vector<std::pair<VulkanTextureCache::SamplerParameters, VkSampler>> 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<draw_util::MemExportRange> memexport_ranges_;
};
} // namespace rex::graphics::vulkan