[Metal] Optimize transfer path and RT handling

- Add tile-instanced transfer batching and per-frame instance buffers
- Specialize transfer shaders and gate sample-id usage for MSAA
- Add blit fast paths for compatible color/depth transfers
- Fix dummy RT MSAA keying and avoid unnecessary load/store
- Fix stencil view lifetime and depth+stencil transfer packing
- Reduce resolve/readback stalls and refresh render-pass/scissor state
This commit is contained in:
Will Martin
2026-02-10 10:01:34 +09:00
committed by Heel
parent eee151ffc1
commit 1d348ac2d3
27 changed files with 2413 additions and 523 deletions
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -88,9 +88,9 @@ bool DxbcToDxilConverter::Initialize() {
dxilconv_path_ = "linked";
is_available_ = true;
if (extra_options && *extra_options) {
XELOGI("DxbcToDxilConverter: Using extra options: {}", extra_options);
XELOGD("DxbcToDxilConverter: Using extra options: {}", extra_options);
} else if (extra_options && !*extra_options) {
XELOGI("DxbcToDxilConverter: Extra options disabled via env");
XELOGD("DxbcToDxilConverter: Extra options disabled via env");
} else {
XELOGI(
"DxbcToDxilConverter: Using default extra options: "
@@ -1,3 +1,12 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
/**
* DXBC to DXIL converter wrapper for Metal backend.
*
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
File diff suppressed because it is too large Load Diff
+66 -4
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,6 +10,7 @@
#ifndef XENIA_GPU_METAL_METAL_COMMAND_PROCESSOR_H_
#define XENIA_GPU_METAL_METAL_COMMAND_PROCESSOR_H_
#include <atomic>
#include <chrono>
#include <filesystem>
#include <memory>
@@ -41,6 +42,14 @@ class Heap;
class SharedEvent;
} // namespace MTL
namespace xe {
namespace ui {
namespace metal {
class MetalGPUCompletionTimeline;
} // namespace metal
} // namespace ui
} // namespace xe
namespace xe {
namespace gpu {
namespace metal {
@@ -78,7 +87,19 @@ class MetalCommandProcessor : public CommandProcessor {
MTL::CommandBuffer* GetCurrentCommandBuffer() const {
return current_command_buffer_;
}
// Debug marker methods - public so subsystems can annotate their operations.
void UpdateDebugMarkersEnabled();
void PushDebugMarker(const char* format, ...);
void PopDebugMarker();
void InsertDebugMarker(const char* format, ...);
bool debug_markers_enabled() const { return debug_markers_enabled_; }
void RequestCapture();
uint32_t current_draw_index() const { return current_draw_index_; }
uint64_t GetCurrentSubmission() const;
uint64_t GetCompletedSubmission() const;
uint64_t GetCurrentFrame() const { return frame_current_; }
uint64_t GetCompletedFrame() const { return frame_completed_; }
MTL::CommandBuffer* EnsureCommandBuffer();
void EndRenderEncoder();
void ResetRenderEncoderResourceUsage();
@@ -116,6 +137,7 @@ class MetalCommandProcessor : public CommandProcessor {
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
void OnPrimaryBufferEnd() override;
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
const uint32_t* host_address,
@@ -143,6 +165,12 @@ class MetalCommandProcessor : public CommandProcessor {
void BeginCommandBuffer();
void EndCommandBuffer();
void ProcessCompletedSubmissions();
void CheckSubmissionCompletion(uint64_t await_submission);
bool BeginSubmission(bool is_guest_command);
bool EndSubmission(bool is_swap);
void MaybeStartCapture();
void StopCaptureIfActive();
bool CanEndSubmissionImmediately() const;
void EnsureDrawRingCapacity();
void UseRenderEncoderAttachmentHeaps(MTL::RenderPassDescriptor* descriptor);
void UseRenderEncoderHeap(MTL::Heap* heap);
@@ -354,7 +382,6 @@ class MetalCommandProcessor : public CommandProcessor {
// Shared memory for Xbox 360 memory access
std::unique_ptr<MetalSharedMemory> shared_memory_;
std::unique_ptr<MetalPrimitiveProcessor> primitive_processor_;
bool frame_open_ = false;
bool saw_swap_ = false;
uint32_t last_swap_ptr_ = 0;
@@ -476,10 +503,24 @@ class MetalCommandProcessor : public CommandProcessor {
MTL::BinaryArchive* pipeline_binary_archive_ = nullptr;
bool pipeline_binary_archive_dirty_ = false;
std::atomic<uint64_t> completed_command_buffers_{0};
uint64_t submission_current_ = 0;
static constexpr uint32_t kQueueFrames = 3;
std::unique_ptr<ui::metal::MetalGPUCompletionTimeline> completion_timeline_;
bool submission_open_ = false;
uint64_t submission_completed_processed_ = 0;
bool frame_open_ = false;
uint64_t frame_current_ = 1;
uint64_t frame_completed_ = 0;
uint64_t closed_frame_submissions_[kQueueFrames] = {};
enum class DebugMarkerTarget {
kCommandBuffer,
kRenderEncoder,
};
bool debug_markers_enabled_ = false;
std::vector<DebugMarkerTarget> debug_marker_stack_;
// Draw counter for ring-buffer descriptor heap allocation
// Each draw uses a different region of the descriptor heap to avoid
// overwriting previous draws' descriptors before GPU execution
@@ -491,6 +532,23 @@ class MetalCommandProcessor : public CommandProcessor {
bool gamma_ramp_256_entry_table_up_to_date_ = false;
bool gamma_ramp_pwl_up_to_date_ = false;
// Resolve downscale compute shader for scaled resolution readback.
MTL::ComputePipelineState* resolve_downscale_pipeline_ = nullptr;
MTL::Buffer* resolve_downscale_buffer_ = nullptr;
uint32_t resolve_downscale_buffer_size_ = 0;
// Per-resolve double-buffered readback for delayed sync.
struct ReadbackBuffer {
MTL::Buffer* buffers[2] = {nullptr, nullptr};
uint32_t sizes[2] = {0, 0};
uint64_t submission_ids[2] = {0, 0};
uint32_t current_index = 0;
uint64_t last_used_frame = 0;
};
void EvictOldReadbackBuffers(
std::unordered_map<uint64_t, ReadbackBuffer>& buffer_map);
std::unordered_map<uint64_t, ReadbackBuffer> readback_buffers_;
// Track memory regions written by IssueCopy (resolve) during trace playback.
// This prevents the trace player from overwriting resolved data with stale
@@ -500,6 +558,10 @@ class MetalCommandProcessor : public CommandProcessor {
uint32_t length;
};
std::vector<ResolvedRange> resolved_memory_ranges_;
std::atomic<bool> capture_requested_{false};
MTL::CaptureManager* capture_manager_ = nullptr;
bool capture_active_ = false;
};
} // namespace metal
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2024 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,6 +10,7 @@
#ifndef XENIA_GPU_METAL_METAL_RENDER_TARGET_CACHE_H_
#define XENIA_GPU_METAL_METAL_RENDER_TARGET_CACHE_H_
#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
@@ -53,13 +54,23 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
MTL::Texture* msaa_transfer_texture() const {
return msaa_transfer_texture_ ? msaa_transfer_texture_ : msaa_texture_;
}
MTL::Texture* stencil_view() const { return stencil_view_; }
void SetStencilView(MTL::Texture* view) { stencil_view_ = view; }
void SetTemporarySortIndex(uint32_t index) {
temporary_sort_index_ = index;
}
uint32_t temporary_sort_index() const { return temporary_sort_index_; }
void SetTexture(MTL::Texture* texture) { texture_ = texture; }
void SetTexture(MTL::Texture* texture) {
if (texture_ != texture) {
if (stencil_view_) {
stencil_view_->release();
stencil_view_ = nullptr;
}
texture_ = texture;
}
}
void SetMsaaTexture(MTL::Texture* texture) { msaa_texture_ = texture; }
void SetDrawTexture(MTL::Texture* texture) { draw_texture_ = texture; }
void SetTransferTexture(MTL::Texture* texture) {
@@ -86,6 +97,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
MTL::Texture* transfer_texture_ = nullptr;
MTL::Texture* msaa_draw_texture_ = nullptr;
MTL::Texture* msaa_transfer_texture_ = nullptr;
MTL::Texture* stencil_view_ = nullptr;
uint32_t temporary_sort_index_ = UINT32_MAX;
bool needs_initial_clear_ = true;
};
@@ -223,7 +235,6 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
MTL::ComputePipelineState* edram_dump_color_32bpp_1xmsaa_pipeline_ = nullptr;
MTL::ComputePipelineState* edram_dump_color_32bpp_2xmsaa_pipeline_ = nullptr;
MTL::ComputePipelineState* edram_dump_color_32bpp_4xmsaa_pipeline_ = nullptr;
// EDRAM blend compute shaders (host RT -> EDRAM with blend/keep mask).
// Color, 64bpp.
MTL::ComputePipelineState* edram_dump_color_64bpp_1xmsaa_pipeline_ = nullptr;
MTL::ComputePipelineState* edram_dump_color_64bpp_2xmsaa_pipeline_ = nullptr;
@@ -260,8 +271,6 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
// Host depth store compute shaders (1x/2x/4x MSAA).
MTL::ComputePipelineState* host_depth_store_pipelines_[3] = {};
// Per-draw ordered-blend coverage attachment.
// Transfer shaders (host RT ownership transfers) - modeled after D3D12.
// TransferMode list mirrors D3D12RenderTargetCache::TransferMode so logs and
@@ -284,6 +293,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
xenos::MsaaSamples host_depth_source_msaa_samples;
uint32_t source_resource_format;
uint32_t dest_resource_format;
uint32_t dest_sample_id_from_sample;
uint32_t host_depth_source_is_copy;
bool operator==(const TransferShaderKey& other) const {
@@ -294,6 +304,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
other.host_depth_source_msaa_samples &&
source_resource_format == other.source_resource_format &&
dest_resource_format == other.dest_resource_format &&
dest_sample_id_from_sample == other.dest_sample_id_from_sample &&
host_depth_source_is_copy == other.host_depth_source_is_copy;
}
bool operator!=(const TransferShaderKey& other) const {
@@ -320,6 +331,9 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
if (dest_resource_format != other.dest_resource_format) {
return dest_resource_format < other.dest_resource_format;
}
if (dest_sample_id_from_sample != other.dest_sample_id_from_sample) {
return dest_sample_id_from_sample < other.dest_sample_id_from_sample;
}
return host_depth_source_is_copy < other.host_depth_source_is_copy;
}
@@ -331,7 +345,8 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
h ^= (size_t(key.host_depth_source_msaa_samples) << 12);
h ^= (size_t(key.source_resource_format) << 16);
h ^= (size_t(key.dest_resource_format) << 24);
h ^= (size_t(key.host_depth_source_is_copy) << 28);
h ^= (size_t(key.dest_sample_id_from_sample) << 28);
h ^= (size_t(key.host_depth_source_is_copy) << 29);
return h ^ (h >> 16);
}
};
@@ -369,10 +384,22 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
std::unordered_map<TransferShaderKey, MTL::RenderPipelineState*,
TransferShaderKey::Hasher>
transfer_pipelines_;
std::unordered_map<TransferShaderKey, MTL::RenderPipelineState*,
TransferShaderKey::Hasher>
transfer_tile_pipelines_;
std::vector<TransferInvocation> transfer_invocations_;
MTL::Library* transfer_library_ = nullptr;
std::unordered_map<uint32_t, MTL::RenderPipelineState*>
transfer_clear_pipelines_;
static constexpr uint32_t kTransferInstanceBufferCount = 3;
std::array<MTL::Buffer*, kTransferInstanceBufferCount>
transfer_tile_instance_buffers_ = {};
std::array<size_t, kTransferInstanceBufferCount>
transfer_tile_instance_buffer_sizes_ = {};
std::array<std::vector<MTL::Buffer*>, kTransferInstanceBufferCount>
transfer_tile_instance_retired_buffers_ = {};
uint64_t transfer_tile_instance_buffer_frame_id_ = 0;
size_t transfer_tile_instance_buffer_offset_ = 0;
MTL::DepthStencilState* transfer_depth_state_ = nullptr;
MTL::DepthStencilState* transfer_depth_state_none_ = nullptr;
MTL::DepthStencilState* transfer_depth_clear_state_ = nullptr;
@@ -400,6 +427,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
// Render pass descriptor cache
MTL::RenderPassDescriptor* cached_render_pass_descriptor_ = nullptr;
bool render_pass_descriptor_dirty_ = true;
uint32_t cached_render_pass_descriptor_sample_count_ = 0;
// Dummy render target for when no render targets are bound
struct DummyColorTargetEntry {
@@ -423,6 +451,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
MTL::Texture* CreateDepthTexture(uint32_t width, uint32_t height,
xenos::DepthRenderTargetFormat format,
uint32_t samples);
MTL::Texture* GetStencilTextureView(MetalRenderTarget* render_target);
MTL::PixelFormat GetColorResourcePixelFormat(
xenos::ColorRenderTargetFormat format) const;
@@ -441,7 +470,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache {
// D3D12RenderTargetCache::GetOrCreateTransferPipelines.
MTL::RenderPipelineState* GetOrCreateTransferPipelines(
const TransferShaderKey& key, MTL::PixelFormat dest_format,
bool dest_is_uint);
bool dest_is_uint, bool tile_instanced);
MTL::RenderPipelineState* GetOrCreateTransferClearPipeline(
MTL::PixelFormat dest_format, bool dest_is_uint, bool is_depth,
uint32_t sample_count);
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+1 -1
View File
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
+5 -2
View File
@@ -1,7 +1,10 @@
/**
*******************************************************************************
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
*******************************************************************************
******************************************************************************
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/metal/metal_shader_cache.h"
+5 -2
View File
@@ -1,7 +1,10 @@
/**
*******************************************************************************
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
*******************************************************************************
******************************************************************************
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_METAL_METAL_SHADER_CACHE_H_
@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Ben Vanik. All rights reserved. *
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/

Some files were not shown because too many files have changed in this diff Show More