[GPU/Metal] Add Metal Shader Converter backend

This commit is contained in:
XeniOS
2026-06-03 23:04:08 +09:00
parent 07021f019c
commit 0ccfa7981c
98 changed files with 32976 additions and 13266 deletions
+7 -6
View File
@@ -154,11 +154,11 @@ jobs:
- name: Setup
run: |
# Update submodules. Skip DirectXShaderCompiler (we don't use the
# Metal shader converter / dxilconv pipeline on edge).
# Update submodules.
git submodule update --init --depth=1 -j$(sysctl -n hw.ncpu) \
$(grep -oE 'path = .+' .gitmodules | sed 's/path = //' \
| grep -v 'DirectXShaderCompiler')
$(grep -oE 'path = .+' .gitmodules | sed 's/path = //')
git -C third_party/DirectXShaderCompiler submodule update --init --depth=1 \
external/SPIRV-Headers external/SPIRV-Tools
# wxWidgets has nested submodules (pcre, libpng, ...) needed when
# building from vendored source.
@@ -245,8 +245,9 @@ jobs:
- name: Setup
run: |
git submodule update --init --depth=1 -j$(sysctl -n hw.ncpu) \
$(grep -oE 'path = .+' .gitmodules | sed 's/path = //' \
| grep -v 'DirectXShaderCompiler')
$(grep -oE 'path = .+' .gitmodules | sed 's/path = //')
git -C third_party/DirectXShaderCompiler submodule update --init --depth=1 \
external/SPIRV-Headers external/SPIRV-Tools
git submodule update --init --recursive --depth=1 \
-j$(sysctl -n hw.ncpu) third_party/wxWidgets
./xenia-build.py fetchdata
+6 -1
View File
@@ -40,7 +40,8 @@
url = https://github.com/xenia-canary/disruptorplus.git
[submodule "third_party/DirectXShaderCompiler"]
path = third_party/DirectXShaderCompiler
url = https://github.com/microsoft/DirectXShaderCompiler.git
url = https://github.com/xenios-jp/DirectXShaderCompiler.git
branch = dxilconv-ios-support
[submodule "third_party/date"]
path = third_party/date
url = https://github.com/HowardHinnant/date.git
@@ -130,3 +131,7 @@
[submodule "third_party/boost_context/context"]
path = third_party/boost_context/context
url = https://github.com/boostorg/context.git
[submodule "third_party/metal-shader-converter"]
path = third_party/metal-shader-converter
url = https://github.com/xenios-jp/metal-shader-converter
branch = ios-support-3.1.1
+1
View File
@@ -300,6 +300,7 @@ else()
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20)
add_compile_options(
$<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-literal-operator>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-specialization>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-nontrivial-memcall>
)
endif()
+14
View File
@@ -183,6 +183,20 @@ elseif(APPLE)
target_link_options(xenia-app PRIVATE
"-Wl,-rpath,@executable_path/../Frameworks"
"-Wl,-rpath,@loader_path/../Frameworks")
foreach(_xe_app_runtime_dylib
xenia-third-party-metal-shader-converter
xenia-third-party-dxilconv)
if(TARGET ${_xe_app_runtime_dylib})
add_custom_command(TARGET xenia-app POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory
"$<TARGET_BUNDLE_DIR:xenia-app>/Contents/Frameworks"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:${_xe_app_runtime_dylib}>"
"$<TARGET_BUNDLE_DIR:xenia-app>/Contents/Frameworks/$<TARGET_FILE_NAME:${_xe_app_runtime_dylib}>"
VERBATIM)
endif()
endforeach()
endif()
xe_target_defaults(xenia-app)
+30 -6
View File
@@ -61,7 +61,8 @@ namespace a64 {
uint64_t ResolveFunction(void* raw_context, uint64_t target_address);
uint32_t FindStackpointSyncDepth(const A64BackendStackpoint* stackpoints,
uint32_t current_depth, uint32_t guest_sp) {
uint32_t current_depth, uint32_t guest_sp,
uint32_t guest_return_address) {
if (!stackpoints || current_depth == 0) {
return 0;
}
@@ -77,6 +78,26 @@ uint32_t FindStackpointSyncDepth(const A64BackendStackpoint* stackpoints,
if (idx == 0xFFFFFFFFu || frames_skipped <= 1) {
return 0;
}
// x64 breaks ties between equal guest stacks with guest_return_address_ and
// restores the caller of the matching frame. Without this, A64 can choose a
// deeper equal-stack frame and resume a return-site with the wrong host SP.
if (guest_return_address) {
const uint32_t matching_guest_sp = stackpoints[idx].guest_stack_;
uint32_t search_idx = idx;
while (stackpoints[search_idx].guest_stack_ == matching_guest_sp) {
if (stackpoints[search_idx].guest_return_address_ ==
guest_return_address) {
return search_idx == 0 ? 0 : search_idx;
}
if (search_idx == 0) {
return 1;
}
--search_idx;
}
return search_idx + 2;
}
return idx + 1;
}
@@ -431,8 +452,9 @@ void* A64HelperEmitter::EmitGuestAndHostSynchronizeStackHelper() {
current_stackpoint_depth))));
// w13 = target depth computed by ResolveFunction.
ldr(w13, ptr(x19, static_cast<uint32_t>(offsetof(
A64BackendContext, pending_stackpoint_sync_depth))));
ldr(w13, ptr(x19, static_cast<uint32_t>(
offsetof(A64BackendContext,
pending_stackpoint_sync_depth))));
auto& underflow = NewCachedLabel();
cbz(x10, underflow);
@@ -462,8 +484,9 @@ void* A64HelperEmitter::EmitGuestAndHostSynchronizeStackHelper() {
str(w13, ptr(x19, static_cast<uint32_t>(offsetof(A64BackendContext,
current_stackpoint_depth))));
mov(w15, 0);
str(w15, ptr(x19, static_cast<uint32_t>(offsetof(
A64BackendContext, pending_stackpoint_sync_depth))));
str(w15, ptr(x19, static_cast<uint32_t>(
offsetof(A64BackendContext,
pending_stackpoint_sync_depth))));
// Jump back to the caller.
br(x8);
@@ -647,7 +670,8 @@ uint64_t ResolveFunction(void* raw_context, uint64_t target_address) {
const uint32_t sync_depth = FindStackpointSyncDepth(
backend_context->stackpoints,
backend_context->current_stackpoint_depth,
static_cast<uint32_t>(guest_context->r[1]));
static_cast<uint32_t>(guest_context->r[1]),
static_cast<uint32_t>(target_address));
if (sync_depth != 0) {
backend_context->pending_stackpoint_sync_depth = sync_depth;
return host_address;
+2 -1
View File
@@ -55,7 +55,8 @@ struct A64BackendStackpoint {
};
uint32_t FindStackpointSyncDepth(const A64BackendStackpoint* stackpoints,
uint32_t current_depth, uint32_t guest_sp);
uint32_t current_depth, uint32_t guest_sp,
uint32_t guest_return_address);
enum : uint32_t {
kA64BackendFPCRModeBit = 0,
+3 -2
View File
@@ -738,8 +738,9 @@ void A64Emitter::EnsureSynchronizedGuestAndHostStack() {
// still point at a skipped frame here.
auto& return_from_sync = NewCachedLabel();
ldr(w16, ptr(x19, static_cast<uint32_t>(offsetof(
A64BackendContext, pending_stackpoint_sync_depth))));
ldr(w16, ptr(x19, static_cast<uint32_t>(
offsetof(A64BackendContext,
pending_stackpoint_sync_depth))));
cbz(w16, return_from_sync);
auto& sync_label = AddToTail([](A64Emitter& e, Label& lbl) {
@@ -192,6 +192,178 @@ TEST_CASE("HOST_GUEST_HOST_ROUNDTRIP", "[backend]") {
memory->SystemHeapFree(stack_address);
}
#if XE_ARCH_ARM64
TEST_CASE("A64_STACKPOINT_SYNC_DEPTH_SELECTION", "[backend]") {
using xe::cpu::backend::a64::A64BackendStackpoint;
using xe::cpu::backend::a64::FindStackpointSyncDepth;
A64BackendStackpoint stackpoints[4] = {};
stackpoints[0].guest_stack_ = 0xA000;
stackpoints[1].guest_stack_ = 0x9000;
stackpoints[2].guest_stack_ = 0x8000;
stackpoints[3].guest_stack_ = 0x7000;
REQUIRE(FindStackpointSyncDepth(nullptr, 4, 0x8500, 0x8123456C) == 0);
REQUIRE(FindStackpointSyncDepth(stackpoints, 0, 0x8500, 0x8123456C) == 0);
// Two skipped deeper frames: target stackpoint depth is the existing caller.
REQUIRE(FindStackpointSyncDepth(stackpoints, 4, 0x8500, 0x8123456C) == 2);
// One skipped frame can be an early guest SP restore, so it must not repair.
REQUIRE(FindStackpointSyncDepth(stackpoints, 4, 0x7500, 0x8123456C) == 0);
// Guest SP unwound past every recorded stackpoint: no safe repair target.
REQUIRE(FindStackpointSyncDepth(stackpoints, 4, 0xB000, 0x8123456C) == 0);
}
TEST_CASE("A64_STACKPOINT_SYNC_DEPTH_DISAMBIGUATES_EQUAL_GUEST_STACKS",
"[backend]") {
using xe::cpu::backend::a64::A64BackendStackpoint;
using xe::cpu::backend::a64::FindStackpointSyncDepth;
A64BackendStackpoint stackpoints[5] = {};
stackpoints[0].guest_stack_ = 0xA000;
stackpoints[0].guest_return_address_ = 0x80000100;
stackpoints[1].guest_stack_ = 0x9000;
stackpoints[1].guest_return_address_ = 0x80000200;
stackpoints[2].guest_stack_ = 0x9000;
stackpoints[2].guest_return_address_ = 0x80000300;
stackpoints[3].guest_stack_ = 0x8000;
stackpoints[3].guest_return_address_ = 0x80000400;
stackpoints[4].guest_stack_ = 0x7000;
stackpoints[4].guest_return_address_ = 0x80000500;
// The guest SP search stops at index 2. The return address identifies the
// frame being popped, so the sync target is its caller at depth 2.
REQUIRE(FindStackpointSyncDepth(stackpoints, 5, 0x8800, 0x80000300) == 2);
// A match at the shallower equal-stack frame pops to its caller.
REQUIRE(FindStackpointSyncDepth(stackpoints, 5, 0x8800, 0x80000200) == 1);
// If no return address in the equal-stack group matches, mirror x64 by
// choosing the shallowest equal-stack frame rather than the deepest one.
REQUIRE(FindStackpointSyncDepth(stackpoints, 5, 0x8800, 0x80000900) == 2);
}
static std::atomic<uint32_t> a64_pending_sync_builtin_count{0};
static uint32_t a64_pending_sync_observed_depth = 0;
static void MarkA64PendingSyncForCaller(ppc::PPCContext* ctx, void* arg0,
void* arg1) {
auto* a64_backend = static_cast<xe::cpu::backend::a64::A64Backend*>(
ctx->thread_state->processor()->backend());
auto* bctx = a64_backend->BackendContextForGuestContext(ctx);
a64_pending_sync_observed_depth = bctx->current_stackpoint_depth;
if (bctx->current_stackpoint_depth > 1) {
bctx->pending_stackpoint_sync_depth = bctx->current_stackpoint_depth - 1;
}
a64_pending_sync_builtin_count.fetch_add(1);
}
TEST_CASE("A64_PENDING_STACKPOINT_SYNC_AFTER_GUEST_CALL", "[backend]") {
constexpr uint32_t kCallerAddr = 0x80000000;
constexpr uint32_t kCalleeAddr = 0x80001000;
constexpr uint64_t kPostCallMarker = 0xA64A64A64A64A64Aull;
a64_pending_sync_builtin_count = 0;
a64_pending_sync_observed_depth = 0;
auto memory = std::make_unique<Memory>();
memory->Initialize();
auto backend = CreateBackend();
REQUIRE(backend);
auto processor = std::make_unique<Processor>(memory.get(), nullptr);
processor->Setup(std::move(backend));
auto* marker_fn = processor->DefineBuiltin(
"MarkA64PendingSync", MarkA64PendingSyncForCaller, nullptr, nullptr);
int gen_invocation = 0;
Function* callee_fn = nullptr;
auto module = std::make_unique<TestModule>(
processor.get(), "Test",
[](uint32_t address) {
return address == kCallerAddr || address == kCalleeAddr;
},
[&](HIRBuilder& b) {
if (gen_invocation++ == 0) {
b.CallExtern(marker_fn);
b.Return();
} else {
REQUIRE(callee_fn != nullptr);
b.Call(callee_fn);
StoreGPR(b, 3, b.LoadConstantUint64(kPostCallMarker));
b.Return();
}
return true;
},
/*skip_cf_simplification=*/true);
processor->AddModule(std::move(module));
processor->backend()->CommitExecutableRange(kCallerAddr,
kCalleeAddr + 0x1000);
callee_fn = processor->ResolveFunction(kCalleeAddr);
REQUIRE(callee_fn != nullptr);
auto* caller_fn = processor->ResolveFunction(kCallerAddr);
REQUIRE(caller_fn != nullptr);
uint32_t stack_size = 64 * 1024;
uint32_t stack_address = memory->SystemHeapAlloc(stack_size);
auto thread_state = std::make_unique<ThreadState>(processor.get(), 0x100,
stack_address + stack_size);
auto ctx = thread_state->context();
ctx->lr = 0xBCBCBCBC;
ctx->r[3] = 0;
caller_fn->Call(thread_state.get(), uint32_t(ctx->lr));
auto* a64_backend =
static_cast<xe::cpu::backend::a64::A64Backend*>(processor->backend());
auto* bctx = a64_backend->BackendContextForGuestContext(ctx);
REQUIRE(a64_pending_sync_builtin_count == 1);
REQUIRE(a64_pending_sync_observed_depth > 1);
REQUIRE(ctx->r[3] == kPostCallMarker);
REQUIRE(bctx->pending_stackpoint_sync_depth == 0);
REQUIRE(bctx->current_stackpoint_depth == 0);
memory->SystemHeapFree(stack_address);
}
TEST_CASE("A64_PREPARE_FOR_REENTRY_CLEARS_PENDING_STACKPOINT_SYNC",
"[backend]") {
auto memory = std::make_unique<Memory>();
memory->Initialize();
auto backend = CreateBackend();
REQUIRE(backend);
auto processor = std::make_unique<Processor>(memory.get(), nullptr);
processor->Setup(std::move(backend));
uint32_t stack_size = 64 * 1024;
uint32_t stack_address = memory->SystemHeapAlloc(stack_size);
auto thread_state = std::make_unique<ThreadState>(processor.get(), 0x100,
stack_address + stack_size);
auto ctx = thread_state->context();
auto* a64_backend =
static_cast<xe::cpu::backend::a64::A64Backend*>(processor->backend());
auto* bctx = a64_backend->BackendContextForGuestContext(ctx);
bctx->current_stackpoint_depth = 3;
bctx->pending_stackpoint_sync_depth = 2;
processor->backend()->PrepareForReentry(ctx);
REQUIRE(bctx->current_stackpoint_depth == 0);
REQUIRE(bctx->pending_stackpoint_sync_depth == 0);
memory->SystemHeapFree(stack_address);
}
#endif // XE_ARCH_ARM64
// =============================================================================
// GPR preservation across GuestToHostThunk
// =============================================================================
File diff suppressed because it is too large Load Diff
+5 -39
View File
@@ -32,6 +32,7 @@
#include "xenia/base/threading.h"
#include "xenia/gpu/d3d12/d3d12_render_target_cache.h"
#include "xenia/gpu/d3d12/d3d12_shader.h"
#include "xenia/gpu/dxbc_geometry_shader.h"
#include "xenia/gpu/dxbc_shader_translator.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/primitive_processor.h"
@@ -151,12 +152,9 @@ class PipelineCache {
kTriangle,
};
enum class PipelineGeometryShader : uint32_t {
kNone,
kPointList,
kRectangleList,
kQuadList,
};
// Geometry-shader key + DXBC generation are shared across backends; see
// xenia/gpu/dxbc_geometry_shader.h.
using PipelineGeometryShader = xe::gpu::PipelineGeometryShader;
enum class PipelineCullMode : uint32_t {
kNone,
@@ -251,32 +249,7 @@ class PipelineCache {
PipelineDescription description;
};
union GeometryShaderKey {
uint32_t key;
struct {
PipelineGeometryShader type : 2;
uint32_t interpolator_count : 5;
uint32_t user_clip_plane_count : 3;
uint32_t user_clip_plane_cull : 1;
uint32_t has_vertex_kill_and : 1;
uint32_t has_point_size : 1;
uint32_t has_point_coordinates : 1;
};
GeometryShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const GeometryShaderKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const GeometryShaderKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const GeometryShaderKey& other_key) const {
return !(*this == other_key);
}
};
using GeometryShaderKey = xe::gpu::GeometryShaderKey;
D3D12Shader* LoadShader(xenos::ShaderType shader_type,
const uint32_t* host_address, uint32_t dword_count,
@@ -312,13 +285,6 @@ class PipelineCache {
PipelineRuntimeDescription& runtime_description_out,
bool for_placeholder = false);
static bool GetGeometryShaderKey(
PipelineGeometryShader geometry_shader_type,
DxbcShaderTranslator::Modification vertex_shader_modification,
DxbcShaderTranslator::Modification pixel_shader_modification,
GeometryShaderKey& key_out);
static void CreateDxbcGeometryShader(GeometryShaderKey key,
std::vector<uint32_t>& shader_out);
const std::vector<uint32_t>& GetGeometryShader(GeometryShaderKey key);
ID3D12PipelineState* CreateD3D12Pipeline(
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
/**
******************************************************************************
* 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_DXBC_GEOMETRY_SHADER_H_
#define XENIA_GPU_DXBC_GEOMETRY_SHADER_H_
#include <cstdint>
#include <functional>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/gpu/dxbc_shader_translator.h"
namespace xe {
namespace gpu {
// Geometry-shader emulation modes shared by the host backends. The Xbox 360
// expands point/rectangle/quad lists that desktop APIs render via a geometry
// shader; this DXBC generator is shared by the D3D12 and Metal backends (Metal
// then converts the DXBC through the Metal Shader Converter). Vulkan uses a
// separate SPIR-V path and is intentionally not part of this module.
enum class PipelineGeometryShader : uint32_t {
kNone,
kPointList,
kRectangleList,
kQuadList,
};
union GeometryShaderKey {
uint32_t key;
struct {
PipelineGeometryShader type : 2;
uint32_t interpolator_count : 5;
uint32_t user_clip_plane_count : 3;
uint32_t user_clip_plane_cull : 1;
uint32_t has_vertex_kill_and : 1;
uint32_t has_point_size : 1;
uint32_t has_point_coordinates : 1;
};
GeometryShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const GeometryShaderKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const GeometryShaderKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const GeometryShaderKey& other_key) const {
return !(*this == other_key);
}
};
// Builds the geometry-shader cache key from the vertex/pixel shader
// modifications. Returns false if no geometry shader is needed for the type.
bool GetGeometryShaderKey(
PipelineGeometryShader geometry_shader_type,
DxbcShaderTranslator::Modification vertex_shader_modification,
DxbcShaderTranslator::Modification pixel_shader_modification,
GeometryShaderKey& key_out);
// Generates the DXBC geometry shader bytecode for the given key.
void CreateDxbcGeometryShader(GeometryShaderKey key,
std::vector<uint32_t>& shader_out);
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_DXBC_GEOMETRY_SHADER_H_
+18
View File
@@ -20,6 +20,24 @@ DxbcShader::DxbcShader(xenos::ShaderType shader_type, uint64_t ucode_data_hash,
: Shader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count,
ucode_source_endian) {}
DxbcShader::TranslationMetadata DxbcShader::GetTranslationMetadata() const {
TranslationMetadata metadata;
metadata.texture_bindings = texture_bindings_;
metadata.sampler_bindings = sampler_bindings_;
metadata.used_texture_mask = used_texture_mask_;
metadata.used_cbuffer_mask = used_cbuffer_mask_;
metadata.fetch_constant_dword_mask = fetch_constant_dword_mask_;
return metadata;
}
void DxbcShader::SetTranslationMetadata(const TranslationMetadata& metadata) {
texture_bindings_ = metadata.texture_bindings;
sampler_bindings_ = metadata.sampler_bindings;
used_texture_mask_ = metadata.used_texture_mask;
used_cbuffer_mask_ = metadata.used_cbuffer_mask;
fetch_constant_dword_mask_ = metadata.fetch_constant_dword_mask;
}
Shader::Translation* DxbcShader::CreateTranslationInstance(
uint64_t modification) {
return new DxbcTranslation(*this, modification);
+28
View File
@@ -10,6 +10,7 @@
#ifndef XENIA_GPU_DXBC_SHADER_H_
#define XENIA_GPU_DXBC_SHADER_H_
#include <array>
#include <atomic>
#include <vector>
@@ -74,6 +75,31 @@ class DxbcShader : public Shader {
return sampler_bindings_;
}
static constexpr uint32_t kFetchConstantDwordCount =
xenos::kTextureFetchConstantCount * 6;
static constexpr uint32_t kFetchConstantDwordMaskWordCount =
kFetchConstantDwordCount / 32;
using FetchConstantDwordMask =
std::array<uint32_t, kFetchConstantDwordMaskWordCount>;
uint32_t GetUsedCbufferMaskAfterTranslation() const {
return used_cbuffer_mask_;
}
const FetchConstantDwordMask& GetFetchConstantDwordMaskAfterTranslation()
const {
return fetch_constant_dword_mask_;
}
struct TranslationMetadata {
std::vector<TextureBinding> texture_bindings;
std::vector<SamplerBinding> sampler_bindings;
uint32_t used_texture_mask = 0;
uint32_t used_cbuffer_mask = 0;
FetchConstantDwordMask fetch_constant_dword_mask = {};
};
TranslationMetadata GetTranslationMetadata() const;
void SetTranslationMetadata(const TranslationMetadata& metadata);
protected:
Translation* CreateTranslationInstance(uint64_t modification) override;
@@ -84,6 +110,8 @@ class DxbcShader : public Shader {
std::vector<TextureBinding> texture_bindings_;
std::vector<SamplerBinding> sampler_bindings_;
uint32_t used_texture_mask_ = 0;
uint32_t used_cbuffer_mask_ = 0;
FetchConstantDwordMask fetch_constant_dword_mask_ = {};
};
} // namespace gpu
+39
View File
@@ -136,6 +136,7 @@ void DxbcShaderTranslator::Reset() {
cbuffer_index_bool_loop_constants_ = kBindingIndexUnallocated;
cbuffer_index_fetch_constants_ = kBindingIndexUnallocated;
cbuffer_index_descriptor_indices_ = kBindingIndexUnallocated;
texture_fetch_constant_dword_mask_.fill(0);
system_constants_used_ = 0;
@@ -1368,6 +1369,44 @@ void DxbcShaderTranslator::PostTranslation() {
DxbcShader* dxbc_shader = dynamic_cast<DxbcShader*>(&translation.shader());
if (dxbc_shader && !dxbc_shader->bindings_setup_entered_.test_and_set(
std::memory_order_relaxed)) {
uint32_t used_cbuffer_mask = 0;
auto mark_used_cbuffer = [&](CbufferRegister cbuffer_register) {
used_cbuffer_mask |= uint32_t(1) << uint32_t(cbuffer_register);
};
mark_used_cbuffer(CbufferRegister::kSystemConstants);
if (cbuffer_index_float_constants_ != kBindingIndexUnallocated) {
mark_used_cbuffer(CbufferRegister::kFloatConstants);
}
if (cbuffer_index_bool_loop_constants_ != kBindingIndexUnallocated) {
mark_used_cbuffer(CbufferRegister::kBoolLoopConstants);
}
if (cbuffer_index_fetch_constants_ != kBindingIndexUnallocated) {
mark_used_cbuffer(CbufferRegister::kFetchConstants);
}
if (cbuffer_index_descriptor_indices_ != kBindingIndexUnallocated) {
mark_used_cbuffer(CbufferRegister::kDescriptorIndices);
}
dxbc_shader->used_cbuffer_mask_ = used_cbuffer_mask;
dxbc_shader->fetch_constant_dword_mask_ =
texture_fetch_constant_dword_mask_;
const Shader::ConstantRegisterMap& constant_map =
dxbc_shader->constant_register_map();
for (uint32_t i = 0; i < xe::countof(constant_map.vertex_fetch_bitmap);
++i) {
uint32_t vfetch_bits_remaining = constant_map.vertex_fetch_bitmap[i];
uint32_t bit_index;
while (xe::bit_scan_forward(vfetch_bits_remaining, &bit_index)) {
vfetch_bits_remaining = xe::clear_lowest_bit(vfetch_bits_remaining);
const uint32_t vfetch_index = i * 32 + bit_index;
const uint32_t dword_index = vfetch_index * 2;
dxbc_shader->fetch_constant_dword_mask_[dword_index >> 5] |=
uint32_t(1) << (dword_index & 31);
dxbc_shader->fetch_constant_dword_mask_[(dword_index + 1) >> 5] |=
uint32_t(1) << ((dword_index + 1) & 31);
}
}
dxbc_shader->texture_bindings_.clear();
dxbc_shader->texture_bindings_.reserve(texture_bindings_.size());
dxbc_shader->used_texture_mask_ = 0;
+32 -5
View File
@@ -10,6 +10,7 @@
#ifndef XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_
#define XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_
#include <array>
#include <cstddef>
#include <cstring>
#include <string>
@@ -951,17 +952,41 @@ class DxbcShaderTranslator : public ShaderTranslator {
if (cbuffer_index_fetch_constants_ == kBindingIndexUnallocated) {
cbuffer_index_fetch_constants_ = cbuffer_count_++;
}
MarkTextureFetchConstantDwordUsed(fetch_constant_index * 6 +
pair_index * 2);
MarkTextureFetchConstantDwordUsed(fetch_constant_index * 6 +
pair_index * 2 + 1);
return GetTextureFetchConstantWordPairSource(fetch_constant_index,
pair_index);
}
dxbc::Src RequestTextureFetchConstantWord(uint32_t fetch_constant_index,
uint32_t word_index) {
if (cbuffer_index_fetch_constants_ == kBindingIndexUnallocated) {
cbuffer_index_fetch_constants_ = cbuffer_count_++;
}
MarkTextureFetchConstantDwordUsed(fetch_constant_index * 6 + word_index);
return GetTextureFetchConstantWordPairSource(fetch_constant_index,
word_index >> 1)
.SelectFromSwizzled(word_index & 1);
}
dxbc::Src GetTextureFetchConstantWordPairSource(uint32_t fetch_constant_index,
uint32_t pair_index) const {
uint32_t total_pair_index = fetch_constant_index * 3 + pair_index;
return dxbc::Src::CB(cbuffer_index_fetch_constants_,
uint32_t(CbufferRegister::kFetchConstants),
total_pair_index >> 1,
(total_pair_index & 1) ? 0b10101110 : 0b00000100);
}
dxbc::Src RequestTextureFetchConstantWord(uint32_t fetch_constant_index,
uint32_t word_index) {
return RequestTextureFetchConstantWordPair(fetch_constant_index,
word_index >> 1)
.SelectFromSwizzled(word_index & 1);
void MarkTextureFetchConstantDwordUsed(uint32_t dword_index) {
constexpr uint32_t kFetchConstantDwordCount =
xenos::kTextureFetchConstantCount * 6;
if (dword_index >= kFetchConstantDwordCount) {
assert_always();
return;
}
texture_fetch_constant_dword_mask_[dword_index >> 5] |=
uint32_t(1) << (dword_index & 31);
}
void KillPixel(bool condition, const dxbc::Src& condition_src,
@@ -1084,6 +1109,8 @@ class DxbcShaderTranslator : public ShaderTranslator {
uint32_t cbuffer_index_bool_loop_constants_;
uint32_t cbuffer_index_fetch_constants_;
uint32_t cbuffer_index_descriptor_indices_;
std::array<uint32_t, xenos::kTextureFetchConstantCount * 6 / 32>
texture_fetch_constant_dword_mask_;
struct SystemConstantRdef {
const char* name;
+54
View File
@@ -93,6 +93,13 @@ DEFINE_int32(occlusion_query_fake_upper_threshold, 100,
"GPU");
DEFINE_bool(occlusion_query_log, false,
"Log occlusion query lifetime and summary stats.", "GPU");
DEFINE_bool(
occlusion_query_fast_trust_report, false,
"Prefer the current query report over cached fast mode values.\n"
"Can improve occlusion accuracy in fast mode by reducing stale results,\n"
"but may also regress occlusion culling in titles that already have\n"
"issues with fast mode.",
"GPU");
DEFINE_int32(occlusion_query_querybatch_range, 0,
"Range of fake sample count values to walk for titles using the\n"
"D3D QueryBatch standard before wrapping back to\n"
@@ -210,6 +217,53 @@ DEFINE_int32(anisotropic_override, -1,
" 5 = Force 16x anisotropic filtering",
"GPU");
DEFINE_bool(metal_shader_disk_cache, true,
"Cache translated Metal shader artifacts and binding metadata in "
"the packed Metal artifact store.",
"Metal");
DEFINE_bool(metal_pipeline_binary_archive, true,
"Use MTLBinaryArchive for Metal pipeline compilation caching. "
"Requires store_shaders and a compatible OS/driver.",
"Metal");
DEFINE_int32(
metal_draw_ring_count, 128,
"Metal per-command-buffer draw ring size (descriptor-table pages). "
"Higher reduces ring churn but uses more memory.",
"Metal");
DEFINE_bool(metal_backend_telemetry, true,
"Log concise Metal backend decision counters for render encoder "
"lifetime, resolve/transfer planning, bindless binding, and "
"texture upload/load behavior.",
"Metal");
DEFINE_int32(metal_backend_telemetry_interval, 120,
"Number of guest swaps between Metal backend telemetry summaries. "
"Set to 0 to log only on shutdown.",
"Metal");
DEFINE_bool(
metal_root_rebuild_detail_telemetry, false,
"Collect expensive Metal root argument rebuild diagnostics, including slot "
"change histograms and CBV resource identity details. Leave disabled for "
"normal profiling.",
"Metal");
DEFINE_bool(metal_constant_payload_cache, false,
"Reuse identical Metal constant/descriptor payload uploads across "
"draws within a frame. Disable to A/B hash/cache CPU cost against "
"extra CBV uploads and root argument churn.",
"Metal");
DEFINE_string(
metal_residency_sets, "auto",
"Use Metal residency sets for stable Metal allocations where supported.\n"
" auto: Enable when the runtime exposes MTLResidencySet.\n"
" true: Require creating a residency set, falling back only if creation "
"fails.\n"
" false: Use per-encoder useResource/useHeap only.",
"Metal");
DEFINE_bool(
ac6_ground_fix, false,
"This fixes(hide) issues with black ground in AC6. Use only in AC6. "
+15
View File
@@ -37,6 +37,8 @@ DECLARE_int32(occlusion_query_fake_upper_threshold);
DECLARE_bool(occlusion_query_log);
DECLARE_bool(occlusion_query_fast_trust_report);
DECLARE_int32(occlusion_query_querybatch_range);
DECLARE_double(occlusion_query_saturation);
@@ -68,6 +70,19 @@ DECLARE_bool(readback_resolve_half_pixel_offset);
DECLARE_bool(gpu_3d_to_2d_texture);
DECLARE_bool(metal_shader_disk_cache);
DECLARE_bool(metal_pipeline_binary_archive);
DECLARE_int32(metal_draw_ring_count);
DECLARE_bool(metal_use_heaps);
DECLARE_int32(metal_heap_min_bytes);
DECLARE_bool(metal_texture_cache_use_private);
DECLARE_bool(metal_texture_upload_via_blit);
DECLARE_bool(metal_constant_payload_cache);
DECLARE_string(metal_residency_sets);
DECLARE_bool(metal_backend_telemetry);
DECLARE_int32(metal_backend_telemetry_interval);
DECLARE_bool(metal_root_rebuild_detail_telemetry);
DECLARE_bool(ac6_ground_fix);
#define XE_GPU_FINE_GRAINED_DRAW_SCOPES 1
+24
View File
@@ -83,6 +83,30 @@ GraphicsSystem::GraphicsSystem() : frame_limiter_worker_running_(false) {
GraphicsSystem::~GraphicsSystem() = default;
void GraphicsSystem::SetScaledAspectRatio(uint32_t x, uint32_t y) {
uint32_t old_x = scaled_aspect_x_.exchange(x, std::memory_order_relaxed);
uint32_t old_y = scaled_aspect_y_.exchange(y, std::memory_order_relaxed);
if (old_x == x && old_y == y) {
return;
}
ScaledAspectRatioChangedCallback callback;
{
std::lock_guard<std::mutex> lock(
scaled_aspect_ratio_changed_callback_mutex_);
callback = scaled_aspect_ratio_changed_callback_;
}
if (callback) {
callback(x, y);
}
}
void GraphicsSystem::SetScaledAspectRatioChangedCallback(
ScaledAspectRatioChangedCallback callback) {
std::lock_guard<std::mutex> lock(scaled_aspect_ratio_changed_callback_mutex_);
scaled_aspect_ratio_changed_callback_ = std::move(callback);
}
X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::WindowedAppContext* app_context,
+167 -9
View File
@@ -1,37 +1,195 @@
add_library(xenia-gpu-metal STATIC
dxbc_to_dxil_converter.cc
dxbc_to_dxil_converter.h
ir_runtime_impl.mm
metal_backend_telemetry.cc
metal_backend_telemetry.h
metal_command_processor.cc
metal_command_processor.h
metal_direct_host_resolve.cc
metal_geometry_shader.cc
metal_geometry_shader.h
metal_graphics_system.cc
metal_graphics_system.h
metal_heap_pool.cc
metal_heap_pool.h
metal_pipeline_cache.cc
metal_pipeline_cache.h
metal_primitive_processor.cc
metal_primitive_processor.h
metal_render_target_cache.cc
metal_render_target_cache.h
metal_shader.cc
metal_shader.h
metal_shader_cache.cc
metal_shader_cache.h
metal_shader_converter.cc
metal_shader_converter.h
metal_stage_compile_cache.cc
metal_stage_compile_cache.h
metal_shared_memory.cc
metal_shared_memory.h
metal_texture_cache.cc
metal_texture_cache.h
msl_bindings.h
msl_shader.cc
msl_shader.h
msl_tess_factor_kernels.h
)
target_include_directories(xenia-gpu-metal PRIVATE
${PROJECT_SOURCE_DIR}/third_party/glslang
metal_upload_buffer_pool.cc
metal_upload_buffer_pool.h
metal_zpd_visibility_pool.cc
metal_zpd_visibility_pool.h
)
target_link_libraries(xenia-gpu-metal PUBLIC
xenia-base
xenia-gpu
xenia-ui-metal
spirv-cross
xenia-third-party-dxilconv
xenia-third-party-metal-shader-converter
"-framework Foundation"
"-framework Metal"
"-framework MetalKit"
)
target_link_libraries(xenia-gpu-metal PRIVATE glslang-spirv)
xe_shader_rules_metal(xenia-gpu-metal ${CMAKE_CURRENT_SOURCE_DIR}/../shaders)
if(APPLE)
option(XE_METAL_SHADER_DEBUG_INFO
"Embed MSL source + line tables in generated Metal metallibs so the compute/resolve shaders are viewable with per-line cost in the Xcode GPU trace (increases generated header/binary size)."
ON)
set(_metal_shader_args --msl)
if(XE_METAL_SHADER_DEBUG_INFO)
list(APPEND _metal_shader_args --metal-debug)
endif()
set(_direct_host_resolve_generated_root "${PROJECT_BINARY_DIR}/generated")
set(_direct_host_resolve_bytecode_dir
"${_direct_host_resolve_generated_root}/xenia/gpu/shaders/bytecode/metal")
set(_direct_host_resolve_outputs)
file(MAKE_DIRECTORY "${_direct_host_resolve_bytecode_dir}")
macro(_xe_metal_direct_host_resolve_variant id input)
set(_variant_define_args)
foreach(_define ${ARGN})
list(APPEND _variant_define_args --define "${_define}")
endforeach()
set(_variant_out "${_direct_host_resolve_bytecode_dir}/${id}.h")
set(_variant_dep "${_variant_out}.d")
list(APPEND _direct_host_resolve_outputs "${_variant_out}")
add_custom_command(
OUTPUT "${_variant_out}"
COMMAND $<TARGET_FILE:xenia-shader-cc>
${_metal_shader_args}
--identifier "${id}"
${_variant_define_args}
--depfile "${_variant_dep}"
"${input}"
"${_variant_out}"
DEPENDS "${input}" xenia-shader-cc
DEPFILE "${_variant_dep}"
COMMENT "Metal: ${id}"
VERBATIM
)
endmacro()
set(_resolve_host_color_entry
"${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_color_entry.xesli")
set(_resolve_host_color_full_entry
"${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_color_full_entry.xesli")
set(_resolve_host_depth_entry
"${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_depth_entry.xesli")
foreach(_source_uint 0 1)
foreach(_bpp 32 64)
foreach(_msaa 1 2 4)
foreach(_scaled 0 1)
set(_id "resolve_host_color")
if(_source_uint)
string(APPEND _id "_uint")
endif()
string(APPEND _id "_${_bpp}bpp_${_msaa}xmsaa")
set(_defines
"XE_RESOLVE_HOST_COLOR_BPP=${_bpp}"
"XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES=${_msaa}"
"XE_RESOLVE_HOST_COLOR_SOURCE_UINT=${_source_uint}")
if(_scaled)
string(APPEND _id "_scaled")
list(APPEND _defines "XE_RESOLVE_RESOLUTION_SCALED=1")
endif()
string(APPEND _id "_cs")
_xe_metal_direct_host_resolve_variant(
"${_id}" "${_resolve_host_color_entry}" ${_defines})
endforeach()
endforeach()
endforeach()
foreach(_bpp 8 16 32 64 128)
foreach(_msaa 1 2 4)
foreach(_scaled 0 1)
set(_id "resolve_host_color_full")
if(_source_uint)
string(APPEND _id "_uint")
endif()
string(APPEND _id "_${_bpp}bpp_${_msaa}xmsaa")
set(_defines
"XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP=${_bpp}"
"XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES=${_msaa}"
"XE_RESOLVE_HOST_COLOR_SOURCE_UINT=${_source_uint}")
if(_scaled)
string(APPEND _id "_scaled")
list(APPEND _defines "XE_RESOLVE_RESOLUTION_SCALED=1")
endif()
string(APPEND _id "_cs")
_xe_metal_direct_host_resolve_variant(
"${_id}" "${_resolve_host_color_full_entry}" ${_defines})
endforeach()
endforeach()
endforeach()
endforeach()
foreach(_msaa 1 2 4)
foreach(_scaled 0 1)
set(_id "resolve_host_depth_32bpp_${_msaa}xmsaa")
set(_defines "XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES=${_msaa}")
if(_scaled)
string(APPEND _id "_scaled")
list(APPEND _defines "XE_RESOLVE_RESOLUTION_SCALED=1")
endif()
string(APPEND _id "_cs")
_xe_metal_direct_host_resolve_variant(
"${_id}" "${_resolve_host_depth_entry}" ${_defines})
endforeach()
endforeach()
add_custom_target(xenia-gpu-metal-direct-host-resolve-shaders
DEPENDS ${_direct_host_resolve_outputs})
add_dependencies(xenia-gpu-metal
xenia-gpu-metal-direct-host-resolve-shaders)
target_include_directories(xenia-gpu-metal BEFORE PRIVATE
"${_direct_host_resolve_generated_root}")
set(_texture_upload_repack_shader
"${CMAKE_CURRENT_SOURCE_DIR}/../shaders/texture_upload_repack.metal")
set(_texture_upload_repack_generated_root
"${PROJECT_BINARY_DIR}/generated")
set(_texture_upload_repack_out
"${_texture_upload_repack_generated_root}/xenia/gpu/shaders/bytecode/metal/texture_upload_repack.h")
set(_texture_upload_repack_dep "${_texture_upload_repack_out}.d")
file(MAKE_DIRECTORY
"${_texture_upload_repack_generated_root}/xenia/gpu/shaders/bytecode/metal")
add_custom_command(
OUTPUT "${_texture_upload_repack_out}"
COMMAND $<TARGET_FILE:xenia-shader-cc>
${_metal_shader_args}
--depfile "${_texture_upload_repack_dep}"
"${_texture_upload_repack_shader}"
"${_texture_upload_repack_out}"
DEPENDS "${_texture_upload_repack_shader}" xenia-shader-cc
DEPFILE "${_texture_upload_repack_dep}"
COMMENT "Metal: texture_upload_repack.metal"
VERBATIM
)
add_custom_target(xenia-gpu-metal-texture-upload-repack-shader
DEPENDS "${_texture_upload_repack_out}")
add_dependencies(xenia-gpu-metal
xenia-gpu-metal-texture-upload-repack-shader)
target_include_directories(xenia-gpu-metal BEFORE PRIVATE
"${_texture_upload_repack_generated_root}")
set_source_files_properties("${_texture_upload_repack_shader}"
PROPERTIES HEADER_FILE_ONLY TRUE)
target_sources(xenia-gpu-metal PRIVATE "${_texture_upload_repack_shader}")
endif()
xe_target_defaults(xenia-gpu-metal)

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