980 lines
44 KiB
C++
980 lines
44 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 <cstdint>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
|
|
#include <rex/graphics/flags.h>
|
|
#include <rex/graphics/pipeline/render_target/cache.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/ui/vulkan/single_layout_descriptor_set_pool.h>
|
|
#include <rex/ui/vulkan/upload_buffer_pool.h>
|
|
|
|
namespace rex::graphics::vulkan {
|
|
|
|
class VulkanCommandProcessor;
|
|
|
|
class VulkanRenderTargetCache final : public RenderTargetCache {
|
|
public:
|
|
union RenderPassKey {
|
|
struct {
|
|
// If emulating 2x as 4x, this is still 2x for simplicity of using this
|
|
// field to make guest-related decisions. Render pass objects are not very
|
|
// expensive, and their dependencies can't be shared between 2x-as-4x and
|
|
// true 4x MSAA passes (framebuffers because render target cache render
|
|
// targets are different for 2x and 4x guest MSAA, pipelines because the
|
|
// sample mask will have 2 samples excluded for 2x-as-4x).
|
|
// This has effect only on the attachments, but even in cases when there
|
|
// are no attachments, it can be used to pass the sample count between
|
|
// subsystems, for instance, to specify the desired number of samples to
|
|
// use when there are no attachments in pipelines.
|
|
// Also, without attachments, using separate render passes for different
|
|
// sample counts ensures that if the variableMultisampleRate feature is
|
|
// not supported, no draws with different rasterization sample counts end
|
|
// up in one render pass.
|
|
xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; // 2
|
|
// << 0 is depth, << 1...4 is color.
|
|
uint32_t depth_and_color_used : 1 + xenos::kMaxColorRenderTargets; // 7
|
|
// 0 for unused attachments.
|
|
// If VK_FORMAT_D24_UNORM_S8_UINT is not supported, this must be kD24FS8
|
|
// even for kD24S8.
|
|
xenos::DepthRenderTargetFormat depth_format : xenos::kDepthRenderTargetFormatBits; // 8
|
|
// Linear or sRGB included if host sRGB is used.
|
|
xenos::ColorRenderTargetFormat color_0_view_format
|
|
: xenos::kColorRenderTargetFormatBits; // 12
|
|
xenos::ColorRenderTargetFormat color_1_view_format
|
|
: xenos::kColorRenderTargetFormatBits; // 16
|
|
xenos::ColorRenderTargetFormat color_2_view_format
|
|
: xenos::kColorRenderTargetFormatBits; // 20
|
|
xenos::ColorRenderTargetFormat color_3_view_format
|
|
: xenos::kColorRenderTargetFormatBits; // 24
|
|
uint32_t color_rts_use_transfer_formats : 1; // 25
|
|
};
|
|
uint32_t key = 0;
|
|
struct Hasher {
|
|
size_t operator()(const RenderPassKey& key) const { return std::hash<uint32_t>{}(key.key); }
|
|
};
|
|
bool operator==(const RenderPassKey& other_key) const { return key == other_key.key; }
|
|
bool operator!=(const RenderPassKey& other_key) const { return !(*this == other_key); }
|
|
bool operator<(const RenderPassKey& other_key) const { return key < other_key.key; }
|
|
};
|
|
static_assert_size(RenderPassKey, sizeof(uint32_t));
|
|
|
|
struct Framebuffer {
|
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
|
VkExtent2D host_extent{};
|
|
Framebuffer() = default;
|
|
Framebuffer(VkFramebuffer framebuffer, const VkExtent2D& host_extent)
|
|
: framebuffer(framebuffer), host_extent(host_extent) {}
|
|
};
|
|
|
|
VulkanRenderTargetCache(const RegisterFile& register_file, const memory::Memory& memory,
|
|
TraceWriter& trace_writer, uint32_t draw_resolution_scale_x,
|
|
uint32_t draw_resolution_scale_y,
|
|
VulkanCommandProcessor& command_processor);
|
|
~VulkanRenderTargetCache();
|
|
|
|
// Transient descriptor set layouts must be initialized in the command
|
|
// processor.
|
|
bool Initialize(uint32_t shared_memory_binding_count);
|
|
void Shutdown(bool from_destructor = false);
|
|
void ClearCache() override;
|
|
|
|
void CompletedSubmissionUpdated();
|
|
void EndSubmission();
|
|
|
|
Path GetPath() const override { return path_; }
|
|
|
|
VkBuffer edram_buffer() const { return edram_buffer_; }
|
|
|
|
// Performs the resolve to a shared memory area according to the current
|
|
// register values, and also clears the render targets if needed. Must be in a
|
|
// frame for calling.
|
|
bool Resolve(const memory::Memory& memory, VulkanSharedMemory& shared_memory,
|
|
VulkanTextureCache& texture_cache, uint32_t& written_address_out,
|
|
uint32_t& written_length_out,
|
|
bool debug_log_team_profile_background_trace = false);
|
|
void DebugLogLastUpdateRenderTargetsForTeamProfileBackgroundDraw(uint64_t frame) const;
|
|
|
|
// Returns true if any downloads were submitted to the command processor.
|
|
bool InitializeTraceSubmitDownloads();
|
|
void InitializeTraceCompleteDownloads();
|
|
void RestoreEdramSnapshot(const void* snapshot);
|
|
|
|
bool Update(bool is_rasterization_done, reg::RB_DEPTHCONTROL normalized_depth_control,
|
|
uint32_t normalized_color_mask, const Shader& vertex_shader) override;
|
|
// Binding information for the last successful update.
|
|
RenderPassKey last_update_render_pass_key() const { return last_update_render_pass_key_; }
|
|
VkRenderPass last_update_render_pass() const { return last_update_render_pass_; }
|
|
const Framebuffer* last_update_framebuffer() const { return last_update_framebuffer_; }
|
|
void GetLastUpdateRenderingAttachments(VkRenderingAttachmentInfo* color_attachments,
|
|
uint32_t* color_attachment_count_out,
|
|
VkRenderingAttachmentInfo* depth_attachment,
|
|
VkRenderingAttachmentInfo* stencil_attachment) const;
|
|
// To be called by the command processor right after entering the render pass
|
|
// for guest draws - replays the deferred resolve clears pending on the bound
|
|
// render targets with vkCmdClearAttachments inside that render pass.
|
|
// render_area_extent is the render area of the entered render pass which the
|
|
// clear rectangles must be contained in (entries that don't fit are kept
|
|
// deferred).
|
|
void FlushDeferredResolveClearsIntoEnteredRenderPass(const VkExtent2D& render_area_extent);
|
|
|
|
// Using R16G16[B16A16]_SNORM, which are -1...1, not the needed -32...32.
|
|
// Persistent data doesn't depend on this, so can be overriden by per-game
|
|
// configuration.
|
|
bool IsFixedRG16TruncatedToMinus1To1() const {
|
|
return GetPath() == Path::kHostRenderTargets && !color_rg16_draw_format_fallback_to_float_ &&
|
|
!REXCVAR_GET(snorm16_render_target_full_range);
|
|
}
|
|
bool IsFixedRGBA16TruncatedToMinus1To1() const {
|
|
return GetPath() == Path::kHostRenderTargets && !color_rgba16_draw_format_fallback_to_float_ &&
|
|
!REXCVAR_GET(snorm16_render_target_full_range);
|
|
}
|
|
bool gamma_render_target_as_unorm16() const { return gamma_render_target_as_unorm16_; }
|
|
|
|
bool depth_unorm24_vulkan_format_supported() const {
|
|
return depth_unorm24_vulkan_format_supported_;
|
|
}
|
|
bool depth_float24_round() const { return depth_float24_round_; }
|
|
bool depth_float24_convert_in_pixel_shader() const {
|
|
return depth_float24_convert_in_pixel_shader_;
|
|
}
|
|
|
|
bool msaa_2x_attachments_supported() const { return msaa_2x_attachments_supported_; }
|
|
bool msaa_2x_no_attachments_supported() const { return msaa_2x_no_attachments_supported_; }
|
|
bool IsMsaa2xSupported(bool subpass_has_attachments) const {
|
|
return subpass_has_attachments ? msaa_2x_attachments_supported_
|
|
: msaa_2x_no_attachments_supported_;
|
|
}
|
|
|
|
// Returns the render pass object, or VK_NULL_HANDLE if failed to create.
|
|
// A render pass managed by the render target cache may be ended and resumed
|
|
// at any time (to allow for things like copying and texture loading).
|
|
VkRenderPass GetHostRenderTargetsRenderPass(RenderPassKey key);
|
|
VkRenderPass GetFragmentShaderInterlockRenderPass() const {
|
|
assert_true(GetPath() == Path::kPixelShaderInterlock);
|
|
return fsi_render_pass_;
|
|
}
|
|
|
|
VkFormat GetDepthVulkanFormat(xenos::DepthRenderTargetFormat format) const;
|
|
VkFormat GetColorVulkanFormat(xenos::ColorRenderTargetFormat format) const;
|
|
VkFormat GetColorOwnershipTransferVulkanFormat(
|
|
xenos::ColorRenderTargetFormat format,
|
|
xenos::MsaaSamples msaa_samples = xenos::MsaaSamples::k1X,
|
|
bool* is_integer_out = nullptr) const;
|
|
|
|
protected:
|
|
uint32_t GetMaxRenderTargetWidth() const override;
|
|
uint32_t GetMaxRenderTargetHeight() const override;
|
|
|
|
RenderTarget* CreateRenderTarget(RenderTargetKey key) override;
|
|
|
|
bool IsHostDepthEncodingDifferent(xenos::DepthRenderTargetFormat format) const override;
|
|
|
|
bool IsGammaFormatHostStorageSeparate() const override;
|
|
|
|
void RequestPixelShaderInterlockBarrier() override;
|
|
|
|
private:
|
|
enum class EdramBufferUsage {
|
|
// There's no need for combined fragment and compute usages.
|
|
// With host render targets, the usual usage sequence is as follows:
|
|
// - Optionally compute writes - host depth copy storing for EDRAM range
|
|
// ownership transfers.
|
|
// - Optionally fragment reads - host depth copy storing for EDRAM range
|
|
// ownership transfers.
|
|
// - Compute writes - copying from host render targets during resolving.
|
|
// - Compute reads - writing to the shared memory during resolving.
|
|
// With the render backend implementation based on fragment shader
|
|
// interlocks, it's:
|
|
// - Fragment reads and writes - depth / stencil and color operations.
|
|
// - Compute reads - writing to the shared memory during resolving.
|
|
// So, fragment reads and compute reads normally don't follow each other,
|
|
// and there's no need to amortize the cost of a read > read barrier in an
|
|
// exceptional situation by using a wider barrier in the normal scenario.
|
|
|
|
// Host depth copy storing.
|
|
kFragmentRead,
|
|
// Fragment shader interlock depth / stencil and color operations.
|
|
kFragmentReadWrite,
|
|
// Resolve - copying to the shared memory.
|
|
kComputeRead,
|
|
// Resolve - copying from host render targets.
|
|
kComputeWrite,
|
|
// Trace recording.
|
|
kTransferRead,
|
|
// Trace playback.
|
|
kTransferWrite,
|
|
};
|
|
|
|
enum class EdramBufferModificationStatus {
|
|
// The values are ordered by how strong the barrier conditions are.
|
|
// No uncommitted shader writes.
|
|
kUnmodified,
|
|
// Need to commit before the next fragment shader interlock usage with
|
|
// overlap.
|
|
kViaFragmentShaderInterlock,
|
|
// Need to commit before any next fragment shader interlock usage.
|
|
kViaUnordered,
|
|
};
|
|
|
|
enum ResolveCopyDescriptorSet : uint32_t {
|
|
// Never changes.
|
|
kResolveCopyDescriptorSetEdram,
|
|
// Shared memory or a region in it.
|
|
kResolveCopyDescriptorSetDest,
|
|
|
|
kResolveCopyDescriptorSetCount,
|
|
};
|
|
|
|
struct ResolveCopyShaderCode {
|
|
const uint32_t* unscaled;
|
|
size_t unscaled_size_bytes;
|
|
const uint32_t* scaled;
|
|
size_t scaled_size_bytes;
|
|
};
|
|
|
|
static void GetEdramBufferUsageMasks(EdramBufferUsage usage, VkPipelineStageFlags& stage_mask_out,
|
|
VkAccessFlags& access_mask_out);
|
|
bool IsColor16FormatFloatLike(xenos::ColorRenderTargetFormat format) const;
|
|
void UseEdramBuffer(EdramBufferUsage new_usage);
|
|
void MarkEdramBufferModified(EdramBufferModificationStatus modification_status =
|
|
EdramBufferModificationStatus::kViaUnordered);
|
|
void CommitEdramBufferShaderWrites(
|
|
EdramBufferModificationStatus commit_status =
|
|
EdramBufferModificationStatus::kViaFragmentShaderInterlock);
|
|
|
|
VulkanCommandProcessor& command_processor_;
|
|
TraceWriter& trace_writer_;
|
|
|
|
Path path_ = Path::kHostRenderTargets;
|
|
|
|
// Accessible in fragment and compute shaders.
|
|
VkDescriptorSetLayout descriptor_set_layout_storage_buffer_ = VK_NULL_HANDLE;
|
|
VkDescriptorSetLayout descriptor_set_layout_sampled_image_ = VK_NULL_HANDLE;
|
|
VkDescriptorSetLayout descriptor_set_layout_sampled_image_x2_ = VK_NULL_HANDLE;
|
|
|
|
std::unique_ptr<ui::vulkan::SingleLayoutDescriptorSetPool> descriptor_set_pool_sampled_image_;
|
|
std::unique_ptr<ui::vulkan::SingleLayoutDescriptorSetPool> descriptor_set_pool_sampled_image_x2_;
|
|
|
|
VkDeviceMemory edram_buffer_memory_ = VK_NULL_HANDLE;
|
|
VkBuffer edram_buffer_ = VK_NULL_HANDLE;
|
|
EdramBufferUsage edram_buffer_usage_;
|
|
EdramBufferModificationStatus edram_buffer_modification_status_ =
|
|
EdramBufferModificationStatus::kUnmodified;
|
|
VkDescriptorPool edram_storage_buffer_descriptor_pool_ = VK_NULL_HANDLE;
|
|
VkDescriptorSet edram_storage_buffer_descriptor_set_;
|
|
|
|
VkPipelineLayout resolve_copy_pipeline_layout_ = VK_NULL_HANDLE;
|
|
static const ResolveCopyShaderCode
|
|
kResolveCopyShaders[size_t(draw_util::ResolveCopyShaderIndex::kCount)];
|
|
std::array<VkPipeline, size_t(draw_util::ResolveCopyShaderIndex::kCount)>
|
|
resolve_copy_pipelines_{};
|
|
|
|
// On the fragment shader interlock path, the render pass key is used purely
|
|
// for passing parameters to pipeline setup - there's always only one render
|
|
// pass.
|
|
RenderPassKey last_update_render_pass_key_;
|
|
VkRenderPass last_update_render_pass_ = VK_NULL_HANDLE;
|
|
// The pitch is not used on the fragment shader interlock path.
|
|
uint32_t last_update_framebuffer_pitch_tiles_at_32bpp_ = 0;
|
|
// The attachments are not used on the fragment shader interlock path.
|
|
const RenderTarget* const*
|
|
last_update_framebuffer_attachments_[1 + xenos::kMaxColorRenderTargets] = {};
|
|
const Framebuffer* last_update_framebuffer_ = VK_NULL_HANDLE;
|
|
|
|
// For host render targets.
|
|
|
|
// Can only be destroyed when framebuffers referencing it are destroyed!
|
|
class VulkanRenderTarget final : public RenderTarget {
|
|
public:
|
|
static constexpr VkPipelineStageFlags kColorDrawStageMask =
|
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
static constexpr VkAccessFlags kColorDrawAccessMask =
|
|
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
static constexpr VkImageLayout kColorDrawLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
static constexpr VkPipelineStageFlags kDepthDrawStageMask =
|
|
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
static constexpr VkAccessFlags kDepthDrawAccessMask =
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
static constexpr VkImageLayout kDepthDrawLayout =
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
// Takes ownership of the Vulkan objects passed to the constructor.
|
|
VulkanRenderTarget(RenderTargetKey key, VulkanRenderTargetCache& render_target_cache,
|
|
VkImage image, VkDeviceMemory memory, VkImageView view_depth_color,
|
|
VkImageView view_depth_stencil, VkImageView view_stencil,
|
|
VkImageView view_srgb, VkImageView view_color_transfer_separate,
|
|
size_t descriptor_set_index_transfer_source)
|
|
: RenderTarget(key),
|
|
render_target_cache_(render_target_cache),
|
|
image_(image),
|
|
memory_(memory),
|
|
view_depth_color_(view_depth_color),
|
|
view_depth_stencil_(view_depth_stencil),
|
|
view_stencil_(view_stencil),
|
|
view_srgb_(view_srgb),
|
|
view_color_transfer_separate_(view_color_transfer_separate),
|
|
descriptor_set_index_transfer_source_(descriptor_set_index_transfer_source) {}
|
|
~VulkanRenderTarget();
|
|
|
|
VkImage image() const { return image_; }
|
|
|
|
VkImageView view_depth_color() const { return view_depth_color_; }
|
|
VkImageView view_depth_stencil() const { return view_depth_stencil_; }
|
|
VkImageView view_color_transfer_separate() const { return view_color_transfer_separate_; }
|
|
VkImageView view_color_transfer() const {
|
|
return view_color_transfer_separate_ != VK_NULL_HANDLE ? view_color_transfer_separate_
|
|
: view_depth_color_;
|
|
}
|
|
VkDescriptorSet GetDescriptorSetTransferSource() const {
|
|
ui::vulkan::SingleLayoutDescriptorSetPool& descriptor_set_pool =
|
|
key().is_depth ? *render_target_cache_.descriptor_set_pool_sampled_image_x2_
|
|
: *render_target_cache_.descriptor_set_pool_sampled_image_;
|
|
return descriptor_set_pool.Get(descriptor_set_index_transfer_source_);
|
|
}
|
|
|
|
static void GetDrawUsage(bool is_depth, VkPipelineStageFlags* stage_mask_out,
|
|
VkAccessFlags* access_mask_out, VkImageLayout* layout_out) {
|
|
if (stage_mask_out) {
|
|
*stage_mask_out = is_depth ? kDepthDrawStageMask : kColorDrawStageMask;
|
|
}
|
|
if (access_mask_out) {
|
|
*access_mask_out = is_depth ? kDepthDrawAccessMask : kColorDrawAccessMask;
|
|
}
|
|
if (layout_out) {
|
|
*layout_out = is_depth ? kDepthDrawLayout : kColorDrawLayout;
|
|
}
|
|
}
|
|
void GetDrawUsage(VkPipelineStageFlags* stage_mask_out, VkAccessFlags* access_mask_out,
|
|
VkImageLayout* layout_out) const {
|
|
GetDrawUsage(key().is_depth, stage_mask_out, access_mask_out, layout_out);
|
|
}
|
|
VkPipelineStageFlags current_stage_mask() const { return current_stage_mask_; }
|
|
VkAccessFlags current_access_mask() const { return current_access_mask_; }
|
|
VkImageLayout current_layout() const { return current_layout_; }
|
|
void SetUsage(VkPipelineStageFlags stage_mask, VkAccessFlags access_mask,
|
|
VkImageLayout layout) {
|
|
current_stage_mask_ = stage_mask;
|
|
current_access_mask_ = access_mask;
|
|
current_layout_ = layout;
|
|
}
|
|
|
|
uint32_t temporary_sort_index() const { return temporary_sort_index_; }
|
|
void SetTemporarySortIndex(uint32_t index) { temporary_sort_index_ = index; }
|
|
|
|
// Resolve clears deferred until the next render pass binding this render
|
|
// target, or a standalone flush if its contents are needed earlier.
|
|
struct DeferredClear {
|
|
Transfer::Rectangle rectangle;
|
|
uint64_t clear_value;
|
|
// Absolute EDRAM tile range (may wrap across the addressing period)
|
|
// conservatively covering the rectangle, for read intersection checks.
|
|
uint32_t tiles_start;
|
|
uint32_t tiles_length;
|
|
};
|
|
std::vector<DeferredClear>& deferred_clears() { return deferred_clears_; }
|
|
|
|
private:
|
|
VulkanRenderTargetCache& render_target_cache_;
|
|
|
|
VkImage image_;
|
|
VkDeviceMemory memory_;
|
|
|
|
// TODO(Triang3l): Per-format drawing views for mutable formats with EDRAM
|
|
// aliasing without transfers.
|
|
VkImageView view_depth_color_;
|
|
// Optional views.
|
|
VkImageView view_depth_stencil_;
|
|
VkImageView view_stencil_;
|
|
VkImageView view_srgb_;
|
|
VkImageView view_color_transfer_separate_;
|
|
|
|
// 2 sampled images for depth / stencil, 1 sampled image for color.
|
|
size_t descriptor_set_index_transfer_source_;
|
|
|
|
VkPipelineStageFlags current_stage_mask_ = 0;
|
|
VkAccessFlags current_access_mask_ = 0;
|
|
VkImageLayout current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
// Temporary storage for indices in operations like transfers and dumps.
|
|
uint32_t temporary_sort_index_ = 0;
|
|
|
|
std::vector<DeferredClear> deferred_clears_;
|
|
};
|
|
|
|
struct FramebufferKey {
|
|
RenderPassKey render_pass_key;
|
|
|
|
// Same as RenderTargetKey::pitch_tiles_at_32bpp.
|
|
uint32_t pitch_tiles_at_32bpp : 8; // 8
|
|
// [0, 2047].
|
|
uint32_t depth_base_tiles : xenos::kEdramBaseTilesBits; // 19
|
|
uint32_t color_0_base_tiles : xenos::kEdramBaseTilesBits; // 30
|
|
|
|
uint32_t color_1_base_tiles : xenos::kEdramBaseTilesBits; // 43
|
|
uint32_t color_2_base_tiles : xenos::kEdramBaseTilesBits; // 54
|
|
|
|
uint32_t color_3_base_tiles : xenos::kEdramBaseTilesBits; // 75
|
|
|
|
// Including all the padding, for a stable hash.
|
|
FramebufferKey() { Reset(); }
|
|
FramebufferKey(const FramebufferKey& key) { std::memcpy(this, &key, sizeof(*this)); }
|
|
FramebufferKey& operator=(const FramebufferKey& key) {
|
|
std::memcpy(this, &key, sizeof(*this));
|
|
return *this;
|
|
}
|
|
bool operator==(const FramebufferKey& key) const {
|
|
return std::memcmp(this, &key, sizeof(*this)) == 0;
|
|
}
|
|
using Hasher = rex::XXHasher<FramebufferKey>;
|
|
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
|
};
|
|
|
|
enum TransferUsedDescriptorSet : uint32_t {
|
|
// Ordered from the least to the most frequently changed.
|
|
kTransferUsedDescriptorSetHostDepthBuffer,
|
|
kTransferUsedDescriptorSetHostDepthStencilTextures,
|
|
kTransferUsedDescriptorSetDepthStencilTextures,
|
|
// Mutually exclusive with kTransferUsedDescriptorSetDepthStencilTextures.
|
|
kTransferUsedDescriptorSetColorTexture,
|
|
|
|
kTransferUsedDescriptorSetCount,
|
|
|
|
kTransferUsedDescriptorSetHostDepthBufferBit = uint32_t(1)
|
|
<< kTransferUsedDescriptorSetHostDepthBuffer,
|
|
kTransferUsedDescriptorSetHostDepthStencilTexturesBit =
|
|
uint32_t(1) << kTransferUsedDescriptorSetHostDepthStencilTextures,
|
|
kTransferUsedDescriptorSetDepthStencilTexturesBit =
|
|
uint32_t(1) << kTransferUsedDescriptorSetDepthStencilTextures,
|
|
kTransferUsedDescriptorSetColorTextureBit = uint32_t(1)
|
|
<< kTransferUsedDescriptorSetColorTexture,
|
|
};
|
|
|
|
// 32-bit push constants (for simplicity of size calculation and to avoid
|
|
// std140 packing issues).
|
|
enum TransferUsedPushConstantDword : uint32_t {
|
|
kTransferUsedPushConstantDwordHostDepthAddress,
|
|
kTransferUsedPushConstantDwordAddress,
|
|
// Changed 8 times per transfer.
|
|
kTransferUsedPushConstantDwordStencilMask,
|
|
|
|
kTransferUsedPushConstantDwordCount,
|
|
|
|
kTransferUsedPushConstantDwordHostDepthAddressBit =
|
|
uint32_t(1) << kTransferUsedPushConstantDwordHostDepthAddress,
|
|
kTransferUsedPushConstantDwordAddressBit = uint32_t(1) << kTransferUsedPushConstantDwordAddress,
|
|
kTransferUsedPushConstantDwordStencilMaskBit = uint32_t(1)
|
|
<< kTransferUsedPushConstantDwordStencilMask,
|
|
};
|
|
|
|
enum class TransferPipelineLayoutIndex {
|
|
kColor,
|
|
kDepth,
|
|
kColorToStencilBit,
|
|
kDepthToStencilBit,
|
|
kColorAndHostDepthTexture,
|
|
kColorAndHostDepthBuffer,
|
|
kDepthAndHostDepthTexture,
|
|
kDepthAndHostDepthBuffer,
|
|
|
|
kCount,
|
|
};
|
|
|
|
struct TransferPipelineLayoutInfo {
|
|
uint32_t used_descriptor_sets;
|
|
uint32_t used_push_constant_dwords;
|
|
};
|
|
|
|
static const TransferPipelineLayoutInfo
|
|
kTransferPipelineLayoutInfos[size_t(TransferPipelineLayoutIndex::kCount)];
|
|
|
|
enum class TransferMode : uint32_t {
|
|
kColorToDepth,
|
|
kColorToColor,
|
|
|
|
kDepthToDepth,
|
|
kDepthToColor,
|
|
|
|
kColorToStencilBit,
|
|
kDepthToStencilBit,
|
|
|
|
// Two-source modes, using the host depth if it, when converted to the guest
|
|
// format, matches what's in the owner source (not modified, keep host
|
|
// precision), or the guest data otherwise (significantly modified, possibly
|
|
// cleared). Stencil for FragStencilRef is always taken from the guest
|
|
// source.
|
|
|
|
kColorAndHostDepthToDepth,
|
|
// When using different source and destination depth formats.
|
|
kDepthAndHostDepthToDepth,
|
|
|
|
// If host depth is fetched, but it's the same image as the destination,
|
|
// it's copied to the EDRAM buffer (but since it's just a scratch buffer,
|
|
// with tiles laid out linearly with the same pitch as in the original
|
|
// render target; also no swapping of 40-sample columns as opposed to the
|
|
// host render target - this is done only for the color source) and fetched
|
|
// from there instead of the host depth texture.
|
|
kColorAndHostDepthCopyToDepth,
|
|
kDepthAndHostDepthCopyToDepth,
|
|
|
|
kCount,
|
|
};
|
|
|
|
enum class TransferOutput {
|
|
kColor,
|
|
kDepth,
|
|
kStencilBit,
|
|
};
|
|
|
|
struct TransferModeInfo {
|
|
TransferOutput output;
|
|
TransferPipelineLayoutIndex pipeline_layout;
|
|
};
|
|
|
|
static const TransferModeInfo kTransferModes[size_t(TransferMode::kCount)];
|
|
|
|
union TransferShaderKey {
|
|
uint32_t key;
|
|
struct {
|
|
xenos::MsaaSamples dest_msaa_samples : xenos::kMsaaSamplesBits;
|
|
uint32_t dest_color_rt_index : xenos::kColorRenderTargetIndexBits;
|
|
uint32_t dest_resource_format : xenos::kRenderTargetFormatBits;
|
|
xenos::MsaaSamples source_msaa_samples : xenos::kMsaaSamplesBits;
|
|
// Always 1x when the host depth is a copy from a buffer rather than an
|
|
// image, not to create the same pipeline for different MSAA sample counts
|
|
// as it doesn't matter in this case.
|
|
xenos::MsaaSamples host_depth_source_msaa_samples : xenos::kMsaaSamplesBits;
|
|
uint32_t source_resource_format : xenos::kRenderTargetFormatBits;
|
|
|
|
// Last bits because this affects the pipeline layout - after sorting,
|
|
// only change it as fewer times as possible. Depth buffers have an
|
|
// additional stencil texture.
|
|
static_assert(size_t(TransferMode::kCount) <= (size_t(1) << 4));
|
|
TransferMode mode : 4;
|
|
};
|
|
|
|
TransferShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
|
|
|
|
struct Hasher {
|
|
size_t operator()(const TransferShaderKey& key) const {
|
|
return std::hash<uint32_t>{}(key.key);
|
|
}
|
|
};
|
|
bool operator==(const TransferShaderKey& other_key) const { return key == other_key.key; }
|
|
bool operator!=(const TransferShaderKey& other_key) const { return !(*this == other_key); }
|
|
bool operator<(const TransferShaderKey& other_key) const { return key < other_key.key; }
|
|
};
|
|
|
|
struct TransferPipelineKey {
|
|
RenderPassKey render_pass_key;
|
|
TransferShaderKey shader_key;
|
|
|
|
TransferPipelineKey(RenderPassKey render_pass_key, TransferShaderKey shader_key)
|
|
: render_pass_key(render_pass_key), shader_key(shader_key) {}
|
|
|
|
struct Hasher {
|
|
size_t operator()(const TransferPipelineKey& key) const {
|
|
XXH3_state_t hash_state;
|
|
XXH3_64bits_reset(&hash_state);
|
|
XXH3_64bits_update(&hash_state, &key.render_pass_key, sizeof(key.render_pass_key));
|
|
XXH3_64bits_update(&hash_state, &key.shader_key, sizeof(key.shader_key));
|
|
return static_cast<size_t>(XXH3_64bits_digest(&hash_state));
|
|
}
|
|
};
|
|
bool operator==(const TransferPipelineKey& other_key) const {
|
|
return render_pass_key == other_key.render_pass_key && shader_key == other_key.shader_key;
|
|
}
|
|
bool operator!=(const TransferPipelineKey& other_key) const { return !(*this == other_key); }
|
|
bool operator<(const TransferPipelineKey& other_key) const {
|
|
if (render_pass_key != other_key.render_pass_key) {
|
|
return render_pass_key < other_key.render_pass_key;
|
|
}
|
|
return shader_key < other_key.shader_key;
|
|
}
|
|
};
|
|
|
|
union TransferAddressConstant {
|
|
uint32_t constant;
|
|
struct {
|
|
// All in tiles.
|
|
uint32_t dest_pitch : xenos::kEdramPitchTilesBits;
|
|
uint32_t source_pitch : xenos::kEdramPitchTilesBits;
|
|
// Destination base in tiles minus source base in tiles (not vice versa
|
|
// because this is a transform of the coordinate system, not addresses
|
|
// themselves).
|
|
// + 1 bit because this is a signed difference between two EDRAM bases.
|
|
// 0 for host_depth_source_is_copy (ignored in this case anyway as
|
|
// destination == source anyway).
|
|
int32_t source_to_dest : xenos::kEdramBaseTilesBits + 1;
|
|
};
|
|
TransferAddressConstant() : constant(0) { static_assert_size(*this, sizeof(constant)); }
|
|
bool operator==(const TransferAddressConstant& other_constant) const {
|
|
return constant == other_constant.constant;
|
|
}
|
|
bool operator!=(const TransferAddressConstant& other_constant) const {
|
|
return !(*this == other_constant);
|
|
}
|
|
};
|
|
|
|
struct TransferInvocation {
|
|
Transfer transfer;
|
|
TransferShaderKey shader_key;
|
|
TransferInvocation(const Transfer& transfer, const TransferShaderKey& shader_key)
|
|
: transfer(transfer), shader_key(shader_key) {}
|
|
bool operator<(const TransferInvocation& other_invocation) const {
|
|
// TODO(Triang3l): See if it may be better to sort by the source in the
|
|
// first place, especially when reading the same data multiple times (like
|
|
// to write the stencil bits after depth) for better read locality.
|
|
// Sort by the shader key primarily to reduce pipeline state (context)
|
|
// switches.
|
|
if (shader_key != other_invocation.shader_key) {
|
|
return shader_key < other_invocation.shader_key;
|
|
}
|
|
// Host depth render targets are changed rarely if they exist, won't save
|
|
// many binding changes, ignore them for simplicity (their existence is
|
|
// caught by the shader key change).
|
|
assert_not_null(transfer.source);
|
|
assert_not_null(other_invocation.transfer.source);
|
|
uint32_t source_index =
|
|
static_cast<const VulkanRenderTarget*>(transfer.source)->temporary_sort_index();
|
|
uint32_t other_source_index =
|
|
static_cast<const VulkanRenderTarget*>(other_invocation.transfer.source)
|
|
->temporary_sort_index();
|
|
if (source_index != other_source_index) {
|
|
return source_index < other_source_index;
|
|
}
|
|
return transfer.start_tiles < other_invocation.transfer.start_tiles;
|
|
}
|
|
bool CanBeMergedIntoOneDraw(const TransferInvocation& other_invocation) const {
|
|
return shader_key == other_invocation.shader_key &&
|
|
transfer.AreSourcesSame(other_invocation.transfer);
|
|
}
|
|
};
|
|
|
|
union DumpPipelineKey {
|
|
uint32_t key;
|
|
struct {
|
|
xenos::MsaaSamples msaa_samples : 2;
|
|
uint32_t resource_format : 4;
|
|
// Last bit because this affects the pipeline - after sorting, only change
|
|
// it at most once. Depth buffers have an additional stencil SRV.
|
|
uint32_t is_depth : 1;
|
|
};
|
|
|
|
DumpPipelineKey() : key(0) { static_assert_size(*this, sizeof(key)); }
|
|
|
|
struct Hasher {
|
|
size_t operator()(const DumpPipelineKey& key) const { return std::hash<uint32_t>{}(key.key); }
|
|
};
|
|
bool operator==(const DumpPipelineKey& other_key) const { return key == other_key.key; }
|
|
bool operator!=(const DumpPipelineKey& other_key) const { return !(*this == other_key); }
|
|
bool operator<(const DumpPipelineKey& other_key) const { return key < other_key.key; }
|
|
|
|
xenos::ColorRenderTargetFormat GetColorFormat() const {
|
|
assert_false(is_depth);
|
|
return xenos::ColorRenderTargetFormat(resource_format);
|
|
}
|
|
xenos::DepthRenderTargetFormat GetDepthFormat() const {
|
|
assert_true(is_depth);
|
|
return xenos::DepthRenderTargetFormat(resource_format);
|
|
}
|
|
};
|
|
|
|
// There's no strict dependency on the group size in dumping, for simplicity
|
|
// calculations especially with resolution scaling, dividing manually (as the
|
|
// group size is not unlimited). The only restriction is that an integer
|
|
// multiple of it must be 80x16 samples (and no larger than that) for 32bpp,
|
|
// or 40x16 samples for 64bpp (because only a half of the pair of tiles may
|
|
// need to be dumped). Using 8x16 since that's 128 - the minimum required
|
|
// group size on Vulkan, and the maximum number of lanes in a subgroup on
|
|
// Vulkan.
|
|
static constexpr uint32_t kDumpSamplesPerGroupX = 40;
|
|
static constexpr uint32_t kDumpSamplesPerGroupY = 16;
|
|
|
|
union DumpPitches {
|
|
uint32_t pitches;
|
|
struct {
|
|
// Both in tiles.
|
|
uint32_t dest_pitch : xenos::kEdramPitchTilesBits;
|
|
uint32_t source_pitch : xenos::kEdramPitchTilesBits;
|
|
};
|
|
DumpPitches() : pitches(0) { static_assert_size(*this, sizeof(pitches)); }
|
|
bool operator==(const DumpPitches& other_pitches) const {
|
|
return pitches == other_pitches.pitches;
|
|
}
|
|
bool operator!=(const DumpPitches& other_pitches) const { return !(*this == other_pitches); }
|
|
};
|
|
|
|
union DumpOffsets {
|
|
uint32_t offsets;
|
|
struct {
|
|
// May be beyond the EDRAM tile count in case of EDRAM addressing
|
|
// wrapping, thus + 1 bit.
|
|
uint32_t dispatch_first_tile : xenos::kEdramBaseTilesBits + 1;
|
|
uint32_t source_base_tiles : xenos::kEdramBaseTilesBits;
|
|
};
|
|
DumpOffsets() : offsets(0) { static_assert_size(*this, sizeof(offsets)); }
|
|
bool operator==(const DumpOffsets& other_offsets) const {
|
|
return offsets == other_offsets.offsets;
|
|
}
|
|
bool operator!=(const DumpOffsets& other_offsets) const { return !(*this == other_offsets); }
|
|
};
|
|
|
|
enum DumpDescriptorSet : uint32_t {
|
|
// Never changes. Same in both color and depth pipeline layouts, keep the
|
|
// first for pipeline layout compatibility, to only have to set it once.
|
|
kDumpDescriptorSetEdram,
|
|
// One resolve may need multiple sources. Different descriptor set layouts
|
|
// for color and depth.
|
|
kDumpDescriptorSetSource,
|
|
|
|
kDumpDescriptorSetCount,
|
|
};
|
|
|
|
enum DumpPushConstant : uint32_t {
|
|
// May be different for different sources.
|
|
kDumpPushConstantPitches,
|
|
// May be changed multiple times for the same source.
|
|
kDumpPushConstantOffsets,
|
|
|
|
kDumpPushConstantCount,
|
|
};
|
|
|
|
struct DumpInvocation {
|
|
ResolveCopyDumpRectangle rectangle;
|
|
DumpPipelineKey pipeline_key;
|
|
DumpInvocation(const ResolveCopyDumpRectangle& rectangle, const DumpPipelineKey& pipeline_key)
|
|
: rectangle(rectangle), pipeline_key(pipeline_key) {}
|
|
bool operator<(const DumpInvocation& other_invocation) const {
|
|
// Sort by the pipeline key primarily to reduce pipeline state (context)
|
|
// switches.
|
|
if (pipeline_key != other_invocation.pipeline_key) {
|
|
return pipeline_key < other_invocation.pipeline_key;
|
|
}
|
|
assert_not_null(rectangle.render_target);
|
|
uint32_t render_target_index =
|
|
static_cast<const VulkanRenderTarget*>(rectangle.render_target)->temporary_sort_index();
|
|
const ResolveCopyDumpRectangle& other_rectangle = other_invocation.rectangle;
|
|
uint32_t other_render_target_index =
|
|
static_cast<const VulkanRenderTarget*>(other_rectangle.render_target)
|
|
->temporary_sort_index();
|
|
if (render_target_index != other_render_target_index) {
|
|
return render_target_index < other_render_target_index;
|
|
}
|
|
if (rectangle.row_first != other_rectangle.row_first) {
|
|
return rectangle.row_first < other_rectangle.row_first;
|
|
}
|
|
return rectangle.row_first_start < other_rectangle.row_first_start;
|
|
}
|
|
};
|
|
|
|
struct DirectResolvePushConstants {
|
|
draw_util::ResolveCopyShaderConstants resolve;
|
|
uint32_t source_base_tiles;
|
|
uint32_t source_pitch_tiles;
|
|
uint32_t dispatch_first_tile;
|
|
// Resolve rectangle height in pixels (the width is in
|
|
// resolve.dest_relative.coordinate_info, but the height is normally only
|
|
// implied by the dispatch size).
|
|
uint32_t height_pixels;
|
|
// How to pack the sampled host render target texel into the raw 32bpp
|
|
// guest dword: 0 = unorm8x4 (k_8_8_8_8 family), 1 = unorm 2_10_10_10.
|
|
uint32_t source_format_case;
|
|
};
|
|
|
|
struct DirectResolvePipelineKey {
|
|
DumpPipelineKey dump_pipeline_key;
|
|
draw_util::ResolveCopyShaderIndex copy_shader;
|
|
bool draw_resolution_scaled;
|
|
uint64_t packed() const {
|
|
return uint64_t(dump_pipeline_key.key) | (uint64_t(size_t(copy_shader)) << 32) |
|
|
(uint64_t(draw_resolution_scaled ? 1 : 0) << 40);
|
|
}
|
|
struct Hasher {
|
|
size_t operator()(const DirectResolvePipelineKey& key) const {
|
|
return std::hash<uint64_t>{}(key.packed());
|
|
}
|
|
};
|
|
bool operator==(const DirectResolvePipelineKey& other_key) const {
|
|
return packed() == other_key.packed();
|
|
}
|
|
};
|
|
|
|
// Returns the framebuffer object, or VK_NULL_HANDLE if failed to create.
|
|
const Framebuffer* GetHostRenderTargetsFramebuffer(
|
|
RenderPassKey render_pass_key, uint32_t pitch_tiles_at_32bpp,
|
|
const RenderTarget* const* depth_and_color_render_targets);
|
|
|
|
VkShaderModule GetTransferShader(TransferShaderKey key);
|
|
// With sample-rate shading, returns a pointer to one pipeline. Without
|
|
// sample-rate shading, returns a pointer to as many pipelines as there are
|
|
// samples. If there was a failure to create a pipeline, returns nullptr.
|
|
VkPipeline const* GetTransferPipelines(TransferPipelineKey key);
|
|
|
|
// Do ownership transfers for render targets - each render target / vector may
|
|
// be null / empty in case there's nothing to do for them.
|
|
// resolve_clear_rectangle is expected to be provided by
|
|
// PrepareHostRenderTargetsResolveClear which should do all the needed size
|
|
// bound checks.
|
|
void PerformTransfersAndResolveClears(
|
|
uint32_t render_target_count, RenderTarget* const* render_targets,
|
|
const std::vector<Transfer>* render_target_transfers,
|
|
const uint64_t* render_target_resolve_clear_values = nullptr,
|
|
const Transfer::Rectangle* resolve_clear_rectangle = nullptr);
|
|
|
|
VkPipeline GetDumpPipeline(DumpPipelineKey key);
|
|
VkPipeline GetDirectResolvePipeline(DirectResolvePipelineKey key);
|
|
// Compute pipeline reading a 32bpp color or unorm24 depth / stencil host
|
|
// render target (single-sampled, native 2x MSAA or 4x MSAA) and writing the
|
|
// raw resolve destination directly, skipping the EDRAM buffer round trip.
|
|
// VK_NULL_HANDLE if unsupported or creation failed.
|
|
VkPipeline GetDirectResolveFast32Pipeline(bool is_depth, xenos::MsaaSamples source_msaa_samples);
|
|
// When this returns true, the destination has been fully written - the
|
|
// caller must skip both the render target dump and the EDRAM buffer copy.
|
|
bool TryResolveCopyDirectly(const draw_util::ResolveInfo& resolve_info,
|
|
draw_util::ResolveCopyShaderIndex copy_shader,
|
|
bool draw_resolution_scaled, VulkanSharedMemory& shared_memory,
|
|
const draw_util::ResolveCopyShaderConstants& copy_shader_constants);
|
|
|
|
// Deferred resolve clears: instead of an own render pass with a rectangle
|
|
// attachment clear per resolve, clears are accumulated on the render target
|
|
// and replayed with vkCmdClearAttachments inside the next render pass that
|
|
// binds it for drawing (via FlushDeferredResolveClearsIntoEnteredRenderPass)
|
|
// or in a standalone ownership transfer render pass if something needs the
|
|
// render target's contents before then. This removes the per-resolve render
|
|
// pass and layout transition overhead, which is a large fixed cost on some
|
|
// hosts.
|
|
void DeferResolveClear(VulkanRenderTarget& render_target,
|
|
const Transfer::Rectangle& clear_rectangle, uint64_t clear_value);
|
|
// Emits the pending deferred clears of the render target in a standalone
|
|
// ownership transfer render pass.
|
|
void FlushDeferredResolveClears(VulkanRenderTarget& render_target);
|
|
// Flushes the pending deferred clears of any render target whose deferred
|
|
// tile ranges intersect the given absolute EDRAM tile range that's about to
|
|
// be read (such as by a resolve dump).
|
|
void FlushDeferredResolveClearsBeforeEdramRead(uint32_t base_tiles, uint32_t length_tiles);
|
|
// Converts the guest resolve clear value the same way for all the clear
|
|
// paths. through_transfer_view selects between the image's own format (used
|
|
// when clearing through the guest render pass attachment) and the ownership
|
|
// transfer view format (uint for 16- and 32-bit-per-component color).
|
|
VkClearDepthStencilValue GetResolveClearDepthStencilValue(RenderTargetKey key,
|
|
uint64_t clear_value) const;
|
|
VkClearColorValue GetResolveClearColorValue(RenderTargetKey key, uint64_t clear_value,
|
|
bool through_transfer_view) const;
|
|
|
|
// Writes contents of host render targets within rectangles from
|
|
// ResolveInfo::GetCopyEdramTileSpan to edram_buffer_.
|
|
bool DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used, uint32_t dump_rows,
|
|
uint32_t dump_pitch);
|
|
|
|
bool gamma_render_target_as_unorm16_ = false;
|
|
|
|
bool depth_unorm24_vulkan_format_supported_ = false;
|
|
bool depth_float24_round_ = false;
|
|
bool depth_float24_convert_in_pixel_shader_ = false;
|
|
|
|
bool msaa_2x_attachments_supported_ = false;
|
|
bool msaa_2x_no_attachments_supported_ = false;
|
|
bool color_16bit_transfer_uint_formats_supported_ = true;
|
|
bool color_32bit_transfer_uint_formats_supported_ = true;
|
|
bool color_rg16_draw_format_fallback_to_float_ = false;
|
|
bool color_rgba16_draw_format_fallback_to_float_ = false;
|
|
|
|
// VK_NULL_HANDLE if failed to create.
|
|
std::unordered_map<RenderPassKey, VkRenderPass, RenderPassKey::Hasher> render_passes_;
|
|
|
|
std::unordered_map<FramebufferKey, Framebuffer, FramebufferKey::Hasher> framebuffers_;
|
|
|
|
// Set 0 - EDRAM storage buffer, set 1 - source depth sampled image (and
|
|
// unused stencil from the transfer descriptor set), HostDepthStoreConstants
|
|
// passed via push constants.
|
|
VkPipelineLayout host_depth_store_pipeline_layout_ = VK_NULL_HANDLE;
|
|
VkPipeline host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X) + 1] = {};
|
|
|
|
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> transfer_vertex_buffer_pool_;
|
|
VkShaderModule transfer_passthrough_vertex_shader_ = VK_NULL_HANDLE;
|
|
VkPipelineLayout transfer_pipeline_layouts_[size_t(TransferPipelineLayoutIndex::kCount)] = {};
|
|
// VK_NULL_HANDLE if failed to create.
|
|
std::unordered_map<TransferShaderKey, VkShaderModule, TransferShaderKey::Hasher>
|
|
transfer_shaders_;
|
|
// With sample-rate shading, one pipeline per entry. Without sample-rate
|
|
// shading, one pipeline per sample per entry. VK_NULL_HANDLE if failed to
|
|
// create.
|
|
std::unordered_map<TransferPipelineKey, std::array<VkPipeline, 4>, TransferPipelineKey::Hasher>
|
|
transfer_pipelines_;
|
|
|
|
VkPipelineLayout dump_pipeline_layout_color_ = VK_NULL_HANDLE;
|
|
VkPipelineLayout dump_pipeline_layout_depth_ = VK_NULL_HANDLE;
|
|
// Compute pipelines for copying host render target contents to the EDRAM
|
|
// buffer. VK_NULL_HANDLE if failed to create.
|
|
std::unordered_map<DumpPipelineKey, VkPipeline, DumpPipelineKey::Hasher> dump_pipelines_;
|
|
VkPipelineLayout direct_resolve_pipeline_layout_color_ = VK_NULL_HANDLE;
|
|
VkPipelineLayout direct_resolve_pipeline_layout_depth_ = VK_NULL_HANDLE;
|
|
std::unordered_map<DirectResolvePipelineKey, VkPipeline, DirectResolvePipelineKey::Hasher>
|
|
direct_resolve_pipelines_;
|
|
// Lazily created (compiled from GLSL at runtime); the attempt flags prevent
|
|
// retrying the compilation every resolve after a failure. Indexed
|
|
// [is_depth][uint32_t(source MsaaSamples)].
|
|
VkPipeline direct_resolve_fast32_pipelines_[2][3] = {};
|
|
bool direct_resolve_fast32_pipelines_attempted_[2][3] = {};
|
|
|
|
// Render targets that currently have deferred resolve clears pending.
|
|
std::vector<VulkanRenderTarget*> deferred_clear_render_targets_;
|
|
// Temporary storage for FlushDeferredResolveClearsBeforeEdramRead.
|
|
std::vector<VulkanRenderTarget*> flush_deferred_clear_temp_;
|
|
// Deferred resolve clear statistics, logged periodically for diagnostics.
|
|
uint64_t deferred_clear_stat_deferred_ = 0;
|
|
uint64_t deferred_clear_stat_pass_entry_flushed_ = 0;
|
|
uint64_t deferred_clear_stat_standalone_flushed_ = 0;
|
|
uint64_t deferred_clear_stat_immediate_ = 0;
|
|
|
|
// Temporary storage for Resolve.
|
|
std::vector<Transfer> clear_transfers_[2];
|
|
|
|
// Temporary storage for PerformTransfersAndResolveClears.
|
|
std::vector<TransferInvocation> current_transfer_invocations_;
|
|
|
|
// Temporary storage for DumpRenderTargets.
|
|
std::vector<ResolveCopyDumpRectangle> dump_rectangles_;
|
|
std::vector<DumpInvocation> dump_invocations_;
|
|
std::vector<ResolveCopyDispatch> direct_resolve_dispatches_;
|
|
uint64_t direct_resolve_attempt_count_ = 0;
|
|
uint64_t direct_resolve_success_count_ = 0;
|
|
uint64_t direct_resolve_fallback_count_ = 0;
|
|
|
|
// For traces.
|
|
VkBuffer edram_snapshot_download_buffer_ = VK_NULL_HANDLE;
|
|
VkDeviceMemory edram_snapshot_download_buffer_memory_ = VK_NULL_HANDLE;
|
|
uint32_t edram_snapshot_download_buffer_memory_type_ = UINT32_MAX;
|
|
VkDeviceSize edram_snapshot_download_buffer_memory_size_ = 0;
|
|
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> edram_snapshot_restore_pool_;
|
|
void ResetTraceDownload();
|
|
|
|
// For pixel (fragment) shader interlock.
|
|
|
|
VkRenderPass fsi_render_pass_ = VK_NULL_HANDLE;
|
|
Framebuffer fsi_framebuffer_;
|
|
|
|
VkPipelineLayout resolve_fsi_clear_pipeline_layout_ = VK_NULL_HANDLE;
|
|
VkPipeline resolve_fsi_clear_32bpp_pipeline_ = VK_NULL_HANDLE;
|
|
VkPipeline resolve_fsi_clear_64bpp_pipeline_ = VK_NULL_HANDLE;
|
|
};
|
|
|
|
} // namespace rex::graphics::vulkan
|