diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1be95752b..e94b0bd1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index e8cc68172..2eb1c7a5a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index b3c9437c4..fae50b345 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -300,6 +300,7 @@ else() CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) add_compile_options( $<$:-Wno-deprecated-literal-operator> + $<$:-Wno-invalid-specialization> $<$:-Wno-nontrivial-memcall> ) endif() diff --git a/src/xenia/app/CMakeLists.txt b/src/xenia/app/CMakeLists.txt index 8fe146c57..608e59de7 100644 --- a/src/xenia/app/CMakeLists.txt +++ b/src/xenia/app/CMakeLists.txt @@ -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 + "$/Contents/Frameworks" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" + "$/Contents/Frameworks/$" + VERBATIM) + endif() + endforeach() endif() xe_target_defaults(xenia-app) diff --git a/src/xenia/cpu/backend/a64/a64_backend.cc b/src/xenia/cpu/backend/a64/a64_backend.cc index 7b19ad9e6..9e47660ca 100644 --- a/src/xenia/cpu/backend/a64/a64_backend.cc +++ b/src/xenia/cpu/backend/a64/a64_backend.cc @@ -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(offsetof( - A64BackendContext, pending_stackpoint_sync_depth)))); + ldr(w13, ptr(x19, static_cast( + 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(offsetof(A64BackendContext, current_stackpoint_depth)))); mov(w15, 0); - str(w15, ptr(x19, static_cast(offsetof( - A64BackendContext, pending_stackpoint_sync_depth)))); + str(w15, ptr(x19, static_cast( + 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(guest_context->r[1])); + static_cast(guest_context->r[1]), + static_cast(target_address)); if (sync_depth != 0) { backend_context->pending_stackpoint_sync_depth = sync_depth; return host_address; diff --git a/src/xenia/cpu/backend/a64/a64_backend.h b/src/xenia/cpu/backend/a64/a64_backend.h index 5c29a85bf..c047a45a2 100644 --- a/src/xenia/cpu/backend/a64/a64_backend.h +++ b/src/xenia/cpu/backend/a64/a64_backend.h @@ -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, diff --git a/src/xenia/cpu/backend/a64/a64_emitter.cc b/src/xenia/cpu/backend/a64/a64_emitter.cc index 3e7b05bab..1607e5cec 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.cc +++ b/src/xenia/cpu/backend/a64/a64_emitter.cc @@ -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(offsetof( - A64BackendContext, pending_stackpoint_sync_depth)))); + ldr(w16, ptr(x19, static_cast( + offsetof(A64BackendContext, + pending_stackpoint_sync_depth)))); cbz(w16, return_from_sync); auto& sync_label = AddToTail([](A64Emitter& e, Label& lbl) { diff --git a/src/xenia/cpu/testing/backend_integration_test.cc b/src/xenia/cpu/testing/backend_integration_test.cc index f743d2c03..e243de5ba 100644 --- a/src/xenia/cpu/testing/backend_integration_test.cc +++ b/src/xenia/cpu/testing/backend_integration_test.cc @@ -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 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( + 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->Initialize(); + + auto backend = CreateBackend(); + REQUIRE(backend); + + auto processor = std::make_unique(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( + 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(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(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->Initialize(); + + auto backend = CreateBackend(); + REQUIRE(backend); + + auto processor = std::make_unique(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(processor.get(), 0x100, + stack_address + stack_size); + auto ctx = thread_state->context(); + + auto* a64_backend = + static_cast(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 // ============================================================================= diff --git a/src/xenia/gpu/d3d12/pipeline_cache.cc b/src/xenia/gpu/d3d12/pipeline_cache.cc index 25e5b42c7..71dcdcabb 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.cc +++ b/src/xenia/gpu/d3d12/pipeline_cache.cc @@ -1628,1104 +1628,6 @@ bool PipelineCache::GetCurrentStateDescription( return true; } -bool PipelineCache::GetGeometryShaderKey( - PipelineGeometryShader geometry_shader_type, - DxbcShaderTranslator::Modification vertex_shader_modification, - DxbcShaderTranslator::Modification pixel_shader_modification, - GeometryShaderKey& key_out) { - if (geometry_shader_type == PipelineGeometryShader::kNone) { - return false; - } - assert_true(vertex_shader_modification.vertex.interpolator_mask == - pixel_shader_modification.pixel.interpolator_mask); - GeometryShaderKey key; - key.type = geometry_shader_type; - key.interpolator_count = - xe::bit_count(vertex_shader_modification.vertex.interpolator_mask); - key.user_clip_plane_count = - vertex_shader_modification.vertex.user_clip_plane_count; - key.user_clip_plane_cull = - vertex_shader_modification.vertex.user_clip_plane_cull; - key.has_vertex_kill_and = vertex_shader_modification.vertex.vertex_kill_and; - key.has_point_size = vertex_shader_modification.vertex.output_point_size; - key.has_point_coordinates = pixel_shader_modification.pixel.param_gen_point; - key_out = key; - return true; -} - -void PipelineCache::CreateDxbcGeometryShader( - GeometryShaderKey key, std::vector& shader_out) { - shader_out.clear(); - - // RDEF, ISGN, OSG5, SHEX, STAT. - constexpr uint32_t kBlobCount = 5; - - // Allocate space for the container header and the blob offsets. - shader_out.resize(sizeof(dxbc::ContainerHeader) / sizeof(uint32_t) + - kBlobCount); - uint32_t blob_offset_position_dwords = - sizeof(dxbc::ContainerHeader) / sizeof(uint32_t); - uint32_t blob_position_dwords = uint32_t(shader_out.size()); - constexpr uint32_t kBlobHeaderSizeDwords = - sizeof(dxbc::BlobHeader) / sizeof(uint32_t); - - uint32_t name_ptr; - - // *************************************************************************** - // Resource definition - // *************************************************************************** - - shader_out[blob_offset_position_dwords] = - uint32_t(blob_position_dwords * sizeof(uint32_t)); - uint32_t rdef_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; - // Not needed, as the next operation done is resize, to allocate the space for - // both the blob header and the resource definition header. - // shader_out.resize(rdef_position_dwords); - - // RDEF header - the actual definitions will be written if needed. - shader_out.resize(rdef_position_dwords + - sizeof(dxbc::RdefHeader) / sizeof(uint32_t)); - // Generator name. - dxbc::AppendAlignedString(shader_out, "Xenia"); - { - auto& rdef_header = *reinterpret_cast( - shader_out.data() + rdef_position_dwords); - rdef_header.shader_model = dxbc::RdefShaderModel::kGeometryShader5_1; - rdef_header.compile_flags = - dxbc::kCompileFlagNoPreshader | dxbc::kCompileFlagPreferFlowControl | - dxbc::kCompileFlagIeeeStrictness | dxbc::kCompileFlagAllResourcesBound; - // Generator name is right after the header. - rdef_header.generator_name_ptr = sizeof(dxbc::RdefHeader); - rdef_header.fourcc = dxbc::RdefHeader::FourCC::k5_1; - rdef_header.InitializeSizes(); - } - - uint32_t system_cbuffer_size_vector_aligned_bytes = 0; - - if (key.type == PipelineGeometryShader::kPointList) { - // Need point parameters from the system constants. - - // Constant types - float2 only. - // Names. - name_ptr = - uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); - uint32_t rdef_name_ptr_float2 = name_ptr; - name_ptr += dxbc::AppendAlignedString(shader_out, "float2"); - // Types. - uint32_t rdef_type_float2_position_dwords = uint32_t(shader_out.size()); - uint32_t rdef_type_float2_ptr = - uint32_t((rdef_type_float2_position_dwords - rdef_position_dwords) * - sizeof(uint32_t)); - shader_out.resize(rdef_type_float2_position_dwords + - sizeof(dxbc::RdefType) / sizeof(uint32_t)); - { - auto& rdef_type_float2 = *reinterpret_cast( - shader_out.data() + rdef_type_float2_position_dwords); - rdef_type_float2.variable_class = dxbc::RdefVariableClass::kVector; - rdef_type_float2.variable_type = dxbc::RdefVariableType::kFloat; - rdef_type_float2.row_count = 1; - rdef_type_float2.column_count = 2; - rdef_type_float2.name_ptr = rdef_name_ptr_float2; - } - - // Constants: - // - float2 xe_point_constant_diameter - // - float2 xe_point_screen_diameter_to_ndc_radius - enum PointConstant : uint32_t { - kPointConstantConstantDiameter, - kPointConstantScreenDiameterToNDCRadius, - kPointConstantCount, - }; - // Names. - name_ptr = - uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); - uint32_t rdef_name_ptr_xe_point_constant_diameter = name_ptr; - name_ptr += - dxbc::AppendAlignedString(shader_out, "xe_point_constant_diameter"); - uint32_t rdef_name_ptr_xe_point_screen_diameter_to_ndc_radius = name_ptr; - name_ptr += dxbc::AppendAlignedString( - shader_out, "xe_point_screen_diameter_to_ndc_radius"); - // Constants. - uint32_t rdef_constants_position_dwords = uint32_t(shader_out.size()); - uint32_t rdef_constants_ptr = - uint32_t((rdef_constants_position_dwords - rdef_position_dwords) * - sizeof(uint32_t)); - shader_out.resize(rdef_constants_position_dwords + - sizeof(dxbc::RdefVariable) / sizeof(uint32_t) * - kPointConstantCount); - { - auto rdef_constants = reinterpret_cast( - shader_out.data() + rdef_constants_position_dwords); - // float2 xe_point_constant_diameter - static_assert( - sizeof(DxbcShaderTranslator::SystemConstants :: - point_constant_diameter) == sizeof(float) * 2, - "DxbcShaderTranslator point_constant_diameter system constant size " - "differs between the shader translator and geometry shader " - "generation"); - static_assert_size( - DxbcShaderTranslator::SystemConstants::point_constant_diameter, - sizeof(float) * 2); - dxbc::RdefVariable& rdef_constant_point_constant_diameter = - rdef_constants[kPointConstantConstantDiameter]; - rdef_constant_point_constant_diameter.name_ptr = - rdef_name_ptr_xe_point_constant_diameter; - rdef_constant_point_constant_diameter.start_offset_bytes = offsetof( - DxbcShaderTranslator::SystemConstants, point_constant_diameter); - rdef_constant_point_constant_diameter.size_bytes = sizeof(float) * 2; - rdef_constant_point_constant_diameter.flags = dxbc::kRdefVariableFlagUsed; - rdef_constant_point_constant_diameter.type_ptr = rdef_type_float2_ptr; - rdef_constant_point_constant_diameter.start_texture = UINT32_MAX; - rdef_constant_point_constant_diameter.start_sampler = UINT32_MAX; - // float2 xe_point_screen_diameter_to_ndc_radius - static_assert( - sizeof(DxbcShaderTranslator::SystemConstants :: - point_screen_diameter_to_ndc_radius) == sizeof(float) * 2, - "DxbcShaderTranslator point_screen_diameter_to_ndc_radius system " - "constant size differs between the shader translator and geometry " - "shader generation"); - dxbc::RdefVariable& rdef_constant_point_screen_diameter_to_ndc_radius = - rdef_constants[kPointConstantScreenDiameterToNDCRadius]; - rdef_constant_point_screen_diameter_to_ndc_radius.name_ptr = - rdef_name_ptr_xe_point_screen_diameter_to_ndc_radius; - rdef_constant_point_screen_diameter_to_ndc_radius.start_offset_bytes = - offsetof(DxbcShaderTranslator::SystemConstants, - point_screen_diameter_to_ndc_radius); - rdef_constant_point_screen_diameter_to_ndc_radius.size_bytes = - sizeof(float) * 2; - rdef_constant_point_screen_diameter_to_ndc_radius.flags = - dxbc::kRdefVariableFlagUsed; - rdef_constant_point_screen_diameter_to_ndc_radius.type_ptr = - rdef_type_float2_ptr; - rdef_constant_point_screen_diameter_to_ndc_radius.start_texture = - UINT32_MAX; - rdef_constant_point_screen_diameter_to_ndc_radius.start_sampler = - UINT32_MAX; - } - - // Constant buffers - xe_system_cbuffer only. - - // Names. - name_ptr = - uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); - uint32_t rdef_name_ptr_xe_system_cbuffer = name_ptr; - name_ptr += dxbc::AppendAlignedString(shader_out, "xe_system_cbuffer"); - // Constant buffers. - uint32_t rdef_cbuffer_position_dwords = uint32_t(shader_out.size()); - shader_out.resize(rdef_cbuffer_position_dwords + - sizeof(dxbc::RdefCbuffer) / sizeof(uint32_t)); - { - auto& rdef_cbuffer_system = *reinterpret_cast( - shader_out.data() + rdef_cbuffer_position_dwords); - rdef_cbuffer_system.name_ptr = rdef_name_ptr_xe_system_cbuffer; - rdef_cbuffer_system.variable_count = kPointConstantCount; - rdef_cbuffer_system.variables_ptr = rdef_constants_ptr; - auto rdef_constants = reinterpret_cast( - shader_out.data() + rdef_constants_position_dwords); - for (uint32_t i = 0; i < kPointConstantCount; ++i) { - system_cbuffer_size_vector_aligned_bytes = - std::max(system_cbuffer_size_vector_aligned_bytes, - rdef_constants[i].start_offset_bytes + - rdef_constants[i].size_bytes); - } - system_cbuffer_size_vector_aligned_bytes = - xe::align(system_cbuffer_size_vector_aligned_bytes, - uint32_t(sizeof(uint32_t) * 4)); - rdef_cbuffer_system.size_vector_aligned_bytes = - system_cbuffer_size_vector_aligned_bytes; - } - - // Bindings - xe_system_cbuffer only. - uint32_t rdef_binding_position_dwords = uint32_t(shader_out.size()); - shader_out.resize(rdef_binding_position_dwords + - sizeof(dxbc::RdefInputBind) / sizeof(uint32_t)); - { - auto& rdef_binding_cbuffer_system = - *reinterpret_cast(shader_out.data() + - rdef_binding_position_dwords); - rdef_binding_cbuffer_system.name_ptr = rdef_name_ptr_xe_system_cbuffer; - rdef_binding_cbuffer_system.type = dxbc::RdefInputType::kCbuffer; - rdef_binding_cbuffer_system.bind_point = - uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants); - rdef_binding_cbuffer_system.bind_count = 1; - rdef_binding_cbuffer_system.flags = dxbc::kRdefInputFlagUserPacked; - } - - // Pointers in the header. - { - auto& rdef_header = *reinterpret_cast( - shader_out.data() + rdef_position_dwords); - rdef_header.cbuffer_count = 1; - rdef_header.cbuffers_ptr = - uint32_t((rdef_cbuffer_position_dwords - rdef_position_dwords) * - sizeof(uint32_t)); - rdef_header.input_bind_count = 1; - rdef_header.input_binds_ptr = - uint32_t((rdef_binding_position_dwords - rdef_position_dwords) * - sizeof(uint32_t)); - } - } - - { - auto& blob_header = *reinterpret_cast( - shader_out.data() + blob_position_dwords); - blob_header.fourcc = dxbc::BlobHeader::FourCC::kResourceDefinition; - blob_position_dwords = uint32_t(shader_out.size()); - blob_header.size_bytes = - (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - - shader_out[blob_offset_position_dwords++]; - } - - // *************************************************************************** - // Input signature - // *************************************************************************** - - // Clip and cull distances are tightly packed together into registers, but - // have separate signature parameters with each being a vec4-aligned window. - uint32_t input_clip_distance_count = - key.user_clip_plane_cull ? 0 : key.user_clip_plane_count; - uint32_t input_cull_distance_count = - (key.user_clip_plane_cull ? key.user_clip_plane_count : 0) + - key.has_vertex_kill_and; - uint32_t input_clip_and_cull_distance_count = - input_clip_distance_count + input_cull_distance_count; - - // Interpolators, position, clip and cull distances (parameters containing - // only clip or cull distances, and also one parameter containing both if - // present), point size. - uint32_t isgn_parameter_count = - key.interpolator_count + 1 + - ((input_clip_and_cull_distance_count + 3) / 4) + - uint32_t(input_cull_distance_count && - (input_clip_distance_count & 3) != 0) + - key.has_point_size; - - // Reserve space for the header and the parameters. - shader_out[blob_offset_position_dwords] = - uint32_t(blob_position_dwords * sizeof(uint32_t)); - uint32_t isgn_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; - shader_out.resize(isgn_position_dwords + - sizeof(dxbc::Signature) / sizeof(uint32_t) + - sizeof(dxbc::SignatureParameter) / sizeof(uint32_t) * - isgn_parameter_count); - - // Names (after the parameters). - name_ptr = - uint32_t((shader_out.size() - isgn_position_dwords) * sizeof(uint32_t)); - uint32_t isgn_name_ptr_texcoord = name_ptr; - if (key.interpolator_count) { - name_ptr += dxbc::AppendAlignedString(shader_out, "TEXCOORD"); - } - uint32_t isgn_name_ptr_sv_position = name_ptr; - name_ptr += dxbc::AppendAlignedString(shader_out, "SV_Position"); - uint32_t isgn_name_ptr_sv_clip_distance = name_ptr; - if (input_clip_distance_count) { - name_ptr += dxbc::AppendAlignedString(shader_out, "SV_ClipDistance"); - } - uint32_t isgn_name_ptr_sv_cull_distance = name_ptr; - if (input_cull_distance_count) { - name_ptr += dxbc::AppendAlignedString(shader_out, "SV_CullDistance"); - } - uint32_t isgn_name_ptr_xepsize = name_ptr; - if (key.has_point_size) { - name_ptr += dxbc::AppendAlignedString(shader_out, "XEPSIZE"); - } - - // Header and parameters. - uint32_t input_register_interpolators = UINT32_MAX; - uint32_t input_register_position; - uint32_t input_register_clip_and_cull_distances = UINT32_MAX; - uint32_t input_register_point_size = UINT32_MAX; - { - // Header. - auto& isgn_header = *reinterpret_cast( - shader_out.data() + isgn_position_dwords); - isgn_header.parameter_count = isgn_parameter_count; - isgn_header.parameter_info_ptr = sizeof(dxbc::Signature); - - // Parameters. - auto isgn_parameters = reinterpret_cast( - shader_out.data() + isgn_position_dwords + - sizeof(dxbc::Signature) / sizeof(uint32_t)); - uint32_t isgn_parameter_index = 0; - uint32_t input_register_index = 0; - - // Interpolators (TEXCOORD#). - if (key.interpolator_count) { - input_register_interpolators = input_register_index; - for (uint32_t i = 0; i < key.interpolator_count; ++i) { - assert_true(isgn_parameter_index < isgn_parameter_count); - dxbc::SignatureParameter& isgn_interpolator = - isgn_parameters[isgn_parameter_index++]; - isgn_interpolator.semantic_name_ptr = isgn_name_ptr_texcoord; - isgn_interpolator.semantic_index = i; - isgn_interpolator.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - isgn_interpolator.register_index = input_register_index++; - isgn_interpolator.mask = 0b1111; - isgn_interpolator.always_reads_mask = 0b1111; - } - } - - // Position (SV_Position). - input_register_position = input_register_index; - assert_true(isgn_parameter_index < isgn_parameter_count); - dxbc::SignatureParameter& isgn_sv_position = - isgn_parameters[isgn_parameter_index++]; - isgn_sv_position.semantic_name_ptr = isgn_name_ptr_sv_position; - isgn_sv_position.system_value = dxbc::Name::kPosition; - isgn_sv_position.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - isgn_sv_position.register_index = input_register_index++; - isgn_sv_position.mask = 0b1111; - isgn_sv_position.always_reads_mask = 0b1111; - - // Clip and cull distances (SV_ClipDistance#, SV_CullDistance#). - if (input_clip_and_cull_distance_count) { - input_register_clip_and_cull_distances = input_register_index; - uint32_t isgn_cull_distance_semantic_index = 0; - for (uint32_t i = 0; i < input_clip_and_cull_distance_count; i += 4) { - if (i < input_clip_distance_count) { - dxbc::SignatureParameter& isgn_sv_clip_distance = - isgn_parameters[isgn_parameter_index++]; - isgn_sv_clip_distance.semantic_name_ptr = - isgn_name_ptr_sv_clip_distance; - isgn_sv_clip_distance.semantic_index = i / 4; - isgn_sv_clip_distance.system_value = dxbc::Name::kClipDistance; - isgn_sv_clip_distance.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - isgn_sv_clip_distance.register_index = input_register_index; - uint8_t isgn_sv_clip_distance_mask = - (UINT8_C(1) << std::min(input_clip_distance_count - i, - UINT32_C(4))) - - 1; - isgn_sv_clip_distance.mask = isgn_sv_clip_distance_mask; - isgn_sv_clip_distance.always_reads_mask = isgn_sv_clip_distance_mask; - } - if (input_cull_distance_count && i + 4 > input_clip_distance_count) { - dxbc::SignatureParameter& isgn_sv_cull_distance = - isgn_parameters[isgn_parameter_index++]; - isgn_sv_cull_distance.semantic_name_ptr = - isgn_name_ptr_sv_cull_distance; - isgn_sv_cull_distance.semantic_index = - isgn_cull_distance_semantic_index++; - isgn_sv_cull_distance.system_value = dxbc::Name::kCullDistance; - isgn_sv_cull_distance.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - isgn_sv_cull_distance.register_index = input_register_index; - uint8_t isgn_sv_cull_distance_mask = - (UINT8_C(1) << std::min(input_clip_and_cull_distance_count - i, - UINT32_C(4))) - - 1; - if (i < input_clip_distance_count) { - isgn_sv_cull_distance_mask &= - ~((UINT8_C(1) << (input_clip_distance_count - i)) - 1); - } - isgn_sv_cull_distance.mask = isgn_sv_cull_distance_mask; - isgn_sv_cull_distance.always_reads_mask = isgn_sv_cull_distance_mask; - } - ++input_register_index; - } - } - - // Point size (XEPSIZE). - if (key.has_point_size) { - input_register_point_size = input_register_index; - assert_true(isgn_parameter_index < isgn_parameter_count); - dxbc::SignatureParameter& isgn_point_size = - isgn_parameters[isgn_parameter_index++]; - isgn_point_size.semantic_name_ptr = isgn_name_ptr_xepsize; - isgn_point_size.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - isgn_point_size.register_index = input_register_index++; - isgn_point_size.mask = 0b0001; - isgn_point_size.always_reads_mask = - key.type == PipelineGeometryShader::kPointList ? 0b0001 : 0; - } - - assert_true(isgn_parameter_index == isgn_parameter_count); - } - - { - auto& blob_header = *reinterpret_cast( - shader_out.data() + blob_position_dwords); - blob_header.fourcc = dxbc::BlobHeader::FourCC::kInputSignature; - blob_position_dwords = uint32_t(shader_out.size()); - blob_header.size_bytes = - (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - - shader_out[blob_offset_position_dwords++]; - } - - // *************************************************************************** - // Output signature - // *************************************************************************** - - // Interpolators, point coordinates, position, clip distances. - uint32_t osgn_parameter_count = key.interpolator_count + - key.has_point_coordinates + 1 + - ((input_clip_distance_count + 3) / 4); - - // Reserve space for the header and the parameters. - shader_out[blob_offset_position_dwords] = - uint32_t(blob_position_dwords * sizeof(uint32_t)); - uint32_t osgn_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; - shader_out.resize(osgn_position_dwords + - sizeof(dxbc::Signature) / sizeof(uint32_t) + - sizeof(dxbc::SignatureParameterForGS) / sizeof(uint32_t) * - osgn_parameter_count); - - // Names (after the parameters). - name_ptr = - uint32_t((shader_out.size() - osgn_position_dwords) * sizeof(uint32_t)); - uint32_t osgn_name_ptr_texcoord = name_ptr; - if (key.interpolator_count) { - name_ptr += dxbc::AppendAlignedString(shader_out, "TEXCOORD"); - } - uint32_t osgn_name_ptr_xespritetexcoord = name_ptr; - if (key.has_point_coordinates) { - name_ptr += dxbc::AppendAlignedString(shader_out, "XESPRITETEXCOORD"); - } - uint32_t osgn_name_ptr_sv_position = name_ptr; - name_ptr += dxbc::AppendAlignedString(shader_out, "SV_Position"); - uint32_t osgn_name_ptr_sv_clip_distance = name_ptr; - if (input_clip_distance_count) { - name_ptr += dxbc::AppendAlignedString(shader_out, "SV_ClipDistance"); - } - - // Header and parameters. - uint32_t output_register_interpolators = UINT32_MAX; - uint32_t output_register_point_coordinates = UINT32_MAX; - uint32_t output_register_position; - uint32_t output_register_clip_distances = UINT32_MAX; - { - // Header. - auto& osgn_header = *reinterpret_cast( - shader_out.data() + osgn_position_dwords); - osgn_header.parameter_count = osgn_parameter_count; - osgn_header.parameter_info_ptr = sizeof(dxbc::Signature); - - // Parameters. - auto osgn_parameters = reinterpret_cast( - shader_out.data() + osgn_position_dwords + - sizeof(dxbc::Signature) / sizeof(uint32_t)); - uint32_t osgn_parameter_index = 0; - uint32_t output_register_index = 0; - - // Interpolators (TEXCOORD#). - if (key.interpolator_count) { - output_register_interpolators = output_register_index; - for (uint32_t i = 0; i < key.interpolator_count; ++i) { - assert_true(osgn_parameter_index < osgn_parameter_count); - dxbc::SignatureParameterForGS& osgn_interpolator = - osgn_parameters[osgn_parameter_index++]; - osgn_interpolator.semantic_name_ptr = osgn_name_ptr_texcoord; - osgn_interpolator.semantic_index = i; - osgn_interpolator.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - osgn_interpolator.register_index = output_register_index++; - osgn_interpolator.mask = 0b1111; - } - } - - // Point coordinates (XESPRITETEXCOORD). - if (key.has_point_coordinates) { - output_register_point_coordinates = output_register_index; - assert_true(osgn_parameter_index < osgn_parameter_count); - dxbc::SignatureParameterForGS& osgn_point_coordinates = - osgn_parameters[osgn_parameter_index++]; - osgn_point_coordinates.semantic_name_ptr = osgn_name_ptr_xespritetexcoord; - osgn_point_coordinates.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - osgn_point_coordinates.register_index = output_register_index++; - osgn_point_coordinates.mask = 0b0011; - osgn_point_coordinates.never_writes_mask = 0b1100; - } - - // Position (SV_Position). - output_register_position = output_register_index; - assert_true(osgn_parameter_index < osgn_parameter_count); - dxbc::SignatureParameterForGS& osgn_sv_position = - osgn_parameters[osgn_parameter_index++]; - osgn_sv_position.semantic_name_ptr = osgn_name_ptr_sv_position; - osgn_sv_position.system_value = dxbc::Name::kPosition; - osgn_sv_position.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - osgn_sv_position.register_index = output_register_index++; - osgn_sv_position.mask = 0b1111; - - // Clip distances (SV_ClipDistance#). - if (input_clip_distance_count) { - output_register_clip_distances = output_register_index; - for (uint32_t i = 0; i < input_clip_distance_count; i += 4) { - dxbc::SignatureParameterForGS& osgn_sv_clip_distance = - osgn_parameters[osgn_parameter_index++]; - osgn_sv_clip_distance.semantic_name_ptr = - osgn_name_ptr_sv_clip_distance; - osgn_sv_clip_distance.semantic_index = i / 4; - osgn_sv_clip_distance.system_value = dxbc::Name::kClipDistance; - osgn_sv_clip_distance.component_type = - dxbc::SignatureRegisterComponentType::kFloat32; - osgn_sv_clip_distance.register_index = output_register_index++; - uint8_t osgn_sv_clip_distance_mask = - (UINT8_C(1) << std::min(input_clip_distance_count - i, - UINT32_C(4))) - - 1; - osgn_sv_clip_distance.mask = osgn_sv_clip_distance_mask; - osgn_sv_clip_distance.never_writes_mask = - osgn_sv_clip_distance_mask ^ 0b1111; - } - } - - assert_true(osgn_parameter_index == osgn_parameter_count); - } - - { - auto& blob_header = *reinterpret_cast( - shader_out.data() + blob_position_dwords); - blob_header.fourcc = dxbc::BlobHeader::FourCC::kOutputSignatureForGS; - blob_position_dwords = uint32_t(shader_out.size()); - blob_header.size_bytes = - (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - - shader_out[blob_offset_position_dwords++]; - } - - // *************************************************************************** - // Shader program - // *************************************************************************** - - shader_out[blob_offset_position_dwords] = - uint32_t(blob_position_dwords * sizeof(uint32_t)); - uint32_t shex_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; - shader_out.resize(shex_position_dwords); - - shader_out.push_back( - dxbc::VersionToken(dxbc::ProgramType::kGeometryShader, 5, 1)); - // Reserve space for the length token. - shader_out.push_back(0); - - dxbc::Statistics stat; - std::memset(&stat, 0, sizeof(dxbc::Statistics)); - dxbc::Assembler a(shader_out, stat); - - a.OpDclGlobalFlags(dxbc::kGlobalFlagAllResourcesBound); - - if (system_cbuffer_size_vector_aligned_bytes) { - a.OpDclConstantBuffer( - dxbc::Src::CB( - dxbc::Src::Dcl, 0, - uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), - uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants)), - system_cbuffer_size_vector_aligned_bytes / (sizeof(uint32_t) * 4)); - } - - dxbc::Primitive input_primitive = dxbc::Primitive::kUndefined; - uint32_t input_primitive_vertex_count = 0; - dxbc::PrimitiveTopology output_primitive_topology = - dxbc::PrimitiveTopology::kUndefined; - uint32_t max_output_vertex_count = 0; - switch (key.type) { - case PipelineGeometryShader::kPointList: - // Point to a strip of 2 triangles. - input_primitive = dxbc::Primitive::kPoint; - input_primitive_vertex_count = 1; - output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; - max_output_vertex_count = 4; - break; - case PipelineGeometryShader::kRectangleList: - // Triangle to a strip of 2 triangles. - input_primitive = dxbc::Primitive::kTriangle; - input_primitive_vertex_count = 3; - output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; - max_output_vertex_count = 4; - break; - case PipelineGeometryShader::kQuadList: - // 4 vertices passed via kLineWithAdjacency to a strip of 2 triangles. - input_primitive = dxbc::Primitive::kLineWithAdjacency; - input_primitive_vertex_count = 4; - output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; - max_output_vertex_count = 4; - break; - default: - assert_unhandled_case(key.type); - } - - assert_false(key.interpolator_count && - input_register_interpolators == UINT32_MAX); - for (uint32_t i = 0; i < key.interpolator_count; ++i) { - a.OpDclInput(dxbc::Dest::V2D(input_primitive_vertex_count, - input_register_interpolators + i)); - } - a.OpDclInputSIV( - dxbc::Dest::V2D(input_primitive_vertex_count, input_register_position), - dxbc::Name::kPosition); - // Clip and cull plane declarations are separate in FXC-generated code even - // for a single register. - assert_false(input_clip_and_cull_distance_count && - input_register_clip_and_cull_distances == UINT32_MAX); - for (uint32_t i = 0; i < input_clip_and_cull_distance_count; i += 4) { - if (i < input_clip_distance_count) { - a.OpDclInput( - dxbc::Dest::V2D(input_primitive_vertex_count, - input_register_clip_and_cull_distances + (i >> 2), - (UINT32_C(1) << std::min( - input_clip_distance_count - i, UINT32_C(4))) - - 1)); - } - if (input_cull_distance_count && i + 4 > input_clip_distance_count) { - uint32_t cull_distance_mask = - (UINT32_C(1) << std::min(input_clip_and_cull_distance_count - i, - UINT32_C(4))) - - 1; - if (i < input_clip_distance_count) { - cull_distance_mask &= - ~((UINT32_C(1) << (input_clip_distance_count - i)) - 1); - } - a.OpDclInput( - dxbc::Dest::V2D(input_primitive_vertex_count, - input_register_clip_and_cull_distances + (i >> 2), - cull_distance_mask)); - } - } - if (key.has_point_size && key.type == PipelineGeometryShader::kPointList) { - assert_true(input_register_point_size != UINT32_MAX); - a.OpDclInput(dxbc::Dest::V2D(input_primitive_vertex_count, - input_register_point_size, 0b0001)); - } - - // At least 1 temporary register needed to discard primitives with NaN - // position. - size_t dcl_temps_count_position_dwords = a.OpDclTemps(1); - - a.OpDclInputPrimitive(input_primitive); - dxbc::Dest stream(dxbc::Dest::M(0)); - a.OpDclStream(stream); - a.OpDclOutputTopology(output_primitive_topology); - - assert_false(key.interpolator_count && - output_register_interpolators == UINT32_MAX); - for (uint32_t i = 0; i < key.interpolator_count; ++i) { - a.OpDclOutput(dxbc::Dest::O(output_register_interpolators + i)); - } - if (key.has_point_coordinates) { - assert_true(output_register_point_coordinates != UINT32_MAX); - a.OpDclOutput(dxbc::Dest::O(output_register_point_coordinates, 0b0011)); - } - a.OpDclOutputSIV(dxbc::Dest::O(output_register_position), - dxbc::Name::kPosition); - assert_false(input_clip_distance_count && - output_register_clip_distances == UINT32_MAX); - for (uint32_t i = 0; i < input_clip_distance_count; i += 4) { - a.OpDclOutputSIV( - dxbc::Dest::O(output_register_clip_distances + (i >> 2), - (UINT32_C(1) << std::min(input_clip_distance_count - i, - UINT32_C(4))) - - 1), - dxbc::Name::kClipDistance); - } - - a.OpDclMaxOutputVertexCount(max_output_vertex_count); - - // Note that after every emit, all o# become initialized and must be written - // to again. - // Also, FXC generates only movs (from statically or dynamically indexed - // v[#][#], from r#, or from a literal) to o# for some reason. - // emit_then_cut_stream must not be used - it crashes the shader compiler of - // AMD Software: Adrenalin Edition 23.3.2 on RDNA 3 if it's conditional (after - // a `retc` or inside an `if`), and it doesn't seem to be generated by FXC or - // DXC at all. - - // Discard the whole primitive if any vertex has a NaN position (may also be - // set to NaN for emulation of vertex killing with the OR operator). - for (uint32_t i = 0; i < input_primitive_vertex_count; ++i) { - a.OpNE(dxbc::Dest::R(0), dxbc::Src::V2D(i, input_register_position), - dxbc::Src::V2D(i, input_register_position)); - a.OpOr(dxbc::Dest::R(0, 0b0011), dxbc::Src::R(0, 0b0100), - dxbc::Src::R(0, 0b1110)); - a.OpOr(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX), - dxbc::Src::R(0, dxbc::Src::kYYYY)); - a.OpRetC(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); - } - - // Cull the whole primitive if any cull distance for all vertices in the - // primitive is < 0. - // TODO(Triang3l): For points, handle ps_ucp_mode (transform the host clip - // space to the guest one, calculate the distances to the user clip planes, - // cull using the distance from the center for modes 0, 1 and 2, cull and clip - // per-vertex for modes 2 and 3) - except for the vertex kill flag. - if (input_cull_distance_count) { - for (uint32_t i = 0; i < input_cull_distance_count; ++i) { - uint32_t cull_distance_register = input_register_clip_and_cull_distances + - ((input_clip_distance_count + i) >> 2); - uint32_t cull_distance_component = (input_clip_distance_count + i) & 3; - a.OpLT(dxbc::Dest::R(0, 0b0001), - dxbc::Src::V2D(0, cull_distance_register) - .Select(cull_distance_component), - dxbc::Src::LF(0.0f)); - for (uint32_t j = 1; j < input_primitive_vertex_count; ++j) { - a.OpLT(dxbc::Dest::R(0, 0b0010), - dxbc::Src::V2D(j, cull_distance_register) - .Select(cull_distance_component), - dxbc::Src::LF(0.0f)); - a.OpAnd(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX), - dxbc::Src::R(0, dxbc::Src::kYYYY)); - } - a.OpRetC(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); - } - } - - switch (key.type) { - case PipelineGeometryShader::kPointList: { - // Expand the point sprite, with left-to-right, top-to-bottom UVs. - dxbc::Src point_size_src(dxbc::Src::CB( - 0, uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), - offsetof(DxbcShaderTranslator::SystemConstants, - point_constant_diameter) >> - 4, - ((offsetof(DxbcShaderTranslator::SystemConstants, - point_constant_diameter[0]) >> - 2) & - 3) | - (((offsetof(DxbcShaderTranslator::SystemConstants, - point_constant_diameter[1]) >> - 2) & - 3) - << 2))); - if (key.has_point_size) { - // The vertex shader's header writes -1.0 to point_size by default, so - // any non-negative value means that it was overwritten by the - // translated vertex shader, and needs to be used instead of the - // constant size. The per-vertex diameter is already clamped in the - // vertex shader (combined with making it non-negative). - a.OpGE(dxbc::Dest::R(0, 0b0001), - dxbc::Src::V2D(0, input_register_point_size, dxbc::Src::kXXXX), - dxbc::Src::LF(0.0f)); - a.OpMovC(dxbc::Dest::R(0, 0b0011), dxbc::Src::R(0, dxbc::Src::kXXXX), - dxbc::Src::V2D(0, input_register_point_size, dxbc::Src::kXXXX), - point_size_src); - point_size_src = dxbc::Src::R(0, 0b0100); - } - // 4D5307F1 has zero-size snowflakes, drop them quicker, and also drop - // points with a constant size of zero since point lists may also be used - // as just "compute" with memexport. - // XY may contain the point size with the per-vertex override applied, use - // Z as temporary. - for (uint32_t i = 0; i < 2; ++i) { - a.OpLT(dxbc::Dest::R(0, 0b0100), dxbc::Src::LF(0.0f), - point_size_src.SelectFromSwizzled(i)); - a.OpRetC(false, dxbc::Src::R(0, dxbc::Src::kZZZZ)); - } - // Transform the diameter in the guest screen coordinates to radius in the - // normalized device coordinates, and then to the clip space by - // multiplying by W. - a.OpMul( - dxbc::Dest::R(0, 0b0011), point_size_src, - dxbc::Src::CB( - 0, - uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), - offsetof(DxbcShaderTranslator::SystemConstants, - point_screen_diameter_to_ndc_radius) >> - 4, - ((offsetof(DxbcShaderTranslator::SystemConstants, - point_screen_diameter_to_ndc_radius[0]) >> - 2) & - 3) | - (((offsetof(DxbcShaderTranslator::SystemConstants, - point_screen_diameter_to_ndc_radius[1]) >> - 2) & - 3) - << 2))); - point_size_src = dxbc::Src::R(0, 0b0100); - a.OpMul(dxbc::Dest::R(0, 0b0011), point_size_src, - dxbc::Src::V2D(0, input_register_position, dxbc::Src::kWWWW)); - dxbc::Src point_radius_x_src(point_size_src.SelectFromSwizzled(0)); - dxbc::Src point_radius_y_src(point_size_src.SelectFromSwizzled(1)); - - for (uint32_t i = 0; i < 4; ++i) { - // Same interpolators for the entire sprite. - for (uint32_t j = 0; j < key.interpolator_count; ++j) { - a.OpMov(dxbc::Dest::O(output_register_interpolators + j), - dxbc::Src::V2D(0, input_register_interpolators + j)); - } - // Top-left, top-right, bottom-left, bottom-right order (chosen - // arbitrarily, simply based on clockwise meaning front with - // FrontCounterClockwise = FALSE, but faceness is ignored for - // non-polygon primitive types). - // Bottom is -Y in Direct3D NDC, +V in point sprite coordinates. - if (key.has_point_coordinates) { - a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), - dxbc::Src::LF(float(i & 1), float(i >> 1), 0.0f, 0.0f)); - } - // FXC generates only `mov`s for o#, use temporary registers (r0.zw, as - // r0.xy already used for the point size) for calculations. - a.OpAdd(dxbc::Dest::R(0, 0b0100), - dxbc::Src::V2D(0, input_register_position, dxbc::Src::kXXXX), - (i & 1) ? point_radius_x_src : -point_radius_x_src); - a.OpAdd(dxbc::Dest::R(0, 0b1000), - dxbc::Src::V2D(0, input_register_position, dxbc::Src::kYYYY), - (i >> 1) ? -point_radius_y_src : point_radius_y_src); - a.OpMov(dxbc::Dest::O(output_register_position, 0b0011), - dxbc::Src::R(0, 0b1110)); - a.OpMov(dxbc::Dest::O(output_register_position, 0b1100), - dxbc::Src::V2D(0, input_register_position)); - // TODO(Triang3l): Handle ps_ucp_mode properly, clip expanded points if - // needed. - for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { - a.OpMov( - dxbc::Dest::O(output_register_clip_distances + (j >> 2), - (UINT32_C(1) << std::min( - input_clip_distance_count - j, UINT32_C(4))) - - 1), - dxbc::Src::V2D( - 0, input_register_clip_and_cull_distances + (j >> 2))); - } - a.OpEmitStream(stream); - } - a.OpCutStream(stream); - } break; - - case PipelineGeometryShader::kRectangleList: { - // Construct a strip with the fourth vertex generated by mirroring a - // vertex across the longest edge (the diagonal). - // - // Possible options: - // - // 0---1 - // | /| - // | / | - 12 is the longest edge, strip 0123 (most commonly used) - // |/ | v3 = v0 + (v1 - v0) + (v2 - v0), or v3 = -v0 + v1 + v2 - // 2--[3] - // - // 1---2 - // | /| - // | / | - 20 is the longest edge, strip 1203 - // |/ | - // 0--[3] - // - // 2---0 - // | /| - // | / | - 01 is the longest edge, strip 2013 - // |/ | - // 1--[3] - // - // Input vertices are implicitly indexable, dcl_indexRange is not needed - // for the first dimension of a v[#][#] index. - - // Get squares of edge lengths into r0.xyz to choose the longest edge. - // r0.x = ||12||^2 - a.OpAdd(dxbc::Dest::R(0, 0b0011), - dxbc::Src::V2D(2, input_register_position, 0b0100), - -dxbc::Src::V2D(1, input_register_position, 0b0100)); - a.OpDP2(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, 0b0100), - dxbc::Src::R(0, 0b0100)); - // r0.y = ||20||^2 - a.OpAdd(dxbc::Dest::R(0, 0b0110), - dxbc::Src::V2D(0, input_register_position, 0b0100 << 2), - -dxbc::Src::V2D(2, input_register_position, 0b0100 << 2)); - a.OpDP2(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(0, 0b1001), - dxbc::Src::R(0, 0b1001)); - // r0.z = ||01||^2 - a.OpAdd(dxbc::Dest::R(0, 0b1100), - dxbc::Src::V2D(1, input_register_position, 0b0100 << 4), - -dxbc::Src::V2D(0, input_register_position, 0b0100 << 4)); - a.OpDP2(dxbc::Dest::R(0, 0b0100), dxbc::Src::R(0, 0b1110), - dxbc::Src::R(0, 0b1110)); - - // Find the longest edge, and select the strip vertex indices into r0.xyz. - // r0.w = 12 > 20 - a.OpLT(dxbc::Dest::R(0, 0b1000), dxbc::Src::R(0, dxbc::Src::kYYYY), - dxbc::Src::R(0, dxbc::Src::kXXXX)); - // r0.x = 12 > 01 - a.OpLT(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kZZZZ), - dxbc::Src::R(0, dxbc::Src::kXXXX)); - // r0.x = 12 > 20 && 12 > 01 - a.OpAnd(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kWWWW), - dxbc::Src::R(0, dxbc::Src::kXXXX)); - a.OpIf(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); - { - // 12 is the longest edge, the first triangle in the strip is 012. - a.OpMov(dxbc::Dest::R(0, 0b0111), dxbc::Src::LU(0, 1, 2, 0)); - } - a.OpElse(); - { - // r0.x = 20 > 01 - a.OpLT(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kZZZZ), - dxbc::Src::R(0, dxbc::Src::kYYYY)); - // If 20 is the longest edge, the first triangle in the strip is 120. - // Otherwise, it's 201. - a.OpMovC(dxbc::Dest::R(0, 0b0111), dxbc::Src::R(0, dxbc::Src::kXXXX), - dxbc::Src::LU(1, 2, 0, 0), dxbc::Src::LU(2, 0, 1, 0)); - } - a.OpEndIf(); - - // Emit the triangle in the strip that consists of the original vertices. - for (uint32_t i = 0; i < 3; ++i) { - dxbc::Index input_vertex_index(0, i); - for (uint32_t j = 0; j < key.interpolator_count; ++j) { - a.OpMov(dxbc::Dest::O(output_register_interpolators + j), - dxbc::Src::V2D(input_vertex_index, - input_register_interpolators + j)); - } - if (key.has_point_coordinates) { - a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), - dxbc::Src::LF(0.0f)); - } - a.OpMov(dxbc::Dest::O(output_register_position), - dxbc::Src::V2D(input_vertex_index, input_register_position)); - for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { - a.OpMov( - dxbc::Dest::O(output_register_clip_distances + (j >> 2), - (UINT32_C(1) << std::min( - input_clip_distance_count - j, UINT32_C(4))) - - 1), - dxbc::Src::V2D( - input_vertex_index, - input_register_clip_and_cull_distances + (j >> 2))); - } - a.OpEmitStream(stream); - } - - // Construct the fourth vertex using r1 as temporary storage, including - // for the final operation as FXC generates only `mov`s for o#. - stat.temp_register_count = - std::max(UINT32_C(2), stat.temp_register_count); - for (uint32_t j = 0; j < key.interpolator_count; ++j) { - uint32_t input_register_interpolator = input_register_interpolators + j; - a.OpAdd(dxbc::Dest::R(1), - -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_interpolator), - dxbc::Src::V2D(dxbc::Index(0, 1), input_register_interpolator)); - a.OpAdd(dxbc::Dest::R(1), dxbc::Src::R(1), - dxbc::Src::V2D(dxbc::Index(0, 2), input_register_interpolator)); - a.OpMov(dxbc::Dest::O(output_register_interpolators + j), - dxbc::Src::R(1)); - } - if (key.has_point_coordinates) { - a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), - dxbc::Src::LF(0.0f)); - } - a.OpAdd(dxbc::Dest::R(1), - -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_position), - dxbc::Src::V2D(dxbc::Index(0, 1), input_register_position)); - a.OpAdd(dxbc::Dest::R(1), dxbc::Src::R(1), - dxbc::Src::V2D(dxbc::Index(0, 2), input_register_position)); - a.OpMov(dxbc::Dest::O(output_register_position), dxbc::Src::R(1)); - for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { - uint32_t clip_distance_mask = - (UINT32_C(1) << std::min(input_clip_distance_count - j, - UINT32_C(4))) - - 1; - uint32_t input_register_clip_distance = - input_register_clip_and_cull_distances + (j >> 2); - a.OpAdd( - dxbc::Dest::R(1, clip_distance_mask), - -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_clip_distance), - dxbc::Src::V2D(dxbc::Index(0, 1), input_register_clip_distance)); - a.OpAdd( - dxbc::Dest::R(1, clip_distance_mask), dxbc::Src::R(1), - dxbc::Src::V2D(dxbc::Index(0, 2), input_register_clip_distance)); - a.OpMov(dxbc::Dest::O(output_register_clip_distances + (j >> 2), - clip_distance_mask), - dxbc::Src::R(1)); - } - a.OpEmitStream(stream); - a.OpCutStream(stream); - } break; - - case PipelineGeometryShader::kQuadList: { - // Build the triangle strip from the original quad vertices in the - // 0, 1, 3, 2 order (like specified for GL_QUAD_STRIP). - // TODO(Triang3l): Find the correct decomposition of quads into triangles - // on the real hardware. - for (uint32_t i = 0; i < 4; ++i) { - uint32_t input_vertex_index = i ^ (i >> 1); - for (uint32_t j = 0; j < key.interpolator_count; ++j) { - a.OpMov(dxbc::Dest::O(output_register_interpolators + j), - dxbc::Src::V2D(input_vertex_index, - input_register_interpolators + j)); - } - if (key.has_point_coordinates) { - a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), - dxbc::Src::LF(0.0f)); - } - a.OpMov(dxbc::Dest::O(output_register_position), - dxbc::Src::V2D(input_vertex_index, input_register_position)); - for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { - a.OpMov( - dxbc::Dest::O(output_register_clip_distances + (j >> 2), - (UINT32_C(1) << std::min( - input_clip_distance_count - j, UINT32_C(4))) - - 1), - dxbc::Src::V2D( - input_vertex_index, - input_register_clip_and_cull_distances + (j >> 2))); - } - a.OpEmitStream(stream); - } - a.OpCutStream(stream); - } break; - - default: - assert_unhandled_case(key.type); - } - - a.OpRet(); - - // Write the actual number of temporary registers used. - shader_out[dcl_temps_count_position_dwords] = stat.temp_register_count; - - // Write the shader program length in dwords. - shader_out[shex_position_dwords + 1] = - uint32_t(shader_out.size()) - shex_position_dwords; - - { - auto& blob_header = *reinterpret_cast( - shader_out.data() + blob_position_dwords); - blob_header.fourcc = dxbc::BlobHeader::FourCC::kShaderEx; - blob_position_dwords = uint32_t(shader_out.size()); - blob_header.size_bytes = - (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - - shader_out[blob_offset_position_dwords++]; - } - - // *************************************************************************** - // Statistics - // *************************************************************************** - - shader_out[blob_offset_position_dwords] = - uint32_t(blob_position_dwords * sizeof(uint32_t)); - uint32_t stat_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; - shader_out.resize(stat_position_dwords + - sizeof(dxbc::Statistics) / sizeof(uint32_t)); - std::memcpy(shader_out.data() + stat_position_dwords, &stat, - sizeof(dxbc::Statistics)); - - { - auto& blob_header = *reinterpret_cast( - shader_out.data() + blob_position_dwords); - blob_header.fourcc = dxbc::BlobHeader::FourCC::kStatistics; - blob_position_dwords = uint32_t(shader_out.size()); - blob_header.size_bytes = - (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - - shader_out[blob_offset_position_dwords++]; - } - - // *************************************************************************** - // Container header - // *************************************************************************** - - uint32_t shader_size_bytes = uint32_t(shader_out.size() * sizeof(uint32_t)); - { - auto& container_header = - *reinterpret_cast(shader_out.data()); - container_header.InitializeIdentification(); - container_header.size_bytes = shader_size_bytes; - container_header.blob_count = kBlobCount; - CalculateDXBCChecksum( - reinterpret_cast(shader_out.data()), - static_cast(shader_size_bytes), - reinterpret_cast(&container_header.hash)); - } -} - const std::vector& PipelineCache::GetGeometryShader( GeometryShaderKey key) { auto it = geometry_shaders_.find(key); diff --git a/src/xenia/gpu/d3d12/pipeline_cache.h b/src/xenia/gpu/d3d12/pipeline_cache.h index 503e747c8..e4cc12fe3 100644 --- a/src/xenia/gpu/d3d12/pipeline_cache.h +++ b/src/xenia/gpu/d3d12/pipeline_cache.h @@ -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{}(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& shader_out); const std::vector& GetGeometryShader(GeometryShaderKey key); ID3D12PipelineState* CreateD3D12Pipeline( diff --git a/src/xenia/gpu/dxbc_geometry_shader.cc b/src/xenia/gpu/dxbc_geometry_shader.cc new file mode 100644 index 000000000..f1ee3d56a --- /dev/null +++ b/src/xenia/gpu/dxbc_geometry_shader.cc @@ -0,0 +1,1129 @@ +/** + ****************************************************************************** + * 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/dxbc_geometry_shader.h" + +#include +#include +#include +#include +#include + +#include "third_party/dxbc/DXBCChecksum.h" +#include "xenia/base/assert.h" +#include "xenia/base/math.h" +#include "xenia/gpu/dxbc.h" + +namespace xe { +namespace gpu { + +bool GetGeometryShaderKey( + PipelineGeometryShader geometry_shader_type, + DxbcShaderTranslator::Modification vertex_shader_modification, + DxbcShaderTranslator::Modification pixel_shader_modification, + GeometryShaderKey& key_out) { + if (geometry_shader_type == PipelineGeometryShader::kNone) { + return false; + } + assert_true(vertex_shader_modification.vertex.interpolator_mask == + pixel_shader_modification.pixel.interpolator_mask); + GeometryShaderKey key; + key.type = geometry_shader_type; + key.interpolator_count = + xe::bit_count(vertex_shader_modification.vertex.interpolator_mask); + key.user_clip_plane_count = + vertex_shader_modification.vertex.user_clip_plane_count; + key.user_clip_plane_cull = + vertex_shader_modification.vertex.user_clip_plane_cull; + key.has_vertex_kill_and = vertex_shader_modification.vertex.vertex_kill_and; + key.has_point_size = vertex_shader_modification.vertex.output_point_size; + key.has_point_coordinates = pixel_shader_modification.pixel.param_gen_point; + key_out = key; + return true; +} + +void CreateDxbcGeometryShader(GeometryShaderKey key, + std::vector& shader_out) { + shader_out.clear(); + + // RDEF, ISGN, OSG5, SHEX, STAT. + constexpr uint32_t kBlobCount = 5; + + // Allocate space for the container header and the blob offsets. + shader_out.resize(sizeof(dxbc::ContainerHeader) / sizeof(uint32_t) + + kBlobCount); + uint32_t blob_offset_position_dwords = + sizeof(dxbc::ContainerHeader) / sizeof(uint32_t); + uint32_t blob_position_dwords = uint32_t(shader_out.size()); + constexpr uint32_t kBlobHeaderSizeDwords = + sizeof(dxbc::BlobHeader) / sizeof(uint32_t); + + uint32_t name_ptr; + + // *************************************************************************** + // Resource definition + // *************************************************************************** + + shader_out[blob_offset_position_dwords] = + uint32_t(blob_position_dwords * sizeof(uint32_t)); + uint32_t rdef_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; + // Not needed, as the next operation done is resize, to allocate the space for + // both the blob header and the resource definition header. + // shader_out.resize(rdef_position_dwords); + + // RDEF header - the actual definitions will be written if needed. + shader_out.resize(rdef_position_dwords + + sizeof(dxbc::RdefHeader) / sizeof(uint32_t)); + // Generator name. + dxbc::AppendAlignedString(shader_out, "Xenia"); + { + auto& rdef_header = *reinterpret_cast( + shader_out.data() + rdef_position_dwords); + rdef_header.shader_model = dxbc::RdefShaderModel::kGeometryShader5_1; + rdef_header.compile_flags = + dxbc::kCompileFlagNoPreshader | dxbc::kCompileFlagPreferFlowControl | + dxbc::kCompileFlagIeeeStrictness | dxbc::kCompileFlagAllResourcesBound; + // Generator name is right after the header. + rdef_header.generator_name_ptr = sizeof(dxbc::RdefHeader); + rdef_header.fourcc = dxbc::RdefHeader::FourCC::k5_1; + rdef_header.InitializeSizes(); + } + + uint32_t system_cbuffer_size_vector_aligned_bytes = 0; + + if (key.type == PipelineGeometryShader::kPointList) { + // Need point parameters from the system constants. + + // Constant types - float2 only. + // Names. + name_ptr = + uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); + uint32_t rdef_name_ptr_float2 = name_ptr; + name_ptr += dxbc::AppendAlignedString(shader_out, "float2"); + // Types. + uint32_t rdef_type_float2_position_dwords = uint32_t(shader_out.size()); + uint32_t rdef_type_float2_ptr = + uint32_t((rdef_type_float2_position_dwords - rdef_position_dwords) * + sizeof(uint32_t)); + shader_out.resize(rdef_type_float2_position_dwords + + sizeof(dxbc::RdefType) / sizeof(uint32_t)); + { + auto& rdef_type_float2 = *reinterpret_cast( + shader_out.data() + rdef_type_float2_position_dwords); + rdef_type_float2.variable_class = dxbc::RdefVariableClass::kVector; + rdef_type_float2.variable_type = dxbc::RdefVariableType::kFloat; + rdef_type_float2.row_count = 1; + rdef_type_float2.column_count = 2; + rdef_type_float2.name_ptr = rdef_name_ptr_float2; + } + + // Constants: + // - float2 xe_point_constant_diameter + // - float2 xe_point_screen_diameter_to_ndc_radius + enum PointConstant : uint32_t { + kPointConstantConstantDiameter, + kPointConstantScreenDiameterToNDCRadius, + kPointConstantCount, + }; + // Names. + name_ptr = + uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); + uint32_t rdef_name_ptr_xe_point_constant_diameter = name_ptr; + name_ptr += + dxbc::AppendAlignedString(shader_out, "xe_point_constant_diameter"); + uint32_t rdef_name_ptr_xe_point_screen_diameter_to_ndc_radius = name_ptr; + name_ptr += dxbc::AppendAlignedString( + shader_out, "xe_point_screen_diameter_to_ndc_radius"); + // Constants. + uint32_t rdef_constants_position_dwords = uint32_t(shader_out.size()); + uint32_t rdef_constants_ptr = + uint32_t((rdef_constants_position_dwords - rdef_position_dwords) * + sizeof(uint32_t)); + shader_out.resize(rdef_constants_position_dwords + + sizeof(dxbc::RdefVariable) / sizeof(uint32_t) * + kPointConstantCount); + { + auto rdef_constants = reinterpret_cast( + shader_out.data() + rdef_constants_position_dwords); + // float2 xe_point_constant_diameter + static_assert( + sizeof(DxbcShaderTranslator::SystemConstants :: + point_constant_diameter) == sizeof(float) * 2, + "DxbcShaderTranslator point_constant_diameter system constant size " + "differs between the shader translator and geometry shader " + "generation"); + static_assert_size( + DxbcShaderTranslator::SystemConstants::point_constant_diameter, + sizeof(float) * 2); + dxbc::RdefVariable& rdef_constant_point_constant_diameter = + rdef_constants[kPointConstantConstantDiameter]; + rdef_constant_point_constant_diameter.name_ptr = + rdef_name_ptr_xe_point_constant_diameter; + rdef_constant_point_constant_diameter.start_offset_bytes = offsetof( + DxbcShaderTranslator::SystemConstants, point_constant_diameter); + rdef_constant_point_constant_diameter.size_bytes = sizeof(float) * 2; + rdef_constant_point_constant_diameter.flags = dxbc::kRdefVariableFlagUsed; + rdef_constant_point_constant_diameter.type_ptr = rdef_type_float2_ptr; + rdef_constant_point_constant_diameter.start_texture = UINT32_MAX; + rdef_constant_point_constant_diameter.start_sampler = UINT32_MAX; + // float2 xe_point_screen_diameter_to_ndc_radius + static_assert( + sizeof(DxbcShaderTranslator::SystemConstants :: + point_screen_diameter_to_ndc_radius) == sizeof(float) * 2, + "DxbcShaderTranslator point_screen_diameter_to_ndc_radius system " + "constant size differs between the shader translator and geometry " + "shader generation"); + dxbc::RdefVariable& rdef_constant_point_screen_diameter_to_ndc_radius = + rdef_constants[kPointConstantScreenDiameterToNDCRadius]; + rdef_constant_point_screen_diameter_to_ndc_radius.name_ptr = + rdef_name_ptr_xe_point_screen_diameter_to_ndc_radius; + rdef_constant_point_screen_diameter_to_ndc_radius.start_offset_bytes = + offsetof(DxbcShaderTranslator::SystemConstants, + point_screen_diameter_to_ndc_radius); + rdef_constant_point_screen_diameter_to_ndc_radius.size_bytes = + sizeof(float) * 2; + rdef_constant_point_screen_diameter_to_ndc_radius.flags = + dxbc::kRdefVariableFlagUsed; + rdef_constant_point_screen_diameter_to_ndc_radius.type_ptr = + rdef_type_float2_ptr; + rdef_constant_point_screen_diameter_to_ndc_radius.start_texture = + UINT32_MAX; + rdef_constant_point_screen_diameter_to_ndc_radius.start_sampler = + UINT32_MAX; + } + + // Constant buffers - xe_system_cbuffer only. + + // Names. + name_ptr = + uint32_t((shader_out.size() - rdef_position_dwords) * sizeof(uint32_t)); + uint32_t rdef_name_ptr_xe_system_cbuffer = name_ptr; + name_ptr += dxbc::AppendAlignedString(shader_out, "xe_system_cbuffer"); + // Constant buffers. + uint32_t rdef_cbuffer_position_dwords = uint32_t(shader_out.size()); + shader_out.resize(rdef_cbuffer_position_dwords + + sizeof(dxbc::RdefCbuffer) / sizeof(uint32_t)); + { + auto& rdef_cbuffer_system = *reinterpret_cast( + shader_out.data() + rdef_cbuffer_position_dwords); + rdef_cbuffer_system.name_ptr = rdef_name_ptr_xe_system_cbuffer; + rdef_cbuffer_system.variable_count = kPointConstantCount; + rdef_cbuffer_system.variables_ptr = rdef_constants_ptr; + auto rdef_constants = reinterpret_cast( + shader_out.data() + rdef_constants_position_dwords); + for (uint32_t i = 0; i < kPointConstantCount; ++i) { + system_cbuffer_size_vector_aligned_bytes = + std::max(system_cbuffer_size_vector_aligned_bytes, + rdef_constants[i].start_offset_bytes + + rdef_constants[i].size_bytes); + } + system_cbuffer_size_vector_aligned_bytes = + xe::align(system_cbuffer_size_vector_aligned_bytes, + uint32_t(sizeof(uint32_t) * 4)); + rdef_cbuffer_system.size_vector_aligned_bytes = + system_cbuffer_size_vector_aligned_bytes; + } + + // Bindings - xe_system_cbuffer only. + uint32_t rdef_binding_position_dwords = uint32_t(shader_out.size()); + shader_out.resize(rdef_binding_position_dwords + + sizeof(dxbc::RdefInputBind) / sizeof(uint32_t)); + { + auto& rdef_binding_cbuffer_system = + *reinterpret_cast(shader_out.data() + + rdef_binding_position_dwords); + rdef_binding_cbuffer_system.name_ptr = rdef_name_ptr_xe_system_cbuffer; + rdef_binding_cbuffer_system.type = dxbc::RdefInputType::kCbuffer; + rdef_binding_cbuffer_system.bind_point = + uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants); + rdef_binding_cbuffer_system.bind_count = 1; + rdef_binding_cbuffer_system.flags = dxbc::kRdefInputFlagUserPacked; + } + + // Pointers in the header. + { + auto& rdef_header = *reinterpret_cast( + shader_out.data() + rdef_position_dwords); + rdef_header.cbuffer_count = 1; + rdef_header.cbuffers_ptr = + uint32_t((rdef_cbuffer_position_dwords - rdef_position_dwords) * + sizeof(uint32_t)); + rdef_header.input_bind_count = 1; + rdef_header.input_binds_ptr = + uint32_t((rdef_binding_position_dwords - rdef_position_dwords) * + sizeof(uint32_t)); + } + } + + { + auto& blob_header = *reinterpret_cast( + shader_out.data() + blob_position_dwords); + blob_header.fourcc = dxbc::BlobHeader::FourCC::kResourceDefinition; + blob_position_dwords = uint32_t(shader_out.size()); + blob_header.size_bytes = + (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - + shader_out[blob_offset_position_dwords++]; + } + + // *************************************************************************** + // Input signature + // *************************************************************************** + + // Clip and cull distances are tightly packed together into registers, but + // have separate signature parameters with each being a vec4-aligned window. + uint32_t input_clip_distance_count = + key.user_clip_plane_cull ? 0 : key.user_clip_plane_count; + uint32_t input_cull_distance_count = + (key.user_clip_plane_cull ? key.user_clip_plane_count : 0) + + key.has_vertex_kill_and; + uint32_t input_clip_and_cull_distance_count = + input_clip_distance_count + input_cull_distance_count; + + // Interpolators, position, clip and cull distances (parameters containing + // only clip or cull distances, and also one parameter containing both if + // present), point size. + uint32_t isgn_parameter_count = + key.interpolator_count + 1 + + ((input_clip_and_cull_distance_count + 3) / 4) + + uint32_t(input_cull_distance_count && + (input_clip_distance_count & 3) != 0) + + key.has_point_size; + + // Reserve space for the header and the parameters. + shader_out[blob_offset_position_dwords] = + uint32_t(blob_position_dwords * sizeof(uint32_t)); + uint32_t isgn_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; + shader_out.resize(isgn_position_dwords + + sizeof(dxbc::Signature) / sizeof(uint32_t) + + sizeof(dxbc::SignatureParameter) / sizeof(uint32_t) * + isgn_parameter_count); + + // Names (after the parameters). + name_ptr = + uint32_t((shader_out.size() - isgn_position_dwords) * sizeof(uint32_t)); + uint32_t isgn_name_ptr_texcoord = name_ptr; + if (key.interpolator_count) { + name_ptr += dxbc::AppendAlignedString(shader_out, "TEXCOORD"); + } + uint32_t isgn_name_ptr_sv_position = name_ptr; + name_ptr += dxbc::AppendAlignedString(shader_out, "SV_Position"); + uint32_t isgn_name_ptr_sv_clip_distance = name_ptr; + if (input_clip_distance_count) { + name_ptr += dxbc::AppendAlignedString(shader_out, "SV_ClipDistance"); + } + uint32_t isgn_name_ptr_sv_cull_distance = name_ptr; + if (input_cull_distance_count) { + name_ptr += dxbc::AppendAlignedString(shader_out, "SV_CullDistance"); + } + uint32_t isgn_name_ptr_xepsize = name_ptr; + if (key.has_point_size) { + name_ptr += dxbc::AppendAlignedString(shader_out, "XEPSIZE"); + } + + // Header and parameters. + uint32_t input_register_interpolators = UINT32_MAX; + uint32_t input_register_position; + uint32_t input_register_clip_and_cull_distances = UINT32_MAX; + uint32_t input_register_point_size = UINT32_MAX; + { + // Header. + auto& isgn_header = *reinterpret_cast( + shader_out.data() + isgn_position_dwords); + isgn_header.parameter_count = isgn_parameter_count; + isgn_header.parameter_info_ptr = sizeof(dxbc::Signature); + + // Parameters. + auto isgn_parameters = reinterpret_cast( + shader_out.data() + isgn_position_dwords + + sizeof(dxbc::Signature) / sizeof(uint32_t)); + uint32_t isgn_parameter_index = 0; + uint32_t input_register_index = 0; + + // Interpolators (TEXCOORD#). + if (key.interpolator_count) { + input_register_interpolators = input_register_index; + for (uint32_t i = 0; i < key.interpolator_count; ++i) { + assert_true(isgn_parameter_index < isgn_parameter_count); + dxbc::SignatureParameter& isgn_interpolator = + isgn_parameters[isgn_parameter_index++]; + isgn_interpolator.semantic_name_ptr = isgn_name_ptr_texcoord; + isgn_interpolator.semantic_index = i; + isgn_interpolator.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + isgn_interpolator.register_index = input_register_index++; + isgn_interpolator.mask = 0b1111; + isgn_interpolator.always_reads_mask = 0b1111; + } + } + + // Position (SV_Position). + input_register_position = input_register_index; + assert_true(isgn_parameter_index < isgn_parameter_count); + dxbc::SignatureParameter& isgn_sv_position = + isgn_parameters[isgn_parameter_index++]; + isgn_sv_position.semantic_name_ptr = isgn_name_ptr_sv_position; + isgn_sv_position.system_value = dxbc::Name::kPosition; + isgn_sv_position.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + isgn_sv_position.register_index = input_register_index++; + isgn_sv_position.mask = 0b1111; + isgn_sv_position.always_reads_mask = 0b1111; + + // Clip and cull distances (SV_ClipDistance#, SV_CullDistance#). + if (input_clip_and_cull_distance_count) { + input_register_clip_and_cull_distances = input_register_index; + uint32_t isgn_cull_distance_semantic_index = 0; + for (uint32_t i = 0; i < input_clip_and_cull_distance_count; i += 4) { + if (i < input_clip_distance_count) { + dxbc::SignatureParameter& isgn_sv_clip_distance = + isgn_parameters[isgn_parameter_index++]; + isgn_sv_clip_distance.semantic_name_ptr = + isgn_name_ptr_sv_clip_distance; + isgn_sv_clip_distance.semantic_index = i / 4; + isgn_sv_clip_distance.system_value = dxbc::Name::kClipDistance; + isgn_sv_clip_distance.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + isgn_sv_clip_distance.register_index = input_register_index; + uint8_t isgn_sv_clip_distance_mask = + (UINT8_C(1) << std::min(input_clip_distance_count - i, + UINT32_C(4))) - + 1; + isgn_sv_clip_distance.mask = isgn_sv_clip_distance_mask; + isgn_sv_clip_distance.always_reads_mask = isgn_sv_clip_distance_mask; + } + if (input_cull_distance_count && i + 4 > input_clip_distance_count) { + dxbc::SignatureParameter& isgn_sv_cull_distance = + isgn_parameters[isgn_parameter_index++]; + isgn_sv_cull_distance.semantic_name_ptr = + isgn_name_ptr_sv_cull_distance; + isgn_sv_cull_distance.semantic_index = + isgn_cull_distance_semantic_index++; + isgn_sv_cull_distance.system_value = dxbc::Name::kCullDistance; + isgn_sv_cull_distance.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + isgn_sv_cull_distance.register_index = input_register_index; + uint8_t isgn_sv_cull_distance_mask = + (UINT8_C(1) << std::min(input_clip_and_cull_distance_count - i, + UINT32_C(4))) - + 1; + if (i < input_clip_distance_count) { + isgn_sv_cull_distance_mask &= + ~((UINT8_C(1) << (input_clip_distance_count - i)) - 1); + } + isgn_sv_cull_distance.mask = isgn_sv_cull_distance_mask; + isgn_sv_cull_distance.always_reads_mask = isgn_sv_cull_distance_mask; + } + ++input_register_index; + } + } + + // Point size (XEPSIZE). + if (key.has_point_size) { + input_register_point_size = input_register_index; + assert_true(isgn_parameter_index < isgn_parameter_count); + dxbc::SignatureParameter& isgn_point_size = + isgn_parameters[isgn_parameter_index++]; + isgn_point_size.semantic_name_ptr = isgn_name_ptr_xepsize; + isgn_point_size.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + isgn_point_size.register_index = input_register_index++; + isgn_point_size.mask = 0b0001; + isgn_point_size.always_reads_mask = + key.type == PipelineGeometryShader::kPointList ? 0b0001 : 0; + } + + assert_true(isgn_parameter_index == isgn_parameter_count); + } + + { + auto& blob_header = *reinterpret_cast( + shader_out.data() + blob_position_dwords); + blob_header.fourcc = dxbc::BlobHeader::FourCC::kInputSignature; + blob_position_dwords = uint32_t(shader_out.size()); + blob_header.size_bytes = + (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - + shader_out[blob_offset_position_dwords++]; + } + + // *************************************************************************** + // Output signature + // *************************************************************************** + + // Interpolators, point coordinates, position, clip distances. + uint32_t osgn_parameter_count = key.interpolator_count + + key.has_point_coordinates + 1 + + ((input_clip_distance_count + 3) / 4); + + // Reserve space for the header and the parameters. + shader_out[blob_offset_position_dwords] = + uint32_t(blob_position_dwords * sizeof(uint32_t)); + uint32_t osgn_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; + shader_out.resize(osgn_position_dwords + + sizeof(dxbc::Signature) / sizeof(uint32_t) + + sizeof(dxbc::SignatureParameterForGS) / sizeof(uint32_t) * + osgn_parameter_count); + + // Names (after the parameters). + name_ptr = + uint32_t((shader_out.size() - osgn_position_dwords) * sizeof(uint32_t)); + uint32_t osgn_name_ptr_texcoord = name_ptr; + if (key.interpolator_count) { + name_ptr += dxbc::AppendAlignedString(shader_out, "TEXCOORD"); + } + uint32_t osgn_name_ptr_xespritetexcoord = name_ptr; + if (key.has_point_coordinates) { + name_ptr += dxbc::AppendAlignedString(shader_out, "XESPRITETEXCOORD"); + } + uint32_t osgn_name_ptr_sv_position = name_ptr; + name_ptr += dxbc::AppendAlignedString(shader_out, "SV_Position"); + uint32_t osgn_name_ptr_sv_clip_distance = name_ptr; + if (input_clip_distance_count) { + name_ptr += dxbc::AppendAlignedString(shader_out, "SV_ClipDistance"); + } + + // Header and parameters. + uint32_t output_register_interpolators = UINT32_MAX; + uint32_t output_register_point_coordinates = UINT32_MAX; + uint32_t output_register_position; + uint32_t output_register_clip_distances = UINT32_MAX; + { + // Header. + auto& osgn_header = *reinterpret_cast( + shader_out.data() + osgn_position_dwords); + osgn_header.parameter_count = osgn_parameter_count; + osgn_header.parameter_info_ptr = sizeof(dxbc::Signature); + + // Parameters. + auto osgn_parameters = reinterpret_cast( + shader_out.data() + osgn_position_dwords + + sizeof(dxbc::Signature) / sizeof(uint32_t)); + uint32_t osgn_parameter_index = 0; + uint32_t output_register_index = 0; + + // Interpolators (TEXCOORD#). + if (key.interpolator_count) { + output_register_interpolators = output_register_index; + for (uint32_t i = 0; i < key.interpolator_count; ++i) { + assert_true(osgn_parameter_index < osgn_parameter_count); + dxbc::SignatureParameterForGS& osgn_interpolator = + osgn_parameters[osgn_parameter_index++]; + osgn_interpolator.semantic_name_ptr = osgn_name_ptr_texcoord; + osgn_interpolator.semantic_index = i; + osgn_interpolator.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + osgn_interpolator.register_index = output_register_index++; + osgn_interpolator.mask = 0b1111; + } + } + + // Point coordinates (XESPRITETEXCOORD). + if (key.has_point_coordinates) { + output_register_point_coordinates = output_register_index; + assert_true(osgn_parameter_index < osgn_parameter_count); + dxbc::SignatureParameterForGS& osgn_point_coordinates = + osgn_parameters[osgn_parameter_index++]; + osgn_point_coordinates.semantic_name_ptr = osgn_name_ptr_xespritetexcoord; + osgn_point_coordinates.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + osgn_point_coordinates.register_index = output_register_index++; + osgn_point_coordinates.mask = 0b0011; + osgn_point_coordinates.never_writes_mask = 0b1100; + } + + // Position (SV_Position). + output_register_position = output_register_index; + assert_true(osgn_parameter_index < osgn_parameter_count); + dxbc::SignatureParameterForGS& osgn_sv_position = + osgn_parameters[osgn_parameter_index++]; + osgn_sv_position.semantic_name_ptr = osgn_name_ptr_sv_position; + osgn_sv_position.system_value = dxbc::Name::kPosition; + osgn_sv_position.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + osgn_sv_position.register_index = output_register_index++; + osgn_sv_position.mask = 0b1111; + + // Clip distances (SV_ClipDistance#). + if (input_clip_distance_count) { + output_register_clip_distances = output_register_index; + for (uint32_t i = 0; i < input_clip_distance_count; i += 4) { + dxbc::SignatureParameterForGS& osgn_sv_clip_distance = + osgn_parameters[osgn_parameter_index++]; + osgn_sv_clip_distance.semantic_name_ptr = + osgn_name_ptr_sv_clip_distance; + osgn_sv_clip_distance.semantic_index = i / 4; + osgn_sv_clip_distance.system_value = dxbc::Name::kClipDistance; + osgn_sv_clip_distance.component_type = + dxbc::SignatureRegisterComponentType::kFloat32; + osgn_sv_clip_distance.register_index = output_register_index++; + uint8_t osgn_sv_clip_distance_mask = + (UINT8_C(1) << std::min(input_clip_distance_count - i, + UINT32_C(4))) - + 1; + osgn_sv_clip_distance.mask = osgn_sv_clip_distance_mask; + osgn_sv_clip_distance.never_writes_mask = + osgn_sv_clip_distance_mask ^ 0b1111; + } + } + + assert_true(osgn_parameter_index == osgn_parameter_count); + } + + { + auto& blob_header = *reinterpret_cast( + shader_out.data() + blob_position_dwords); + blob_header.fourcc = dxbc::BlobHeader::FourCC::kOutputSignatureForGS; + blob_position_dwords = uint32_t(shader_out.size()); + blob_header.size_bytes = + (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - + shader_out[blob_offset_position_dwords++]; + } + + // *************************************************************************** + // Shader program + // *************************************************************************** + + shader_out[blob_offset_position_dwords] = + uint32_t(blob_position_dwords * sizeof(uint32_t)); + uint32_t shex_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; + shader_out.resize(shex_position_dwords); + + shader_out.push_back( + dxbc::VersionToken(dxbc::ProgramType::kGeometryShader, 5, 1)); + // Reserve space for the length token. + shader_out.push_back(0); + + dxbc::Statistics stat; + std::memset(&stat, 0, sizeof(dxbc::Statistics)); + dxbc::Assembler a(shader_out, stat); + + a.OpDclGlobalFlags(dxbc::kGlobalFlagAllResourcesBound); + + if (system_cbuffer_size_vector_aligned_bytes) { + a.OpDclConstantBuffer( + dxbc::Src::CB( + dxbc::Src::Dcl, 0, + uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), + uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants)), + system_cbuffer_size_vector_aligned_bytes / (sizeof(uint32_t) * 4)); + } + + dxbc::Primitive input_primitive = dxbc::Primitive::kUndefined; + uint32_t input_primitive_vertex_count = 0; + dxbc::PrimitiveTopology output_primitive_topology = + dxbc::PrimitiveTopology::kUndefined; + uint32_t max_output_vertex_count = 0; + switch (key.type) { + case PipelineGeometryShader::kPointList: + // Point to a strip of 2 triangles. + input_primitive = dxbc::Primitive::kPoint; + input_primitive_vertex_count = 1; + output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; + max_output_vertex_count = 4; + break; + case PipelineGeometryShader::kRectangleList: + // Triangle to a strip of 2 triangles. + input_primitive = dxbc::Primitive::kTriangle; + input_primitive_vertex_count = 3; + output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; + max_output_vertex_count = 4; + break; + case PipelineGeometryShader::kQuadList: + // 4 vertices passed via kLineWithAdjacency to a strip of 2 triangles. + input_primitive = dxbc::Primitive::kLineWithAdjacency; + input_primitive_vertex_count = 4; + output_primitive_topology = dxbc::PrimitiveTopology::kTriangleStrip; + max_output_vertex_count = 4; + break; + default: + assert_unhandled_case(key.type); + } + + assert_false(key.interpolator_count && + input_register_interpolators == UINT32_MAX); + for (uint32_t i = 0; i < key.interpolator_count; ++i) { + a.OpDclInput(dxbc::Dest::V2D(input_primitive_vertex_count, + input_register_interpolators + i)); + } + a.OpDclInputSIV( + dxbc::Dest::V2D(input_primitive_vertex_count, input_register_position), + dxbc::Name::kPosition); + // Clip and cull plane declarations are separate in FXC-generated code even + // for a single register. + assert_false(input_clip_and_cull_distance_count && + input_register_clip_and_cull_distances == UINT32_MAX); + for (uint32_t i = 0; i < input_clip_and_cull_distance_count; i += 4) { + if (i < input_clip_distance_count) { + a.OpDclInput( + dxbc::Dest::V2D(input_primitive_vertex_count, + input_register_clip_and_cull_distances + (i >> 2), + (UINT32_C(1) << std::min( + input_clip_distance_count - i, UINT32_C(4))) - + 1)); + } + if (input_cull_distance_count && i + 4 > input_clip_distance_count) { + uint32_t cull_distance_mask = + (UINT32_C(1) << std::min(input_clip_and_cull_distance_count - i, + UINT32_C(4))) - + 1; + if (i < input_clip_distance_count) { + cull_distance_mask &= + ~((UINT32_C(1) << (input_clip_distance_count - i)) - 1); + } + a.OpDclInput( + dxbc::Dest::V2D(input_primitive_vertex_count, + input_register_clip_and_cull_distances + (i >> 2), + cull_distance_mask)); + } + } + if (key.has_point_size && key.type == PipelineGeometryShader::kPointList) { + assert_true(input_register_point_size != UINT32_MAX); + a.OpDclInput(dxbc::Dest::V2D(input_primitive_vertex_count, + input_register_point_size, 0b0001)); + } + + // At least 1 temporary register needed to discard primitives with NaN + // position. + size_t dcl_temps_count_position_dwords = a.OpDclTemps(1); + + a.OpDclInputPrimitive(input_primitive); + dxbc::Dest stream(dxbc::Dest::M(0)); + a.OpDclStream(stream); + a.OpDclOutputTopology(output_primitive_topology); + + assert_false(key.interpolator_count && + output_register_interpolators == UINT32_MAX); + for (uint32_t i = 0; i < key.interpolator_count; ++i) { + a.OpDclOutput(dxbc::Dest::O(output_register_interpolators + i)); + } + if (key.has_point_coordinates) { + assert_true(output_register_point_coordinates != UINT32_MAX); + a.OpDclOutput(dxbc::Dest::O(output_register_point_coordinates, 0b0011)); + } + a.OpDclOutputSIV(dxbc::Dest::O(output_register_position), + dxbc::Name::kPosition); + assert_false(input_clip_distance_count && + output_register_clip_distances == UINT32_MAX); + for (uint32_t i = 0; i < input_clip_distance_count; i += 4) { + a.OpDclOutputSIV( + dxbc::Dest::O(output_register_clip_distances + (i >> 2), + (UINT32_C(1) << std::min(input_clip_distance_count - i, + UINT32_C(4))) - + 1), + dxbc::Name::kClipDistance); + } + + a.OpDclMaxOutputVertexCount(max_output_vertex_count); + + // Note that after every emit, all o# become initialized and must be written + // to again. + // Also, FXC generates only movs (from statically or dynamically indexed + // v[#][#], from r#, or from a literal) to o# for some reason. + // emit_then_cut_stream must not be used - it crashes the shader compiler of + // AMD Software: Adrenalin Edition 23.3.2 on RDNA 3 if it's conditional (after + // a `retc` or inside an `if`), and it doesn't seem to be generated by FXC or + // DXC at all. + + // Discard the whole primitive if any vertex has a NaN position (may also be + // set to NaN for emulation of vertex killing with the OR operator). + for (uint32_t i = 0; i < input_primitive_vertex_count; ++i) { + a.OpNE(dxbc::Dest::R(0), dxbc::Src::V2D(i, input_register_position), + dxbc::Src::V2D(i, input_register_position)); + a.OpOr(dxbc::Dest::R(0, 0b0011), dxbc::Src::R(0, 0b0100), + dxbc::Src::R(0, 0b1110)); + a.OpOr(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX), + dxbc::Src::R(0, dxbc::Src::kYYYY)); + a.OpRetC(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); + } + + // Cull the whole primitive if any cull distance for all vertices in the + // primitive is < 0. + // TODO(Triang3l): For points, handle ps_ucp_mode (transform the host clip + // space to the guest one, calculate the distances to the user clip planes, + // cull using the distance from the center for modes 0, 1 and 2, cull and clip + // per-vertex for modes 2 and 3) - except for the vertex kill flag. + if (input_cull_distance_count) { + for (uint32_t i = 0; i < input_cull_distance_count; ++i) { + uint32_t cull_distance_register = input_register_clip_and_cull_distances + + ((input_clip_distance_count + i) >> 2); + uint32_t cull_distance_component = (input_clip_distance_count + i) & 3; + a.OpLT(dxbc::Dest::R(0, 0b0001), + dxbc::Src::V2D(0, cull_distance_register) + .Select(cull_distance_component), + dxbc::Src::LF(0.0f)); + for (uint32_t j = 1; j < input_primitive_vertex_count; ++j) { + a.OpLT(dxbc::Dest::R(0, 0b0010), + dxbc::Src::V2D(j, cull_distance_register) + .Select(cull_distance_component), + dxbc::Src::LF(0.0f)); + a.OpAnd(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kXXXX), + dxbc::Src::R(0, dxbc::Src::kYYYY)); + } + a.OpRetC(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); + } + } + + switch (key.type) { + case PipelineGeometryShader::kPointList: { + // Expand the point sprite, with left-to-right, top-to-bottom UVs. + dxbc::Src point_size_src(dxbc::Src::CB( + 0, uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), + offsetof(DxbcShaderTranslator::SystemConstants, + point_constant_diameter) >> + 4, + ((offsetof(DxbcShaderTranslator::SystemConstants, + point_constant_diameter[0]) >> + 2) & + 3) | + (((offsetof(DxbcShaderTranslator::SystemConstants, + point_constant_diameter[1]) >> + 2) & + 3) + << 2))); + if (key.has_point_size) { + // The vertex shader's header writes -1.0 to point_size by default, so + // any non-negative value means that it was overwritten by the + // translated vertex shader, and needs to be used instead of the + // constant size. The per-vertex diameter is already clamped in the + // vertex shader (combined with making it non-negative). + a.OpGE(dxbc::Dest::R(0, 0b0001), + dxbc::Src::V2D(0, input_register_point_size, dxbc::Src::kXXXX), + dxbc::Src::LF(0.0f)); + a.OpMovC(dxbc::Dest::R(0, 0b0011), dxbc::Src::R(0, dxbc::Src::kXXXX), + dxbc::Src::V2D(0, input_register_point_size, dxbc::Src::kXXXX), + point_size_src); + point_size_src = dxbc::Src::R(0, 0b0100); + } + // 4D5307F1 has zero-size snowflakes, drop them quicker, and also drop + // points with a constant size of zero since point lists may also be used + // as just "compute" with memexport. + // XY may contain the point size with the per-vertex override applied, use + // Z as temporary. + for (uint32_t i = 0; i < 2; ++i) { + a.OpLT(dxbc::Dest::R(0, 0b0100), dxbc::Src::LF(0.0f), + point_size_src.SelectFromSwizzled(i)); + a.OpRetC(false, dxbc::Src::R(0, dxbc::Src::kZZZZ)); + } + // Transform the diameter in the guest screen coordinates to radius in the + // normalized device coordinates, and then to the clip space by + // multiplying by W. + a.OpMul( + dxbc::Dest::R(0, 0b0011), point_size_src, + dxbc::Src::CB( + 0, + uint32_t(DxbcShaderTranslator::CbufferRegister::kSystemConstants), + offsetof(DxbcShaderTranslator::SystemConstants, + point_screen_diameter_to_ndc_radius) >> + 4, + ((offsetof(DxbcShaderTranslator::SystemConstants, + point_screen_diameter_to_ndc_radius[0]) >> + 2) & + 3) | + (((offsetof(DxbcShaderTranslator::SystemConstants, + point_screen_diameter_to_ndc_radius[1]) >> + 2) & + 3) + << 2))); + point_size_src = dxbc::Src::R(0, 0b0100); + a.OpMul(dxbc::Dest::R(0, 0b0011), point_size_src, + dxbc::Src::V2D(0, input_register_position, dxbc::Src::kWWWW)); + dxbc::Src point_radius_x_src(point_size_src.SelectFromSwizzled(0)); + dxbc::Src point_radius_y_src(point_size_src.SelectFromSwizzled(1)); + + for (uint32_t i = 0; i < 4; ++i) { + // Same interpolators for the entire sprite. + for (uint32_t j = 0; j < key.interpolator_count; ++j) { + a.OpMov(dxbc::Dest::O(output_register_interpolators + j), + dxbc::Src::V2D(0, input_register_interpolators + j)); + } + // Top-left, top-right, bottom-left, bottom-right order (chosen + // arbitrarily, simply based on clockwise meaning front with + // FrontCounterClockwise = FALSE, but faceness is ignored for + // non-polygon primitive types). + // Bottom is -Y in Direct3D NDC, +V in point sprite coordinates. + if (key.has_point_coordinates) { + a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), + dxbc::Src::LF(float(i & 1), float(i >> 1), 0.0f, 0.0f)); + } + // FXC generates only `mov`s for o#, use temporary registers (r0.zw, as + // r0.xy already used for the point size) for calculations. + a.OpAdd(dxbc::Dest::R(0, 0b0100), + dxbc::Src::V2D(0, input_register_position, dxbc::Src::kXXXX), + (i & 1) ? point_radius_x_src : -point_radius_x_src); + a.OpAdd(dxbc::Dest::R(0, 0b1000), + dxbc::Src::V2D(0, input_register_position, dxbc::Src::kYYYY), + (i >> 1) ? -point_radius_y_src : point_radius_y_src); + a.OpMov(dxbc::Dest::O(output_register_position, 0b0011), + dxbc::Src::R(0, 0b1110)); + a.OpMov(dxbc::Dest::O(output_register_position, 0b1100), + dxbc::Src::V2D(0, input_register_position)); + // TODO(Triang3l): Handle ps_ucp_mode properly, clip expanded points if + // needed. + for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { + a.OpMov( + dxbc::Dest::O(output_register_clip_distances + (j >> 2), + (UINT32_C(1) << std::min( + input_clip_distance_count - j, UINT32_C(4))) - + 1), + dxbc::Src::V2D( + 0, input_register_clip_and_cull_distances + (j >> 2))); + } + a.OpEmitStream(stream); + } + a.OpCutStream(stream); + } break; + + case PipelineGeometryShader::kRectangleList: { + // Construct a strip with the fourth vertex generated by mirroring a + // vertex across the longest edge (the diagonal). + // + // Possible options: + // + // 0---1 + // | /| + // | / | - 12 is the longest edge, strip 0123 (most commonly used) + // |/ | v3 = v0 + (v1 - v0) + (v2 - v0), or v3 = -v0 + v1 + v2 + // 2--[3] + // + // 1---2 + // | /| + // | / | - 20 is the longest edge, strip 1203 + // |/ | + // 0--[3] + // + // 2---0 + // | /| + // | / | - 01 is the longest edge, strip 2013 + // |/ | + // 1--[3] + // + // Input vertices are implicitly indexable, dcl_indexRange is not needed + // for the first dimension of a v[#][#] index. + + // Get squares of edge lengths into r0.xyz to choose the longest edge. + // r0.x = ||12||^2 + a.OpAdd(dxbc::Dest::R(0, 0b0011), + dxbc::Src::V2D(2, input_register_position, 0b0100), + -dxbc::Src::V2D(1, input_register_position, 0b0100)); + a.OpDP2(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, 0b0100), + dxbc::Src::R(0, 0b0100)); + // r0.y = ||20||^2 + a.OpAdd(dxbc::Dest::R(0, 0b0110), + dxbc::Src::V2D(0, input_register_position, 0b0100 << 2), + -dxbc::Src::V2D(2, input_register_position, 0b0100 << 2)); + a.OpDP2(dxbc::Dest::R(0, 0b0010), dxbc::Src::R(0, 0b1001), + dxbc::Src::R(0, 0b1001)); + // r0.z = ||01||^2 + a.OpAdd(dxbc::Dest::R(0, 0b1100), + dxbc::Src::V2D(1, input_register_position, 0b0100 << 4), + -dxbc::Src::V2D(0, input_register_position, 0b0100 << 4)); + a.OpDP2(dxbc::Dest::R(0, 0b0100), dxbc::Src::R(0, 0b1110), + dxbc::Src::R(0, 0b1110)); + + // Find the longest edge, and select the strip vertex indices into r0.xyz. + // r0.w = 12 > 20 + a.OpLT(dxbc::Dest::R(0, 0b1000), dxbc::Src::R(0, dxbc::Src::kYYYY), + dxbc::Src::R(0, dxbc::Src::kXXXX)); + // r0.x = 12 > 01 + a.OpLT(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kZZZZ), + dxbc::Src::R(0, dxbc::Src::kXXXX)); + // r0.x = 12 > 20 && 12 > 01 + a.OpAnd(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kWWWW), + dxbc::Src::R(0, dxbc::Src::kXXXX)); + a.OpIf(true, dxbc::Src::R(0, dxbc::Src::kXXXX)); + { + // 12 is the longest edge, the first triangle in the strip is 012. + a.OpMov(dxbc::Dest::R(0, 0b0111), dxbc::Src::LU(0, 1, 2, 0)); + } + a.OpElse(); + { + // r0.x = 20 > 01 + a.OpLT(dxbc::Dest::R(0, 0b0001), dxbc::Src::R(0, dxbc::Src::kZZZZ), + dxbc::Src::R(0, dxbc::Src::kYYYY)); + // If 20 is the longest edge, the first triangle in the strip is 120. + // Otherwise, it's 201. + a.OpMovC(dxbc::Dest::R(0, 0b0111), dxbc::Src::R(0, dxbc::Src::kXXXX), + dxbc::Src::LU(1, 2, 0, 0), dxbc::Src::LU(2, 0, 1, 0)); + } + a.OpEndIf(); + + // Emit the triangle in the strip that consists of the original vertices. + for (uint32_t i = 0; i < 3; ++i) { + dxbc::Index input_vertex_index(0, i); + for (uint32_t j = 0; j < key.interpolator_count; ++j) { + a.OpMov(dxbc::Dest::O(output_register_interpolators + j), + dxbc::Src::V2D(input_vertex_index, + input_register_interpolators + j)); + } + if (key.has_point_coordinates) { + a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), + dxbc::Src::LF(0.0f)); + } + a.OpMov(dxbc::Dest::O(output_register_position), + dxbc::Src::V2D(input_vertex_index, input_register_position)); + for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { + a.OpMov( + dxbc::Dest::O(output_register_clip_distances + (j >> 2), + (UINT32_C(1) << std::min( + input_clip_distance_count - j, UINT32_C(4))) - + 1), + dxbc::Src::V2D( + input_vertex_index, + input_register_clip_and_cull_distances + (j >> 2))); + } + a.OpEmitStream(stream); + } + + // Construct the fourth vertex using r1 as temporary storage, including + // for the final operation as FXC generates only `mov`s for o#. + stat.temp_register_count = + std::max(UINT32_C(2), stat.temp_register_count); + for (uint32_t j = 0; j < key.interpolator_count; ++j) { + uint32_t input_register_interpolator = input_register_interpolators + j; + a.OpAdd(dxbc::Dest::R(1), + -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_interpolator), + dxbc::Src::V2D(dxbc::Index(0, 1), input_register_interpolator)); + a.OpAdd(dxbc::Dest::R(1), dxbc::Src::R(1), + dxbc::Src::V2D(dxbc::Index(0, 2), input_register_interpolator)); + a.OpMov(dxbc::Dest::O(output_register_interpolators + j), + dxbc::Src::R(1)); + } + if (key.has_point_coordinates) { + a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), + dxbc::Src::LF(0.0f)); + } + a.OpAdd(dxbc::Dest::R(1), + -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_position), + dxbc::Src::V2D(dxbc::Index(0, 1), input_register_position)); + a.OpAdd(dxbc::Dest::R(1), dxbc::Src::R(1), + dxbc::Src::V2D(dxbc::Index(0, 2), input_register_position)); + a.OpMov(dxbc::Dest::O(output_register_position), dxbc::Src::R(1)); + for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { + uint32_t clip_distance_mask = + (UINT32_C(1) << std::min(input_clip_distance_count - j, + UINT32_C(4))) - + 1; + uint32_t input_register_clip_distance = + input_register_clip_and_cull_distances + (j >> 2); + a.OpAdd( + dxbc::Dest::R(1, clip_distance_mask), + -dxbc::Src::V2D(dxbc::Index(0, 0), input_register_clip_distance), + dxbc::Src::V2D(dxbc::Index(0, 1), input_register_clip_distance)); + a.OpAdd( + dxbc::Dest::R(1, clip_distance_mask), dxbc::Src::R(1), + dxbc::Src::V2D(dxbc::Index(0, 2), input_register_clip_distance)); + a.OpMov(dxbc::Dest::O(output_register_clip_distances + (j >> 2), + clip_distance_mask), + dxbc::Src::R(1)); + } + a.OpEmitStream(stream); + a.OpCutStream(stream); + } break; + + case PipelineGeometryShader::kQuadList: { + // Build the triangle strip from the original quad vertices in the + // 0, 1, 3, 2 order (like specified for GL_QUAD_STRIP). + // TODO(Triang3l): Find the correct decomposition of quads into triangles + // on the real hardware. + for (uint32_t i = 0; i < 4; ++i) { + uint32_t input_vertex_index = i ^ (i >> 1); + for (uint32_t j = 0; j < key.interpolator_count; ++j) { + a.OpMov(dxbc::Dest::O(output_register_interpolators + j), + dxbc::Src::V2D(input_vertex_index, + input_register_interpolators + j)); + } + if (key.has_point_coordinates) { + a.OpMov(dxbc::Dest::O(output_register_point_coordinates, 0b0011), + dxbc::Src::LF(0.0f)); + } + a.OpMov(dxbc::Dest::O(output_register_position), + dxbc::Src::V2D(input_vertex_index, input_register_position)); + for (uint32_t j = 0; j < input_clip_distance_count; j += 4) { + a.OpMov( + dxbc::Dest::O(output_register_clip_distances + (j >> 2), + (UINT32_C(1) << std::min( + input_clip_distance_count - j, UINT32_C(4))) - + 1), + dxbc::Src::V2D( + input_vertex_index, + input_register_clip_and_cull_distances + (j >> 2))); + } + a.OpEmitStream(stream); + } + a.OpCutStream(stream); + } break; + + default: + assert_unhandled_case(key.type); + } + + a.OpRet(); + + // Write the actual number of temporary registers used. + shader_out[dcl_temps_count_position_dwords] = stat.temp_register_count; + + // Write the shader program length in dwords. + shader_out[shex_position_dwords + 1] = + uint32_t(shader_out.size()) - shex_position_dwords; + + { + auto& blob_header = *reinterpret_cast( + shader_out.data() + blob_position_dwords); + blob_header.fourcc = dxbc::BlobHeader::FourCC::kShaderEx; + blob_position_dwords = uint32_t(shader_out.size()); + blob_header.size_bytes = + (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - + shader_out[blob_offset_position_dwords++]; + } + + // *************************************************************************** + // Statistics + // *************************************************************************** + + shader_out[blob_offset_position_dwords] = + uint32_t(blob_position_dwords * sizeof(uint32_t)); + uint32_t stat_position_dwords = blob_position_dwords + kBlobHeaderSizeDwords; + constexpr size_t kStatDwords = sizeof(dxbc::Statistics) / sizeof(uint32_t); + static_assert(sizeof(dxbc::Statistics) % sizeof(uint32_t) == 0); + shader_out.resize(stat_position_dwords + kStatDwords); + + std::array stat_words{}; + std::memcpy(stat_words.data(), &stat, sizeof(stat)); + std::copy(stat_words.begin(), stat_words.end(), + shader_out.begin() + stat_position_dwords); + + { + auto& blob_header = *reinterpret_cast( + shader_out.data() + blob_position_dwords); + blob_header.fourcc = dxbc::BlobHeader::FourCC::kStatistics; + blob_position_dwords = uint32_t(shader_out.size()); + blob_header.size_bytes = + (blob_position_dwords - kBlobHeaderSizeDwords) * sizeof(uint32_t) - + shader_out[blob_offset_position_dwords++]; + } + + // *************************************************************************** + // Container header + // *************************************************************************** + + uint32_t shader_size_bytes = uint32_t(shader_out.size() * sizeof(uint32_t)); + { + auto& container_header = + *reinterpret_cast(shader_out.data()); + container_header.InitializeIdentification(); + container_header.size_bytes = shader_size_bytes; + container_header.blob_count = kBlobCount; + CalculateDXBCChecksum( + reinterpret_cast(shader_out.data()), + static_cast(shader_size_bytes), + reinterpret_cast(&container_header.hash)); + } +} + +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/dxbc_geometry_shader.h b/src/xenia/gpu/dxbc_geometry_shader.h new file mode 100644 index 000000000..52cc214e7 --- /dev/null +++ b/src/xenia/gpu/dxbc_geometry_shader.h @@ -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 +#include +#include + +#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{}(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& shader_out); + +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_DXBC_GEOMETRY_SHADER_H_ diff --git a/src/xenia/gpu/dxbc_shader.cc b/src/xenia/gpu/dxbc_shader.cc index 937d82ffa..71a4e36da 100644 --- a/src/xenia/gpu/dxbc_shader.cc +++ b/src/xenia/gpu/dxbc_shader.cc @@ -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); diff --git a/src/xenia/gpu/dxbc_shader.h b/src/xenia/gpu/dxbc_shader.h index b14d48d5f..095711f9d 100644 --- a/src/xenia/gpu/dxbc_shader.h +++ b/src/xenia/gpu/dxbc_shader.h @@ -10,6 +10,7 @@ #ifndef XENIA_GPU_DXBC_SHADER_H_ #define XENIA_GPU_DXBC_SHADER_H_ +#include #include #include @@ -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 GetUsedCbufferMaskAfterTranslation() const { + return used_cbuffer_mask_; + } + const FetchConstantDwordMask& GetFetchConstantDwordMaskAfterTranslation() + const { + return fetch_constant_dword_mask_; + } + + struct TranslationMetadata { + std::vector texture_bindings; + std::vector 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 texture_bindings_; std::vector sampler_bindings_; uint32_t used_texture_mask_ = 0; + uint32_t used_cbuffer_mask_ = 0; + FetchConstantDwordMask fetch_constant_dword_mask_ = {}; }; } // namespace gpu diff --git a/src/xenia/gpu/dxbc_shader_translator.cc b/src/xenia/gpu/dxbc_shader_translator.cc index e27836736..fcca1ad13 100644 --- a/src/xenia/gpu/dxbc_shader_translator.cc +++ b/src/xenia/gpu/dxbc_shader_translator.cc @@ -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(&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; diff --git a/src/xenia/gpu/dxbc_shader_translator.h b/src/xenia/gpu/dxbc_shader_translator.h index 53271d300..aefa93d7b 100644 --- a/src/xenia/gpu/dxbc_shader_translator.h +++ b/src/xenia/gpu/dxbc_shader_translator.h @@ -10,6 +10,7 @@ #ifndef XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_ #define XENIA_GPU_DXBC_SHADER_TRANSLATOR_H_ +#include #include #include #include @@ -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 + texture_fetch_constant_dword_mask_; struct SystemConstantRdef { const char* name; diff --git a/src/xenia/gpu/gpu_flags.cc b/src/xenia/gpu/gpu_flags.cc index bd52edd1b..c715ec238 100644 --- a/src/xenia/gpu/gpu_flags.cc +++ b/src/xenia/gpu/gpu_flags.cc @@ -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. " diff --git a/src/xenia/gpu/gpu_flags.h b/src/xenia/gpu/gpu_flags.h index b79404674..d3a696aae 100644 --- a/src/xenia/gpu/gpu_flags.h +++ b/src/xenia/gpu/gpu_flags.h @@ -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 diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 235178858..ab526b14c 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -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 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 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, diff --git a/src/xenia/gpu/metal/CMakeLists.txt b/src/xenia/gpu/metal/CMakeLists.txt index 9dd88adfb..004974888 100644 --- a/src/xenia/gpu/metal/CMakeLists.txt +++ b/src/xenia/gpu/metal/CMakeLists.txt @@ -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 $ + ${_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 $ + ${_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) diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_quad_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_quad_hs.h new file mode 100644 index 000000000..83c075523 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_quad_hs.h @@ -0,0 +1,398 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 [unused] +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 QUADEDGE float x +// SV_TessFactor 1 x 1 QUADEDGE float x +// SV_TessFactor 2 x 2 QUADEDGE float x +// SV_TessFactor 3 x 3 QUADEDGE float x +// SV_InsideTessFactor 0 x 4 QUADINT float x +// SV_InsideTessFactor 1 x 5 QUADINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XETESSFACTOR 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Quadrilateral 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 4 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_quad +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][2], immediateIndexed, space=0 +hs_control_point_phase +dcl_input vPrim +dcl_output o0.x +dcl_temps 1 +iadd r0.x, vPrim, CB0[0][1].y +and r0.x, r0.x, l(0x00ffffff) +umax r0.x, r0.x, CB0[0][1].z +umin r0.x, r0.x, CB0[0][1].w +utof o0.x, r0.x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 4 +dcl_input vForkInstanceID +dcl_input vicp[4][0].x +dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor +dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor +dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor +dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 4 +iadd r0.x, vForkInstanceID.x, l(3) +and r0.x, r0.x, l(3) +mov r0.y, vForkInstanceID.x +mov o[r0.y + 0].x, vicp[r0.x + 0][0].x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 2 +dcl_input vForkInstanceID +dcl_input vicp[4][0].x +dcl_output_siv o4.x, finalQuadUInsideTessFactor +dcl_output_siv o5.x, finalQuadVInsideTessFactor +dcl_temps 1 +dcl_indexrange o4.x 2 +ult r0.x, vForkInstanceID.x, l(1) +movc r0.x, r0.x, l(0), l(3) +ineg r0.y, vForkInstanceID.x +min r0.x, vicp[r0.y + 2][0].x, vicp[r0.x + 0][0].x +mov r0.y, vForkInstanceID.x +mov o[r0.y + 4].x, r0.x +ret +// Approximately 18 instruction slots used +#endif + +const BYTE adaptive_quad_hs[] = { + 68, 88, 66, 67, 219, 255, 95, 234, 102, 219, 156, 208, 233, 150, 227, + 140, 59, 51, 198, 162, 1, 0, 0, 0, 48, 15, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 172, 10, 0, 0, 224, + 10, 0, 0, 164, 11, 0, 0, 148, 14, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 2, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 2, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 48, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 84, 69, 83, 83, 70, 65, 67, 84, 79, 82, 0, 171, + 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, + 69, 82, 84, 69, 88, 73, 68, 0, 171, 80, 67, 83, 71, 188, 0, + 0, 0, 6, 0, 0, 0, 8, 0, 0, 0, 152, 0, 0, 0, 0, + 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 1, 14, 0, 0, 152, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0, 0, 152, 0, + 0, 0, 2, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 2, + 0, 0, 0, 1, 14, 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, + 11, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, + 0, 166, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 3, 0, + 0, 0, 4, 0, 0, 0, 1, 14, 0, 0, 166, 0, 0, 0, 1, + 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, + 1, 14, 0, 0, 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, + 111, 114, 0, 83, 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, + 115, 70, 97, 99, 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 232, + 2, 0, 0, 81, 0, 3, 0, 186, 0, 0, 0, 113, 0, 0, 1, + 147, 32, 0, 1, 148, 8, 0, 1, 149, 24, 0, 1, 150, 32, 0, + 1, 151, 24, 0, 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, + 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 1, 95, 0, 0, 2, + 0, 176, 0, 0, 101, 0, 0, 3, 18, 32, 16, 0, 0, 0, 0, + 0, 104, 0, 0, 2, 1, 0, 0, 0, 30, 0, 0, 8, 18, 0, + 16, 0, 0, 0, 0, 0, 1, 176, 0, 0, 26, 128, 48, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7, + 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 255, 255, 255, 0, 83, 0, 0, 9, 18, 0, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, + 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 84, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 58, 128, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 86, 0, 0, 5, 18, 32, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 115, 0, 0, 1, 153, 0, 0, 2, 4, 0, 0, 0, 95, 0, 0, + 2, 0, 112, 1, 0, 95, 0, 0, 4, 18, 144, 33, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, + 0, 0, 0, 11, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, + 1, 0, 0, 0, 12, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 2, 0, 0, 0, 13, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 3, 0, 0, 0, 14, 0, 0, 0, 104, 0, 0, 2, 1, + 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 30, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, + 0, 10, 112, 1, 0, 1, 64, 0, 0, 3, 0, 0, 0, 1, 0, + 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 1, 64, 0, 0, 3, 0, 0, 0, 54, 0, 0, 4, + 34, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, + 8, 18, 32, 144, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 144, + 161, 0, 10, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 115, 0, 0, 1, 153, 0, 0, 2, 2, 0, 0, 0, + 95, 0, 0, 2, 0, 112, 1, 0, 95, 0, 0, 4, 18, 144, 33, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 4, 0, 0, 0, 15, 0, 0, 0, 103, 0, 0, 4, 18, + 32, 16, 0, 5, 0, 0, 0, 16, 0, 0, 0, 104, 0, 0, 2, + 1, 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 4, 0, 0, + 0, 2, 0, 0, 0, 79, 0, 0, 6, 18, 0, 16, 0, 0, 0, + 0, 0, 10, 112, 1, 0, 1, 64, 0, 0, 1, 0, 0, 0, 55, + 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 3, 0, 0, 0, 40, 0, 0, 4, 34, 0, 16, 0, 0, 0, + 0, 0, 10, 112, 1, 0, 51, 0, 0, 12, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 144, 225, 0, 2, 0, 0, 0, 26, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 10, 144, 161, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 4, 34, 0, + 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, 7, 18, + 32, 208, 0, 4, 0, 0, 0, 26, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, + 84, 148, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_triangle_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_triangle_hs.h new file mode 100644 index 000000000..2400bb130 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_triangle_hs.h @@ -0,0 +1,373 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 [unused] +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 TRIEDGE float x +// SV_TessFactor 1 x 1 TRIEDGE float x +// SV_TessFactor 2 x 2 TRIEDGE float x +// SV_InsideTessFactor 0 x 3 TRIINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XETESSFACTOR 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Triangle 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 3 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_tri +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][2], immediateIndexed, space=0 +hs_control_point_phase +dcl_input vPrim +dcl_output o0.x +dcl_temps 1 +iadd r0.x, vPrim, CB0[0][1].y +and r0.x, r0.x, l(0x00ffffff) +umax r0.x, r0.x, CB0[0][1].z +umin r0.x, r0.x, CB0[0][1].w +utof o0.x, r0.x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 3 +dcl_input vForkInstanceID +dcl_input vicp[3][0].x +dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor +dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor +dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 3 +iadd r0.x, vForkInstanceID.x, l(1) +udiv null, r0.x, r0.x, l(3) +mov r0.y, vForkInstanceID.x +mov o[r0.y + 0].x, vicp[r0.x + 0][0].x +ret +hs_fork_phase +dcl_input vicp[3][0].x +dcl_output_siv o3.x, finalTriInsideTessFactor +dcl_temps 1 +min r0.x, vicp[2][0].x, vicp[1][0].x +min o3.x, r0.x, vicp[0][0].x +ret +// Approximately 14 instruction slots used +#endif + +const BYTE adaptive_triangle_hs[] = { + 68, 88, 66, 67, 119, 178, 165, 28, 236, 219, 246, 2, 103, 49, 167, + 10, 199, 138, 243, 175, 1, 0, 0, 0, 96, 14, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 172, 10, 0, 0, 224, + 10, 0, 0, 116, 11, 0, 0, 196, 13, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 2, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 2, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 48, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 84, 69, 83, 83, 70, 65, 67, 84, 79, 82, 0, 171, + 171, 171, 79, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, + 69, 82, 84, 69, 88, 73, 68, 0, 171, 80, 67, 83, 71, 140, 0, + 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 104, 0, 0, 0, 0, + 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 1, 14, 0, 0, 104, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, + 0, 3, 0, 0, 0, 1, 0, 0, 0, 1, 14, 0, 0, 104, 0, + 0, 0, 2, 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 2, + 0, 0, 0, 1, 14, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, + 14, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, + 0, 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, + 83, 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, + 99, 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 72, 2, 0, 0, + 81, 0, 3, 0, 146, 0, 0, 0, 113, 0, 0, 1, 147, 24, 0, + 1, 148, 8, 0, 1, 149, 16, 0, 1, 150, 32, 0, 1, 151, 24, + 0, 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 114, 0, 0, 1, 95, 0, 0, 2, 0, 176, 0, + 0, 101, 0, 0, 3, 18, 32, 16, 0, 0, 0, 0, 0, 104, 0, + 0, 2, 1, 0, 0, 0, 30, 0, 0, 8, 18, 0, 16, 0, 0, + 0, 0, 0, 1, 176, 0, 0, 26, 128, 48, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 7, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 255, 255, 255, 0, 83, 0, 0, 9, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 84, 0, 0, + 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 58, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 86, 0, 0, 5, 18, 32, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, 0, + 1, 153, 0, 0, 2, 3, 0, 0, 0, 95, 0, 0, 2, 0, 112, + 1, 0, 95, 0, 0, 4, 18, 144, 33, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, + 17, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 1, 0, 0, + 0, 18, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 2, 0, + 0, 0, 19, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 91, + 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 30, 0, 0, 6, 18, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, + 0, 1, 64, 0, 0, 1, 0, 0, 0, 78, 0, 0, 8, 0, 208, + 0, 0, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, + 0, 0, 0, 1, 64, 0, 0, 3, 0, 0, 0, 54, 0, 0, 4, + 34, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, + 8, 18, 32, 144, 0, 26, 0, 16, 0, 0, 0, 0, 0, 10, 144, + 161, 0, 10, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 115, 0, 0, 1, 95, 0, 0, 4, 18, 144, 33, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 3, 0, 0, 0, 20, 0, 0, 0, 104, 0, 0, 2, 1, 0, + 0, 0, 51, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 144, 33, 0, 2, 0, 0, 0, 0, 0, 0, 0, 10, 144, 33, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 8, 18, 32, 16, + 0, 3, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 10, 144, + 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, + 84, 65, 84, 148, 0, 0, 0, 14, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, + 0, 4, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, + 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_1cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_1cp_hs.h new file mode 100644 index 000000000..33ac40635 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_1cp_hs.h @@ -0,0 +1,363 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 QUADEDGE float x +// SV_TessFactor 1 x 1 QUADEDGE float x +// SV_TessFactor 2 x 2 QUADEDGE float x +// SV_TessFactor 3 x 3 QUADEDGE float x +// SV_InsideTessFactor 0 x 4 QUADINT float x +// SV_InsideTessFactor 1 x 5 QUADINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Quadrilateral 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 1 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_quad +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_control_point_phase +dcl_input v[1][0].x +dcl_output o0.x +mov o0.x, v[0][0].x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 4 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor +dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor +dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor +dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 4 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 2 +dcl_input vForkInstanceID +dcl_output_siv o4.x, finalQuadUInsideTessFactor +dcl_output_siv o5.x, finalQuadVInsideTessFactor +dcl_temps 1 +dcl_indexrange o4.x 2 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 4].x, CB0[0][0].z +ret +// Approximately 8 instruction slots used +#endif + +const BYTE continuous_quad_1cp_hs[] = { + 68, 88, 66, 67, 75, 3, 204, 69, 118, 69, 237, 244, 19, 228, 4, + 30, 244, 91, 101, 237, 1, 0, 0, 0, 228, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 160, 11, 0, 0, 72, 13, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 188, 0, 0, 0, 6, 0, + 0, 0, 8, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 11, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 152, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 152, 0, 0, 0, 2, 0, + 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, 11, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 166, 0, 0, + 0, 0, 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 1, 14, 0, 0, 166, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 1, 14, 0, 0, + 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, + 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, + 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 160, 1, 0, 0, 81, + 0, 3, 0, 104, 0, 0, 0, 113, 0, 0, 1, 147, 8, 0, 1, + 148, 8, 0, 1, 149, 24, 0, 1, 150, 32, 0, 1, 151, 24, 0, + 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 114, 0, 0, 1, 95, 0, 0, 4, 18, 16, 32, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 3, 18, 32, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 6, 18, 32, 16, 0, 0, 0, + 0, 0, 10, 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 115, 0, 0, 1, 153, 0, 0, 2, 4, 0, 0, 0, + 95, 0, 0, 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 1, 0, 0, 0, 12, 0, 0, 0, 103, 0, 0, 4, 18, + 32, 16, 0, 2, 0, 0, 0, 13, 0, 0, 0, 103, 0, 0, 4, + 18, 32, 16, 0, 3, 0, 0, 0, 14, 0, 0, 0, 104, 0, 0, + 2, 1, 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, + 0, 1, 153, 0, 0, 2, 2, 0, 0, 0, 95, 0, 0, 2, 0, + 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, + 15, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 5, 0, 0, + 0, 16, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 91, 0, + 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, 2, 0, 0, 0, 54, + 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, + 54, 0, 0, 9, 18, 32, 208, 0, 4, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 148, + 0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_4cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_4cp_hs.h new file mode 100644 index 000000000..4e86df37b --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_4cp_hs.h @@ -0,0 +1,354 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 QUADEDGE float x +// SV_TessFactor 1 x 1 QUADEDGE float x +// SV_TessFactor 2 x 2 QUADEDGE float x +// SV_TessFactor 3 x 3 QUADEDGE float x +// SV_InsideTessFactor 0 x 4 QUADINT float x +// SV_InsideTessFactor 1 x 5 QUADINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Quadrilateral 4 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 4 +dcl_output_control_point_count 4 +dcl_tessellator_domain domain_quad +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_fork_phase +dcl_hs_fork_phase_instance_count 4 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor +dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor +dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor +dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 4 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 2 +dcl_input vForkInstanceID +dcl_output_siv o4.x, finalQuadUInsideTessFactor +dcl_output_siv o5.x, finalQuadVInsideTessFactor +dcl_temps 1 +dcl_indexrange o4.x 2 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 4].x, CB0[0][0].z +ret +// Approximately 6 instruction slots used +#endif + +const BYTE continuous_quad_4cp_hs[] = { + 68, 88, 66, 67, 6, 62, 31, 216, 142, 131, 210, 70, 184, 74, 84, + 23, 232, 171, 185, 244, 1, 0, 0, 0, 168, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 160, 11, 0, 0, 12, 13, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 188, 0, 0, 0, 6, 0, + 0, 0, 8, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 11, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 152, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 152, 0, 0, 0, 2, 0, + 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, 11, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 166, 0, 0, + 0, 0, 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 1, 14, 0, 0, 166, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 1, 14, 0, 0, + 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, + 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, + 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 100, 1, 0, 0, 81, + 0, 3, 0, 89, 0, 0, 0, 113, 0, 0, 1, 147, 32, 0, 1, + 148, 32, 0, 1, 149, 24, 0, 1, 150, 32, 0, 1, 151, 24, 0, + 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 115, 0, 0, 1, 153, 0, 0, 2, 4, 0, 0, 0, + 95, 0, 0, 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 1, 0, 0, 0, 12, 0, 0, 0, 103, 0, 0, 4, 18, + 32, 16, 0, 2, 0, 0, 0, 13, 0, 0, 0, 103, 0, 0, 4, + 18, 32, 16, 0, 3, 0, 0, 0, 14, 0, 0, 0, 104, 0, 0, + 2, 1, 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, + 0, 1, 153, 0, 0, 2, 2, 0, 0, 0, 95, 0, 0, 2, 0, + 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, + 15, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 5, 0, 0, + 0, 16, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 91, 0, + 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, 2, 0, 0, 0, 54, + 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, + 54, 0, 0, 9, 18, 32, 208, 0, 4, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 148, + 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_1cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_1cp_hs.h new file mode 100644 index 000000000..79ef2988f --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_1cp_hs.h @@ -0,0 +1,344 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 TRIEDGE float x +// SV_TessFactor 1 x 1 TRIEDGE float x +// SV_TessFactor 2 x 2 TRIEDGE float x +// SV_InsideTessFactor 0 x 3 TRIINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Triangle 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 1 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_tri +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_control_point_phase +dcl_input v[1][0].x +dcl_output o0.x +mov o0.x, v[0][0].x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 3 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor +dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor +dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 3 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_output_siv o3.x, finalTriInsideTessFactor +mov o3.x, CB0[0][0].z +ret +// Approximately 7 instruction slots used +#endif + +const BYTE continuous_triangle_1cp_hs[] = { + 68, 88, 66, 67, 7, 179, 83, 139, 111, 125, 9, 167, 130, 181, 49, + 127, 87, 182, 14, 89, 1, 0, 0, 0, 84, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 112, 11, 0, 0, 184, 12, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 140, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 13, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 104, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 104, 0, 0, 0, 2, 0, + 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 83, 86, 95, + 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, 86, 95, 73, + 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, + 0, 171, 171, 83, 72, 69, 88, 64, 1, 0, 0, 81, 0, 3, 0, + 80, 0, 0, 0, 113, 0, 0, 1, 147, 8, 0, 1, 148, 8, 0, + 1, 149, 16, 0, 1, 150, 32, 0, 1, 151, 24, 0, 1, 106, 8, + 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 114, 0, 0, 1, 95, 0, 0, 4, 18, 16, 32, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 101, 0, 0, 3, 18, 32, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 6, 18, 32, 16, 0, 0, 0, 0, 0, 10, + 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 115, 0, 0, 1, 153, 0, 0, 2, 3, 0, 0, 0, 95, 0, 0, + 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 17, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 1, + 0, 0, 0, 18, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, + 2, 0, 0, 0, 19, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, + 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, 0, 1, 103, 0, + 0, 4, 18, 32, 16, 0, 3, 0, 0, 0, 20, 0, 0, 0, 54, + 0, 0, 7, 18, 32, 16, 0, 3, 0, 0, 0, 42, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 148, 0, 0, 0, 7, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_3cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_3cp_hs.h new file mode 100644 index 000000000..e68f021ec --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_3cp_hs.h @@ -0,0 +1,335 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 TRIEDGE float x +// SV_TessFactor 1 x 1 TRIEDGE float x +// SV_TessFactor 2 x 2 TRIEDGE float x +// SV_InsideTessFactor 0 x 3 TRIINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Triangle 3 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Even Fractional +// +hs_5_1 +hs_decls +dcl_input_control_point_count 3 +dcl_output_control_point_count 3 +dcl_tessellator_domain domain_tri +dcl_tessellator_partitioning partitioning_fractional_even +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_fork_phase +dcl_hs_fork_phase_instance_count 3 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor +dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor +dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 3 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_output_siv o3.x, finalTriInsideTessFactor +mov o3.x, CB0[0][0].z +ret +// Approximately 5 instruction slots used +#endif + +const BYTE continuous_triangle_3cp_hs[] = { + 68, 88, 66, 67, 231, 10, 78, 117, 211, 32, 114, 128, 168, 236, 27, + 22, 27, 130, 116, 185, 1, 0, 0, 0, 24, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 112, 11, 0, 0, 124, 12, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 140, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 13, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 104, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 104, 0, 0, 0, 2, 0, + 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 83, 86, 95, + 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, 86, 95, 73, + 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, + 0, 171, 171, 83, 72, 69, 88, 4, 1, 0, 0, 81, 0, 3, 0, + 65, 0, 0, 0, 113, 0, 0, 1, 147, 24, 0, 1, 148, 24, 0, + 1, 149, 16, 0, 1, 150, 32, 0, 1, 151, 24, 0, 1, 106, 8, + 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 115, 0, 0, 1, 153, 0, 0, 2, 3, 0, 0, 0, 95, 0, 0, + 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 17, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 1, + 0, 0, 0, 18, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, + 2, 0, 0, 0, 19, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, + 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, 0, 1, 103, 0, + 0, 4, 18, 32, 16, 0, 3, 0, 0, 0, 20, 0, 0, 0, 54, + 0, 0, 7, 18, 32, 16, 0, 3, 0, 0, 0, 42, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_1cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_1cp_hs.h new file mode 100644 index 000000000..084e0eab5 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_1cp_hs.h @@ -0,0 +1,363 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 QUADEDGE float x +// SV_TessFactor 1 x 1 QUADEDGE float x +// SV_TessFactor 2 x 2 QUADEDGE float x +// SV_TessFactor 3 x 3 QUADEDGE float x +// SV_InsideTessFactor 0 x 4 QUADINT float x +// SV_InsideTessFactor 1 x 5 QUADINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Quadrilateral 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Integer +// +hs_5_1 +hs_decls +dcl_input_control_point_count 1 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_quad +dcl_tessellator_partitioning partitioning_integer +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_control_point_phase +dcl_input v[1][0].x +dcl_output o0.x +mov o0.x, v[0][0].x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 4 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor +dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor +dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor +dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 4 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 2 +dcl_input vForkInstanceID +dcl_output_siv o4.x, finalQuadUInsideTessFactor +dcl_output_siv o5.x, finalQuadVInsideTessFactor +dcl_temps 1 +dcl_indexrange o4.x 2 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 4].x, CB0[0][0].z +ret +// Approximately 8 instruction slots used +#endif + +const BYTE discrete_quad_1cp_hs[] = { + 68, 88, 66, 67, 42, 101, 116, 28, 76, 214, 165, 58, 178, 236, 109, + 197, 185, 155, 144, 115, 1, 0, 0, 0, 228, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 160, 11, 0, 0, 72, 13, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 188, 0, 0, 0, 6, 0, + 0, 0, 8, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 11, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 152, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 152, 0, 0, 0, 2, 0, + 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, 11, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 166, 0, 0, + 0, 0, 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 1, 14, 0, 0, 166, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 1, 14, 0, 0, + 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, + 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, + 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 160, 1, 0, 0, 81, + 0, 3, 0, 104, 0, 0, 0, 113, 0, 0, 1, 147, 8, 0, 1, + 148, 8, 0, 1, 149, 24, 0, 1, 150, 8, 0, 1, 151, 24, 0, + 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 114, 0, 0, 1, 95, 0, 0, 4, 18, 16, 32, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 3, 18, 32, 16, + 0, 0, 0, 0, 0, 54, 0, 0, 6, 18, 32, 16, 0, 0, 0, + 0, 0, 10, 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 115, 0, 0, 1, 153, 0, 0, 2, 4, 0, 0, 0, + 95, 0, 0, 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 1, 0, 0, 0, 12, 0, 0, 0, 103, 0, 0, 4, 18, + 32, 16, 0, 2, 0, 0, 0, 13, 0, 0, 0, 103, 0, 0, 4, + 18, 32, 16, 0, 3, 0, 0, 0, 14, 0, 0, 0, 104, 0, 0, + 2, 1, 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, + 0, 1, 153, 0, 0, 2, 2, 0, 0, 0, 95, 0, 0, 2, 0, + 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, + 15, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 5, 0, 0, + 0, 16, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 91, 0, + 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, 2, 0, 0, 0, 54, + 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, + 54, 0, 0, 9, 18, 32, 208, 0, 4, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 148, + 0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_4cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_4cp_hs.h new file mode 100644 index 000000000..5ae3740e2 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_4cp_hs.h @@ -0,0 +1,354 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 QUADEDGE float x +// SV_TessFactor 1 x 1 QUADEDGE float x +// SV_TessFactor 2 x 2 QUADEDGE float x +// SV_TessFactor 3 x 3 QUADEDGE float x +// SV_InsideTessFactor 0 x 4 QUADINT float x +// SV_InsideTessFactor 1 x 5 QUADINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Quadrilateral 4 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Integer +// +hs_5_1 +hs_decls +dcl_input_control_point_count 4 +dcl_output_control_point_count 4 +dcl_tessellator_domain domain_quad +dcl_tessellator_partitioning partitioning_integer +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_fork_phase +dcl_hs_fork_phase_instance_count 4 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalQuadUeq0EdgeTessFactor +dcl_output_siv o1.x, finalQuadVeq0EdgeTessFactor +dcl_output_siv o2.x, finalQuadUeq1EdgeTessFactor +dcl_output_siv o3.x, finalQuadVeq1EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 4 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 2 +dcl_input vForkInstanceID +dcl_output_siv o4.x, finalQuadUInsideTessFactor +dcl_output_siv o5.x, finalQuadVInsideTessFactor +dcl_temps 1 +dcl_indexrange o4.x 2 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 4].x, CB0[0][0].z +ret +// Approximately 6 instruction slots used +#endif + +const BYTE discrete_quad_4cp_hs[] = { + 68, 88, 66, 67, 158, 194, 46, 27, 202, 133, 207, 72, 70, 10, 181, + 145, 243, 232, 70, 63, 1, 0, 0, 0, 168, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 160, 11, 0, 0, 12, 13, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 188, 0, 0, 0, 6, 0, + 0, 0, 8, 0, 0, 0, 152, 0, 0, 0, 0, 0, 0, 0, 11, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 152, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 152, 0, 0, 0, 2, 0, + 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 152, 0, 0, 0, 3, 0, 0, 0, 11, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 166, 0, 0, + 0, 0, 0, 0, 0, 12, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 1, 14, 0, 0, 166, 0, 0, 0, 1, 0, 0, 0, 12, + 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 1, 14, 0, 0, + 83, 86, 95, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, + 86, 95, 73, 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, + 116, 111, 114, 0, 171, 171, 83, 72, 69, 88, 100, 1, 0, 0, 81, + 0, 3, 0, 89, 0, 0, 0, 113, 0, 0, 1, 147, 32, 0, 1, + 148, 32, 0, 1, 149, 24, 0, 1, 150, 8, 0, 1, 151, 24, 0, + 1, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 115, 0, 0, 1, 153, 0, 0, 2, 4, 0, 0, 0, + 95, 0, 0, 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 103, 0, 0, 4, 18, 32, + 16, 0, 1, 0, 0, 0, 12, 0, 0, 0, 103, 0, 0, 4, 18, + 32, 16, 0, 2, 0, 0, 0, 13, 0, 0, 0, 103, 0, 0, 4, + 18, 32, 16, 0, 3, 0, 0, 0, 14, 0, 0, 0, 104, 0, 0, + 2, 1, 0, 0, 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, + 0, 0, 0, 10, 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, + 0, 1, 153, 0, 0, 2, 2, 0, 0, 0, 95, 0, 0, 2, 0, + 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, + 15, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 5, 0, 0, + 0, 16, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 91, 0, + 0, 4, 18, 32, 16, 0, 4, 0, 0, 0, 2, 0, 0, 0, 54, + 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, 112, 1, 0, + 54, 0, 0, 9, 18, 32, 208, 0, 4, 0, 0, 0, 10, 0, 16, + 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, 148, + 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_1cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_1cp_hs.h new file mode 100644 index 000000000..9c1b0fcf5 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_1cp_hs.h @@ -0,0 +1,344 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 TRIEDGE float x +// SV_TessFactor 1 x 1 TRIEDGE float x +// SV_TessFactor 2 x 2 TRIEDGE float x +// SV_InsideTessFactor 0 x 3 TRIINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Triangle 1 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Integer +// +hs_5_1 +hs_decls +dcl_input_control_point_count 1 +dcl_output_control_point_count 1 +dcl_tessellator_domain domain_tri +dcl_tessellator_partitioning partitioning_integer +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_control_point_phase +dcl_input v[1][0].x +dcl_output o0.x +mov o0.x, v[0][0].x +ret +hs_fork_phase +dcl_hs_fork_phase_instance_count 3 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor +dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor +dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 3 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_output_siv o3.x, finalTriInsideTessFactor +mov o3.x, CB0[0][0].z +ret +// Approximately 7 instruction slots used +#endif + +const BYTE discrete_triangle_1cp_hs[] = { + 68, 88, 66, 67, 115, 241, 168, 52, 141, 229, 16, 29, 225, 108, 55, + 105, 208, 26, 191, 168, 1, 0, 0, 0, 84, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 112, 11, 0, 0, 184, 12, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 140, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 13, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 104, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 104, 0, 0, 0, 2, 0, + 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 83, 86, 95, + 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, 86, 95, 73, + 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, + 0, 171, 171, 83, 72, 69, 88, 64, 1, 0, 0, 81, 0, 3, 0, + 80, 0, 0, 0, 113, 0, 0, 1, 147, 8, 0, 1, 148, 8, 0, + 1, 149, 16, 0, 1, 150, 8, 0, 1, 151, 24, 0, 1, 106, 8, + 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 114, 0, 0, 1, 95, 0, 0, 4, 18, 16, 32, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 101, 0, 0, 3, 18, 32, 16, 0, 0, 0, + 0, 0, 54, 0, 0, 6, 18, 32, 16, 0, 0, 0, 0, 0, 10, + 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, + 115, 0, 0, 1, 153, 0, 0, 2, 3, 0, 0, 0, 95, 0, 0, + 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 17, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 1, + 0, 0, 0, 18, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, + 2, 0, 0, 0, 19, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, + 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, 0, 1, 103, 0, + 0, 4, 18, 32, 16, 0, 3, 0, 0, 0, 20, 0, 0, 0, 54, + 0, 0, 7, 18, 32, 16, 0, 3, 0, 0, 0, 42, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 148, 0, 0, 0, 7, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_3cp_hs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_3cp_hs.h new file mode 100644 index 000000000..337f72cd8 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_3cp_hs.h @@ -0,0 +1,335 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 [unused] +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Patch Constant signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_TessFactor 0 x 0 TRIEDGE float x +// SV_TessFactor 1 x 1 TRIEDGE float x +// SV_TessFactor 2 x 2 TRIEDGE float x +// SV_InsideTessFactor 0 x 3 TRIINT float x +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +// Tessellation Domain # of control points +// -------------------- -------------------- +// Triangle 3 +// +// Tessellation Output Primitive Partitioning Type +// ------------------------------ ------------------ +// Clockwise Triangles Integer +// +hs_5_1 +hs_decls +dcl_input_control_point_count 3 +dcl_output_control_point_count 3 +dcl_tessellator_domain domain_tri +dcl_tessellator_partitioning partitioning_integer +dcl_tessellator_output_primitive output_triangle_cw +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][1], immediateIndexed, space=0 +hs_fork_phase +dcl_hs_fork_phase_instance_count 3 +dcl_input vForkInstanceID +dcl_output_siv o0.x, finalTriUeq0EdgeTessFactor +dcl_output_siv o1.x, finalTriVeq0EdgeTessFactor +dcl_output_siv o2.x, finalTriWeq0EdgeTessFactor +dcl_temps 1 +dcl_indexrange o0.x 3 +mov r0.x, vForkInstanceID.x +mov o[r0.x + 0].x, CB0[0][0].z +ret +hs_fork_phase +dcl_output_siv o3.x, finalTriInsideTessFactor +mov o3.x, CB0[0][0].z +ret +// Approximately 5 instruction slots used +#endif + +const BYTE discrete_triangle_3cp_hs[] = { + 68, 88, 66, 67, 32, 22, 215, 129, 127, 110, 70, 162, 42, 32, 73, + 43, 11, 32, 196, 23, 1, 0, 0, 0, 24, 13, 0, 0, 6, 0, + 0, 0, 56, 0, 0, 0, 116, 10, 0, 0, 168, 10, 0, 0, 220, + 10, 0, 0, 112, 11, 0, 0, 124, 12, 0, 0, 82, 68, 69, 70, + 52, 10, 0, 0, 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, + 0, 60, 0, 0, 0, 1, 5, 83, 72, 0, 5, 0, 0, 10, 10, + 0, 0, 19, 19, 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, + 0, 0, 0, 40, 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, + 101, 95, 115, 121, 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, + 114, 0, 171, 171, 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, + 0, 208, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, + 0, 8, 0, 0, 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, + 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 5, 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, + 0, 0, 32, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 192, 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, + 0, 0, 144, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 51, 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, + 0, 0, 168, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, + 0, 32, 0, 0, 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 188, 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, + 0, 0, 212, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, + 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 7, 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, + 0, 0, 228, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, + 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 98, 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, + 0, 0, 0, 1, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, + 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, + 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 205, 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, + 0, 0, 0, 0, 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, + 0, 0, 48, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 97, 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, + 0, 0, 0, 0, 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, + 0, 0, 144, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, + 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, + 255, 255, 255, 255, 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, + 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, + 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, + 0, 0, 0, 242, 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, + 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, + 95, 102, 108, 97, 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, + 0, 19, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 73, 5, 0, 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, + 97, 116, 105, 111, 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, + 110, 103, 101, 0, 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, + 0, 0, 120, 101, 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, + 99, 108, 111, 115, 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, + 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, + 101, 110, 100, 105, 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, + 120, 95, 105, 110, 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, + 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, + 95, 109, 105, 110, 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, + 171, 1, 0, 19, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 29, 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, + 99, 108, 105, 112, 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, + 97, 116, 52, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, + 100, 99, 95, 115, 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, + 0, 1, 0, 3, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 149, 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, + 95, 118, 101, 114, 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, + 114, 95, 109, 105, 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, + 3, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 221, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, + 101, 116, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, + 120, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, + 95, 114, 97, 100, 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, + 117, 114, 101, 95, 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, + 103, 110, 115, 0, 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, + 19, 0, 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 143, 7, 0, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, + 95, 114, 101, 115, 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, + 108, 101, 100, 0, 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, + 111, 117, 110, 116, 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, + 112, 104, 97, 95, 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, + 110, 99, 101, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, + 95, 109, 97, 115, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 51, 50, 98, 112, 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, + 104, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, + 95, 98, 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, + 97, 108, 101, 100, 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, + 120, 112, 95, 98, 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, + 101, 95, 101, 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, + 102, 115, 101, 116, 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, + 116, 95, 98, 97, 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 115, 116, 101, 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, + 1, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, + 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, + 97, 115, 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, + 101, 100, 0, 171, 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, + 108, 97, 103, 115, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, + 116, 95, 99, 108, 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, + 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, + 112, 95, 109, 97, 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, + 110, 100, 95, 102, 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, + 120, 101, 95, 101, 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, + 99, 111, 110, 115, 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, + 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, + 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, + 48, 46, 49, 0, 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, + 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, 171, 79, 83, + 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 0, 0, 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, + 88, 73, 68, 0, 171, 80, 67, 83, 71, 140, 0, 0, 0, 4, 0, + 0, 0, 8, 0, 0, 0, 104, 0, 0, 0, 0, 0, 0, 0, 13, + 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 14, 0, 0, + 104, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 3, 0, 0, + 0, 1, 0, 0, 0, 1, 14, 0, 0, 104, 0, 0, 0, 2, 0, + 0, 0, 13, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1, + 14, 0, 0, 118, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, 1, 14, 0, 0, 83, 86, 95, + 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, 0, 83, 86, 95, 73, + 110, 115, 105, 100, 101, 84, 101, 115, 115, 70, 97, 99, 116, 111, 114, + 0, 171, 171, 83, 72, 69, 88, 4, 1, 0, 0, 81, 0, 3, 0, + 65, 0, 0, 0, 113, 0, 0, 1, 147, 24, 0, 1, 148, 24, 0, + 1, 149, 16, 0, 1, 150, 8, 0, 1, 151, 24, 0, 1, 106, 8, + 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 115, 0, 0, 1, 153, 0, 0, 2, 3, 0, 0, 0, 95, 0, 0, + 2, 0, 112, 1, 0, 103, 0, 0, 4, 18, 32, 16, 0, 0, 0, + 0, 0, 17, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, 1, + 0, 0, 0, 18, 0, 0, 0, 103, 0, 0, 4, 18, 32, 16, 0, + 2, 0, 0, 0, 19, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, + 0, 91, 0, 0, 4, 18, 32, 16, 0, 0, 0, 0, 0, 3, 0, + 0, 0, 54, 0, 0, 4, 18, 0, 16, 0, 0, 0, 0, 0, 10, + 112, 1, 0, 54, 0, 0, 8, 18, 32, 144, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 62, 0, 0, 1, 115, 0, 0, 1, 103, 0, + 0, 4, 18, 32, 16, 0, 3, 0, 0, 0, 20, 0, 0, 0, 54, + 0, 0, 7, 18, 32, 16, 0, 3, 0, 0, 0, 42, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, + 1, 83, 84, 65, 84, 148, 0, 0, 0, 5, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_adaptive_vs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_adaptive_vs.h new file mode 100644 index 000000000..d796f54be --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_adaptive_vs.h @@ -0,0 +1,324 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 [unused] +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 [unused] +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_VertexID 0 x 0 VERTID uint x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XETESSFACTOR 0 x 0 NONE float x +// +vs_5_1 +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][2], immediateIndexed, space=0 +dcl_input_sgv v0.x, vertex_id +dcl_output o0.x +dcl_temps 1 +ieq r0.xyz, CB0[0][1].xxxx, l(1, 2, 3, 0) +or r0.xy, r0.yzyy, r0.xyxx +if_nz r0.x + ishl r0.x, v0.x, l(8) + ushr r0.z, v0.x, l(8) + and r0.xz, r0.xxzx, l(0xff00ff00, 0, 0x00ff00ff, 0) + iadd r0.x, r0.z, r0.x +else + mov r0.x, v0.x +endif +if_nz r0.y + ushr r0.y, r0.x, l(16) + bfi r0.x, l(16), l(16), r0.x, r0.y +endif +add r0.x, r0.x, l(1.000000) +max r0.x, r0.x, CB0[0][0].y +min o0.x, r0.x, CB0[0][0].z +ret +// Approximately 18 instruction slots used +#endif + +const BYTE tessellation_adaptive_vs[] = { + 68, 88, 66, 67, 176, 83, 38, 124, 12, 115, 219, 54, 63, 56, 200, + 154, 202, 77, 95, 132, 1, 0, 0, 0, 124, 13, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, 112, 10, 0, 0, 164, 10, 0, 0, 220, + 10, 0, 0, 224, 12, 0, 0, 82, 68, 69, 70, 52, 10, 0, 0, + 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, 0, 60, 0, 0, + 0, 1, 5, 254, 255, 0, 5, 0, 0, 10, 10, 0, 0, 19, 19, + 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0, 40, + 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, + 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 101, 95, 115, 121, + 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, 114, 0, 171, 171, + 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, 0, 208, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, 0, 8, 0, 0, + 0, 2, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 188, + 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, 0, 0, 16, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 5, + 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, 0, 0, 32, 0, + 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, 0, 12, 0, 0, + 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 192, + 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, 0, 0, 144, 0, + 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 51, + 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, 0, 0, 168, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 188, + 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, 0, 0, 212, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 7, + 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, 0, 0, 228, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 98, + 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, 0, 0, 0, 1, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 205, + 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, 0, 0, 48, 1, + 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 97, + 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, + 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, 0, 0, 144, 1, + 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, 9, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 242, + 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, 95, 102, 108, 97, + 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, 0, 19, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 5, 0, + 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, 97, 116, 105, 111, + 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, 110, 103, 101, 0, + 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, 1, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, 0, 0, 120, 101, + 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, 99, 108, 111, 115, + 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, 101, 95, 118, 101, + 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, 101, 110, 100, 105, + 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, + 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, 120, 101, 95, 118, + 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, 109, 105, 110, + 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, 171, 1, 0, 19, + 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, + 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, 99, 108, 105, 112, + 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, 97, 116, 52, 0, + 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 115, + 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, 0, 1, 0, 3, + 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, + 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 105, + 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, 3, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221, 6, 0, 0, + 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, 101, 116, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, 116, 101, 120, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, 120, 0, 120, 101, + 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, 116, 97, 110, 116, + 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, 101, 95, 112, 111, + 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, 100, 105, 97, 109, + 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, 95, 114, 97, 100, + 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 95, + 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, 103, 110, 115, 0, + 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, 95, 114, 101, 115, + 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, 108, 101, 100, 0, + 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, 111, 117, 110, 116, + 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, + 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, 110, 99, 101, 0, + 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, 95, 109, 97, 115, + 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 51, 50, 98, 112, + 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, 104, 95, 100, 119, + 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, 0, 120, 101, 95, + 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, 95, 98, 97, 115, + 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, 120, 112, 95, 98, + 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, 116, + 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, 116, 95, 98, 97, + 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 115, 116, 101, + 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, 1, 0, 4, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, + 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 97, 115, 101, 95, + 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, 0, 171, + 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, 108, 97, 103, 115, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 99, 108, + 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, 112, 95, 109, 97, + 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, 4, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, 110, 100, 95, 102, + 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, 48, 46, 49, 0, + 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 83, 86, 95, + 86, 101, 114, 116, 101, 120, 73, 68, 0, 79, 83, 71, 78, 48, 0, + 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 1, 14, 0, 0, 88, 69, 84, 69, 83, 83, 70, 65, 67, 84, 79, + 82, 0, 171, 171, 171, 83, 72, 69, 88, 252, 1, 0, 0, 81, 0, + 1, 0, 127, 0, 0, 0, 106, 8, 0, 1, 89, 0, 0, 7, 70, + 142, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 4, 18, 16, 16, + 0, 0, 0, 0, 0, 6, 0, 0, 0, 101, 0, 0, 3, 18, 32, + 16, 0, 0, 0, 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 32, + 0, 0, 12, 114, 0, 16, 0, 0, 0, 0, 0, 6, 128, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 64, 0, + 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 60, 0, 0, 7, 50, 0, 16, 0, 0, 0, 0, 0, 150, + 5, 16, 0, 0, 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, + 31, 0, 4, 3, 10, 0, 16, 0, 0, 0, 0, 0, 41, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 16, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 8, 0, 0, 0, 85, 0, 0, 7, 66, + 0, 16, 0, 0, 0, 0, 0, 10, 16, 16, 0, 0, 0, 0, 0, + 1, 64, 0, 0, 8, 0, 0, 0, 1, 0, 0, 10, 82, 0, 16, + 0, 0, 0, 0, 0, 6, 2, 16, 0, 0, 0, 0, 0, 2, 64, + 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 255, 0, 255, 0, 0, + 0, 0, 0, 30, 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, + 42, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 18, 0, 0, 1, 54, 0, 0, 5, 18, 0, 16, 0, 0, 0, + 0, 0, 10, 16, 16, 0, 0, 0, 0, 0, 21, 0, 0, 1, 31, + 0, 4, 3, 26, 0, 16, 0, 0, 0, 0, 0, 85, 0, 0, 7, + 34, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, + 0, 1, 64, 0, 0, 16, 0, 0, 0, 140, 0, 0, 11, 18, 0, + 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, 16, 0, 0, 0, 1, + 64, 0, 0, 16, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 26, 0, 16, 0, 0, 0, 0, 0, 21, 0, 0, 1, 0, 0, 0, + 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 0, 0, 128, 63, 52, 0, 0, 9, 18, + 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, + 26, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 51, 0, 0, 9, 18, 32, 16, 0, 0, 0, 0, 0, 10, 0, + 16, 0, 0, 0, 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 1, 83, 84, 65, 84, + 148, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 4, 0, + 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0}; diff --git a/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_indexed_vs.h b/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_indexed_vs.h new file mode 100644 index 000000000..867bd2bf3 --- /dev/null +++ b/src/xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_indexed_vs.h @@ -0,0 +1,329 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 10.1 +// +// +// Buffer Definitions: +// +// cbuffer xe_system_cbuffer +// { +// +// uint xe_flags; // Offset: 0 Size: 4 [unused] +// float2 xe_tessellation_factor_range;// Offset: 4 Size: 8 [unused] +// uint xe_line_loop_closing_index; // Offset: 12 Size: 4 [unused] +// uint xe_vertex_index_endian; // Offset: 16 Size: 4 +// uint xe_vertex_index_offset; // Offset: 20 Size: 4 +// uint2 xe_vertex_index_min_max; // Offset: 24 Size: 8 +// float4 xe_user_clip_planes[6]; // Offset: 32 Size: 96 [unused] +// float3 xe_ndc_scale; // Offset: 128 Size: 12 [unused] +// float xe_point_vertex_diameter_min;// Offset: 140 Size: 4 [unused] +// float3 xe_ndc_offset; // Offset: 144 Size: 12 [unused] +// float xe_point_vertex_diameter_max;// Offset: 156 Size: 4 [unused] +// float2 xe_point_constant_diameter; // Offset: 160 Size: 8 [unused] +// float2 xe_point_screen_diameter_to_ndc_radius;// Offset: 168 Size: 8 [unused] +// uint4 xe_texture_swizzled_signs[2];// Offset: 176 Size: 32 [unused] +// uint xe_textures_resolution_scaled;// Offset: 208 Size: 4 [unused] +// uint2 xe_sample_count_log2; // Offset: 212 Size: 8 [unused] +// float xe_alpha_test_reference; // Offset: 220 Size: 4 [unused] +// uint xe_alpha_to_mask; // Offset: 224 Size: 4 [unused] +// uint xe_edram_32bpp_tile_pitch_dwords_scaled;// Offset: 228 Size: 4 [unused] +// uint xe_edram_depth_base_dwords_scaled;// Offset: 232 Size: 4 [unused] +// float4 xe_color_exp_bias; // Offset: 240 Size: 16 [unused] +// float2 xe_edram_poly_offset_front; // Offset: 256 Size: 8 [unused] +// float2 xe_edram_poly_offset_back; // Offset: 264 Size: 8 [unused] +// uint4 xe_edram_stencil[2]; // Offset: 272 Size: 32 [unused] +// uint4 xe_edram_rt_base_dwords_scaled;// Offset: 304 Size: 16 [unused] +// uint4 xe_edram_rt_format_flags; // Offset: 320 Size: 16 [unused] +// float4 xe_edram_rt_clamp[4]; // Offset: 336 Size: 64 [unused] +// uint4 xe_edram_rt_keep_mask[2]; // Offset: 400 Size: 32 [unused] +// uint4 xe_edram_rt_blend_factors_ops;// Offset: 432 Size: 16 [unused] +// float4 xe_edram_blend_constant; // Offset: 448 Size: 16 [unused] +// +// } +// +// +// Resource Bindings: +// +// Name Type Format Dim ID HLSL Bind Count +// ------------------------------ ---------- ------- ----------- ------- -------------- ------ +// xe_system_cbuffer cbuffer NA NA CB0 cb0 1 +// +// +// +// Input signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// SV_VertexID 0 x 0 VERTID uint x +// +// +// Output signature: +// +// Name Index Mask Register SysValue Format Used +// -------------------- ----- ------ -------- -------- ------- ------ +// XEVERTEXID 0 x 0 NONE float x +// +vs_5_1 +dcl_globalFlags refactoringAllowed +dcl_constantbuffer CB0[0:0][2], immediateIndexed, space=0 +dcl_input_sgv v0.x, vertex_id +dcl_output o0.x +dcl_temps 1 +ieq r0.xyz, CB0[0][1].xxxx, l(1, 2, 3, 0) +or r0.xy, r0.yzyy, r0.xyxx +if_nz r0.x + ishl r0.x, v0.x, l(8) + ushr r0.z, v0.x, l(8) + and r0.xz, r0.xxzx, l(0xff00ff00, 0, 0x00ff00ff, 0) + iadd r0.x, r0.z, r0.x +else + mov r0.x, v0.x +endif +if_nz r0.y + ushr r0.y, r0.x, l(16) + bfi r0.x, l(16), l(16), r0.x, r0.y +endif +iadd r0.x, r0.x, CB0[0][1].y +and r0.x, r0.x, l(0x00ffffff) +umax r0.x, r0.x, CB0[0][1].z +umin r0.x, r0.x, CB0[0][1].w +utof o0.x, r0.x +ret +// Approximately 20 instruction slots used +#endif + +const BYTE tessellation_indexed_vs[] = { + 68, 88, 66, 67, 141, 104, 237, 168, 205, 100, 22, 191, 159, 23, 183, + 42, 54, 72, 27, 224, 1, 0, 0, 0, 176, 13, 0, 0, 5, 0, + 0, 0, 52, 0, 0, 0, 112, 10, 0, 0, 164, 10, 0, 0, 216, + 10, 0, 0, 20, 13, 0, 0, 82, 68, 69, 70, 52, 10, 0, 0, + 1, 0, 0, 0, 120, 0, 0, 0, 1, 0, 0, 0, 60, 0, 0, + 0, 1, 5, 254, 255, 0, 5, 0, 0, 10, 10, 0, 0, 19, 19, + 68, 37, 60, 0, 0, 0, 24, 0, 0, 0, 40, 0, 0, 0, 40, + 0, 0, 0, 36, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, + 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 101, 95, 115, 121, + 115, 116, 101, 109, 95, 99, 98, 117, 102, 102, 101, 114, 0, 171, 171, + 100, 0, 0, 0, 30, 0, 0, 0, 144, 0, 0, 0, 208, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 5, 0, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 116, 5, 0, 0, 4, 0, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 188, + 5, 0, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 215, 5, 0, 0, 16, 0, + 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 238, 5, 0, 0, 20, 0, 0, 0, 4, 0, 0, + 0, 2, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 5, + 6, 0, 0, 24, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, + 36, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 72, 6, 0, 0, 32, 0, + 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 100, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 136, 6, 0, 0, 128, 0, 0, 0, 12, 0, 0, + 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 192, + 6, 0, 0, 140, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 8, 7, 0, 0, 144, 0, + 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 156, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 22, 7, 0, 0, 156, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 51, + 7, 0, 0, 160, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 78, 7, 0, 0, 168, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 117, 7, 0, 0, 176, 0, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 152, 7, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 188, + 7, 0, 0, 208, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 218, 7, 0, 0, 212, 0, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 36, 6, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 239, 7, 0, 0, 220, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 228, 6, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 7, + 8, 0, 0, 224, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 24, 8, 0, 0, 228, 0, + 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 64, 8, 0, 0, 232, 0, 0, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 80, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 98, + 8, 0, 0, 240, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 8, 0, 0, 0, 1, + 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 179, 8, 0, 0, 8, 1, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 152, 5, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 205, + 8, 0, 0, 16, 1, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, + 224, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 4, 9, 0, 0, 48, 1, + 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 72, 9, 0, 0, 64, 1, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 97, + 9, 0, 0, 80, 1, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, + 116, 9, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 152, 9, 0, 0, 144, 1, + 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 176, 9, 0, 0, 0, + 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, + 0, 0, 0, 0, 212, 9, 0, 0, 176, 1, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 36, 9, 0, 0, 0, 0, 0, 0, 255, 255, + 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 242, + 9, 0, 0, 192, 1, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 116, 8, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, + 0, 255, 255, 255, 255, 0, 0, 0, 0, 120, 101, 95, 102, 108, 97, + 103, 115, 0, 100, 119, 111, 114, 100, 0, 171, 0, 0, 19, 0, 1, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 5, 0, + 0, 120, 101, 95, 116, 101, 115, 115, 101, 108, 108, 97, 116, 105, 111, + 110, 95, 102, 97, 99, 116, 111, 114, 95, 114, 97, 110, 103, 101, 0, + 102, 108, 111, 97, 116, 50, 0, 1, 0, 3, 0, 1, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 5, 0, 0, 120, 101, + 95, 108, 105, 110, 101, 95, 108, 111, 111, 112, 95, 99, 108, 111, 115, + 105, 110, 103, 95, 105, 110, 100, 101, 120, 0, 120, 101, 95, 118, 101, + 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, 101, 110, 100, 105, + 97, 110, 0, 120, 101, 95, 118, 101, 114, 116, 101, 120, 95, 105, 110, + 100, 101, 120, 95, 111, 102, 102, 115, 101, 116, 0, 120, 101, 95, 118, + 101, 114, 116, 101, 120, 95, 105, 110, 100, 101, 120, 95, 109, 105, 110, + 95, 109, 97, 120, 0, 117, 105, 110, 116, 50, 0, 171, 1, 0, 19, + 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, + 6, 0, 0, 120, 101, 95, 117, 115, 101, 114, 95, 99, 108, 105, 112, + 95, 112, 108, 97, 110, 101, 115, 0, 102, 108, 111, 97, 116, 52, 0, + 171, 1, 0, 3, 0, 1, 0, 4, 0, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 110, 100, 99, 95, 115, + 99, 97, 108, 101, 0, 102, 108, 111, 97, 116, 51, 0, 1, 0, 3, + 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, + 6, 0, 0, 120, 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, + 116, 101, 120, 95, 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 105, + 110, 0, 102, 108, 111, 97, 116, 0, 171, 0, 0, 3, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221, 6, 0, 0, + 120, 101, 95, 110, 100, 99, 95, 111, 102, 102, 115, 101, 116, 0, 120, + 101, 95, 112, 111, 105, 110, 116, 95, 118, 101, 114, 116, 101, 120, 95, + 100, 105, 97, 109, 101, 116, 101, 114, 95, 109, 97, 120, 0, 120, 101, + 95, 112, 111, 105, 110, 116, 95, 99, 111, 110, 115, 116, 97, 110, 116, + 95, 100, 105, 97, 109, 101, 116, 101, 114, 0, 120, 101, 95, 112, 111, + 105, 110, 116, 95, 115, 99, 114, 101, 101, 110, 95, 100, 105, 97, 109, + 101, 116, 101, 114, 95, 116, 111, 95, 110, 100, 99, 95, 114, 97, 100, + 105, 117, 115, 0, 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 95, + 115, 119, 105, 122, 122, 108, 101, 100, 95, 115, 105, 103, 110, 115, 0, + 117, 105, 110, 116, 52, 0, 171, 171, 171, 1, 0, 19, 0, 1, 0, + 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, + 120, 101, 95, 116, 101, 120, 116, 117, 114, 101, 115, 95, 114, 101, 115, + 111, 108, 117, 116, 105, 111, 110, 95, 115, 99, 97, 108, 101, 100, 0, + 120, 101, 95, 115, 97, 109, 112, 108, 101, 95, 99, 111, 117, 110, 116, + 95, 108, 111, 103, 50, 0, 120, 101, 95, 97, 108, 112, 104, 97, 95, + 116, 101, 115, 116, 95, 114, 101, 102, 101, 114, 101, 110, 99, 101, 0, + 120, 101, 95, 97, 108, 112, 104, 97, 95, 116, 111, 95, 109, 97, 115, + 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 51, 50, 98, 112, + 112, 95, 116, 105, 108, 101, 95, 112, 105, 116, 99, 104, 95, 100, 119, + 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, 0, 120, 101, 95, + 101, 100, 114, 97, 109, 95, 100, 101, 112, 116, 104, 95, 98, 97, 115, + 101, 95, 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, + 0, 120, 101, 95, 99, 111, 108, 111, 114, 95, 101, 120, 112, 95, 98, + 105, 97, 115, 0, 1, 0, 3, 0, 1, 0, 4, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 101, 100, + 114, 97, 109, 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, 116, + 95, 102, 114, 111, 110, 116, 0, 120, 101, 95, 101, 100, 114, 97, 109, + 95, 112, 111, 108, 121, 95, 111, 102, 102, 115, 101, 116, 95, 98, 97, + 99, 107, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 115, 116, 101, + 110, 99, 105, 108, 0, 171, 171, 1, 0, 19, 0, 1, 0, 4, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, + 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 98, 97, 115, 101, 95, + 100, 119, 111, 114, 100, 115, 95, 115, 99, 97, 108, 101, 100, 0, 171, + 1, 0, 19, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, + 114, 116, 95, 102, 111, 114, 109, 97, 116, 95, 102, 108, 97, 103, 115, + 0, 120, 101, 95, 101, 100, 114, 97, 109, 95, 114, 116, 95, 99, 108, + 97, 109, 112, 0, 171, 1, 0, 3, 0, 1, 0, 4, 0, 4, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 92, 6, 0, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 114, 116, 95, 107, 101, 101, 112, 95, 109, 97, + 115, 107, 0, 171, 171, 1, 0, 19, 0, 1, 0, 4, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 143, 7, 0, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 114, 116, 95, 98, 108, 101, 110, 100, 95, 102, + 97, 99, 116, 111, 114, 115, 95, 111, 112, 115, 0, 120, 101, 95, 101, + 100, 114, 97, 109, 95, 98, 108, 101, 110, 100, 95, 99, 111, 110, 115, + 116, 97, 110, 116, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, + 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, 48, 46, 49, 0, + 171, 171, 73, 83, 71, 78, 44, 0, 0, 0, 1, 0, 0, 0, 8, + 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 83, 86, 95, + 86, 101, 114, 116, 101, 120, 73, 68, 0, 79, 83, 71, 78, 44, 0, + 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 1, 14, 0, 0, 88, 69, 86, 69, 82, 84, 69, 88, 73, 68, 0, + 171, 83, 72, 69, 88, 52, 2, 0, 0, 81, 0, 1, 0, 141, 0, + 0, 0, 106, 8, 0, 1, 89, 0, 0, 7, 70, 142, 48, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 96, 0, 0, 4, 18, 16, 16, 0, 0, 0, 0, + 0, 6, 0, 0, 0, 101, 0, 0, 3, 18, 32, 16, 0, 0, 0, + 0, 0, 104, 0, 0, 2, 1, 0, 0, 0, 32, 0, 0, 12, 114, + 0, 16, 0, 0, 0, 0, 0, 6, 128, 48, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 1, 0, 0, + 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 60, 0, + 0, 7, 50, 0, 16, 0, 0, 0, 0, 0, 150, 5, 16, 0, 0, + 0, 0, 0, 70, 0, 16, 0, 0, 0, 0, 0, 31, 0, 4, 3, + 10, 0, 16, 0, 0, 0, 0, 0, 41, 0, 0, 7, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 16, 16, 0, 0, 0, 0, 0, 1, 64, + 0, 0, 8, 0, 0, 0, 85, 0, 0, 7, 66, 0, 16, 0, 0, + 0, 0, 0, 10, 16, 16, 0, 0, 0, 0, 0, 1, 64, 0, 0, + 8, 0, 0, 0, 1, 0, 0, 10, 82, 0, 16, 0, 0, 0, 0, + 0, 6, 2, 16, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 255, + 0, 255, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 30, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 42, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 18, 0, 0, + 1, 54, 0, 0, 5, 18, 0, 16, 0, 0, 0, 0, 0, 10, 16, + 16, 0, 0, 0, 0, 0, 21, 0, 0, 1, 31, 0, 4, 3, 26, + 0, 16, 0, 0, 0, 0, 0, 85, 0, 0, 7, 34, 0, 16, 0, + 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 1, 64, 0, + 0, 16, 0, 0, 0, 140, 0, 0, 11, 18, 0, 16, 0, 0, 0, + 0, 0, 1, 64, 0, 0, 16, 0, 0, 0, 1, 64, 0, 0, 16, + 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 0, 16, 0, + 0, 0, 0, 0, 21, 0, 0, 1, 30, 0, 0, 9, 18, 0, 16, + 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 26, 128, + 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, + 0, 0, 7, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, + 0, 0, 0, 0, 1, 64, 0, 0, 255, 255, 255, 0, 83, 0, 0, + 9, 18, 0, 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, + 0, 0, 42, 128, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 84, 0, 0, 9, 18, 0, 16, 0, 0, 0, 0, 0, + 10, 0, 16, 0, 0, 0, 0, 0, 58, 128, 48, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 86, 0, 0, 5, 18, 32, + 16, 0, 0, 0, 0, 0, 10, 0, 16, 0, 0, 0, 0, 0, 62, + 0, 0, 1, 83, 84, 65, 84, 148, 0, 0, 0, 20, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 0, 4, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0}; diff --git a/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc b/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc new file mode 100644 index 000000000..fed6b210c --- /dev/null +++ b/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc @@ -0,0 +1,258 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +/** + * DXBC to DXIL converter implementation + * Uses the in-process dxilconv library on macOS. + */ + +#include "dxbc_to_dxil_converter.h" + +#include +#include +#include +#include +#include +#include + +#include "DxbcConverter.h" +#include "third_party/xxhash/xxhash.h" +#include "xenia/base/logging.h" + +#if !defined(_WIN32) && defined(__EMULATE_UUID) +size_t UuidStrHash(const char* key) { + long hash = 0; + while (*key) { + hash = (hash << 4) + *(key++); + long high_bits = hash & 0xF0000000L; + if (high_bits) { + hash ^= high_bits >> 24; + } + hash &= ~high_bits; + } + return static_cast(hash); +} + +DEFINE_CROSS_PLATFORM_UUIDOF(IUnknown) +#endif + +namespace xe { +namespace gpu { +namespace metal { + +namespace { +constexpr wchar_t kDefaultExtraOptions[] = L"-skip-container-parts"; + +const CLSID kClsidDxbcConverter = { + 0x4900391e, + 0xb752, + 0x4edd, + {0xa8, 0x85, 0x6f, 0xb7, 0x6e, 0x25, 0xad, 0xdb}}; +std::wstring WidenAscii(const std::string& value) { + std::wstring out; + out.reserve(value.size()); + for (char c : value) { + out.push_back(static_cast(c)); + } + return out; +} + +std::string HResultHex(HRESULT hr) { + char buffer[11]; + std::snprintf(buffer, sizeof(buffer), "%08X", static_cast(hr)); + return std::string(buffer); +} + +struct ThreadConverter { + IDxbcConverter* converter = nullptr; + ~ThreadConverter() { + if (converter) { + converter->Release(); + } + } +}; +} // namespace + +DxbcToDxilConverter::DxbcToDxilConverter() = default; + +DxbcToDxilConverter::~DxbcToDxilConverter() = default; + +uint64_t DxbcToDxilConverter::GetOptionsHash() const { + if (extra_options_.empty()) { + return 0; + } + return XXH3_64bits(extra_options_.data(), + extra_options_.size() * sizeof(wchar_t)); +} + +bool DxbcToDxilConverter::Initialize() { + const char* extra_options = std::getenv("XENIA_DXBC2DXIL_FLAGS"); + if (extra_options) { + extra_options_ = WidenAscii(extra_options); + } else { + extra_options_ = kDefaultExtraOptions; + } + + IDxbcConverter* test_converter = nullptr; + HRESULT hr = DxcCreateInstance(kClsidDxbcConverter, __uuidof(IDxbcConverter), + reinterpret_cast(&test_converter)); + if (hr != S_OK || !test_converter) { + XELOGE("DxbcToDxilConverter: Failed to create IDxbcConverter (hr=0x{:08X})", + static_cast(hr)); + is_available_ = false; + return false; + } + test_converter->Release(); + + is_available_ = true; + if (extra_options && *extra_options) { + XELOGI("DxbcToDxilConverter: Using extra options: {}", extra_options); + } else if (extra_options && !*extra_options) { + XELOGI("DxbcToDxilConverter: Extra options disabled via env"); + } else { + XELOGI( + "DxbcToDxilConverter: Using default extra options: " + "-skip-container-parts"); + } + return true; +} + +bool DxbcToDxilConverter::Convert(const std::vector& dxbc_data, + std::vector& dxil_data_out, + std::string* error_message) { + if (!is_available_) { + if (error_message) { + *error_message = + "DxbcToDxilConverter not initialized or dxilconv unavailable"; + } + return false; + } + + // Validate DXBC header + if (dxbc_data.size() < 4 || dxbc_data[0] != 'D' || dxbc_data[1] != 'X' || + dxbc_data[2] != 'B' || dxbc_data[3] != 'C') { + if (error_message) { + *error_message = "Invalid DXBC data - missing DXBC magic header"; + } + return false; + } + + // Check for debug output directories from environment + const char* dxbc_dir = std::getenv("XENIA_DXBC_OUTPUT_DIR"); + const char* dxil_dir = std::getenv("XENIA_DXIL_OUTPUT_DIR"); + + // Generate unique shader ID based on data hash + uint64_t hash = 0; + const size_t hash_bytes = std::min(dxbc_data.size(), size_t(64)); + for (size_t i = 0; i < hash_bytes; ++i) { + hash = hash * 31 + dxbc_data[i]; + } + std::string shader_id = std::to_string(hash) + "_" + std::to_string(getpid()); + + // Save DXBC to debug directory if requested. + if (dxbc_dir) { + std::string debug_input = + std::string(dxbc_dir) + "/shader_" + shader_id + ".dxbc"; + WriteFile(debug_input, dxbc_data); + } + + IDxbcConverter* converter = GetThreadConverter(error_message); + if (!converter) { + return false; + } + + void* dxil_ptr = nullptr; + UINT32 dxil_size = 0; + wchar_t* diag = nullptr; + + HRESULT hr = converter->Convert( + dxbc_data.data(), static_cast(dxbc_data.size()), + extra_options_.empty() ? nullptr : extra_options_.c_str(), &dxil_ptr, + &dxil_size, &diag); + + if (hr != S_OK || dxil_ptr == nullptr || dxil_size == 0) { + if (error_message) { + if (diag) { + std::string diag_utf8; + for (const wchar_t* p = diag; *p; ++p) { + diag_utf8.push_back(static_cast(*p)); + } + *error_message = "dxbc2dxil failed: " + diag_utf8; + } else { + *error_message = "dxbc2dxil failed with HRESULT 0x" + HResultHex(hr); + } + } + CoTaskMemFree(diag); + CoTaskMemFree(dxil_ptr); + return false; + } + + dxil_data_out.assign(reinterpret_cast(dxil_ptr), + reinterpret_cast(dxil_ptr) + dxil_size); + + CoTaskMemFree(diag); + CoTaskMemFree(dxil_ptr); + + // Copy to debug directory if specified. + if (dxil_dir) { + std::string debug_output = + std::string(dxil_dir) + "/shader_" + shader_id + ".dxil"; + WriteFile(debug_output, dxil_data_out); + } + + // Validate DXIL header (DXBC magic for container, or DXIL for raw). + if (dxil_data_out.size() < 4) { + if (error_message) { + *error_message = "Output DXIL blob too small"; + } + return false; + } + + XELOGD( + "DxbcToDxilConverter: Successfully converted {} bytes DXBC to {} bytes " + "DXIL", + dxbc_data.size(), dxil_data_out.size()); + + return true; +} + +IDxbcConverter* DxbcToDxilConverter::GetThreadConverter( + std::string* error_message) { + static thread_local ThreadConverter thread_state; + if (thread_state.converter) { + return thread_state.converter; + } + + HRESULT hr = + DxcCreateInstance(kClsidDxbcConverter, __uuidof(IDxbcConverter), + reinterpret_cast(&thread_state.converter)); + if (hr != S_OK || !thread_state.converter) { + if (error_message) { + *error_message = + "Failed to create IDxbcConverter (HRESULT 0x" + HResultHex(hr) + ")"; + } + return nullptr; + } + return thread_state.converter; +} + +bool DxbcToDxilConverter::WriteFile(const std::string& path, + const std::vector& data) { + std::ofstream file(path, std::ios::binary); + if (!file) { + return false; + } + + file.write(reinterpret_cast(data.data()), data.size()); + return file.good(); +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/dxbc_to_dxil_converter.h b/src/xenia/gpu/metal/dxbc_to_dxil_converter.h new file mode 100644 index 000000000..198fbd2b3 --- /dev/null +++ b/src/xenia/gpu/metal/dxbc_to_dxil_converter.h @@ -0,0 +1,64 @@ +/** + ****************************************************************************** + * 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. + * + * Converts DXBC to DXIL using the in-process dxilconv library (no CLI spawn). + */ + +#ifndef DXBC_TO_DXIL_CONVERTER_H_ +#define DXBC_TO_DXIL_CONVERTER_H_ + +#include +#include +#include + +struct IDxbcConverter; + +namespace xe { +namespace gpu { +namespace metal { + +class DxbcToDxilConverter { + public: + static constexpr uint32_t kCacheVersion = 1; + + DxbcToDxilConverter(); + ~DxbcToDxilConverter(); + + // Initialize the converter (ensure dxilconv is available). + bool Initialize(); + + // Convert DXBC bytecode to DXIL bytecode + // Returns true on success, false on failure + bool Convert(const std::vector& dxbc_data, + std::vector& dxil_data_out, + std::string* error_message = nullptr); + + // Check if the converter is available + bool IsAvailable() const { return is_available_; } + uint64_t GetOptionsHash() const; + + private: + bool is_available_ = false; + std::wstring extra_options_; + + // Lazily created per-thread converter instance. + IDxbcConverter* GetThreadConverter(std::string* error_message); + + // Write vector to file (for debug dumps). + bool WriteFile(const std::string& path, const std::vector& data); +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // DXBC_TO_DXIL_CONVERTER_H_ diff --git a/src/xenia/gpu/metal/ir_runtime_impl.mm b/src/xenia/gpu/metal/ir_runtime_impl.mm new file mode 100644 index 000000000..e81c54d86 --- /dev/null +++ b/src/xenia/gpu/metal/ir_runtime_impl.mm @@ -0,0 +1,19 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +// Single compilation unit for Metal IR Converter Runtime implementation +#include "third_party/metal-cpp/Metal/Metal.hpp" + +#ifndef IR_RUNTIME_METALCPP +#define IR_RUNTIME_METALCPP +#endif +#define IR_PRIVATE_IMPLEMENTATION // Generate the implementation exactly once + +// Use the actual runtime header with absolute path +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" diff --git a/src/xenia/gpu/metal/metal_backend_telemetry.cc b/src/xenia/gpu/metal/metal_backend_telemetry.cc new file mode 100644 index 000000000..bc27127c5 --- /dev/null +++ b/src/xenia/gpu/metal/metal_backend_telemetry.cc @@ -0,0 +1,234 @@ +/** + ****************************************************************************** + * 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_backend_telemetry.h" + +#include "third_party/fmt/include/fmt/format.h" + +namespace xe { +namespace gpu { +namespace metal { + +const char* MetalRenderEncoderEndReasonName(size_t reason) { + switch (reason) { + case 0: + return "unknown"; + case 1: + return "prepare_wait"; + case 2: + return "swap"; + case 3: + return "command_buffer_end"; + case 4: + return "transfer_request"; + case 5: + return "shared_memory_read"; + case 6: + return "rt_update_descriptor_dirty"; + case 7: + return "pipeline_descriptor_incompatible"; + case 8: + return "texture_upload_before_draw_pass"; + case 9: + return "shared_memory_upload_before_draw_pass"; + case 10: + return "resolve_needs_boundary"; + case 11: + return "begin_descriptor_changed"; + default: + return "invalid"; + } +} + +const char* MetalTransferRequestSourceName(size_t source) { + switch (source) { + case 0: + return "unknown"; + case 1: + return "shared_memory_upload"; + case 2: + return "guest_index_copy"; + case 3: + return "render_target_transfer"; + default: + return "invalid"; + } +} + +const char* MetalSharedMemoryRequestReasonName(size_t reason) { + switch (reason) { + case 0: + return "unknown"; + case 1: + return "vertex_fetch"; + case 2: + return "memexport_stream"; + case 3: + return "guest_index"; + case 4: + return "shader_primitive_index"; + case 5: + return "index_copy_source"; + case 6: + return "texture_base"; + case 7: + return "texture_mips"; + case 8: + return "texture_base_and_mips"; + case 9: + return "resolve_copy_dest"; + case 10: + return "draw_materialization"; + default: + return "invalid"; + } +} + +const char* MetalSharedMemoryRequestOutcomeName(size_t outcome) { + switch (outcome) { + case 0: + return "already_resident"; + case 1: + return "upload_before_render_encoder"; + case 2: + return "upload_inside_render_encoder"; + case 3: + return "request_failed"; + case 4: + return "no_shared_memory"; + case 5: + return "texture_deferred_upload_flush"; + default: + return "invalid"; + } +} + +const char* MetalSharedMemoryUploadRouteName(size_t route) { + switch (route) { + case 0: + return "staged_blit"; + case 1: + return "direct_write"; + default: + return "invalid"; + } +} + +const char* MetalSharedMemoryDirectWriteRejectReasonName(size_t reason) { + switch (reason) { + case 0: + return "main_gpu_access_in_flight"; + case 1: + return "standalone_access_in_flight"; + case 2: + return "no_shared_buffer_contents"; + case 3: + return "mixed_range_split"; + default: + return "invalid"; + } +} + +const char* MetalSharedMemoryUploadEncoderEndReasonName(size_t reason) { + switch (reason) { + case 0: + return "unknown"; + case 1: + return "render_begin"; + case 2: + return "transfer_request"; + case 3: + return "texture_compute"; + case 4: + return "texture_blit"; + case 5: + return "texture_deferred_blit"; + case 6: + return "scaled_resolve_blit"; + case 7: + return "command_buffer_end"; + case 8: + return "swap"; + case 9: + return "upload_failure"; + case 10: + return "shutdown"; + case 11: + return "materialization_drain"; + default: + return "invalid"; + } +} + +const char* MetalTextureUploadSourceRouteName(size_t route) { + switch (route) { + case 0: + return "cpu_guest"; + case 1: + return "resident_shared_memory"; + case 2: + return "scaled_resolve"; + default: + return "invalid"; + } +} + +const char* MetalTextureUploadSourceFallbackReasonName(size_t reason) { + switch (reason) { + case 0: + return "mixed_validity"; + case 1: + return "scaled_resolve"; + case 2: + return "source_already_resident"; + case 3: + return "cpu_source_load_failed"; + case 4: + return "unknown"; + default: + return "invalid"; + } +} + +std::string MetalFormatNamedCounts(const uint64_t* values, size_t count, + MetalTelemetryNameCallback name_callback) { + std::string formatted; + for (size_t i = 0; i < count; ++i) { + if (!values[i]) { + continue; + } + if (!formatted.empty()) { + formatted += ", "; + } + formatted += fmt::format("{}={}", name_callback(i), values[i]); + } + return formatted.empty() ? "none" : formatted; +} + +std::string MetalFormatNamedTriplets(const uint64_t* total, + const uint64_t* active, + const uint64_t* no_active, size_t count, + MetalTelemetryNameCallback name_callback) { + std::string formatted; + for (size_t i = 0; i < count; ++i) { + if (!total[i]) { + continue; + } + if (!formatted.empty()) { + formatted += ", "; + } + formatted += fmt::format("{}={}/{}/{}", name_callback(i), total[i], + active[i], no_active[i]); + } + return formatted.empty() ? "none" : formatted; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_backend_telemetry.h b/src/xenia/gpu/metal/metal_backend_telemetry.h new file mode 100644 index 000000000..cdff96cf4 --- /dev/null +++ b/src/xenia/gpu/metal/metal_backend_telemetry.h @@ -0,0 +1,61 @@ +/** + ****************************************************************************** + * 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_BACKEND_TELEMETRY_H_ +#define XENIA_GPU_METAL_METAL_BACKEND_TELEMETRY_H_ + +#include +#include +#include +#include + +namespace xe { +namespace gpu { +namespace metal { + +using MetalTelemetryNameCallback = const char* (*)(size_t); + +const char* MetalRenderEncoderEndReasonName(size_t reason); +const char* MetalTransferRequestSourceName(size_t source); +const char* MetalSharedMemoryRequestReasonName(size_t reason); +const char* MetalSharedMemoryRequestOutcomeName(size_t outcome); +const char* MetalSharedMemoryUploadRouteName(size_t route); +const char* MetalSharedMemoryDirectWriteRejectReasonName(size_t reason); +const char* MetalSharedMemoryUploadEncoderEndReasonName(size_t reason); +const char* MetalTextureUploadSourceRouteName(size_t route); +const char* MetalTextureUploadSourceFallbackReasonName(size_t reason); + +std::string MetalFormatNamedCounts(const uint64_t* values, size_t count, + MetalTelemetryNameCallback name_callback); +std::string MetalFormatNamedTriplets(const uint64_t* total, + const uint64_t* active, + const uint64_t* no_active, size_t count, + MetalTelemetryNameCallback name_callback); + +template +std::string MetalFormatNamedCounts(const std::array& values, + MetalTelemetryNameCallback name_callback) { + return MetalFormatNamedCounts(values.data(), values.size(), name_callback); +} + +template +std::string MetalFormatNamedTriplets( + const std::array& total, + const std::array& active, + const std::array& no_active, + MetalTelemetryNameCallback name_callback) { + return MetalFormatNamedTriplets(total.data(), active.data(), no_active.data(), + total.size(), name_callback); +} + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_BACKEND_TELEMETRY_H_ diff --git a/src/xenia/gpu/metal/metal_command_processor.cc b/src/xenia/gpu/metal/metal_command_processor.cc index 43b277657..dbe9647ad 100644 --- a/src/xenia/gpu/metal/metal_command_processor.cc +++ b/src/xenia/gpu/metal/metal_command_processor.cc @@ -8,43 +8,40 @@ */ #include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/metal/msl_bindings.h" #include #include #include #include -#include #include #include -#include #include #include #include #include -#include -#include +#include #include #include -#include "third_party/metal-cpp/Foundation/NSProcessInfo.hpp" +#include +#include + #include "third_party/metal-cpp/Foundation/NSURL.hpp" #include "third_party/metal-cpp/Metal/MTLEvent.hpp" +#include "third_party/metal-cpp/Metal/MTLResidencySet.hpp" -#include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/cvar.h" -#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/memory.h" #include "xenia/base/profiling.h" #include "xenia/base/xxhash.h" #include "xenia/gpu/draw_util.h" #include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/graphics_system.h" +#include "xenia/gpu/metal/metal_backend_telemetry.h" #include "xenia/gpu/metal/metal_graphics_system.h" -#include "xenia/gpu/metal/metal_shader_cache.h" #include "xenia/gpu/packet_disassembler.h" #include "xenia/gpu/registers.h" #include "xenia/gpu/texture_util.h" @@ -53,25 +50,25 @@ #include "xenia/kernel/user_module.h" #include "xenia/ui/metal/metal_presenter.h" +// Metal IR Converter Runtime - defines IRDescriptorTableEntry and bind points. +#ifndef IR_RUNTIME_METALCPP +#define IR_RUNTIME_METALCPP +#endif +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" + #ifndef DISPATCH_DATA_DESTRUCTOR_NONE #define DISPATCH_DATA_DESTRUCTOR_NONE DISPATCH_DATA_DESTRUCTOR_DEFAULT #endif +DECLARE_bool(async_shader_compilation); DECLARE_bool(clear_memory_page_state); DECLARE_bool(submit_on_primary_buffer_end); -DECLARE_bool(metal_shader_disk_cache); -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_int32( - metal_pipeline_creation_threads, -1, - "Number of threads used for SPIRV-Cross shader and render pipeline " - "compilation in the Metal backend. -1 to calculate automatically (75% of " - "logical CPU cores), a positive number to specify the number of threads " - "explicitly (up to the number of logical CPU cores), 0 to disable " - "multithreaded compilation.", + +DEFINE_bool( + metal_float_constants_dirty_on_change, true, + "Only invalidate Metal float constant CBVs when a register write changes a " + "currently live float constant value. Disable to restore conservative " + "dirty-on-write behavior.", "Metal"); namespace xe { @@ -79,7 +76,385 @@ namespace gpu { namespace metal { namespace { -bool UseSpirvCrossPath() { return true; } +constexpr size_t kMaxPendingSharedMemoryWrites = 16; +constexpr size_t kMaxPendingSharedMemoryWriteCapacity = 64; +constexpr size_t kMaxSharedMemoryWaitSegmentsPerPending = 64; +constexpr size_t kMaxCurrentDrawVertexFetchRanges = + xenos::kVertexFetchConstantCount; +const char* MetalTelemetryCbvSlotName(size_t slot) { + switch (slot) { + case 0: + return "system"; + case 1: + return "float"; + case 2: + return "bool_loop"; + case 3: + return "fetch"; + case 4: + return "descriptor_indices"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryShaderStageName(size_t stage) { + switch (stage) { + case 0: + return "vertex"; + case 1: + return "pixel"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryRootScopeName(size_t stage) { + switch (stage) { + case 0: + return "graphics"; + case 1: + return "unused"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryRenderResourceSetName(size_t set) { + switch (set) { + case 0: + return "fixed"; + case 1: + return "texture"; + case 2: + return "root"; + default: + return "invalid"; + } +} + +MTL::RenderStages MetalAllGraphicsRenderStages() { + return MTL::RenderStages(MTL::RenderStageVertex | MTL::RenderStageFragment | + MTL::RenderStageObject | MTL::RenderStageMesh); +} + +uint32_t MetalRenderStageBits(MTL::RenderStages stages) { + return uint32_t(NS::UInteger(stages)); +} + +uint32_t MetalResourceUsageBits(MTL::ResourceUsage usage) { + return uint32_t(NS::UInteger(usage)); +} + +bool MetalObjectRespondsToSelector(const void* object, + const char* selector_name) { + if (!object || !selector_name) { + return false; + } + SEL selector = sel_registerName(selector_name); + SEL responds_to_selector = sel_registerName("respondsToSelector:"); + using RespondsToSelectorFn = BOOL (*)(id, SEL, SEL); + return reinterpret_cast(objc_msgSend)( + reinterpret_cast(const_cast(object)), + responds_to_selector, selector) != 0; +} + +bool MetalResidencySetCvarAllowsEnable(bool supported) { + const std::string& mode = cvars::metal_residency_sets; + if (mode == "0" || mode == "false" || mode == "off" || mode == "disabled") { + return false; + } + if (mode == "1" || mode == "true" || mode == "on" || mode == "enabled") { + return true; + } + return supported; +} + +const char* MetalTelemetryRootRebuildReasonName(size_t reason) { + switch (reason) { + case 0: + return "frame_open"; + case 1: + return "descriptor_indices_pointer_tuple"; + case 2: + return "other_cbv_pointer_tuple"; + case 3: + return "shared_memory_uav_mode"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryRootSlotsChangedName(size_t bin) { + switch (bin) { + case 0: + return "0"; + case 1: + return "1"; + case 2: + return "2"; + case 3: + return "3"; + case 4: + return "4"; + case 5: + return "5_plus"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryRootRebuildDetailName(size_t detail) { + switch (detail) { + case 0: + return "same_buffer_offset_changed"; + case 1: + return "different_buffer"; + case 2: + return "descriptor_indices_only"; + case 3: + return "other_cbv_only"; + case 4: + return "mixed_descriptor_and_other"; + case 5: + return "resource_identity_changed"; + case 6: + return "resource_identity_same"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryDrawMaterializationSourceName(size_t source) { + switch (source) { + case 0: + return "vertex_fetch"; + case 1: + return "guest_index"; + case 2: + return "memexport"; + case 3: + return "texture_source"; + default: + return "invalid"; + } +} + +const char* MetalPreparedDrawFlushReasonName(size_t reason) { + switch (reason) { + case 0: + return "manual"; + case 1: + return "rt_update"; + case 2: + return "rt_key_mismatch"; + case 3: + return "queue_budget"; + case 4: + return "queue_reject"; + case 5: + return "prepare_wait"; + case 6: + return "swap"; + case 7: + return "copy"; + case 8: + return "transfer_request"; + case 9: + return "render_encoder_end"; + case 10: + return "command_buffer_end"; + case 11: + return "query"; + default: + return "invalid"; + } +} + +const char* MetalPreparedDrawQueueRejectReasonName(size_t reason) { + switch (reason) { + case 0: + return "none"; + case 1: + return "resident_no_active_queue"; + case 2: + return "no_shared_memory_ranges"; + case 3: + return "memexport"; + case 4: + return "texture_upload"; + case 5: + return "texture_request_load_data"; + case 6: + return "pending_draw_pass_transfers"; + case 7: + return "zpd_active"; + case 8: + return "rt_key_mismatch"; + case 9: + return "queue_budget"; + default: + return "invalid"; + } +} + +const char* MetalTelemetryRootArgSlotName(size_t slot) { + switch (slot) { + case 0: + return "srv0"; + case 1: + return "srv1"; + case 2: + return "srv2"; + case 3: + return "srv3"; + case 4: + return "srv10"; + case 5: + return "uav0"; + case 6: + return "uav1"; + case 7: + return "uav2"; + case 8: + return "uav3"; + case 9: + return "sampler0"; + case 10: + return "cbv_system"; + case 11: + return "cbv_float"; + case 12: + return "cbv_bool_loop"; + case 13: + return "cbv_fetch"; + case 14: + return "cbv_descriptor_indices"; + case 15: + return "cbv_hull_float"; + case 16: + return "cbv_hull_fetch"; + case 17: + return "cbv_hull_descriptor_indices"; + case 18: + return "cbv_domain_float"; + case 19: + return "cbv_domain_fetch"; + case 20: + return "cbv_domain_descriptor_indices"; + case 21: + return "cbv_pixel_float"; + case 22: + return "cbv_pixel_fetch"; + case 23: + return "cbv_pixel_descriptor_indices"; + default: + return "unused"; + } +} + +const char* MetalTelemetryRenderEncoderBufferStageName(size_t stage) { + switch (stage) { + case 0: + return "vertex"; + case 1: + return "fragment"; + case 2: + return "object"; + case 3: + return "mesh"; + default: + return "invalid"; + } +} + +struct SharedMemoryRangeSegment { + uint32_t start; + uint32_t end; +}; + +template +void ForEachSharedMemoryRangeSegment( + const MetalCommandProcessor::SharedMemoryRange& range, Visitor&& visitor) { + if (!range.length) { + return; + } + constexpr uint32_t kSharedMemoryMask = SharedMemory::kBufferSize - 1; + uint32_t remaining = std::min(range.length, SharedMemory::kBufferSize); + uint32_t start = range.start & kSharedMemoryMask; + while (remaining) { + uint32_t segment_length = + std::min(remaining, SharedMemory::kBufferSize - start); + if (segment_length) { + visitor(SharedMemoryRangeSegment{start, start + segment_length}); + } + remaining -= segment_length; + start = 0; + } +} + +bool SharedMemoryRangeOverlapsSegment( + const MetalCommandProcessor::SharedMemoryRange& range, + uint32_t segment_start, uint32_t segment_end) { + if (segment_start >= segment_end) { + return false; + } + bool overlaps = false; + ForEachSharedMemoryRangeSegment( + range, [&](SharedMemoryRangeSegment range_segment) { + overlaps = overlaps || (segment_start < range_segment.end && + segment_end > range_segment.start); + }); + return overlaps; +} + +bool GetTextureSize(MTL::Texture* texture, uint32_t& width_out, + uint32_t& height_out) { + if (!texture) { + return false; + } + width_out = std::max(static_cast(texture->width()), uint32_t(1)); + height_out = std::max(static_cast(texture->height()), uint32_t(1)); + return true; +} + +bool GetRenderPassDescriptorSize(MTL::RenderPassDescriptor* pass_descriptor, + uint32_t& width_out, uint32_t& height_out) { + if (!pass_descriptor) { + return false; + } + + const uint32_t constrained_width = + static_cast(pass_descriptor->renderTargetWidth()); + const uint32_t constrained_height = + static_cast(pass_descriptor->renderTargetHeight()); + if (constrained_width && constrained_height) { + width_out = std::max(constrained_width, uint32_t(1)); + height_out = std::max(constrained_height, uint32_t(1)); + return true; + } + + if (auto* color_attachments = pass_descriptor->colorAttachments()) { + for (uint32_t i = 0; i < 8; ++i) { + auto* attachment = color_attachments->object(i); + if (attachment && + GetTextureSize(attachment->texture(), width_out, height_out)) { + return true; + } + } + } + if (auto* depth_attachment = pass_descriptor->depthAttachment()) { + if (GetTextureSize(depth_attachment->texture(), width_out, height_out)) { + return true; + } + } + if (auto* stencil_attachment = pass_descriptor->stencilAttachment()) { + if (GetTextureSize(stencil_attachment->texture(), width_out, height_out)) { + return true; + } + } + return false; +} void GetBoundRenderTargetSize(const MetalRenderTargetCache* render_target_cache, uint32_t fallback_width, uint32_t fallback_height, @@ -99,10 +474,20 @@ void GetBoundRenderTargetSize(const MetalRenderTargetCache* render_target_cache, if (!pass_size_texture) { return; } - width_out = - std::max(static_cast(pass_size_texture->width()), uint32_t(1)); - height_out = - std::max(static_cast(pass_size_texture->height()), uint32_t(1)); + GetTextureSize(pass_size_texture, width_out, height_out); +} + +void GetActiveRenderTargetSize( + MTL::RenderPassDescriptor* pass_descriptor, + const MetalRenderTargetCache* render_target_cache, uint32_t fallback_width, + uint32_t fallback_height, uint32_t& width_out, uint32_t& height_out) { + width_out = std::max(fallback_width, uint32_t(1)); + height_out = std::max(fallback_height, uint32_t(1)); + if (GetRenderPassDescriptorSize(pass_descriptor, width_out, height_out)) { + return; + } + GetBoundRenderTargetSize(render_target_cache, fallback_width, fallback_height, + width_out, height_out); } void ClampScissorToBounds(draw_util::Scissor& scissor, uint32_t width, @@ -119,79 +504,81 @@ void ClampScissorToBounds(draw_util::Scissor& scissor, uint32_t width, scissor.extent[1] = std::min(scissor.extent[1], max_scissor_height); } -void LogMetalErrorDetails(const char* label, NS::Error* error) { - if (!error) { - return; +PipelineAttachmentFormats ResolvePipelineAttachmentFormats( + const MetalRenderTargetCache* render_target_cache, + MTL::RenderPassDescriptor* pass_descriptor, bool pixel_shader_writes_depth, + const char* pipeline_name) { + PipelineAttachmentFormats result; + result.sample_count = 1; + for (uint32_t i = 0; i < 4; ++i) { + result.color_formats[i] = MTL::PixelFormatInvalid; } - const char* desc = error->localizedDescription() - ? error->localizedDescription()->utf8String() - : nullptr; - const char* failure = error->localizedFailureReason() - ? error->localizedFailureReason()->utf8String() - : nullptr; - const char* recovery = - error->localizedRecoverySuggestion() - ? error->localizedRecoverySuggestion()->utf8String() - : nullptr; - const char* domain = - error->domain() ? error->domain()->utf8String() : nullptr; - int64_t code = error->code(); - XELOGE("{}: domain={} code={} desc='{}' failure='{}' recovery='{}'", label, - domain ? domain : "", code, desc ? desc : "", - failure ? failure : "", recovery ? recovery : ""); - NS::Dictionary* user_info = error->userInfo(); - if (user_info) { - auto* info_desc = user_info->description(); - XELOGE("{}: userInfo={}", label, - info_desc ? info_desc->utf8String() : ""); - } -} + result.depth_format = MTL::PixelFormatInvalid; + result.stencil_format = MTL::PixelFormatInvalid; -constexpr int64_t kMslAsyncLogIntervalNs = - int64_t(std::chrono::nanoseconds(std::chrono::seconds(1)).count()); -constexpr size_t kResolvedMemoryRangesMax = 8192; - -int64_t GetSteadyTimeNs() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); -} - -bool ShouldLogRateLimited(std::atomic& last_log_ns, - int64_t interval_ns) { - const int64_t now = GetSteadyTimeNs(); - int64_t previous = last_log_ns.load(std::memory_order_relaxed); - while (now - previous >= interval_ns) { - if (last_log_ns.compare_exchange_weak(previous, now, - std::memory_order_relaxed, - std::memory_order_relaxed)) { - return true; + if (render_target_cache) { + for (uint32_t i = 0; i < 4; ++i) { + if (MTL::Texture* rt = render_target_cache->GetColorTargetForDraw(i)) { + result.color_formats[i] = rt->pixelFormat(); + if (rt->sampleCount() > 0) { + result.sample_count = std::max( + result.sample_count, static_cast(rt->sampleCount())); + } + } + } + if (result.color_formats[0] == MTL::PixelFormatInvalid) { + if (MTL::Texture* dummy = + render_target_cache->GetDummyColorTargetForDraw()) { + result.color_formats[0] = dummy->pixelFormat(); + if (dummy->sampleCount() > 0) { + result.sample_count = std::max( + result.sample_count, static_cast(dummy->sampleCount())); + } + } + } + if (MTL::Texture* depth_tex = + render_target_cache->GetDepthTargetForDraw()) { + result.depth_format = depth_tex->pixelFormat(); + switch (result.depth_format) { + case MTL::PixelFormatDepth32Float_Stencil8: + case MTL::PixelFormatDepth24Unorm_Stencil8: + case MTL::PixelFormatX32_Stencil8: + result.stencil_format = result.depth_format; + break; + default: + result.stencil_format = MTL::PixelFormatInvalid; + break; + } + if (depth_tex->sampleCount() > 0) { + result.sample_count = + std::max(result.sample_count, + static_cast(depth_tex->sampleCount())); + } } } - return false; -} -void PopulatePipelineFormatsFromRenderPassDescriptor( - MTL::RenderPassDescriptor* pass_descriptor, MTL::PixelFormat* color_formats, - uint32_t color_count, MTL::PixelFormat* depth_format, - MTL::PixelFormat* stencil_format, uint32_t* sample_count) { - if (!pass_descriptor) { - return; - } - auto update_sample_count = [&](MTL::Texture* texture) { - if (!texture || !sample_count) { - return; + if (pass_descriptor) { + // Rebuild strictly from the active encoder descriptor. + result.sample_count = 1; + for (uint32_t i = 0; i < 4; ++i) { + result.color_formats[i] = MTL::PixelFormatInvalid; } - NS::UInteger sc = texture->sampleCount(); - if (sc > 0) { - *sample_count = - std::max(*sample_count, static_cast(sc)); - } - }; + result.depth_format = MTL::PixelFormatInvalid; + result.stencil_format = MTL::PixelFormatInvalid; + + auto update_sample_count = [&](MTL::Texture* texture) { + if (!texture) { + return; + } + NS::UInteger sc = texture->sampleCount(); + if (sc > 0) { + result.sample_count = + std::max(result.sample_count, static_cast(sc)); + } + }; - if (color_formats) { auto* color_attachments = pass_descriptor->colorAttachments(); - for (uint32_t i = 0; i < color_count; ++i) { + for (uint32_t i = 0; i < 4; ++i) { auto* attachment = color_attachments ? color_attachments->object(i) : nullptr; if (!attachment) { @@ -201,154 +588,67 @@ void PopulatePipelineFormatsFromRenderPassDescriptor( if (!texture) { continue; } - color_formats[i] = texture->pixelFormat(); + result.color_formats[i] = texture->pixelFormat(); update_sample_count(texture); } - } - - if (depth_format) { if (auto* depth_attachment = pass_descriptor->depthAttachment()) { MTL::Texture* texture = depth_attachment->texture(); if (texture) { - *depth_format = texture->pixelFormat(); + result.depth_format = texture->pixelFormat(); update_sample_count(texture); } } - } - - if (stencil_format) { if (auto* stencil_attachment = pass_descriptor->stencilAttachment()) { MTL::Texture* texture = stencil_attachment->texture(); if (texture) { - *stencil_format = texture->pixelFormat(); + result.stencil_format = texture->pixelFormat(); update_sample_count(texture); } } - } - - if (depth_format && stencil_format) { - if (*depth_format != MTL::PixelFormatInvalid && - *stencil_format == MTL::PixelFormatInvalid) { - switch (*depth_format) { + // Propagate combined depth-stencil formats. + if (result.depth_format != MTL::PixelFormatInvalid && + result.stencil_format == MTL::PixelFormatInvalid) { + switch (result.depth_format) { case MTL::PixelFormatDepth32Float_Stencil8: case MTL::PixelFormatDepth24Unorm_Stencil8: case MTL::PixelFormatX32_Stencil8: - *stencil_format = *depth_format; + result.stencil_format = result.depth_format; break; default: break; } - } else if (*stencil_format != MTL::PixelFormatInvalid && - *depth_format == MTL::PixelFormatInvalid) { - switch (*stencil_format) { + } else if (result.stencil_format != MTL::PixelFormatInvalid && + result.depth_format == MTL::PixelFormatInvalid) { + switch (result.stencil_format) { case MTL::PixelFormatDepth32Float_Stencil8: case MTL::PixelFormatDepth24Unorm_Stencil8: case MTL::PixelFormatX32_Stencil8: - *depth_format = *stencil_format; + result.depth_format = result.stencil_format; break; default: break; } } } + + // Metal pipeline attachment formats must match the active framebuffer + // exactly. If no depth attachment is bound, a depth-writing fragment output + // must not fabricate a depth format in the pipeline descriptor. + if (pixel_shader_writes_depth && + result.depth_format == MTL::PixelFormatInvalid) { + static bool logged = false; + if (!logged) { + logged = true; + XELOGW( + "{}: fragment writes depth without a bound depth attachment; " + "using no depth attachment in the pipeline descriptor", + pipeline_name); + } + } + + return result; } -void EnsureDepthFormatForDepthWritingFragment(const char* pipeline_name, - bool fragment_writes_depth, - MTL::PixelFormat* depth_format) { - if (!fragment_writes_depth || !depth_format || - *depth_format != MTL::PixelFormatInvalid) { - return; - } - // Metal requires a valid depth attachment format if the fragment shader - // writes depth even when the current render pass has no depth target bound. - *depth_format = MTL::PixelFormatDepth32Float; - static bool logged = false; - if (!logged) { - logged = true; - XELOGW( - "{}: fragment writes depth without a bound depth attachment; " - "using Depth32Float pipeline fallback", - pipeline_name); - } -} - -MTL::ComputePipelineState* CreateComputePipelineFromEmbeddedLibrary( - MTL::Device* device, const void* metallib_data, size_t metallib_size, - const char* debug_name) { - if (!device || !metallib_data || !metallib_size) { - return nullptr; - } - - NS::Error* error = nullptr; - dispatch_data_t data = dispatch_data_create( - metallib_data, metallib_size, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* lib = device->newLibrary(data, &error); - dispatch_release(data); - if (!lib) { - XELOGE("Metal: failed to create {} library: {}", debug_name, - error ? error->localizedDescription()->utf8String() : "unknown"); - return nullptr; - } - - // XeSL compute entrypoint name used in the embedded metallibs. - NS::String* fn_name = NS::String::string("entry_xe", NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGE("Metal: {} missing entry_xe", debug_name); - lib->release(); - return nullptr; - } - - MTL::ComputePipelineState* pipeline = - device->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - - if (!pipeline) { - XELOGE("Metal: failed to create {} pipeline: {}", debug_name, - error ? error->localizedDescription()->utf8String() : "unknown"); - return nullptr; - } - - return pipeline; -} - -constexpr uint32_t kPipelineDiskCacheMagic = 0x43504D58; // 'XMPC' -constexpr uint32_t kPipelineDiskCacheVersion = 2; -constexpr size_t kPipelineDiskCacheMaxEntrySize = 1 << 20; - -XEPACKEDSTRUCT(PipelineDiskCacheHeader, { - uint32_t magic; - uint32_t version; - uint32_t reserved[2]; -}); - -XEPACKEDSTRUCT(PipelineDiskCacheEntryHeader, { - uint32_t entry_size; - uint32_t reserved; -}); - -XEPACKEDSTRUCT(PipelineDiskCacheEntryBase, { - uint64_t pipeline_key; - uint64_t vertex_shader_cache_key; - uint64_t pixel_shader_cache_key; - uint32_t sample_count; - uint32_t depth_format; - uint32_t stencil_format; - uint32_t color_formats[4]; - uint32_t normalized_color_mask; - uint32_t alpha_to_mask_enable; - uint32_t blendcontrol[4]; - uint32_t vertex_attribute_count; - uint32_t vertex_layout_count; -}); - -static_assert(sizeof(PipelineDiskCacheHeader) == 16, - "Unexpected pipeline disk cache header size."); -static_assert(sizeof(PipelineDiskCacheEntryHeader) == 8, - "Unexpected pipeline disk cache entry header size."); - bool ShaderUsesVertexFetch(const Shader& shader) { if (!shader.vertex_bindings().empty()) { return true; @@ -363,6 +663,68 @@ bool ShaderUsesVertexFetch(const Shader& shader) { return false; } +bool CollectMetalVertexFetchSharedMemoryRanges(const RegisterFile& regs, + const Shader& vertex_shader, + SharedMemory::Range* ranges, + uint32_t max_ranges, + uint32_t* range_count, + bool strict_logging) { + *range_count = 0; + const Shader::ConstantRegisterMap& constant_map = + vertex_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 j; + while (xe::bit_scan_forward(vfetch_bits_remaining, &j)) { + vfetch_bits_remaining &= ~(uint32_t(1) << j); + uint32_t vfetch_index = i * 32 + j; + xenos::xe_gpu_vertex_fetch_t vfetch = regs.GetVertexFetch(vfetch_index); + switch (vfetch.type) { + case xenos::FetchConstantType::kVertex: + break; + case xenos::FetchConstantType::kInvalidVertex: + if (::cvars::gpu_allow_invalid_fetch_constants) { + break; + } + if (strict_logging) { + XELOGW( + "Vertex fetch constant {} ({:08X} {:08X}) has \"invalid\" " + "type. Use --gpu_allow_invalid_fetch_constants to bypass.", + vfetch_index, vfetch.dword_0, vfetch.dword_1); + } + return false; + default: + if (strict_logging) { + XELOGW("Vertex fetch constant {} ({:08X} {:08X}) is invalid.", + vfetch_index, vfetch.dword_0, vfetch.dword_1); + } + return false; + } + uint32_t buffer_offset = vfetch.address << 2; + uint32_t buffer_length = vfetch.size << 2; + if (buffer_offset > SharedMemory::kBufferSize || + SharedMemory::kBufferSize - buffer_offset < buffer_length) { + if (strict_logging) { + XELOGW( + "Vertex fetch constant {} out of range (offset=0x{:08X} " + "size={})", + vfetch_index, buffer_offset, buffer_length); + } + return false; + } + if (*range_count >= max_ranges) { + if (strict_logging) { + XELOGW("Too many vertex fetch shared-memory ranges"); + } + return false; + } + ranges[(*range_count)++] = + SharedMemory::Range{buffer_offset, buffer_length}; + } + } + return true; +} + MTL::CompareFunction ToMetalCompareFunction(xenos::CompareFunction compare) { static const MTL::CompareFunction kCompareMap[8] = { MTL::CompareFunctionNever, // 0 @@ -391,109 +753,59 @@ MTL::StencilOperation ToMetalStencilOperation(xenos::StencilOp op) { return kStencilOpMap[uint32_t(op) & 0x7]; } -MTL::ColorWriteMask ToMetalColorWriteMask(uint32_t write_mask) { - MTL::ColorWriteMask mtl_mask = MTL::ColorWriteMaskNone; - if (write_mask & 0x1) { - mtl_mask |= MTL::ColorWriteMaskRed; +bool FetchConstantDwordMasksOverlap( + const DxbcShader::FetchConstantDwordMask& a, + const DxbcShader::FetchConstantDwordMask& b) { + for (size_t i = 0; i < a.size(); ++i) { + if (a[i] & b[i]) { + return true; + } } - if (write_mask & 0x2) { - mtl_mask |= MTL::ColorWriteMaskGreen; - } - if (write_mask & 0x4) { - mtl_mask |= MTL::ColorWriteMaskBlue; - } - if (write_mask & 0x8) { - mtl_mask |= MTL::ColorWriteMaskAlpha; - } - return mtl_mask; + return false; } -MTL::BlendOperation ToMetalBlendOperation(xenos::BlendOp blend_op) { - // 8 entries for safety since 3 bits from the guest are passed directly. - static const MTL::BlendOperation kBlendOpMap[8] = { - MTL::BlendOperationAdd, // 0 - MTL::BlendOperationSubtract, // 1 - MTL::BlendOperationMin, // 2 - MTL::BlendOperationMax, // 3 - MTL::BlendOperationReverseSubtract, // 4 - MTL::BlendOperationAdd, // 5 - MTL::BlendOperationAdd, // 6 - MTL::BlendOperationAdd, // 7 - }; - return kBlendOpMap[uint32_t(blend_op) & 0x7]; +bool FetchConstantDwordMaskEmpty( + const DxbcShader::FetchConstantDwordMask& mask) { + for (uint32_t word : mask) { + if (word) { + return false; + } + } + return true; } -MTL::BlendFactor ToMetalBlendFactorRgb(xenos::BlendFactor blend_factor) { - // 32 because of 0x1F mask, for safety (all unknown to zero). - static const MTL::BlendFactor kBlendFactorMap[32] = { - /* 0 */ MTL::BlendFactorZero, - /* 1 */ MTL::BlendFactorOne, - /* 2 */ MTL::BlendFactorZero, // ? - /* 3 */ MTL::BlendFactorZero, // ? - /* 4 */ MTL::BlendFactorSourceColor, - /* 5 */ MTL::BlendFactorOneMinusSourceColor, - /* 6 */ MTL::BlendFactorSourceAlpha, - /* 7 */ MTL::BlendFactorOneMinusSourceAlpha, - /* 8 */ MTL::BlendFactorDestinationColor, - /* 9 */ MTL::BlendFactorOneMinusDestinationColor, - /* 10 */ MTL::BlendFactorDestinationAlpha, - /* 11 */ MTL::BlendFactorOneMinusDestinationAlpha, - /* 12 */ MTL::BlendFactorBlendColor, // CONSTANT_COLOR - /* 13 */ MTL::BlendFactorOneMinusBlendColor, - /* 14 */ MTL::BlendFactorBlendAlpha, // CONSTANT_ALPHA - /* 15 */ MTL::BlendFactorOneMinusBlendAlpha, - /* 16 */ MTL::BlendFactorSourceAlphaSaturated, - }; - return kBlendFactorMap[uint32_t(blend_factor) & 0x1F]; +void MarkAllFetchConstantDwords(DxbcShader::FetchConstantDwordMask& mask) { + mask.fill(UINT32_MAX); } -MTL::BlendFactor ToMetalBlendFactorAlpha(xenos::BlendFactor blend_factor) { - // Like the RGB map, but with color modes changed to alpha. - static const MTL::BlendFactor kBlendFactorAlphaMap[32] = { - /* 0 */ MTL::BlendFactorZero, - /* 1 */ MTL::BlendFactorOne, - /* 2 */ MTL::BlendFactorZero, // ? - /* 3 */ MTL::BlendFactorZero, // ? - /* 4 */ MTL::BlendFactorSourceAlpha, - /* 5 */ MTL::BlendFactorOneMinusSourceAlpha, - /* 6 */ MTL::BlendFactorSourceAlpha, - /* 7 */ MTL::BlendFactorOneMinusSourceAlpha, - /* 8 */ MTL::BlendFactorDestinationAlpha, - /* 9 */ MTL::BlendFactorOneMinusDestinationAlpha, - /* 10 */ MTL::BlendFactorDestinationAlpha, - /* 11 */ MTL::BlendFactorOneMinusDestinationAlpha, - /* 12 */ MTL::BlendFactorBlendAlpha, - /* 13 */ MTL::BlendFactorOneMinusBlendAlpha, - /* 14 */ MTL::BlendFactorBlendAlpha, - /* 15 */ MTL::BlendFactorOneMinusBlendAlpha, - /* 16 */ MTL::BlendFactorSourceAlphaSaturated, - }; - return kBlendFactorAlphaMap[uint32_t(blend_factor) & 0x1F]; +void MarkFetchConstantDword(DxbcShader::FetchConstantDwordMask& mask, + uint32_t dword_index) { + if (dword_index >= DxbcShader::kFetchConstantDwordCount) { + assert_always(); + return; + } + mask[dword_index >> 5] |= uint32_t(1) << (dword_index & 31); +} + +void MergeFetchConstantDwordMask( + DxbcShader::FetchConstantDwordMask& dest, + const DxbcShader::FetchConstantDwordMask& src) { + for (size_t i = 0; i < dest.size(); ++i) { + dest[i] |= src[i]; + } } } // namespace MetalCommandProcessor::MetalCommandProcessor( MetalGraphicsSystem* graphics_system, kernel::KernelState* kernel_state) - : CommandProcessor(graphics_system, kernel_state) {} - -std::string MetalCommandProcessor::GetTitleStateSuffix() const { - if (!render_target_cache_) { - return {}; - } - std::ostringstream suffix; - suffix << " - SPIRV-Cross"; - uint32_t draw_resolution_scale_x = - texture_cache_ ? texture_cache_->draw_resolution_scale_x() : 1; - uint32_t draw_resolution_scale_y = - texture_cache_ ? texture_cache_->draw_resolution_scale_y() : 1; - if (draw_resolution_scale_x > 1 || draw_resolution_scale_y > 1) { - suffix << ' ' << draw_resolution_scale_x << 'x' << draw_resolution_scale_y; - } - return suffix.str(); + : CommandProcessor(graphics_system, kernel_state) { + pending_shared_memory_writes_.reserve(kMaxPendingSharedMemoryWriteCapacity); } MetalCommandProcessor::~MetalCommandProcessor() { + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kShutdown); // End any active render encoder before releasing // Note: Only call endEncoding if the encoder is still active // (not already ended by a committed command buffer) @@ -502,25 +814,21 @@ MetalCommandProcessor::~MetalCommandProcessor() { // In that case, just release it current_render_encoder_->release(); current_render_encoder_ = nullptr; + current_render_encoder_has_zpd_visibility_ = false; + ResetRenderEncoderBufferBindings(); + } + if (current_render_pass_descriptor_) { + current_render_pass_descriptor_->release(); + current_render_pass_descriptor_ = nullptr; } if (current_command_buffer_) { current_command_buffer_->release(); current_command_buffer_ = nullptr; } WaitForPendingCompletionHandlers(); - ShutdownMslAsyncCompilation(); - if (render_pass_descriptor_) { - render_pass_descriptor_->release(); - render_pass_descriptor_ = nullptr; - } - if (render_target_texture_) { - render_target_texture_->release(); - render_target_texture_ = nullptr; - } - if (depth_stencil_texture_) { - depth_stencil_texture_->release(); - depth_stencil_texture_ = nullptr; - } + + // Release pipeline cache (owns shaders, pipelines, shader translation). + pipeline_cache_.reset(); for (auto& pair : depth_stencil_state_cache_) { if (pair.second) { @@ -542,410 +850,25 @@ MetalCommandProcessor::~MetalCommandProcessor() { null_sampler_->release(); null_sampler_ = nullptr; } - uniforms_buffer_ = nullptr; - command_buffer_spirv_uniform_buffers_.clear(); - { - std::lock_guard lock(spirv_uniforms_mutex_); - spirv_uniforms_available_.clear(); - for (MTL::Buffer* pool_uniforms : spirv_uniforms_pool_) { - if (pool_uniforms) { - pool_uniforms->release(); - } - } - spirv_uniforms_pool_.clear(); - spirv_uniforms_pool_initialized_ = false; - } - if (spirv_uniforms_available_semaphore_) { -#if !OS_OBJECT_USE_OBJC - dispatch_release(spirv_uniforms_available_semaphore_); -#endif - spirv_uniforms_available_semaphore_ = nullptr; - } -} - -void MetalCommandProcessor::InitializeMslAsyncCompilation() { - ShutdownMslAsyncCompilation(); - - if (!cvars::async_shader_compilation) { - return; - } - - uint32_t logical_processor_count = std::thread::hardware_concurrency(); - if (!logical_processor_count) { - logical_processor_count = 6; - } - - if (cvars::metal_pipeline_creation_threads == 0) { - return; - } - - size_t thread_count = 0; - if (cvars::metal_pipeline_creation_threads < 0) { - thread_count = std::max(logical_processor_count * 3 / 4, 1); - } else { - thread_count = - std::min(uint32_t(cvars::metal_pipeline_creation_threads), - logical_processor_count); - } - if (!thread_count) { - return; - } - - { - std::lock_guard lock(msl_shader_compile_mutex_); - msl_shader_compile_shutdown_ = false; - } - - msl_shader_compile_threads_.reserve(thread_count); - for (size_t i = 0; i < thread_count; ++i) { - msl_shader_compile_threads_.emplace_back( - [this, i]() { MslShaderCompileThread(i); }); - } - - XELOGI( - "SPIRV-Cross: async Metal shader/pipeline compilation enabled with {} " - "worker " - "thread(s)", - thread_count); -} - -void MetalCommandProcessor::ShutdownMslAsyncCompilation() { - { - std::lock_guard lock(msl_shader_compile_mutex_); - msl_shader_compile_shutdown_ = true; - } - msl_shader_compile_cv_.notify_all(); - - for (std::thread& thread : msl_shader_compile_threads_) { - if (thread.joinable()) { - thread.join(); - } - } - msl_shader_compile_threads_.clear(); - - std::lock_guard lock(msl_shader_compile_mutex_); - std::priority_queue, - MslShaderCompileRequestCompare> - empty_queue; - std::swap(msl_shader_compile_queue_, empty_queue); - while (!msl_pipeline_compile_queue_.empty()) { - auto request = msl_pipeline_compile_queue_.top(); - msl_pipeline_compile_queue_.pop(); - if (request.vertex_function) { - request.vertex_function->release(); - request.vertex_function = nullptr; - } - if (request.fragment_function) { - request.fragment_function->release(); - request.fragment_function = nullptr; - } - } - msl_shader_compile_pending_.clear(); - msl_shader_compile_failed_.clear(); - msl_pipeline_compile_pending_.clear(); - msl_pipeline_compile_failed_.clear(); - msl_shader_compile_busy_ = 0; - msl_shader_compile_shutdown_ = false; -} - -MetalCommandProcessor::MslShaderCompileStatus -MetalCommandProcessor::GetMslShaderCompileStatus( - MslShader::MslTranslation* translation) { - if (!translation) { - return MslShaderCompileStatus::kFailed; - } - - std::lock_guard lock(msl_shader_compile_mutex_); - if (msl_shader_compile_failed_.find(translation) != - msl_shader_compile_failed_.end()) { - return MslShaderCompileStatus::kFailed; - } - if (msl_shader_compile_pending_.find(translation) != - msl_shader_compile_pending_.end()) { - return MslShaderCompileStatus::kPending; - } - return translation->is_valid() ? MslShaderCompileStatus::kReady - : MslShaderCompileStatus::kNotQueued; -} - -bool MetalCommandProcessor::EnqueueMslShaderCompilation( - MslShader::MslTranslation* translation, bool is_ios, uint8_t priority) { - if (!translation || !cvars::async_shader_compilation || - msl_shader_compile_threads_.empty()) { - return false; - } - - if (translation->is_valid()) { - return true; - } - - { - std::lock_guard lock(msl_shader_compile_mutex_); - if (msl_shader_compile_failed_.find(translation) != - msl_shader_compile_failed_.end()) { - return false; - } - if (msl_shader_compile_pending_.find(translation) != - msl_shader_compile_pending_.end()) { - return true; - } - - MslShaderCompileRequest request; - request.translation = translation; - request.shader_hash = translation->shader().ucode_data_hash(); - request.modification = translation->modification(); - request.is_ios = is_ios; - request.priority = priority; - msl_shader_compile_pending_.insert(translation); - msl_shader_compile_queue_.push(request); - } - msl_shader_compile_cv_.notify_one(); - return true; -} - -bool MetalCommandProcessor::EnqueueMslPipelineCompilation( - const MslPipelineCompileRequest& request) { - if (!cvars::async_shader_compilation || msl_shader_compile_threads_.empty() || - !request.vertex_function) { - return false; - } - - MslPipelineCompileRequest queued_request = request; - { - std::lock_guard lock(msl_shader_compile_mutex_); - if (msl_pipeline_cache_.find(request.pipeline_key) != - msl_pipeline_cache_.end()) { - return true; - } - if (msl_pipeline_compile_failed_.find(request.pipeline_key) != - msl_pipeline_compile_failed_.end()) { - return false; - } - if (msl_pipeline_compile_pending_.find(request.pipeline_key) != - msl_pipeline_compile_pending_.end()) { - return true; - } - - queued_request.vertex_function->retain(); - if (queued_request.fragment_function) { - queued_request.fragment_function->retain(); - } - msl_pipeline_compile_pending_.insert(request.pipeline_key); - msl_pipeline_compile_queue_.push(queued_request); - } - - msl_shader_compile_cv_.notify_one(); - return true; -} - -MTL::RenderPipelineState* MetalCommandProcessor::CreateMslPipelineState( - const MslPipelineCompileRequest& request, std::string* error_out) { - if (error_out) { - error_out->clear(); - } - if (!request.vertex_function) { - if (error_out) { - *error_out = "missing vertex shader function"; - } - return nullptr; - } - - MTL::RenderPipelineDescriptor* desc = - MTL::RenderPipelineDescriptor::alloc()->init(); - desc->setVertexFunction(request.vertex_function); - if (request.fragment_function) { - desc->setFragmentFunction(request.fragment_function); - } - - for (uint32_t i = 0; i < 4; ++i) { - desc->colorAttachments()->object(i)->setPixelFormat( - request.color_formats[i]); - } - desc->setDepthAttachmentPixelFormat(request.depth_format); - desc->setStencilAttachmentPixelFormat(request.stencil_format); - desc->setSampleCount(request.sample_count); - desc->setAlphaToCoverageEnabled(request.alpha_to_mask_enable != 0); - - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - if (request.color_formats[i] == MTL::PixelFormatInvalid) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - - uint32_t rt_write_mask = (request.normalized_color_mask >> (i * 4)) & 0xF; - color_attachment->setWriteMask(ToMetalColorWriteMask(rt_write_mask)); - if (!rt_write_mask) { - color_attachment->setBlendingEnabled(false); - continue; - } - - reg::RB_BLENDCONTROL blendcontrol; - blendcontrol.value = request.blendcontrol[i]; - - MTL::BlendFactor src_rgb = - ToMetalBlendFactorRgb(blendcontrol.color_srcblend); - MTL::BlendFactor dst_rgb = - ToMetalBlendFactorRgb(blendcontrol.color_destblend); - MTL::BlendOperation op_rgb = - ToMetalBlendOperation(blendcontrol.color_comb_fcn); - MTL::BlendFactor src_alpha = - ToMetalBlendFactorAlpha(blendcontrol.alpha_srcblend); - MTL::BlendFactor dst_alpha = - ToMetalBlendFactorAlpha(blendcontrol.alpha_destblend); - MTL::BlendOperation op_alpha = - ToMetalBlendOperation(blendcontrol.alpha_comb_fcn); - - bool blending_enabled = - src_rgb != MTL::BlendFactorOne || dst_rgb != MTL::BlendFactorZero || - op_rgb != MTL::BlendOperationAdd || src_alpha != MTL::BlendFactorOne || - dst_alpha != MTL::BlendFactorZero || op_alpha != MTL::BlendOperationAdd; - color_attachment->setBlendingEnabled(blending_enabled); - if (blending_enabled) { - color_attachment->setSourceRGBBlendFactor(src_rgb); - color_attachment->setDestinationRGBBlendFactor(dst_rgb); - color_attachment->setRgbBlendOperation(op_rgb); - color_attachment->setSourceAlphaBlendFactor(src_alpha); - color_attachment->setDestinationAlphaBlendFactor(dst_alpha); - color_attachment->setAlphaBlendOperation(op_alpha); - } - } - - NS::Error* error = nullptr; - MTL::RenderPipelineState* pipeline = - device_->newRenderPipelineState(desc, &error); - desc->release(); - - if (!pipeline && error_out && error) { - NS::String* description = error->localizedDescription(); - if (description) { - *error_out = description->utf8String(); - } - } - - return pipeline; -} - -void MetalCommandProcessor::MslShaderCompileThread(size_t thread_index) { - while (true) { - MslShaderCompileRequest shader_request; - MslPipelineCompileRequest pipeline_request; - bool process_pipeline_request = false; - { - std::unique_lock lock(msl_shader_compile_mutex_); - msl_shader_compile_cv_.wait(lock, [this]() { - return msl_shader_compile_shutdown_ || - !msl_shader_compile_queue_.empty() || - !msl_pipeline_compile_queue_.empty(); - }); - if (msl_shader_compile_shutdown_) { - return; - } - if (!msl_pipeline_compile_queue_.empty()) { - process_pipeline_request = true; - pipeline_request = msl_pipeline_compile_queue_.top(); - msl_pipeline_compile_queue_.pop(); - } else { - shader_request = msl_shader_compile_queue_.top(); - msl_shader_compile_queue_.pop(); - } - ++msl_shader_compile_busy_; - } - - NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); - if (process_pipeline_request) { - std::string pipeline_error; - MTL::RenderPipelineState* pipeline = - CreateMslPipelineState(pipeline_request, &pipeline_error); - bool compiled = pipeline != nullptr; - - { - std::lock_guard lock(msl_shader_compile_mutex_); - msl_pipeline_compile_pending_.erase(pipeline_request.pipeline_key); - if (compiled) { - auto insert_result = msl_pipeline_cache_.emplace( - pipeline_request.pipeline_key, pipeline); - if (!insert_result.second && pipeline) { - pipeline->release(); - } - msl_pipeline_compile_failed_.erase(pipeline_request.pipeline_key); - } else { - msl_pipeline_compile_failed_.insert(pipeline_request.pipeline_key); - } - if (msl_shader_compile_busy_) { - --msl_shader_compile_busy_; - } - } - - if (pipeline_request.vertex_function) { - pipeline_request.vertex_function->release(); - } - if (pipeline_request.fragment_function) { - pipeline_request.fragment_function->release(); - } - - if (!compiled && - ShouldLogRateLimited(msl_pipeline_compile_failure_last_log_ns_, - kMslAsyncLogIntervalNs)) { - if (!pipeline_error.empty()) { - XELOGE( - "SPIRV-Cross: async Metal pipeline compile failed on worker {} " - "(VS {:016X} mod {:016X}, PS {:016X} mod {:016X}): {}", - thread_index, pipeline_request.vertex_shader_hash, - pipeline_request.vertex_modification, - pipeline_request.pixel_shader_hash, - pipeline_request.pixel_modification, pipeline_error); - } else { - XELOGE( - "SPIRV-Cross: async Metal pipeline compile failed on worker {} " - "(VS {:016X} mod {:016X}, PS {:016X} mod {:016X})", - thread_index, pipeline_request.vertex_shader_hash, - pipeline_request.vertex_modification, - pipeline_request.pixel_shader_hash, - pipeline_request.pixel_modification); - } - } - } else { - bool compiled = false; - if (shader_request.translation) { - compiled = shader_request.translation->CompileToMsl( - device_, shader_request.is_ios); - } - - { - std::lock_guard lock(msl_shader_compile_mutex_); - if (shader_request.translation) { - msl_shader_compile_pending_.erase(shader_request.translation); - if (!compiled) { - msl_shader_compile_failed_.insert(shader_request.translation); - } - } - if (msl_shader_compile_busy_) { - --msl_shader_compile_busy_; - } - } - - if (!compiled && - ShouldLogRateLimited(msl_shader_compile_failure_last_log_ns_, - kMslAsyncLogIntervalNs)) { - XELOGE( - "SPIRV-Cross: async Metal compile failed on worker {} (shader " - "{:016X}, mod {:016X})", - thread_index, shader_request.shader_hash, - shader_request.modification); - } - } - pool->release(); - } -} - -MetalCommandProcessor::SpirvArgumentBufferPage::~SpirvArgumentBufferPage() { - if (buffer) { - buffer->release(); - buffer = nullptr; - } + current_bindless_stage_root_valid_.fill(false); + current_bindless_stage_root_serials_.fill(0); + current_bindless_cbv_gpu_addresses_ = {}; + current_bindless_cbv_buffers_ = {}; + current_bindless_cbv_offsets_ = {}; + current_bindless_active_cbv_masks_.fill(0); + current_bindless_fixed_resource_set_ = {}; + current_bindless_texture_resource_set_ = {}; + current_bindless_root_resource_set_ = {}; + current_bindless_fixed_resource_source_serial_ = 0; + current_bindless_texture_resource_source_serial_ = 0; + current_bindless_root_resource_source_serial_ = 0; + render_encoder_bindless_fixed_resources_serial_ = 0; + render_encoder_bindless_texture_resources_serial_ = 0; + render_encoder_bindless_root_resources_serial_ = 0; + render_encoder_bindless_stage_root_bind_serials_.fill(0); + render_encoder_bindless_table_bind_mesh_path_ = false; + render_encoder_bindless_table_bind_tessellation_ = false; + current_bindless_stage_root_arguments_ = {}; } void MetalCommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr, @@ -976,9 +899,6 @@ void MetalCommandProcessor::RestoreEdramSnapshot(const void* snapshot) { "cache initialization"); return; } - // Trace playback frame boundary: drop resolve-write tracking from previous - // frame before restoring a new snapshot. - ClearResolvedMemory(); render_target_cache_->RestoreEdramSnapshot(snapshot); } @@ -1011,117 +931,13 @@ uint64_t MetalCommandProcessor::GetCompletedSubmission() const { return completed_command_buffers_.load(std::memory_order_relaxed); } -void MetalCommandProcessor::MarkResolvedMemory(uint32_t base_ptr, - uint32_t length) { - if (length == 0) { - return; - } - constexpr uint64_t kAddressLimit = - uint64_t(std::numeric_limits::max()) + 1ull; - uint64_t merged_base = base_ptr; - uint64_t merged_end = - std::min(merged_base + uint64_t(length), kAddressLimit); - if (merged_end <= merged_base) { - return; - } - - for (size_t i = 0; i < resolved_memory_ranges_.size();) { - const auto& range = resolved_memory_ranges_[i]; - const uint64_t range_base = range.base; - const uint64_t range_end = - std::min(range_base + uint64_t(range.length), kAddressLimit); - // Merge overlapping or adjacent ranges. - if (merged_end + 1 < range_base || range_end + 1 < merged_base) { - ++i; - continue; - } - merged_base = std::min(merged_base, range_base); - merged_end = std::max(merged_end, range_end); - resolved_memory_ranges_.erase(resolved_memory_ranges_.begin() + i); - } - - const uint64_t merged_length_64 = - std::min(merged_end - merged_base, kAddressLimit - merged_base); - if (!merged_length_64) { - return; - } - ResolvedRange merged_range = {uint32_t(merged_base), - uint32_t(merged_length_64)}; - auto insert_it = std::lower_bound( - resolved_memory_ranges_.begin(), resolved_memory_ranges_.end(), - merged_range, [](const ResolvedRange& lhs, const ResolvedRange& rhs) { - return lhs.base < rhs.base; - }); - resolved_memory_ranges_.insert(insert_it, merged_range); - - if (resolved_memory_ranges_.size() <= kResolvedMemoryRangesMax) { - return; - } - - std::sort(resolved_memory_ranges_.begin(), resolved_memory_ranges_.end(), - [](const ResolvedRange& lhs, const ResolvedRange& rhs) { - return lhs.base < rhs.base; - }); - while (resolved_memory_ranges_.size() > kResolvedMemoryRangesMax) { - size_t best_index = std::numeric_limits::max(); - uint64_t best_gap = std::numeric_limits::max(); - for (size_t i = 0; i + 1 < resolved_memory_ranges_.size(); ++i) { - const auto& left = resolved_memory_ranges_[i]; - const auto& right = resolved_memory_ranges_[i + 1]; - const uint64_t left_end = uint64_t(left.base) + uint64_t(left.length); - const uint64_t right_base = uint64_t(right.base); - const uint64_t gap = right_base > left_end ? right_base - left_end : 0; - if (gap < best_gap) { - best_gap = gap; - best_index = i; - if (!gap) { - break; - } - } - } - if (best_index == std::numeric_limits::max()) { - break; - } - auto& left = resolved_memory_ranges_[best_index]; - const auto& right = resolved_memory_ranges_[best_index + 1]; - const uint64_t merged_base_64 = - std::min(left.base, uint64_t(right.base)); - const uint64_t merged_end_64 = - std::max(uint64_t(left.base) + uint64_t(left.length), - uint64_t(right.base) + uint64_t(right.length)); - const uint64_t merged_len_64 = std::min( - merged_end_64 - merged_base_64, kAddressLimit - merged_base_64); - left.base = uint32_t(merged_base_64); - left.length = uint32_t(std::max(1, merged_len_64)); - resolved_memory_ranges_.erase(resolved_memory_ranges_.begin() + best_index + - 1); - } -} - -bool MetalCommandProcessor::IsResolvedMemory(uint32_t base_ptr, - uint32_t length) const { - const uint64_t end_ptr = uint64_t(base_ptr) + uint64_t(length); - for (const auto& range : resolved_memory_ranges_) { - const uint64_t range_end = uint64_t(range.base) + uint64_t(range.length); - // Check if ranges overlap - if (uint64_t(base_ptr) < range_end && end_ptr > uint64_t(range.base)) { - return true; - } - } - return false; -} - -void MetalCommandProcessor::ClearResolvedMemory() { - resolved_memory_ranges_.clear(); -} - void MetalCommandProcessor::ForceIssueSwap() { // Force a swap to push any pending render target to presenter // This is used by trace dumps to capture output when there's no explicit swap if (saw_swap_) { return; } - IssueSwap(0, render_target_width_, render_target_height_); + IssueSwap(0, 1280, 720); } void MetalCommandProcessor::SetSwapDestSwap(uint32_t dest_base, bool swap) { @@ -1148,6 +964,146 @@ bool MetalCommandProcessor::ConsumeSwapDestSwap(uint32_t dest_base, return true; } +void MetalCommandProcessor::InitializeResidencySet() { + residency_set_supported_ = false; + residency_set_enabled_ = false; + residency_set_attached_ = false; + residency_set_resources_.clear(); + residency_set_heaps_.clear(); + if (!device_ || !command_queue_) { + return; + } + + residency_set_supported_ = + MetalObjectRespondsToSelector(device_, + "newResidencySetWithDescriptor:error:") && + MetalObjectRespondsToSelector(command_queue_, "addResidencySet:") && + MetalObjectRespondsToSelector(command_queue_, "removeResidencySet:"); + if (!MetalResidencySetCvarAllowsEnable(residency_set_supported_)) { + return; + } + if (!residency_set_supported_) { + XELOGW( + "Metal residency sets requested, but this Metal runtime does not " + "expose MTLResidencySet"); + return; + } + + MTL::ResidencySetDescriptor* descriptor = + MTL::ResidencySetDescriptor::alloc()->init(); + descriptor->setLabel( + NS::String::string("XeniaResidencySet", NS::UTF8StringEncoding)); + descriptor->setInitialCapacity(512); + NS::Error* error = nullptr; + residency_set_ = device_->newResidencySet(descriptor, &error); + descriptor->release(); + if (!residency_set_) { + XELOGW("Metal residency set creation failed: {}", + error && error->localizedDescription() + ? error->localizedDescription()->utf8String() + : "unknown"); + return; + } + + command_queue_->addResidencySet(residency_set_); + residency_set_enabled_ = true; + residency_set_attached_ = true; +} + +void MetalCommandProcessor::ShutdownResidencySet() { + if (residency_set_attached_ && command_queue_ && residency_set_) { + command_queue_->removeResidencySet(residency_set_); + } + residency_set_attached_ = false; + residency_set_enabled_ = false; + residency_set_resources_.clear(); + residency_set_heaps_.clear(); + if (residency_set_) { + residency_set_->removeAllAllocations(); + residency_set_->commit(); + residency_set_->release(); + residency_set_ = nullptr; + } +} + +bool MetalCommandProcessor::AddResidencySetResource(MTL::Resource* resource) { + if (!residency_set_enabled_ || !residency_set_ || !resource) { + return false; + } + auto [it, inserted] = residency_set_resources_.insert(resource); + if (!inserted) { + ++backend_telemetry_.residency_set_allocation_duplicates; + return true; + } + residency_set_->addAllocation(resource); + residency_set_->commit(); + ++backend_telemetry_.residency_set_allocations_added; + ++backend_telemetry_.residency_set_commits; + return true; +} + +bool MetalCommandProcessor::IsResidencySetResourceCovered( + MTL::Resource* resource) const { + if (!residency_set_enabled_ || !resource) { + return false; + } + // TODO (xenios-jp): Revisit heap-backed texture / render-target + // coverage only after the backend owns the full hazard model for those + // allocations. MTLResidencySet proves that an allocation is resident, but it + // doesn't replace the encoder usage declarations the current texture and + // render-target paths rely on for hazard visibility. A safe removal of + // per-encoder useResource / useHeap for heap-backed allocations needs: + // * read/write tracking for texture-cache and render-target-cache resources, + // including color, depth, stencil, MSAA, resolve, and transfer views; + // * parent-resource / texture-view / heap alias handling so subresource use + // is attributed to the actual allocation that may conflict; + // * explicit ordering between render, compute, and blit encoders with + // fences, events, or conservative encoder boundaries before conflicting + // uses; + // * validation coverage for unpooled textures, memoryless attachments, and + // direct device->newTexture fallback paths. + // + // Until that exists, only resources explicitly added to + // residency_set_resources_ are considered safe to suppress here. Heap-backed + // textures and render targets must still go through the encoder usage path. + return residency_set_resources_.find(resource) != + residency_set_resources_.end(); +} + +bool MetalCommandProcessor::AddResidencySetHeap(MTL::Heap* heap) { + if (!residency_set_enabled_ || !residency_set_ || !heap) { + return false; + } + auto [it, inserted] = residency_set_heaps_.insert(heap); + if (!inserted) { + ++backend_telemetry_.residency_set_allocation_duplicates; + return true; + } + residency_set_->addAllocation(heap); + residency_set_->commit(); + ++backend_telemetry_.residency_set_allocations_added; + ++backend_telemetry_.residency_set_commits; + return true; +} + +bool MetalCommandProcessor::IsResidencySetHeapCovered(MTL::Heap* heap) const { + return residency_set_enabled_ && heap && + residency_set_heaps_.find(heap) != residency_set_heaps_.end(); +} + +void MetalCommandProcessor::RegisterInitialResidencySetResources() { + AddResidencySetResource(shared_memory_ ? shared_memory_->GetBuffer() + : nullptr); + AddResidencySetResource( + render_target_cache_ ? render_target_cache_->GetEdramBuffer() : nullptr); + AddResidencySetResource(tessellator_tables_buffer_); + AddResidencySetResource(null_buffer_); + AddResidencySetResource(null_texture_); + AddResidencySetResource(view_bindless_heap_); + AddResidencySetResource(sampler_bindless_heap_); + AddResidencySetResource(system_view_tables_); +} + bool MetalCommandProcessor::SetupContext() { saw_swap_ = false; last_swap_ptr_ = 0; @@ -1156,6 +1112,10 @@ bool MetalCommandProcessor::SetupContext() { swap_dest_swaps_by_base_.clear(); gamma_ramp_256_entry_table_up_to_date_ = false; gamma_ramp_pwl_up_to_date_ = false; + frame_open_ = false; + frame_current_ = 1; + frame_completed_ = 0; + std::memset(closed_frame_submissions_, 0, sizeof(closed_frame_submissions_)); if (!CommandProcessor::SetupContext()) { XELOGE("Failed to initialize base command processor context"); return false; @@ -1170,6 +1130,8 @@ bool MetalCommandProcessor::SetupContext() { return false; } + InitializeResidencySet(); + wait_shared_event_ = device_->newSharedEvent(); if (wait_shared_event_) { wait_shared_event_->setLabel( @@ -1181,60 +1143,20 @@ bool MetalCommandProcessor::SetupContext() { "waitUntilCompleted"); } + shared_memory_fence_ = device_->newFence(); + if (!shared_memory_fence_) { + XELOGE( + "MetalCommandProcessor: MTLFence unavailable; cannot safely order " + "shared-memory GPU write hazards"); + return false; + } + shared_memory_fence_->setLabel( + NS::String::string("XeniaSharedMemoryFence", NS::UTF8StringEncoding)); + bool supports_apple7 = device_->supportsFamily(MTL::GPUFamilyApple7); bool supports_mac2 = device_->supportsFamily(MTL::GPUFamilyMac2); mesh_shader_supported_ = supports_apple7 || supports_mac2; - draw_ring_count_ = std::max(1, ::cvars::metal_draw_ring_count); - if (UseSpirvCrossPath()) { - // Large per-command-buffer ring sizes have been observed to corrupt - // SPIRV-Cross uniform/constant data; cap here and rely on multi-buffer - // pool growth in EnsureSpirvUniformBuffer* for throughput. - constexpr size_t kMaxSpirvRingPagesPerCommandBuffer = 8; - if (draw_ring_count_ > kMaxSpirvRingPagesPerCommandBuffer) { - XELOGW( - "SPIRV-Cross: clamping per-command-buffer ring pages from {} to {} " - "for correctness", - draw_ring_count_, kMaxSpirvRingPagesPerCommandBuffer); - draw_ring_count_ = kMaxSpirvRingPagesPerCommandBuffer; - } - } - msl_system_constants_version_ = 1; - msl_clip_plane_constants_version_ = 1; - msl_tessellation_constants_version_ = 1; - msl_constants_versioned_uniform_buffer_ = nullptr; - msl_system_constants_written_vertex_versions_.assign(draw_ring_count_, 0); - msl_system_constants_written_pixel_versions_.assign(draw_ring_count_, 0); - msl_clip_plane_constants_written_vertex_versions_.assign(draw_ring_count_, 0); - msl_tessellation_constants_written_vertex_versions_.assign(draw_ring_count_, - 0); - msl_tessellation_constants_written_pixel_versions_.assign(draw_ring_count_, - 0); - msl_current_float_constant_map_vertex_.fill(0); - msl_current_float_constant_map_pixel_.fill(0); - msl_float_constants_dirty_vertex_ = true; - msl_float_constants_dirty_pixel_ = true; - msl_bool_loop_constants_dirty_ = true; - msl_fetch_constants_dirty_ = true; - msl_bound_vertex_texture_binding_uid_ = 0; - msl_bound_pixel_texture_binding_uid_ = 0; - msl_bound_vertex_sampler_binding_uid_ = 0; - msl_bound_pixel_sampler_binding_uid_ = 0; - msl_bound_vertex_argument_buffer_offset_ = 0; - msl_bound_pixel_argument_buffer_offset_ = 0; - msl_bound_vertex_argument_buffer_offset_valid_ = false; - msl_bound_pixel_argument_buffer_offset_valid_ = false; - msl_last_argbuf_vertex_translation_ = nullptr; - msl_last_argbuf_vertex_encoded_length_ = 0; - msl_last_argbuf_vertex_layout_uid_ = 0; - msl_last_argbuf_pixel_translation_ = nullptr; - msl_last_argbuf_pixel_encoded_length_ = 0; - msl_last_argbuf_pixel_layout_uid_ = 0; - msl_bound_uniforms_buffer_ = nullptr; - msl_bound_uniforms_vs_base_offset_ = 0; - msl_bound_uniforms_ps_base_offset_ = 0; - msl_bound_uniforms_offsets_valid_ = false; - // Initialize shared memory shared_memory_ = std::make_unique(*this, *memory_); if (!shared_memory_->Initialize()) { @@ -1250,6 +1172,40 @@ bool MetalCommandProcessor::SetupContext() { return false; } + // Create persistent bindless descriptor heaps BEFORE the texture/sampler + // caches, because their Initialize() allocates persistent heap slots for + // null textures and samplers. + view_bindless_heap_ = device_->newBuffer( + kViewBindlessHeapSize * sizeof(IRDescriptorTableEntry), + MTL::ResourceStorageModeShared | MTL::ResourceCPUCacheModeWriteCombined); + if (!view_bindless_heap_) { + XELOGE("Failed to create view bindless heap"); + return false; + } + view_bindless_heap_->setLabel( + NS::String::string("XeniaViewBindlessHeap", NS::UTF8StringEncoding)); + memset(view_bindless_heap_->contents(), 0, view_bindless_heap_->length()); + + sampler_bindless_heap_ = device_->newBuffer( + kSamplerBindlessHeapSize * sizeof(IRDescriptorTableEntry), + MTL::ResourceStorageModeShared | MTL::ResourceCPUCacheModeWriteCombined); + if (!sampler_bindless_heap_) { + XELOGE("Failed to create sampler bindless heap"); + return false; + } + sampler_bindless_heap_->setLabel( + NS::String::string("XeniaSamplerBindlessHeap", NS::UTF8StringEncoding)); + memset(sampler_bindless_heap_->contents(), 0, + sampler_bindless_heap_->length()); + + // The bindless heaps are dedicated to dynamically indexed textures and + // samplers only. System descriptors such as shared memory / EDRAM use small + // explicit-layout top-level tables populated below once all resources exist. + view_bindless_heap_next_ = 0; + view_bindless_heap_exhausted_logged_ = false; + sampler_bindless_heap_next_ = 0; + sampler_bindless_heap_exhausted_logged_ = false; + texture_cache_ = std::make_unique(this, *register_file_, *shared_memory_, 1, 1); if (!texture_cache_->Initialize()) { @@ -1265,81 +1221,49 @@ bool MetalCommandProcessor::SetupContext() { return false; } - // Initialize shader translation pipeline - if (!InitializeShaderTranslation()) { - XELOGE("Failed to initialize shader translation"); - return false; + // Create and initialize pipeline cache (shader translation + pipeline + // management). + pipeline_cache_ = + std::make_unique(device_, *register_file_); + { + bool edram_rov_used = false; + bool gamma_render_target_as_unorm8 = + !(edram_rov_used || + render_target_cache_->gamma_render_target_as_unorm16()); + if (!pipeline_cache_->InitializeShaderTranslation( + gamma_render_target_as_unorm8, + render_target_cache_->msaa_2x_supported(), + render_target_cache_->draw_resolution_scale_x(), + render_target_cache_->draw_resolution_scale_y())) { + XELOGE("Failed to initialize shader translation"); + return false; + } } - if (UseSpirvCrossPath()) { - InitializeMslAsyncCompilation(); + if (mesh_shader_supported_) { + uint64_t tess_tables_size = IRRuntimeTessellatorTablesSize(); + tessellator_tables_buffer_ = + device_->newBuffer(tess_tables_size, MTL::ResourceStorageModeShared); + if (!tessellator_tables_buffer_) { + XELOGE("Failed to allocate tessellator tables buffer ({} bytes)", + tess_tables_size); + return false; + } + tessellator_tables_buffer_->setLabel( + NS::String::string("XeniaTessellatorTables", NS::UTF8StringEncoding)); + IRRuntimeLoadTessellatorTables(tessellator_tables_buffer_); } - // Create render target texture for offscreen rendering - MTL::TextureDescriptor* color_desc = MTL::TextureDescriptor::alloc()->init(); - color_desc->setTextureType(MTL::TextureType2D); - color_desc->setPixelFormat(MTL::PixelFormatBGRA8Unorm); - color_desc->setWidth(render_target_width_); - color_desc->setHeight(render_target_height_); - color_desc->setStorageMode(MTL::StorageModePrivate); - color_desc->setUsage(MTL::TextureUsageRenderTarget | - MTL::TextureUsageShaderRead); + // Create the upload buffer pool for per-draw constant buffer allocations. + constant_buffer_pool_ = std::make_unique(device_); + constant_buffer_pool_->SetPageCreatedCallback( + [this](MTL::Buffer* buffer) { AddResidencySetResource(buffer); }); - render_target_texture_ = device_->newTexture(color_desc); - color_desc->release(); + // Initialize the ZPD occlusion query pool and resources. + zpd_visibility_pool_ = std::make_unique(); + EnsureZPDQueryResources(); - if (!render_target_texture_) { - XELOGE("Failed to create render target texture"); - return false; - } - render_target_texture_->setLabel( - NS::String::string("XeniaRenderTarget", NS::UTF8StringEncoding)); - - // Create depth/stencil texture - MTL::TextureDescriptor* depth_desc = MTL::TextureDescriptor::alloc()->init(); - depth_desc->setTextureType(MTL::TextureType2D); - depth_desc->setPixelFormat(MTL::PixelFormatDepth32Float_Stencil8); - depth_desc->setWidth(render_target_width_); - depth_desc->setHeight(render_target_height_); -#if XE_PLATFORM_IOS - // This fallback depth/stencil target is transient (clear/dontcare only) and - // never sampled, so memoryless is the most efficient iOS storage mode. - depth_desc->setStorageMode(MTL::StorageModeMemoryless); -#else - depth_desc->setStorageMode(MTL::StorageModePrivate); -#endif - depth_desc->setUsage(MTL::TextureUsageRenderTarget); - - depth_stencil_texture_ = device_->newTexture(depth_desc); - depth_desc->release(); - - if (!depth_stencil_texture_) { - XELOGE("Failed to create depth/stencil texture"); - return false; - } - depth_stencil_texture_->setLabel( - NS::String::string("XeniaDepthStencil", NS::UTF8StringEncoding)); - - // Create render pass descriptor - render_pass_descriptor_ = MTL::RenderPassDescriptor::alloc()->init(); - - auto color_attachment = - render_pass_descriptor_->colorAttachments()->object(0); - color_attachment->setTexture(render_target_texture_); - color_attachment->setLoadAction(MTL::LoadActionClear); - color_attachment->setStoreAction(MTL::StoreActionStore); - color_attachment->setClearColor(MTL::ClearColor(0.0, 0.0, 0.0, 1.0)); - - auto depth_attachment = render_pass_descriptor_->depthAttachment(); - depth_attachment->setTexture(depth_stencil_texture_); - depth_attachment->setLoadAction(MTL::LoadActionClear); - depth_attachment->setStoreAction(MTL::StoreActionDontCare); - depth_attachment->setClearDepth(1.0); - - auto stencil_attachment = render_pass_descriptor_->stencilAttachment(); - stencil_attachment->setTexture(depth_stencil_texture_); - stencil_attachment->setLoadAction(MTL::LoadActionClear); - stencil_attachment->setStoreAction(MTL::StoreActionDontCare); - stencil_attachment->setClearStencil(0); + render_encoder_resource_usage_table_.reserve(256); + render_encoder_heap_usage_.reserve(32); // Create a null buffer for unused descriptor entries // This prevents shader validation errors when accessing unpopulated @@ -1403,125 +1327,92 @@ bool MetalCommandProcessor::SetupContext() { return false; } - // SPIRV-Cross path: use command-buffer-scoped uniforms buffers so CPU writes - // to the next submission can't race with in-flight GPU reads. - if (UseSpirvCrossPath()) { - if (!EnsureSpirvUniformBuffer()) { + system_view_tables_ = device_->newBuffer( + kSystemViewTableEntryCount * sizeof(IRDescriptorTableEntry), + MTL::ResourceStorageModeShared | MTL::ResourceCPUCacheModeWriteCombined); + if (!system_view_tables_) { + XELOGE("Failed to create system descriptor tables"); + return false; + } + system_view_tables_->setLabel( + NS::String::string("XeniaSystemViewTables", NS::UTF8StringEncoding)); + std::memset(system_view_tables_->contents(), 0, + system_view_tables_->length()); + { + auto* system_entries = reinterpret_cast( + system_view_tables_->contents()); + MTL::Buffer* shared_mem_buffer = + shared_memory_ ? shared_memory_->GetBuffer() : nullptr; + const uint64_t shared_memory_metadata = + shared_mem_buffer ? shared_mem_buffer->length() : kNullBufferSize; + IRDescriptorTableSetBuffer(&system_entries[kSystemViewTableSRVSharedMemory], + shared_mem_buffer + ? shared_mem_buffer->gpuAddress() + : null_buffer_->gpuAddress(), + shared_memory_metadata); + IRDescriptorTableSetBuffer(&system_entries[kSystemViewTableSRVNull], + null_buffer_->gpuAddress(), kNullBufferSize); + IRDescriptorTableSetBuffer(&system_entries[kSystemViewTableUAVNullStart], + null_buffer_->gpuAddress(), kNullBufferSize); + if (!render_target_cache_ || + !render_target_cache_->WriteEdramUintPow2BindlessDescriptor( + &system_entries[kSystemViewTableUAVNullStart + 1], 2)) { + XELOGE("Failed to encode typed EDRAM UAV system descriptor"); + return false; + } + IRDescriptorTableSetBuffer( + &system_entries[kSystemViewTableUAVSharedMemoryStart], + shared_mem_buffer ? shared_mem_buffer->gpuAddress() + : null_buffer_->gpuAddress(), + shared_memory_metadata); + if (!render_target_cache_ || + !render_target_cache_->WriteEdramUintPow2BindlessDescriptor( + &system_entries[kSystemViewTableUAVSharedMemoryStart + 1], 2)) { + XELOGE("Failed to encode typed EDRAM UAV system descriptor"); return false; } } - + RegisterInitialResidencySetResources(); return true; } -bool MetalCommandProcessor::InitializeShaderTranslation() { - // Initialize DXBC shader translator (use Apple vendor ID for Metal) - // Must query render_target_cache_ for actual runtime parameters. - // Metal doesn't use ROV (rasterizer ordered views) path. - bool edram_rov_used = false; - - // gamma_render_target_as_unorm8: When true, shaders include code to convert - // linear -> gamma for 8-bit gamma render targets. When false, we use 16-bit - // UNORM format where hardware handles gamma implicitly. - bool gamma_render_target_as_unorm8 = !( - edram_rov_used || render_target_cache_->gamma_render_target_as_unorm16()); - - XELOGI( - "DxbcShaderTranslator init: gamma_as_unorm8={}, msaa_2x={}, scale={}x{}", - gamma_render_target_as_unorm8, render_target_cache_->msaa_2x_supported(), - render_target_cache_->draw_resolution_scale_x(), - render_target_cache_->draw_resolution_scale_y()); - - // Initialize SPIRV-Cross (MSL) path. - if (UseSpirvCrossPath()) { - // Build SpirvShaderTranslator::Features for Metal. - // Enable all features as a baseline, then disable what Metal doesn't need. - SpirvShaderTranslator::Features spirv_features(true); - // Not using fragment shader interlock — we use host render targets. - spirv_features.fragment_shader_sample_interlock = false; - // Barycentric interpolation not needed for current Metal path. - spirv_features.fragment_shader_barycentric = false; - // Metal fast-math doesn't guarantee IEEE NaN/Inf preservation. - spirv_features.signed_zero_inf_nan_preserve_float32 = false; - // Metal fast-math flushes denorms. - spirv_features.denorm_flush_to_zero_float32 = true; - // RTE rounding not guaranteed by Metal. - spirv_features.rounding_mode_rte_float32 = false; - - spirv_shader_translator_ = std::make_unique( - spirv_features, - render_target_cache_->msaa_2x_supported(), // native_2x_msaa_with_att - false, // native_2x_msaa_no_att - false, // edram_fragment_shader_interlock (host RT path) - render_target_cache_->draw_resolution_scale_x(), - render_target_cache_->draw_resolution_scale_y()); - - XELOGI( - "SpirvShaderTranslator init (SPIRV-Cross MSL path): msaa_2x={}, " - "scale={}x{}", - render_target_cache_->msaa_2x_supported(), - render_target_cache_->draw_resolution_scale_x(), - render_target_cache_->draw_resolution_scale_y()); - - if (!InitializeMslTessellation()) { - XELOGW( - "SPIRV-Cross: Tessellation factor pipelines failed to init; " - "tessellated draws will be skipped"); - } - } - - return true; -} - -void MetalCommandProcessor::PrepareForWait() { - // Flush any pending Metal command buffers before entering wait state. - // This is critical because: - // 1. The worker thread's autorelease pool will be drained when it exits - // 2. Metal objects in that pool might still be referenced by in-flight - // commands - // 3. Releasing those objects during pool drain can hang waiting for GPU - // completion - // - // By submitting and waiting for all GPU work now, we ensure clean pool - // drainage. - - // End through the shared helper so per-encoder bind caches are reset too. - EndRenderEncoder(); - +void MetalCommandProcessor::FlushCommandBufferAndWait(uint64_t timeout_ns, + const char* context) { + // Phase 1: commit the active command buffer and wait for it. if (current_command_buffer_) { + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kCommandBufferEnd); + if (texture_cache_ && + !texture_cache_ + ->FlushPendingUploadEncodersForCommandEncoderBoundary()) { + XELOGE("Metal: failed to flush texture upload encoder before wait"); + } uint64_t wait_value = 0; if (wait_shared_event_) { wait_value = ++wait_shared_event_value_; current_command_buffer_->encodeSignalEvent(wait_shared_event_, wait_value); } - if (UseSpirvCrossPath()) { - ScheduleSpirvUniformBufferRelease(current_command_buffer_); - } - ScheduleSpirvArgumentBufferRelease(current_command_buffer_); current_command_buffer_->commit(); if (wait_shared_event_) { - wait_shared_event_->waitUntilSignaledValue( - wait_value, std::numeric_limits::max()); + bool signaled = + wait_shared_event_->waitUntilSignaledValue(wait_value, timeout_ns); + if (!signaled) { + XELOGE("{}: GPU timeout (possible GPU hang)", context); + } } else { current_command_buffer_->waitUntilCompleted(); } current_command_buffer_->release(); current_command_buffer_ = nullptr; - current_draw_index_ = 0; - copy_resolve_writes_pending_ = false; + submission_has_draws_ = false; } DrainCommandBufferAutoreleasePool(); - // Even if we have no active command buffer, there might be GPU work from - // previously submitted command buffers that autoreleased objects depend on. - // Submit and wait for a dummy command buffer to ensure ALL GPU work - // completes. + // Phase 2: submit a dummy command buffer to ensure ALL previously committed + // GPU work completes before the caller tears down resources. if (command_queue_) { NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); - // Note: commandBuffer() returns an autoreleased object per metal-cpp docs. - // We do NOT call release() since we didn't retain() it. - // The autorelease pool will handle cleanup. MTL::CommandBuffer* sync_cmd = command_queue_->commandBuffer(); if (sync_cmd) { uint64_t wait_value = 0; @@ -1531,20 +1422,36 @@ void MetalCommandProcessor::PrepareForWait() { } sync_cmd->commit(); if (wait_shared_event_) { - wait_shared_event_->waitUntilSignaledValue( - wait_value, std::numeric_limits::max()); + bool signaled = + wait_shared_event_->waitUntilSignaledValue(wait_value, timeout_ns); + if (!signaled) { + XELOGE("{}: GPU sync timeout (possible GPU hang)", context); + } } else { sync_cmd->waitUntilCompleted(); } - // Don't release - it's autoreleased and will be cleaned up by the pool } pool->release(); } +} - // Also call the base class to flush trace writer +void MetalCommandProcessor::PrepareForWait() { + // Flush pending Metal command buffers before entering wait state so that + // the worker thread's autorelease pool can drain cleanly. + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kPrepareForWait)) { + XELOGE("Metal PrepareForWait: failed to flush prepared draw queue"); + } + EndRenderEncoder(RenderEncoderEndReason::kPrepareForWait); + FlushCommandBufferAndWait(/*timeout_ns=*/5000000000ULL, "PrepareForWait"); CommandProcessor::PrepareForWait(); } +void MetalCommandProcessor::PollCompletedSubmission() { + // The base class calls PollCompletedSubmission during strict mode waits + // and at submission boundaries. Just drain any ready query resolves. + PumpQueryResolves(); +} + void MetalCommandProcessor::WaitForPendingCompletionHandlers() { constexpr auto kMaxWait = std::chrono::seconds(5); const auto wait_start = std::chrono::steady_clock::now(); @@ -1561,175 +1468,285 @@ void MetalCommandProcessor::WaitForPendingCompletionHandlers() { } void MetalCommandProcessor::ShutdownContext() { - // End any active render encoder before shutdown + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kManual)) { + XELOGE("Metal ShutdownContext: failed to flush prepared draw queue"); + } + MaybeDumpBackendTelemetry("shutdown", true); + + // End the render encoder directly (not via EndRenderEncoder — we release + // the encoder object below after the command buffer completes). if (current_render_encoder_) { + UpdateSharedMemoryFenceForActiveRenderEncoder(); current_render_encoder_->endEncoding(); - // Don't release yet - wait until command buffer completes - } - - // Submit and wait for any pending command buffer - if (current_command_buffer_) { - uint64_t wait_value = 0; - if (wait_shared_event_) { - wait_value = ++wait_shared_event_value_; - current_command_buffer_->encodeSignalEvent(wait_shared_event_, - wait_value); - } - if (UseSpirvCrossPath()) { - ScheduleSpirvUniformBufferRelease(current_command_buffer_); - } - ScheduleSpirvArgumentBufferRelease(current_command_buffer_); - current_command_buffer_->commit(); - if (wait_shared_event_) { - wait_shared_event_->waitUntilSignaledValue( - wait_value, std::numeric_limits::max()); - } else { - current_command_buffer_->waitUntilCompleted(); - } - current_command_buffer_->release(); - current_command_buffer_ = nullptr; - current_draw_index_ = 0; - copy_resolve_writes_pending_ = false; - } - - // Even if we have no active command buffer at this point, there may be - // previously committed command buffers still in flight. Submit and wait for - // a dummy command buffer to ensure all GPU work on this queue has completed - // before tearing down resources on thread exit. - if (command_queue_) { - NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); - MTL::CommandBuffer* sync_cmd = command_queue_->commandBuffer(); - if (sync_cmd) { - uint64_t wait_value = 0; - if (wait_shared_event_) { - wait_value = ++wait_shared_event_value_; - sync_cmd->encodeSignalEvent(wait_shared_event_, wait_value); - } - sync_cmd->commit(); - if (wait_shared_event_) { - wait_shared_event_->waitUntilSignaledValue( - wait_value, std::numeric_limits::max()); - } else { - sync_cmd->waitUntilCompleted(); - } - } - pool->release(); } + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kShutdown); + FlushCommandBufferAndWait(std::numeric_limits::max(), + "ShutdownContext"); WaitForPendingCompletionHandlers(); // Now safe to release encoder and command buffer if (current_render_encoder_) { current_render_encoder_->release(); current_render_encoder_ = nullptr; + current_render_encoder_has_zpd_visibility_ = false; + ResetRenderEncoderBufferBindings(); + } + if (current_render_pass_descriptor_) { + current_render_pass_descriptor_->release(); + current_render_pass_descriptor_ = nullptr; } if (current_command_buffer_) { current_command_buffer_->release(); current_command_buffer_ = nullptr; } DrainCommandBufferAutoreleasePool(); + ShutdownResidencySet(); - { - std::lock_guard lock(spirv_argbuf_mutex_); - command_buffer_spirv_argbuf_pages_.clear(); - pending_spirv_argbuf_releases_.clear(); - spirv_argbuf_pool_.clear(); - } + constant_buffer_pool_.reset(); + // Shut down texture cache first so texture/sampler destructors can release + // their bindless heap slots while the heap buffers are still alive. if (texture_cache_) { texture_cache_->Shutdown(); texture_cache_.reset(); } + // Release persistent bindless heaps after texture/sampler caches are + // torn down (destructors write to these heaps during release). + if (view_bindless_heap_) { + view_bindless_heap_->release(); + view_bindless_heap_ = nullptr; + } + if (sampler_bindless_heap_) { + sampler_bindless_heap_->release(); + sampler_bindless_heap_ = nullptr; + } + if (system_view_tables_) { + system_view_tables_->release(); + system_view_tables_ = nullptr; + } + view_bindless_heap_free_.clear(); + sampler_bindless_heap_free_.clear(); + retired_view_bindless_indices_.clear(); + retired_sampler_bindless_indices_.clear(); + for (RetiredMetalBuffer& retired_buffer : retired_memexport_index_buffers_) { + if (retired_buffer.buffer) { + retired_buffer.buffer->release(); + } + } + retired_memexport_index_buffers_.clear(); + view_bindless_heap_next_ = 0; + sampler_bindless_heap_next_ = 0; + view_bindless_heap_exhausted_logged_ = false; + sampler_bindless_heap_exhausted_logged_ = false; + if (primitive_processor_) { primitive_processor_->Shutdown(); primitive_processor_.reset(); } + if (tessellator_tables_buffer_) { + tessellator_tables_buffer_->release(); + tessellator_tables_buffer_ = nullptr; + } frame_open_ = false; - ShutdownMslAsyncCompilation(); + pipeline_cache_.reset(); - // SPIRV-Cross resources. - ShutdownMslTessellation(); - for (auto& [key, pso] : msl_pipeline_cache_) { - if (pso) { - pso->release(); - } - } - msl_pipeline_cache_.clear(); - msl_shader_cache_.clear(); - spirv_shader_translator_.reset(); - - uniforms_buffer_ = nullptr; - command_buffer_spirv_uniform_buffers_.clear(); - { - std::lock_guard lock(spirv_uniforms_mutex_); - spirv_uniforms_available_.clear(); - for (MTL::Buffer* pool_uniforms : spirv_uniforms_pool_) { - if (pool_uniforms) { - pool_uniforms->release(); - } - } - spirv_uniforms_pool_.clear(); - spirv_uniforms_pool_initialized_ = false; - } - if (spirv_uniforms_available_semaphore_) { -#if !OS_OBJECT_USE_OBJC - dispatch_release(spirv_uniforms_available_semaphore_); -#endif - spirv_uniforms_available_semaphore_ = nullptr; - } + // Shut down ZPD query resources. + ShutdownZPDQueryResources(); + zpd_visibility_pool_.reset(); shared_memory_.reset(); + if (shared_memory_fence_) { + shared_memory_fence_->release(); + shared_memory_fence_ = nullptr; + } if (wait_shared_event_) { wait_shared_event_->release(); wait_shared_event_ = nullptr; } - ClearMslShaderSourceCacheDirectory(); - CommandProcessor::ShutdownContext(); } +uint32_t MetalCommandProcessor::AllocateViewBindlessIndex() { + if (!view_bindless_heap_free_.empty()) { + uint32_t idx = view_bindless_heap_free_.back(); + view_bindless_heap_free_.pop_back(); + view_bindless_heap_exhausted_logged_ = false; + return idx; + } + if (view_bindless_heap_next_ >= kViewBindlessHeapSize) { + // Reclaim retired indices from completed submissions first — this is cheap + // and may free slots without needing to evict any textures. + ProcessCompletedSubmissions(); + if (!view_bindless_heap_free_.empty()) { + uint32_t idx = view_bindless_heap_free_.back(); + view_bindless_heap_free_.pop_back(); + view_bindless_heap_exhausted_logged_ = false; + return idx; + } + // Still full — try evicting least-recently-used textures. + if (texture_cache_ && texture_cache_->TrimViewBindlessPressure()) { + if (!view_bindless_heap_free_.empty()) { + uint32_t idx = view_bindless_heap_free_.back(); + view_bindless_heap_free_.pop_back(); + view_bindless_heap_exhausted_logged_ = false; + return idx; + } + } + if (!view_bindless_heap_exhausted_logged_) { + uint64_t completed = + completed_command_buffers_.load(std::memory_order_relaxed); + uint64_t texture_count = + texture_cache_ ? texture_cache_->GetTotalTextureCount() : 0; + XELOGE( + "View bindless heap exhausted ({} allocated, {} free, {} retired, " + "submissions: current={} completed={}, textures={})", + view_bindless_heap_next_, view_bindless_heap_free_.size(), + retired_view_bindless_indices_.size(), submission_current_, completed, + texture_count); + view_bindless_heap_exhausted_logged_ = true; + } + return UINT32_MAX; + } + return view_bindless_heap_next_++; +} + +void MetalCommandProcessor::ReleaseViewBindlessIndex(uint32_t index) { + if (index >= kViewBindlessHeapSize || !view_bindless_heap_) { + return; + } + // Match D3D12's persistent texture-descriptor lifetime more closely: by the + // time a Metal texture reaches destruction through the cache, its last GPU + // use has already completed, so the bindless view slot can be recycled + // immediately rather than waiting for the current submission to end. + FreeViewBindlessIndexNow(index); +} + +void MetalCommandProcessor::RetireViewBindlessIndex(uint32_t index) { + if (index >= kViewBindlessHeapSize || !view_bindless_heap_) { + return; + } + uint64_t retirement_submission = GetBindlessDescriptorRetirementSubmission(); + if (!retirement_submission) { + FreeViewBindlessIndexNow(index); + } else { + retired_view_bindless_indices_.push_back({index, retirement_submission}); + } +} + +uint32_t MetalCommandProcessor::GetViewBindlessHeapAvailableCount() const { + return uint32_t(kViewBindlessHeapSize - view_bindless_heap_next_) + + uint32_t(view_bindless_heap_free_.size()); +} + +uint32_t MetalCommandProcessor::AllocateSamplerBindlessIndex() { + if (!sampler_bindless_heap_free_.empty()) { + uint32_t idx = sampler_bindless_heap_free_.back(); + sampler_bindless_heap_free_.pop_back(); + sampler_bindless_heap_exhausted_logged_ = false; + return idx; + } + if (sampler_bindless_heap_next_ >= kSamplerBindlessHeapSize) { + if (!sampler_bindless_heap_exhausted_logged_) { + XELOGE("Sampler bindless heap exhausted"); + sampler_bindless_heap_exhausted_logged_ = true; + } + return UINT32_MAX; + } + return sampler_bindless_heap_next_++; +} + +void MetalCommandProcessor::ReleaseSamplerBindlessIndex(uint32_t index) { + if (index >= kSamplerBindlessHeapSize || !sampler_bindless_heap_) { + return; + } + uint64_t retirement_submission = GetBindlessDescriptorRetirementSubmission(); + if (!retirement_submission) { + FreeSamplerBindlessIndexNow(index); + } else { + retired_sampler_bindless_indices_.push_back({index, retirement_submission}); + } +} + +uint64_t MetalCommandProcessor::GetBindlessDescriptorRetirementSubmission() + const { + if (!submission_current_) { + return 0; + } + if (current_command_buffer_ || + completed_command_buffers_.load(std::memory_order_relaxed) < + submission_current_) { + return submission_current_; + } + return 0; +} + +void MetalCommandProcessor::FreeViewBindlessIndexNow(uint32_t index) { + if (index >= kViewBindlessHeapSize || !view_bindless_heap_) { + return; + } + if (IRDescriptorTableEntry* entry = GetViewBindlessHeapEntry(index)) { + std::memset(entry, 0, sizeof(IRDescriptorTableEntry)); + } + view_bindless_heap_free_.push_back(index); + view_bindless_heap_exhausted_logged_ = false; +} + +void MetalCommandProcessor::FreeSamplerBindlessIndexNow(uint32_t index) { + if (index >= kSamplerBindlessHeapSize || !sampler_bindless_heap_) { + return; + } + if (IRDescriptorTableEntry* entry = GetSamplerBindlessHeapEntry(index)) { + std::memset(entry, 0, sizeof(IRDescriptorTableEntry)); + } + sampler_bindless_heap_free_.push_back(index); + sampler_bindless_heap_exhausted_logged_ = false; +} + +IRDescriptorTableEntry* MetalCommandProcessor::GetViewBindlessHeapEntry( + uint32_t index) { + if (!view_bindless_heap_) { + XELOGE("GetViewBindlessHeapEntry: heap is null!"); + return nullptr; + } + if (index >= kViewBindlessHeapSize) { + XELOGE("GetViewBindlessHeapEntry: index {} >= heap size {}", index, + kViewBindlessHeapSize); + return nullptr; + } + return reinterpret_cast( + view_bindless_heap_->contents()) + + index; +} + +IRDescriptorTableEntry* MetalCommandProcessor::GetSamplerBindlessHeapEntry( + uint32_t index) { + if (!sampler_bindless_heap_) { + XELOGE("GetSamplerBindlessHeapEntry: heap is null!"); + return nullptr; + } + if (index >= kSamplerBindlessHeapSize) { + XELOGE("GetSamplerBindlessHeapEntry: index {} >= heap size {}", index, + kSamplerBindlessHeapSize); + return nullptr; + } + return reinterpret_cast( + sampler_bindless_heap_->contents()) + + index; +} + void MetalCommandProcessor::InitializeShaderStorage( const std::filesystem::path& cache_root, uint32_t title_id, bool blocking, std::function completion_callback) { CommandProcessor::InitializeShaderStorage(cache_root, title_id, blocking, nullptr); - - if (!device_) { - XELOGW("Metal shader storage init skipped (no device)"); - if (completion_callback) { - completion_callback(); - } - return; + if (pipeline_cache_) { + pipeline_cache_->InitializeShaderStorage(cache_root, title_id, blocking); } - - std::string device_tag = "unknown"; - if (device_->name()) { - device_tag = device_->name()->utf8String(); - } - for (char& ch : device_tag) { - if (!std::isalnum(static_cast(ch))) { - ch = '_'; - } - } - - std::filesystem::path shader_storage_title_root = - cache_root / "shaders" / "metal" / "local" / device_tag / - fmt::format("{:08X}", title_id); - std::error_code ec; - std::filesystem::create_directories(shader_storage_title_root, ec); - if (ec) { - XELOGW("Metal shader storage: Failed to create {}: {}", - shader_storage_title_root.string(), ec.message()); - } else if (::cvars::metal_shader_disk_cache && g_metal_shader_cache) { - g_metal_shader_cache->Initialize(shader_storage_title_root / "metallib"); - SetMslShaderSourceCacheDirectory(shader_storage_title_root / "msl_source"); - } else { - SetMslShaderSourceCacheDirectory(shader_storage_title_root / "msl_source"); - } - if (completion_callback) { completion_callback(); } @@ -1740,48 +1757,60 @@ void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_height) { ProcessCompletedSubmissions(); saw_swap_ = true; + ++backend_telemetry_.swaps; last_swap_ptr_ = frontbuffer_ptr; last_swap_width_ = frontbuffer_width; last_swap_height_ = frontbuffer_height; + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kSwap)) { + XELOGE("Metal IssueSwap: failed to flush prepared draw queue"); + } + // End any active render encoder - EndRenderEncoder(); + EndRenderEncoder(RenderEncoderEndReason::kSwap); + EndSharedMemoryUploadBlitEncoder(SharedMemoryUploadEncoderEndReason::kSwap); + if (texture_cache_ && + !texture_cache_->FlushPendingUploadEncodersForCommandEncoderBoundary()) { + XELOGE("Metal: failed to flush texture upload encoder before swap"); + } // Submit and wait for command buffer if (current_command_buffer_) { - if (UseSpirvCrossPath()) { - ScheduleSpirvUniformBufferRelease(current_command_buffer_); - } - ScheduleSpirvArgumentBufferRelease(current_command_buffer_); current_command_buffer_->commit(); current_command_buffer_->release(); current_command_buffer_ = nullptr; - current_draw_index_ = 0; - copy_resolve_writes_pending_ = false; + submission_has_draws_ = false; + } + DrainCommandBufferAutoreleasePool(); + + CloseFrameLifetime(); + // Proactive descriptor-pressure trimming: at frame boundaries the GPU has + // likely completed prior submissions, so retired descriptor indices can be + // reclaimed and the texture cache can be trimmed before we start the next + // frame under pressure. The high-water mark (75% utilization) gives + // headroom so that mid-frame allocation bursts don't immediately exhaust + // the heap. + if (texture_cache_) { + constexpr uint32_t kDescriptorPressureThreshold = + kViewBindlessHeapSize / 4; // trim when < 25% free + uint32_t available = GetViewBindlessHeapAvailableCount(); + if (available < kDescriptorPressureThreshold) { + texture_cache_->TrimViewBindlessPressure(kDescriptorPressureThreshold); + } } - if (primitive_processor_ && frame_open_) { - primitive_processor_->EndFrame(); - frame_open_ = false; - } - // Frame boundary reached - resolved memory tracking is only needed within a - // frame when trace playback writes memory. - ClearResolvedMemory(); if (shared_memory_ && ::cvars::clear_memory_page_state) { shared_memory_->SetSystemPageBlocksValidWithGpuDataWritten(); } - // Push the rendered frame to the presenter's guest output mailbox - // This is required for trace dumps to capture the output. Use the - // MetalRenderTargetCache color target (like D3D12) rather than the - // legacy standalone render_target_texture_. + // Push the rendered frame to the presenter's guest output mailbox. + // This is required for trace dumps to capture the output via the + // MetalRenderTargetCache color target (like D3D12). auto* presenter = static_cast(graphics_system_->presenter()); if (presenter && render_target_cache_) { - uint32_t output_width = - frontbuffer_width ? frontbuffer_width : render_target_width_; - uint32_t output_height = - frontbuffer_height ? frontbuffer_height : render_target_height_; + uint32_t output_width = frontbuffer_width ? frontbuffer_width : 1280; + uint32_t output_height = frontbuffer_height ? frontbuffer_height : 720; MTL::Texture* source_texture = nullptr; bool use_pwl_gamma_ramp = false; @@ -1797,25 +1826,6 @@ void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, use_pwl_gamma_ramp = swap_format == xenos::TextureFormat::k_2_10_10_10 || swap_format == xenos::TextureFormat::k_2_10_10_10_AS_16_16_16_16; - static MTL::PixelFormat last_format = MTL::PixelFormatInvalid; - static uint32_t last_samples = 0; - static uint32_t last_width = 0; - static uint32_t last_height = 0; - static int last_swap_format = -1; - MTL::PixelFormat src_format = source_texture->pixelFormat(); - uint32_t src_samples = source_texture->sampleCount(); - uint32_t src_width = uint32_t(source_texture->width()); - uint32_t src_height = uint32_t(source_texture->height()); - int swap_format_int = static_cast(swap_format); - if (src_format != last_format || src_samples != last_samples || - src_width != last_width || src_height != last_height || - swap_format_int != last_swap_format) { - last_format = src_format; - last_samples = src_samples; - last_width = src_width; - last_height = src_height; - last_swap_format = swap_format_int; - } if (presenter) { if (!gamma_ramp_256_entry_table_up_to_date_ || !gamma_ramp_pwl_up_to_date_) { @@ -1839,12 +1849,6 @@ void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, bool swap_dest_swap = false; const bool has_swap_dest_swap = ConsumeSwapDestSwap(frontbuffer_ptr, &swap_dest_swap); - if (!has_swap_dest_swap && frontbuffer_ptr) { - static uint32_t swap_dest_miss_count = 0; - if (swap_dest_miss_count < 8) { - ++swap_dest_miss_count; - } - } bool force_swap_rb = has_swap_dest_swap && swap_dest_swap; if (!source_texture) { @@ -1859,6 +1863,7 @@ void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, 0, 0, 0, 0, [](ui::Presenter::GuestOutputRefreshContext&) -> bool { return false; }); + MaybeDumpBackendTelemetry("swap"); return; } @@ -1890,6 +1895,7 @@ void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, }); } } + MaybeDumpBackendTelemetry("swap"); } void MetalCommandProcessor::OnPrimaryBufferEnd() { @@ -1897,17 +1903,15 @@ void MetalCommandProcessor::OnPrimaryBufferEnd() { return; } - // In the SPIRV-Cross path, keep command buffers open across primary-buffer - // boundaries unless a copy->draw visibility boundary is pending. - if (UseSpirvCrossPath() && !copy_resolve_writes_pending_) { - return; - } + // Pump any completed resolves now since the guest is likely about to poll. + PumpQueryResolves(); + PumpPendingRetire(); if (!cvars::submit_on_primary_buffer_end) { return; } - if (!copy_resolve_writes_pending_ && !CanEndSubmissionImmediately()) { + if (!CanEndSubmissionImmediately()) { return; } EndCommandBuffer(); @@ -1915,65 +1919,727 @@ void MetalCommandProcessor::OnPrimaryBufferEnd() { bool MetalCommandProcessor::CanEndSubmissionImmediately() { if (!current_command_buffer_) { + return false; + } + if (pipeline_cache_ && pipeline_cache_->IsCreatingPipelines()) { + return false; + } + return true; +} + +// ============================================================================ +// ZPD (occlusion query) backend overrides. +// ============================================================================ + +void MetalCommandProcessor::EnsureZPDQueryResources() { + if (GetZPDMode() == ZPDMode::kFake || !zpd_visibility_pool_) { + return; + } + + zpd_visibility_pool_->EnsureInitialized(device_, kZPDQueryPoolCapacity); +} + +void MetalCommandProcessor::ShutdownZPDQueryResources() { + if (!zpd_visibility_pool_) { + return; + } + zpd_resolves_in_flight_.clear(); + zpd_active_query_.Reset(); + zpd_visibility_pool_->Shutdown(); +} + +bool MetalCommandProcessor::IsZPDQueryPoolReady() const { + return zpd_visibility_pool_ && zpd_visibility_pool_->is_initialized(); +} + +bool MetalCommandProcessor::CanOpenZPDQuery() const { + // Metal visibility queries can only be enabled on a render encoder whose + // descriptor had visibilityResultBuffer set before the encoder was created. + return current_command_buffer_ != nullptr && + current_render_encoder_ != nullptr && + current_render_encoder_has_zpd_visibility_; +} + +CommandProcessor::QueryOpenResult MetalCommandProcessor::OpenZPDQuery( + ReportHandle report_handle, bool can_close_submission) { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kQuery)) { + return QueryOpenResult::kFailed; + } + if (!zpd_visibility_pool_ || !zpd_visibility_pool_->is_initialized()) { + return QueryOpenResult::kFailed; + } + if (!CanOpenZPDQuery()) { + return QueryOpenResult::kDeferred; + } + + bool is_pool_exhausted = !zpd_visibility_pool_->has_free_indices(); + + if (is_pool_exhausted) { + PumpQueryResolves(); + is_pool_exhausted = !zpd_visibility_pool_->has_free_indices(); + } + + bool waited_for_submission = false; + + if (is_pool_exhausted) { + if (GetZPDMode() == ZPDMode::kFast) { + return QueryOpenResult::kPoolExhausted; + } + + uint64_t wait_for = 0; + if (!zpd_resolves_in_flight_.empty()) { + wait_for = zpd_resolves_in_flight_.front().submission; + } + + uint64_t completed_submission = GetCompletedSubmission(); + if (wait_for > completed_submission) { + if (wait_for >= GetCurrentSubmission()) { + if (can_close_submission) { + EndRenderEncoder(RenderEncoderEndReason::kUnknown); + EndCommandBuffer(); + } + return QueryOpenResult::kDeferred; + } + + if (cvars::occlusion_query_log) { + XELOGI("ZPD: Stall awaiting submission={} completed_before={}", + wait_for, completed_submission); + } + + // Wait for the oldest pending resolve's submission to complete. + { + std::unique_lock lock(completion_mutex_); + completion_cond_.wait(lock, [&]() { + return completed_command_buffers_.load(std::memory_order_acquire) >= + wait_for; + }); + } + waited_for_submission = true; + PumpQueryResolves(); + is_pool_exhausted = !zpd_visibility_pool_->has_free_indices(); + } + } + + if (is_pool_exhausted) { + return waited_for_submission ? QueryOpenResult::kPoolExhausted + : QueryOpenResult::kDeferred; + } + + MetalZPDActiveQuery active_query; + if (!zpd_visibility_pool_->Acquire( + active_query.index, active_query.generation, active_query.offset)) { + return QueryOpenResult::kFailed; + } + + current_render_encoder_->setVisibilityResultMode( + MTL::VisibilityResultModeCounting, active_query.offset); + zpd_active_query_ = active_query; + return QueryOpenResult::kOpened; +} + +bool MetalCommandProcessor::CloseZPDQuery(ReportHandle report_handle, + uint64_t& out_submission) { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kQuery)) { + return false; + } + if (!current_render_encoder_ || !current_render_encoder_has_zpd_visibility_ || + !zpd_active_query_.is_open()) { + return false; + } + + // Disable visibility counting. + current_render_encoder_->setVisibilityResultMode( + MTL::VisibilityResultModeDisabled, 0); + + MetalZPDResolve resolve; + resolve.submission = GetCurrentSubmission(); + resolve.index = zpd_active_query_.index; + resolve.generation = zpd_active_query_.generation; + resolve.report_handle = report_handle; + zpd_resolves_in_flight_.push_back(resolve); + + out_submission = resolve.submission; + + zpd_active_query_.Reset(); + return true; +} + +bool MetalCommandProcessor::DiscardZPDQuery() { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kQuery)) { + return false; + } + if (!zpd_visibility_pool_ || !zpd_active_query_.is_open()) { + return false; + } + + if (current_render_encoder_ && current_render_encoder_has_zpd_visibility_) { + current_render_encoder_->setVisibilityResultMode( + MTL::VisibilityResultModeDisabled, 0); + } + + // The offset was used in this render pass and Metal only allows an offset to + // be selected once per pass. Retire the slot after the submission completes + // instead of making it immediately available for reuse. + MetalZPDResolve resolve; + resolve.submission = GetCurrentSubmission(); + resolve.index = zpd_active_query_.index; + resolve.generation = zpd_active_query_.generation; + resolve.report_handle = kInvalidReportHandle; + zpd_resolves_in_flight_.push_back(resolve); + + zpd_active_query_.Reset(); + return true; +} + +void MetalCommandProcessor::PumpQueryResolves() { + if (!zpd_visibility_pool_) { + return; + } + + uint64_t completed = GetCompletedSubmission(); + if (completed == 0) { + return; + } + + while (!zpd_resolves_in_flight_.empty()) { + if (zpd_resolves_in_flight_.front().submission > completed) { + break; + } + MetalZPDResolve resolve = zpd_resolves_in_flight_.front(); + zpd_resolves_in_flight_.pop_front(); + + if (zpd_visibility_pool_->IsGenerationCurrent(resolve.index, + resolve.generation)) { + uint64_t raw_samples = zpd_visibility_pool_->Read(resolve.index); + zpd_visibility_pool_->Release(resolve.index, resolve.generation); + if (resolve.report_handle != kInvalidReportHandle) { + OnZPDQueryResolved(resolve.report_handle, raw_samples); + } + } else { + if (cvars::occlusion_query_log) { + XELOGI( + "ZPD/Metal: Dropping stale query index={} generation={} " + "handle={}", + resolve.index, resolve.generation, resolve.report_handle); + } + } + } +} + +bool MetalCommandProcessor::AwaitQueryResolve(ReportHandle report_handle, + uint64_t wait_for_submission) { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kQuery)) { + return false; + } + if (GetZPDMode() == ZPDMode::kFake) { + return false; + } + + PumpQueryResolves(); + + auto it = logical_zpd_reports_.find(report_handle); + if (it == logical_zpd_reports_.end()) { return true; } - if (!cvars::async_shader_compilation || msl_shader_compile_threads_.empty()) { + if (it->second.pending_segments == 0 && it->second.ended) { return true; } - std::lock_guard lock(msl_shader_compile_mutex_); - return msl_shader_compile_busy_ == 0 && msl_shader_compile_queue_.empty() && - msl_pipeline_compile_queue_.empty() && - msl_shader_compile_pending_.empty() && - msl_pipeline_compile_pending_.empty(); + if (wait_for_submission == 0) { + return false; + } + + // Ensure the submission is flushed. + if (wait_for_submission >= GetCurrentSubmission()) { + if (!current_command_buffer_) { + return false; + } + // If async pipeline creation is in progress, we can't end the submission + // cleanly. Return false and let PumpPendingRetire handle the stall limit — + // it will abandon the report and write a cached delta if needed. + if (!CanEndSubmissionImmediately()) { + if (cvars::occlusion_query_log) { + XELOGI( + "ZPD/Metal: AwaitQueryResolve cannot end submission " + "(pipelines creating), deferring"); + } + return false; + } + EndRenderEncoder(RenderEncoderEndReason::kUnknown); + EndCommandBuffer(); + } + + if (wait_for_submission > GetCompletedSubmission()) { + std::unique_lock lock(completion_mutex_); + completion_cond_.wait(lock, [&]() { + return completed_command_buffers_.load(std::memory_order_acquire) >= + wait_for_submission; + }); + } + + PumpQueryResolves(); + + it = logical_zpd_reports_.find(report_handle); + return it == logical_zpd_reports_.end() || + (it->second.pending_segments == 0 && it->second.ended); +} + +void MetalCommandProcessor::MarkSharedMemoryComputeWritePending( + uint32_t address, uint32_t length, MTL::ComputeCommandEncoder* encoder) { + if (!length) { + return; + } + if (shared_memory_fence_ && encoder) { + encoder->updateFence(shared_memory_fence_); + MarkSharedMemoryWritePending(address, length, MTL::RenderStages(0), false, + true); + return; + } + XELOGE("MetalCommandProcessor: shared-memory compute write cannot be fenced"); + MarkSharedMemoryWritePending(address, length, MTL::RenderStages(0), false, + false); +} + +void MetalCommandProcessor::MarkSharedMemoryRenderWritePending( + uint32_t address, uint32_t length, MTL::RenderStages stages) { + if (!length) { + return; + } + if (current_render_encoder_ && NS::UInteger(stages)) { + active_render_encoder_shared_memory_write_stages_ = MTL::RenderStages( + NS::UInteger(active_render_encoder_shared_memory_write_stages_) | + NS::UInteger(stages)); + MarkSharedMemoryWritePending(address, length, stages, true, false); + return; + } + XELOGE("MetalCommandProcessor: shared-memory render write cannot be fenced"); + MarkSharedMemoryWritePending(address, length, MTL::RenderStages(0), false, + false); +} + +bool MetalCommandProcessor::PrepareSharedMemoryComputeReadDependency( + const SharedMemoryRange* ranges, uint32_t range_count, + bool consumer_can_join_current_submission, + SharedMemoryReadDependency* dependency_out) { + if (dependency_out) { + *dependency_out = {}; + } + if (!ranges || !range_count || + !PendingSharedMemoryWritesOverlapRanges(ranges, range_count)) { + return true; + } + + if (current_render_encoder_) { + EndRenderEncoder(RenderEncoderEndReason::kSharedMemoryReadDependency); + } + + bool needs_fence_wait = false; + bool needs_submission_boundary = false; + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (!PendingSharedMemoryWriteOverlapsRanges(pending, ranges, range_count)) { + continue; + } + if (pending.fence_updated && shared_memory_fence_) { + needs_fence_wait = true; + continue; + } + needs_submission_boundary = true; + break; + } + + // Ending the render encoder above may make transfer work eligible to join the + // producer command buffer. Only skip a submission boundary when the consumer + // can actually encode into that ordered submission; standalone consumers + // still carry the fence wait after the producer is committed. + const bool consumer_in_current_submission = + consumer_can_join_current_submission && + CanJoinActiveSubmissionForTransfer(); + if (needs_fence_wait && dependency_out) { + dependency_out->needs_fence_wait = true; + } + + if (current_command_buffer_ && + (needs_submission_boundary || + (needs_fence_wait && !consumer_in_current_submission))) { + EndCommandBuffer(); + } + return true; +} + +bool MetalCommandProcessor::EncodeSharedMemoryComputeReadDependency( + MTL::ComputeCommandEncoder* encoder, + const SharedMemoryReadDependency& dependency, + const SharedMemoryRange* ranges, uint32_t range_count) { + if (!dependency.needs_fence_wait) { + return true; + } + if (!encoder || !shared_memory_fence_) { + XELOGE( + "MetalCommandProcessor: compute read cannot wait for shared-memory GPU " + "writes without a fence"); + return false; + } + + encoder->waitForFence(shared_memory_fence_); + RetireFenceWaitedSharedMemoryWrites(ranges, range_count); + return true; +} + +void MetalCommandProcessor::MarkSharedMemoryWritePending( + uint32_t address, uint32_t length, MTL::RenderStages producer_stages, + bool active_render_encoder, bool fence_updated) { + if (!length) { + return; + } + + constexpr uint32_t kSharedMemoryMask = SharedMemory::kBufferSize - 1; + uint32_t remaining = std::min(length, SharedMemory::kBufferSize); + uint32_t start = address & kSharedMemoryMask; + while (remaining) { + uint32_t segment_length = + std::min(remaining, SharedMemory::kBufferSize - start); + if (segment_length) { + PendingSharedMemoryWrite pending = {}; + pending.start = start; + pending.end = start + segment_length; + pending.submission_id = GetCurrentSubmission(); + pending.producer_stages = producer_stages; + pending.active_render_encoder = active_render_encoder; + pending.fence_updated = fence_updated; + pending_shared_memory_writes_.push_back(pending); + } + remaining -= segment_length; + start = 0; + } + + if (pending_shared_memory_writes_.size() <= kMaxPendingSharedMemoryWrites) { + return; + } + + PendingSharedMemoryWrite collapsed = {}; + collapsed.start = 0; + collapsed.end = SharedMemory::kBufferSize; + collapsed.submission_id = GetCurrentSubmission(); + collapsed.fence_updated = true; + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + collapsed.submission_id = + std::max(collapsed.submission_id, pending.submission_id); + collapsed.producer_stages = + MTL::RenderStages(NS::UInteger(collapsed.producer_stages) | + NS::UInteger(pending.producer_stages)); + collapsed.active_render_encoder = + collapsed.active_render_encoder || pending.active_render_encoder; + collapsed.fence_updated = collapsed.fence_updated && pending.fence_updated; + } + if (collapsed.active_render_encoder) { + collapsed.fence_updated = false; + } + pending_shared_memory_writes_.assign(1, collapsed); +} + +bool MetalCommandProcessor::PendingSharedMemoryWritesOverlapRange( + uint32_t start, uint32_t length) const { + SharedMemoryRange range = {start, length}; + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (PendingSharedMemoryWriteOverlapsRanges(pending, &range, 1)) { + return true; + } + } + return false; +} + +bool MetalCommandProcessor::PendingSharedMemoryWriteOverlapsRanges( + const PendingSharedMemoryWrite& pending, const SharedMemoryRange* ranges, + uint32_t range_count) const { + if (!ranges || !range_count || pending.end <= pending.start) { + return false; + } + for (uint32_t i = 0; i < range_count; ++i) { + if (SharedMemoryRangeOverlapsSegment(ranges[i], pending.start, + pending.end)) { + return true; + } + } + return false; +} + +bool MetalCommandProcessor::PendingSharedMemoryWritesOverlapRanges( + const SharedMemoryRange* ranges, uint32_t range_count) const { + if (!ranges || !range_count || pending_shared_memory_writes_.empty()) { + return false; + } + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (PendingSharedMemoryWriteOverlapsRanges(pending, ranges, range_count)) { + return true; + } + } + return false; +} + +void MetalCommandProcessor::UpdateSharedMemoryFenceForActiveRenderEncoder() { + if (!current_render_encoder_ || + !NS::UInteger(active_render_encoder_shared_memory_write_stages_)) { + return; + } + + if (shared_memory_fence_) { + current_render_encoder_->updateFence( + shared_memory_fence_, + active_render_encoder_shared_memory_write_stages_); + } + + for (PendingSharedMemoryWrite& pending : pending_shared_memory_writes_) { + if (!pending.active_render_encoder) { + continue; + } + pending.active_render_encoder = false; + pending.fence_updated = shared_memory_fence_ != nullptr; + } + active_render_encoder_shared_memory_write_stages_ = MTL::RenderStages(0); +} + +void MetalCommandProcessor::PruneCompletedSharedMemoryWrites( + uint64_t completed_submission) { + pending_shared_memory_writes_.erase( + std::remove_if( + pending_shared_memory_writes_.begin(), + pending_shared_memory_writes_.end(), + [completed_submission](const PendingSharedMemoryWrite& pending) { + return !pending.active_render_encoder && + pending.submission_id <= completed_submission; + }), + pending_shared_memory_writes_.end()); +} + +void MetalCommandProcessor::RetireFenceWaitedSharedMemoryWrites( + const SharedMemoryRange* ranges, uint32_t range_count) { + if (!ranges || !range_count) { + return; + } + std::array + retained_entries; + size_t retained_count = 0; + bool retained_overflow = false; + auto retain_entry = [&](const PendingSharedMemoryWrite& pending) { + if (retained_count >= retained_entries.size()) { + retained_overflow = true; + return; + } + retained_entries[retained_count++] = pending; + }; + + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (pending.active_render_encoder || !pending.fence_updated || + !PendingSharedMemoryWriteOverlapsRanges(pending, ranges, range_count)) { + retain_entry(pending); + continue; + } + + std::array + remaining_segments; + std::array + next_segments; + size_t remaining_count = 1; + remaining_segments[0] = {pending.start, pending.end}; + bool segment_overflow = false; + + for (uint32_t i = 0; i < range_count && remaining_count; ++i) { + ForEachSharedMemoryRangeSegment( + ranges[i], [&](SharedMemoryRangeSegment waited_segment) { + if (segment_overflow || !remaining_count) { + return; + } + size_t next_count = 0; + auto append_next = [&](SharedMemoryRangeSegment segment) { + if (next_count >= next_segments.size()) { + segment_overflow = true; + return; + } + next_segments[next_count++] = segment; + }; + + for (size_t segment_index = 0; segment_index < remaining_count; + ++segment_index) { + const SharedMemoryRangeSegment segment = + remaining_segments[segment_index]; + if (waited_segment.start >= segment.end || + waited_segment.end <= segment.start) { + append_next(segment); + continue; + } + if (segment.start < waited_segment.start) { + append_next({segment.start, waited_segment.start}); + } + if (waited_segment.end < segment.end) { + append_next({waited_segment.end, segment.end}); + } + } + + if (!segment_overflow) { + for (size_t segment_index = 0; segment_index < next_count; + ++segment_index) { + remaining_segments[segment_index] = + next_segments[segment_index]; + } + remaining_count = next_count; + } + }); + if (segment_overflow) { + break; + } + } + + if (segment_overflow) { + // Preserve correctness if the range set fragments too much for the fixed + // stack workspace. This may cause one extra future wait, but avoids heap + // churn and never drops an unwaited byte range. + retain_entry(pending); + continue; + } + + for (size_t segment_index = 0; segment_index < remaining_count; + ++segment_index) { + const SharedMemoryRangeSegment segment = + remaining_segments[segment_index]; + if (segment.start >= segment.end) { + continue; + } + PendingSharedMemoryWrite split_pending = pending; + split_pending.start = segment.start; + split_pending.end = segment.end; + retain_entry(split_pending); + } + } + + if (retained_overflow) { + return; + } + + pending_shared_memory_writes_.assign( + retained_entries.begin(), retained_entries.begin() + retained_count); + if (pending_shared_memory_writes_.size() <= kMaxPendingSharedMemoryWrites) { + return; + } + + PendingSharedMemoryWrite collapsed = {}; + collapsed.start = 0; + collapsed.end = SharedMemory::kBufferSize; + collapsed.submission_id = GetCurrentSubmission(); + collapsed.fence_updated = true; + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + collapsed.submission_id = + std::max(collapsed.submission_id, pending.submission_id); + collapsed.producer_stages = + MTL::RenderStages(NS::UInteger(collapsed.producer_stages) | + NS::UInteger(pending.producer_stages)); + collapsed.active_render_encoder = + collapsed.active_render_encoder || pending.active_render_encoder; + collapsed.fence_updated = collapsed.fence_updated && pending.fence_updated; + } + if (collapsed.active_render_encoder) { + collapsed.fence_updated = false; + } + pending_shared_memory_writes_.assign(1, collapsed); +} + +bool MetalCommandProcessor::EncodeSharedMemoryRenderReadDependencies( + const SharedMemoryRange* ranges, uint32_t range_count, + MTL::RenderStages consumer_stages) { + if (!current_render_encoder_ || !ranges || !range_count || + !NS::UInteger(consumer_stages) || pending_shared_memory_writes_.empty()) { + return true; + } + + MTL::RenderStages active_producer_stages = MTL::RenderStages(0); + bool needs_fence_wait = false; + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (!PendingSharedMemoryWriteOverlapsRanges(pending, ranges, range_count)) { + continue; + } + if (pending.active_render_encoder) { + active_producer_stages = + MTL::RenderStages(NS::UInteger(active_producer_stages) | + NS::UInteger(pending.producer_stages)); + } else if (pending.fence_updated && shared_memory_fence_) { + needs_fence_wait = true; + } else { + XELOGE( + "MetalCommandProcessor: render read cannot wait for shared-memory " + "GPU " + "writes without a fence"); + return false; + } + } + + if (NS::UInteger(active_producer_stages)) { + current_render_encoder_->memoryBarrier( + MTL::BarrierScopeBuffers, active_producer_stages, consumer_stages); + } + if (needs_fence_wait && shared_memory_fence_) { + current_render_encoder_->waitForFence(shared_memory_fence_, + consumer_stages); + RetireFenceWaitedSharedMemoryWrites(ranges, range_count); + } + return true; +} + +bool MetalCommandProcessor::EncodeSharedMemoryBlitReadDependency( + MTL::BlitCommandEncoder* encoder, uint32_t start, uint32_t length) { + if (!encoder || !length || + !PendingSharedMemoryWritesOverlapRange(start, length)) { + return true; + } + if (!shared_memory_fence_) { + XELOGE( + "MetalCommandProcessor: blit read cannot wait for shared-memory GPU " + "writes without a fence"); + return false; + } + encoder->waitForFence(shared_memory_fence_); + SharedMemoryRange range = {start, length}; + RetireFenceWaitedSharedMemoryWrites(&range, 1); + return true; } Shader* MetalCommandProcessor::LoadShader(xenos::ShaderType shader_type, const uint32_t* host_address, uint32_t dword_count) { - uint64_t hash = XXH3_64bits(host_address, dword_count * sizeof(uint32_t)); - - auto it = msl_shader_cache_.find(hash); - if (it != msl_shader_cache_.end()) { - return it->second.get(); - } - auto shader = - std::make_unique(shader_type, hash, host_address, dword_count); - MslShader* result = shader.get(); - msl_shader_cache_[hash] = std::move(shader); - return result; + return pipeline_cache_->LoadShader(shader_type, host_address, dword_count); } bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint32_t index_count, IndexBufferInfo* index_buffer_info, bool major_mode_explicit) { + ++backend_telemetry_.draw_calls; const RegisterFile& regs = *register_file_; uint32_t normalized_color_mask = 0; // Check for copy mode xenos::EdramMode edram_mode = regs.Get().edram_mode; - if (edram_mode != xenos::EdramMode::kCopy && copy_resolve_writes_pending_) { - // Preserve resolve write visibility when transitioning from copy-only - // bursts to regular draw work. - // MSC keeps the conservative split behavior; SPIRV-Cross decides in - // IssueDrawMsl based on actual texture overlap with pending resolve writes. - if (!UseSpirvCrossPath()) { - EndCommandBuffer(); - } - } if (edram_mode == xenos::EdramMode::kCopy) { return IssueCopy(); } - // Vertex shader analysis — use Shader* base type so both MSC and - // SPIRV-Cross paths share this common code. + if (regs.Get().surface_pitch == 0) { + // Doesn't actually draw. + return true; + } + + // Vertex shader analysis. Shader* vertex_shader = active_vertex_shader(); if (!vertex_shader) { XELOGW("IssueDraw: No vertex shader"); return false; } if (!vertex_shader->is_ucode_analyzed()) { - vertex_shader->AnalyzeUcode(ucode_disasm_buffer_); + vertex_shader->AnalyzeUcode(pipeline_cache_->ucode_disasm_buffer()); } bool memexport_used_vertex = vertex_shader->memexport_eM_written() != 0; @@ -1987,7 +2653,7 @@ bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, pixel_shader = active_pixel_shader(); if (pixel_shader) { if (!pixel_shader->is_ucode_analyzed()) { - pixel_shader->AnalyzeUcode(ucode_disasm_buffer_); + pixel_shader->AnalyzeUcode(pipeline_cache_->ucode_disasm_buffer()); } if (!draw_util::IsPixelShaderNeededWithRasterization(*pixel_shader, regs)) { @@ -2002,7 +2668,6 @@ bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, } bool memexport_used_pixel = pixel_shader && (pixel_shader->memexport_eM_written() != 0); - bool memexport_used = memexport_used_vertex || memexport_used_pixel; memexport_ranges_.clear(); if (memexport_used_vertex) { draw_util::AddMemExportRanges(regs, *vertex_shader, memexport_ranges_); @@ -2029,12 +2694,22 @@ bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, Shader::HostVertexShaderType::kVertex; } + struct PendingDrawPassTransferGuard { + MetalRenderTargetCache* cache = nullptr; + bool Flush() { + if (!cache || !cache->HasPendingDrawPassTransfers()) { + return true; + } + return cache->FlushPendingDrawPassTransfers(); + } + ~PendingDrawPassTransferGuard() { (void)Flush(); } + } pending_draw_pass_transfer_guard; + + auto normalized_depth_control = draw_util::GetNormalizedDepthControl(regs); + bool use_tessellation_emulation = false; if (primitive_processing_result.IsTessellated()) { - // The MSC path uses mesh shader emulation for tessellation, so it requires - // mesh shader support. The SPIRV-Cross path uses native Metal tessellation - // (drawPatches), so mesh shaders are not needed. - if (!UseSpirvCrossPath() && !mesh_shader_supported_) { + if (!mesh_shader_supported_) { static bool tess_mesh_logged = false; if (!tess_mesh_logged) { tess_mesh_logged = true; @@ -2057,13 +2732,32 @@ bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, } // Configure render targets via MetalRenderTargetCache, similar to D3D12. + // Update() may internally call PerformTransfersAndResolveClears for EDRAM + // ownership transfers -- this is the draw-path transfer entry point and + // no transfer operations bypass the render target cache. + PreparedDrawRenderTargetKey render_target_key = {}; if (render_target_cache_) { - auto normalized_depth_control = draw_util::GetNormalizedDepthControl(regs); uint32_t ps_writes_color_targets = pixel_shader ? pixel_shader->writes_color_targets() : 0; normalized_color_mask = pixel_shader ? draw_util::GetNormalizedColorMask( regs, ps_writes_color_targets) : 0; + render_target_key = BuildPreparedDrawRenderTargetKey( + regs, is_rasterization_done, normalized_depth_control, + normalized_color_mask); + if (!prepared_draw_queue_.empty()) { + if (!prepared_draw_queue_render_target_key_valid_) { + if (!FlushPreparedDrawQueue( + PreparedDrawFlushReason::kRenderTargetUpdate)) { + return false; + } + } else if (render_target_key != prepared_draw_queue_render_target_key_) { + if (!FlushPreparedDrawQueue( + PreparedDrawFlushReason::kRenderTargetKeyMismatch)) { + return false; + } + } + } if (!render_target_cache_->Update(is_rasterization_done, normalized_depth_control, normalized_color_mask, *vertex_shader)) { @@ -2072,350 +2766,598 @@ bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, "failed"); return false; } - } - - // Begin command buffer if needed (will use cache-provided render targets). - BeginCommandBuffer(); - if (!current_command_buffer_ || !current_render_encoder_) { - static bool spirv_no_command_buffer_logged = false; - if (!spirv_no_command_buffer_logged) { - spirv_no_command_buffer_logged = true; - XELOGE( - "IssueDraw: failed to begin Metal command buffer/render encoder; " - "skipping draws until uniforms buffer allocation recovers"); + pending_draw_pass_transfer_guard.cache = render_target_cache_.get(); + if (current_render_encoder_ && + render_target_cache_->IsRenderPassDescriptorDirty() && + !render_target_cache_->IsRenderPassDescriptorCompatible( + current_render_pass_descriptor_, 1)) { + EndRenderEncoder( + RenderEncoderEndReason::kRenderTargetUpdateDescriptorDirty); } - return UseSpirvCrossPath(); - } - if (UseSpirvCrossPath() && !EnsureSpirvUniformBufferCapacity()) { - XELOGE( - "IssueDraw: failed to prepare SPIRV-Cross uniforms ring; skipping " - "draw"); - return true; } - // ========================================================================= - // SPIRV-Cross (MSL) draw path — bypasses the entire MSC / IRRuntime flow. - // ========================================================================= - if (UseSpirvCrossPath()) { - return IssueDrawMsl(vertex_shader, pixel_shader, - primitive_processing_result, primitive_polygonal, - is_rasterization_done, memexport_used, - normalized_color_mask, regs); - } + // Cast to MSC-specific shader types for the rest of this path. + auto* metal_vertex_shader = static_cast(vertex_shader); + auto* metal_pixel_shader = static_cast(pixel_shader); - XELOGE("MSC draw path not available"); - return false; -} - -// ========================================================================== -// SPIRV-Cross (MSL) draw path -// ========================================================================== -bool MetalCommandProcessor::IssueDrawMsl( - Shader* vertex_shader, Shader* pixel_shader, - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - bool primitive_polygonal, bool is_rasterization_done, bool memexport_used, - uint32_t normalized_color_mask, const RegisterFile& regs) { - assert_not_null(vertex_shader); - // Cast to MslShader for the SPIRV-Cross path. - auto* msl_vertex_shader = static_cast(vertex_shader); - auto* msl_pixel_shader = static_cast(pixel_shader); - - // Tessellation draws use Metal's native tessellation: tessellation factors - // are computed on the CPU and the domain shader (TES) runs as the - // post-tessellation vertex function. - const bool is_tessellated = primitive_processing_result.IsTessellated(); - - // Determine the host vertex shader type for geometry expansion. - // The primitive processor has already set kPointListAsTriangleStrip or - // kRectangleListAsTriangleStrip when VS expansion is needed (enabled by - // setting point_sprites_supported_without_vs_expansion = false in the - // primitive processor init when spirvcross is active). - Shader::HostVertexShaderType host_vertex_shader_type = - primitive_processing_result.host_vertex_shader_type; - - // Compute interpolator mask for shader modifications. + MTL::RenderPipelineState* pipeline = nullptr; + // Select per-draw shader modifications (mirrors D3D12 PipelineCache). uint32_t ps_param_gen_pos = UINT32_MAX; uint32_t interpolator_mask = 0; - if (msl_pixel_shader) { - interpolator_mask = msl_vertex_shader->writes_interpolators() & - msl_pixel_shader->GetInterpolatorInputMask( + if (pixel_shader) { + interpolator_mask = vertex_shader->writes_interpolators() & + pixel_shader->GetInterpolatorInputMask( regs.Get(), regs.Get(), ps_param_gen_pos); } - auto normalized_depth_control = draw_util::GetNormalizedDepthControl(regs); - - // Compute SPIRV shader modifications. - SpirvShaderTranslator::Modification vertex_shader_modification = - GetCurrentSpirvVertexShaderModification( - *msl_vertex_shader, host_vertex_shader_type, interpolator_mask); - SpirvShaderTranslator::Modification pixel_shader_modification = - msl_pixel_shader - ? GetCurrentSpirvPixelShaderModification( - *msl_pixel_shader, interpolator_mask, ps_param_gen_pos, - normalized_depth_control, normalized_color_mask) - : SpirvShaderTranslator::Modification(0); - - // Sanity: if a pixel shader writes color targets and any RT is enabled, - // color_targets_used must be non-zero or fragments will produce no output. - if (msl_pixel_shader && msl_pixel_shader->writes_color_targets() && - normalized_color_mask) { - assert_not_zero(pixel_shader_modification.pixel.color_targets_used); - } - - // Get or create shader translations. - constexpr bool kIsIos = -#if XE_PLATFORM_IOS - true; -#else - false; -#endif - auto* vertex_translation = static_cast( - msl_vertex_shader->GetOrCreateTranslation( - vertex_shader_modification.value)); - if (!vertex_translation->is_translated()) { - if (!spirv_shader_translator_->TranslateAnalyzedShader( - *vertex_translation)) { - XELOGE("SPIRV-Cross: Failed to translate vertex shader to SPIR-V"); - return false; - } - } - auto ensure_msl_translation_ready = - [&](MslShader::MslTranslation* translation, - uint8_t priority) -> MslShaderCompileStatus { - if (!translation) { - return MslShaderCompileStatus::kFailed; - } - MslShaderCompileStatus status = GetMslShaderCompileStatus(translation); - if (status == MslShaderCompileStatus::kReady || - status == MslShaderCompileStatus::kPending || - status == MslShaderCompileStatus::kFailed) { - return status; - } - if (EnqueueMslShaderCompilation(translation, kIsIos, priority)) { - return MslShaderCompileStatus::kPending; - } - if (!translation->CompileToMsl(device_, kIsIos)) { - std::lock_guard lock(msl_shader_compile_mutex_); - msl_shader_compile_failed_.insert(translation); - return MslShaderCompileStatus::kFailed; - } - return MslShaderCompileStatus::kReady; - }; - auto log_pending_compile = [&](MslShader::MslTranslation* translation, - const char* stage_tag) { - static auto last_pending_log = std::chrono::steady_clock::time_point{}; - const auto now = std::chrono::steady_clock::now(); - if (now - last_pending_log < std::chrono::seconds(1)) { - return; - } - last_pending_log = now; - XELOGI( - "SPIRV-Cross: Skipping draw - {} shader compile pending " - "(shader={:016X}, mod={:016X})", - stage_tag, translation->shader().ucode_data_hash(), - translation->modification()); - }; - MslShaderCompileStatus vertex_compile_status = - ensure_msl_translation_ready(vertex_translation, 1); - if (vertex_compile_status == MslShaderCompileStatus::kPending) { - log_pending_compile(vertex_translation, "vertex"); - return true; - } - if (vertex_compile_status == MslShaderCompileStatus::kFailed) { - XELOGE("SPIRV-Cross: Failed to prepare vertex shader MSL/library/function"); - return false; - } - - MslShader::MslTranslation* pixel_translation = nullptr; - if (msl_pixel_shader) { - pixel_translation = static_cast( - msl_pixel_shader->GetOrCreateTranslation( - pixel_shader_modification.value)); - if (!pixel_translation->is_translated()) { - if (!spirv_shader_translator_->TranslateAnalyzedShader( - *pixel_translation)) { - XELOGE("SPIRV-Cross: Failed to translate pixel shader to SPIR-V"); - return false; + Shader::HostVertexShaderType host_vertex_shader_type_for_translation = + primitive_processing_result.host_vertex_shader_type; + if (host_vertex_shader_type_for_translation == + Shader::HostVertexShaderType::kPointListAsTriangleStrip || + host_vertex_shader_type_for_translation == + Shader::HostVertexShaderType::kRectangleListAsTriangleStrip) { + if (!mesh_shader_supported_) { + static bool host_vs_expansion_logged = false; + if (!host_vs_expansion_logged) { + host_vs_expansion_logged = true; + XELOGW( + "Metal: Host VS expansion requested without mesh shader support; " + "skipping draw"); } + return pending_draw_pass_transfer_guard.Flush(); } - MslShaderCompileStatus pixel_compile_status = - ensure_msl_translation_ready(pixel_translation, 2); - if (pixel_compile_status == MslShaderCompileStatus::kPending) { - log_pending_compile(pixel_translation, "pixel"); - return true; + // Geometry emulation handles point/rectangle expansion; use the normal + // vertex shader translation path to avoid unsupported host VS types. + host_vertex_shader_type_for_translation = + Shader::HostVertexShaderType::kVertex; + } + DxbcShaderTranslator::Modification vertex_shader_modification = + pipeline_cache_->GetCurrentVertexShaderModification( + *vertex_shader, host_vertex_shader_type_for_translation, + interpolator_mask); + DxbcShaderTranslator::Modification pixel_shader_modification = + pixel_shader ? pipeline_cache_->GetCurrentPixelShaderModification( + *pixel_shader, interpolator_mask, ps_param_gen_pos, + normalized_depth_control) + : DxbcShaderTranslator::Modification(0); + + PipelineGeometryShader geometry_shader_type = PipelineGeometryShader::kNone; + if (!primitive_processing_result.IsTessellated()) { + switch (primitive_processing_result.host_primitive_type) { + case xenos::PrimitiveType::kPointList: + geometry_shader_type = PipelineGeometryShader::kPointList; + break; + case xenos::PrimitiveType::kRectangleList: + geometry_shader_type = PipelineGeometryShader::kRectangleList; + break; + case xenos::PrimitiveType::kQuadList: + geometry_shader_type = PipelineGeometryShader::kQuadList; + break; + default: + break; } - if (pixel_compile_status == MslShaderCompileStatus::kFailed) { - XELOGE( - "SPIRV-Cross: Failed to prepare pixel shader MSL/library/function"); + } + + GeometryShaderKey geometry_shader_key; + bool use_geometry_emulation = false; + if (geometry_shader_type != PipelineGeometryShader::kNone) { + bool can_build_geometry_shader = + pixel_shader || !vertex_shader_modification.vertex.interpolator_mask; + if (!can_build_geometry_shader) { + static bool geom_interp_mismatch_logged = false; + if (!geom_interp_mismatch_logged) { + geom_interp_mismatch_logged = true; + XELOGW( + "Metal: geometry emulation skipped because pixel shader is null " + "but vertex interpolators are present"); + } + } else { + use_geometry_emulation = + GetGeometryShaderKey(geometry_shader_type, vertex_shader_modification, + pixel_shader_modification, geometry_shader_key); + } + } + if (use_geometry_emulation && !mesh_shader_supported_) { + static bool mesh_support_logged = false; + if (!mesh_support_logged) { + mesh_support_logged = true; + XELOGW( + "Metal: geometry emulation requested but mesh shaders are not " + "supported on this device"); + } + use_geometry_emulation = false; + } + if (use_geometry_emulation && !pixel_shader) { + static bool geom_no_ps_logged = false; + if (!geom_no_ps_logged) { + geom_no_ps_logged = true; + XELOGW( + "Metal: geometry emulation requested without a pixel shader; using " + "depth-only PS fallback"); + } + } + + // Get or create shader translations for the selected modifications. + auto vertex_translation = static_cast( + vertex_shader->GetOrCreateTranslation(vertex_shader_modification.value)); + + MetalShader::MetalTranslation* pixel_translation = nullptr; + if (pixel_shader) { + pixel_translation = static_cast( + pixel_shader->GetOrCreateTranslation(pixel_shader_modification.value)); + } + + if (use_tessellation_emulation || use_geometry_emulation) { + if (!pipeline_cache_->EnsureDxbcTranslationReady(vertex_translation, + "vertex")) { + return false; + } + if (!pipeline_cache_->EnsureMetalTranslationReady(pixel_translation)) { return false; } } - // Create or retrieve pipeline state. - MTL::RenderPipelineState* pipeline = nullptr; - MslPipelineCompileStatus pipeline_compile_status = - MslPipelineCompileStatus::kReady; - if (is_tessellated) { - pipeline = GetOrCreateMslTessPipelineState( - vertex_translation, pixel_translation, host_vertex_shader_type, regs); + bool use_fallback_ps = + (use_tessellation_emulation || use_geometry_emulation) && + !pixel_translation; + + // Resolve attachment formats once for all pipeline paths. + bool pixel_shader_writes_depth_for_fmts = + pixel_translation && pixel_translation->shader().writes_depth(); + if (use_fallback_ps) { + pixel_shader_writes_depth_for_fmts = true; // depth-only PS fallback + } + bool fallback_depth_attachment_required = pixel_shader_writes_depth_for_fmts; + if (use_fallback_ps && render_target_cache_ && + !render_target_cache_->GetDepthTargetForDraw() && + !memexport_used_vertex) { + static bool fallback_without_depth_logged = false; + if (!fallback_without_depth_logged) { + fallback_without_depth_logged = true; + XELOGW( + "Metal: depth-only fallback PS requested without a depth attachment " + "or memexport side effects; skipping no-output emulated draw"); + } + return pending_draw_pass_transfer_guard.Flush(); + } + if (render_target_cache_) { + render_target_key = BuildPreparedDrawRenderTargetKey( + regs, is_rasterization_done, normalized_depth_control, + normalized_color_mask); + } + if (current_render_encoder_ && render_target_cache_ && + !render_target_cache_->IsRenderPassDescriptorCompatible( + current_render_pass_descriptor_, 1, + fallback_depth_attachment_required)) { + EndRenderEncoder(RenderEncoderEndReason::kPipelineDescriptorIncompatible); + } + MTL::RenderPassDescriptor* pass_desc_for_fmts = + current_render_pass_descriptor_; + if (render_target_cache_) { + if (MTL::RenderPassDescriptor* cache_desc = + render_target_cache_->GetRenderPassDescriptor( + 1, fallback_depth_attachment_required)) { + pass_desc_for_fmts = cache_desc; + } + } + auto attachment_formats = ResolvePipelineAttachmentFormats( + render_target_cache_.get(), pass_desc_for_fmts, + pixel_shader_writes_depth_for_fmts, "Pipeline"); + + // Derive the shared rendering key (color mask, blend, alpha-to-mask) once + // for all pipeline paths instead of re-reading registers in each method. + auto rendering_key = + ResolvePipelineRenderingKey(regs, pixel_translation, use_fallback_ps); + + MetalPipelineCache::TessellationPipelineState* tessellation_pipeline_state = + nullptr; + MetalPipelineCache::GeometryPipelineState* geometry_pipeline_state = nullptr; + if (use_tessellation_emulation) { + tessellation_pipeline_state = + pipeline_cache_->GetOrCreateTessellationPipelineState( + vertex_translation, pixel_translation, primitive_processing_result, + attachment_formats, rendering_key); + pipeline = tessellation_pipeline_state + ? tessellation_pipeline_state->pipeline + : nullptr; + } else if (use_geometry_emulation) { + geometry_pipeline_state = pipeline_cache_->GetOrCreateGeometryPipelineState( + vertex_translation, pixel_translation, geometry_shader_key, + attachment_formats, rendering_key); + if (!geometry_pipeline_state || !geometry_pipeline_state->pipeline) { + static bool geometry_pipeline_failure_logged = false; + if (!geometry_pipeline_failure_logged) { + geometry_pipeline_failure_logged = true; + XELOGW( + "Metal: geometry emulation pipeline creation failed; skipping " + "geometry-emulated draws instead of aborting the backend draw " + "packet"); + } + return pending_draw_pass_transfer_guard.Flush(); + } + pipeline = geometry_pipeline_state->pipeline; } else { - pipeline = GetOrCreateMslPipelineState( - vertex_translation, pixel_translation, regs, &pipeline_compile_status); - } - if (!pipeline) { - if (!is_tessellated && - pipeline_compile_status == MslPipelineCompileStatus::kPending) { - if (ShouldLogRateLimited(msl_pipeline_pending_last_log_ns_, - kMslAsyncLogIntervalNs)) { - XELOGI( - "SPIRV-Cross: Skipping draw - render pipeline compile pending " - "(VS {:016X} mod {:016X}, PS {:016X} mod {:016X})", - vertex_translation->shader().ucode_data_hash(), - vertex_translation->modification(), - pixel_translation ? pixel_translation->shader().ucode_data_hash() - : 0, - pixel_translation ? pixel_translation->modification() : 0); - } - return true; + auto* pipeline_handle = pipeline_cache_->GetOrCreatePipelineState( + vertex_translation, pixel_translation, attachment_formats, + rendering_key); + if (pipeline_handle) { + pipeline = pipeline_handle->state.load(std::memory_order_acquire); } - XELOGE("SPIRV-Cross: Failed to create pipeline state"); - return false; - } - - // Request textures used by the shaders. - uint32_t used_texture_mask = - msl_vertex_shader->GetUsedTextureMaskAfterTranslation(); - if (msl_pixel_shader) { - used_texture_mask |= msl_pixel_shader->GetUsedTextureMaskAfterTranslation(); - } - - if (copy_resolve_writes_pending_ && used_texture_mask) { - auto overlaps_resolved_texture_ranges = [&](uint32_t texture_fetch_mask) { - uint32_t remaining_fetch_bits = texture_fetch_mask; - uint32_t fetch_index = 0; - while (xe::bit_scan_forward(remaining_fetch_bits, &fetch_index)) { - remaining_fetch_bits &= ~(uint32_t(1) << fetch_index); - xenos::xe_gpu_texture_fetch_t fetch = regs.GetTextureFetch(fetch_index); - uint32_t width_minus_1 = 0; - uint32_t height_minus_1 = 0; - uint32_t depth_or_array_size_minus_1 = 0; - uint32_t base_page = 0; - uint32_t mip_page = 0; - uint32_t mip_max_level = 0; - texture_util::GetSubresourcesFromFetchConstant( - fetch, &width_minus_1, &height_minus_1, - &depth_or_array_size_minus_1, &base_page, &mip_page, nullptr, - &mip_max_level); - if (!base_page && !mip_page) { - continue; - } - auto layout = texture_util::GetGuestTextureLayout( - fetch.dimension, fetch.pitch, width_minus_1 + 1, height_minus_1 + 1, - depth_or_array_size_minus_1 + 1, fetch.tiled, fetch.format, - fetch.packed_mips, true, mip_max_level); - const uint32_t base_size = layout.base.level_data_extent_bytes; - const uint32_t mip_size = layout.mips_total_extent_bytes; - if (base_page && base_size && - IsResolvedMemory(base_page << 12, base_size)) { - return true; - } - if (mip_page && mip_size && - IsResolvedMemory(mip_page << 12, mip_size)) { - return true; - } + if (!pipeline) { + if (cvars::async_shader_compilation && pipeline_handle) { + // Pipeline is being compiled in the background -- skip this draw. + return pending_draw_pass_transfer_guard.Flush(); } + XELOGE("Failed to create pipeline state"); return false; - }; - if (overlaps_resolved_texture_ranges(used_texture_mask)) { - EndCommandBuffer(); - BeginCommandBuffer(); - if (!current_command_buffer_ || !current_render_encoder_) { - XELOGE( - "SPIRV-Cross: failed to re-begin command buffer for copy->draw " - "sync split"); - return true; - } } } - if (texture_cache_ && used_texture_mask && - texture_cache_->AnyUsedTextureRequestWorkPending(used_texture_mask)) { - texture_cache_->RequestTextures(used_texture_mask); + pipeline_cache_->SetupShaderBindingLayoutUserUIDs(*metal_vertex_shader); + if (metal_pixel_shader) { + pipeline_cache_->SetupShaderBindingLayoutUserUIDs(*metal_pixel_shader); } - // Ensure shared-memory ranges used by translated shaders are synchronized. - // The SPIRV-Cross path bypasses the MSC setup path, so it must request - // vertex fetch and memexport ranges explicitly. + uint32_t used_texture_mask = + metal_vertex_shader->GetUsedTextureMaskAfterTranslation(); + if (metal_pixel_shader) { + used_texture_mask |= + metal_pixel_shader->GetUsedTextureMaskAfterTranslation(); + } + uint32_t texture_request_work_mask = 0; + if (texture_cache_ && used_texture_mask) { + texture_request_work_mask = + texture_cache_->GetUsedTextureRequestWorkMask(used_texture_mask); + } + const bool has_texture_request_work = + texture_cache_ && used_texture_mask && texture_request_work_mask; + const bool may_texture_request_load_data = + has_texture_request_work && + texture_cache_->MayRequestTexturesLoadData(used_texture_mask); + + MTL::RenderPassDescriptor* draw_pass_descriptor = + GetDrawRenderPassDescriptor(fallback_depth_attachment_required); + + MetalTextureCache::TextureMaterializationPlan texture_materialization_plan; + bool textures_requested_for_draw = false; + auto request_textures_for_draw = [&](uint64_t& telemetry_counter) -> bool { + if (!EnsureCommandBuffer()) { + return false; + } + if (texture_materialization_plan.NeedsTextureUpload()) { + texture_cache_->RequestTexturesWithoutLoading(used_texture_mask); + } else { + texture_cache_->RequestTextures(used_texture_mask); + } + textures_requested_for_draw = true; + ++telemetry_counter; + return true; + }; + std::array vertex_ranges; + uint32_t vertex_range_count = 0; + const auto& vb_bindings = vertex_shader->vertex_bindings(); + bool uses_vertex_fetch = ShaderUsesVertexFetch(*vertex_shader); + bool memexport_used = memexport_used_vertex || memexport_used_pixel; + MTL::RenderStages memexport_write_stages = MTL::RenderStages(0); + if (memexport_used_vertex) { + memexport_write_stages = + (use_geometry_emulation || use_tessellation_emulation) + ? MTL::RenderStages(MTL::RenderStageObject | MTL::RenderStageMesh) + : MTL::RenderStageVertex; + } + if (memexport_used_pixel && is_rasterization_done) { + memexport_write_stages = + MTL::RenderStages(NS::UInteger(memexport_write_stages) | + NS::UInteger(MTL::RenderStageFragment)); + } + bool shared_memory_is_uav = memexport_used; + MTL::ResourceUsage shared_memory_usage = + shared_memory_is_uav ? (MTL::ResourceUsageRead | MTL::ResourceUsageWrite) + : MTL::ResourceUsageRead; + const bool guest_dma_index_buffer_read = + !memexport_used && + primitive_processing_result.index_buffer_type == + PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA; + const bool shader_primitive_index_load = + primitive_processing_result.index_buffer_type == + PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA; + std::array + vertex_fetch_ranges; + uint32_t vertex_fetch_range_count = 0; + std::vector current_draw_shared_memory_ranges; + current_draw_shared_memory_ranges.reserve(kMaxCurrentDrawVertexFetchRanges + + memexport_ranges_.size() + 2); + auto add_current_draw_shared_memory_range = + [&](DrawMaterializationSource source, uint32_t start, uint32_t length) { + if (!length) { + return; + } + size_t source_index = static_cast(source); + if (source_index < kDrawMaterializationSourceCount) { + ++backend_telemetry_.draw_materialization_source_ranges[source_index]; + backend_telemetry_.draw_materialization_source_bytes[source_index] += + length; + if (shared_memory_ && !shared_memory_->IsRangeValid(start, length)) { + ++backend_telemetry_ + .draw_materialization_source_invalid_ranges[source_index]; + backend_telemetry_ + .draw_materialization_source_invalid_bytes[source_index] += + length; + } + } + current_draw_shared_memory_ranges.push_back({start, length}); + }; + if (texture_cache_ && may_texture_request_load_data) { + if (!texture_cache_->PrepareTextureMaterialization( + regs, used_texture_mask, texture_materialization_plan)) { + return false; + } + for (const SharedMemory::Range& range : + texture_materialization_plan.source_ranges) { + add_current_draw_shared_memory_range( + DrawMaterializationSource::kTextureSource, range.start, range.length); + } + } + + // Sync shared memory before drawing - ensure GPU has latest data + // This is particularly important for trace playback where memory is + // written incrementally if (shared_memory_) { - const Shader::ConstantRegisterMap& constant_map_vertex = - msl_vertex_shader->constant_register_map(); - for (uint32_t i = 0; - i < xe::countof(constant_map_vertex.vertex_fetch_bitmap); ++i) { - uint32_t vfetch_bits_remaining = - constant_map_vertex.vertex_fetch_bitmap[i]; - uint32_t j; - while (xe::bit_scan_forward(vfetch_bits_remaining, &j)) { - vfetch_bits_remaining &= ~(uint32_t(1) << j); - uint32_t vfetch_index = i * 32 + j; - xenos::xe_gpu_vertex_fetch_t vfetch = regs.GetVertexFetch(vfetch_index); - switch (vfetch.type) { - case xenos::FetchConstantType::kVertex: - break; - case xenos::FetchConstantType::kInvalidVertex: - if (::cvars::gpu_allow_invalid_fetch_constants) { - break; - } - XELOGW( - "SPIRV-Cross: Vertex fetch constant {} ({:08X} {:08X}) has " - "\"invalid\" type. Use --gpu_allow_invalid_fetch_constants to " - "bypass.", - vfetch_index, vfetch.dword_0, vfetch.dword_1); - return false; - default: - XELOGW( - "SPIRV-Cross: Vertex fetch constant {} ({:08X} {:08X}) is " - "invalid.", - vfetch_index, vfetch.dword_0, vfetch.dword_1); - return false; - } - uint32_t buffer_offset = vfetch.address << 2; - uint32_t buffer_length = vfetch.size << 2; - if (buffer_offset > SharedMemory::kBufferSize || - SharedMemory::kBufferSize - buffer_offset < buffer_length) { - XELOGW( - "SPIRV-Cross: Vertex fetch constant {} out of range " - "(offset=0x{:08X} size={})", - vfetch_index, buffer_offset, buffer_length); - return false; - } - if (!shared_memory_->RequestRange(buffer_offset, buffer_length)) { - XELOGE( - "SPIRV-Cross: Failed to request vertex buffer at 0x{:08X} " - "(size {}) in shared memory", - buffer_offset, buffer_length); - return false; - } - } + if (!CollectMetalVertexFetchSharedMemoryRanges( + regs, *vertex_shader, vertex_fetch_ranges.data(), + uint32_t(vertex_fetch_ranges.size()), &vertex_fetch_range_count, + true)) { + return false; + } + for (uint32_t i = 0; i < vertex_fetch_range_count; ++i) { + add_current_draw_shared_memory_range( + DrawMaterializationSource::kVertexFetch, vertex_fetch_ranges[i].start, + vertex_fetch_ranges[i].length); } - for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { uint32_t base_bytes = memexport_range.base_address_dwords << 2; - if (!shared_memory_->RequestRange(base_bytes, - memexport_range.size_bytes)) { - XELOGE( - "SPIRV-Cross: Failed to request memexport stream at 0x{:08X} " - "(size {}) in shared memory", - base_bytes, memexport_range.size_bytes); + add_current_draw_shared_memory_range( + DrawMaterializationSource::kMemexport, base_bytes, + memexport_range.size_bytes); + } + + auto add_guest_index_range = [&](uint64_t index_base, uint32_t index_count, + xenos::IndexFormat index_format, + const char* label) -> bool { + uint32_t index_stride = index_format == xenos::IndexFormat::kInt16 + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint64_t index_length = uint64_t(index_count) * index_stride; + if (index_base > SharedMemory::kBufferSize || + SharedMemory::kBufferSize - index_base < index_length) { + XELOGW( + "{} index buffer range out of bounds (base=0x{:08X} size={} " + "count={})", + label, static_cast(index_base), index_length, + index_count); return false; } + add_current_draw_shared_memory_range( + DrawMaterializationSource::kGuestIndex, + static_cast(index_base), + static_cast(index_length)); + return true; + }; + if (guest_dma_index_buffer_read && + !add_guest_index_range( + primitive_processing_result.guest_index_base, + primitive_processing_result.host_draw_vertex_count, + primitive_processing_result.host_index_format, "guest DMA")) { + return false; + } + if (shader_primitive_index_load && + !add_guest_index_range( + primitive_processing_result.guest_index_base, + primitive_processing_result.guest_draw_vertex_count, + regs.Get().index_size, + "shader primitive")) { + return false; + } + + for (const auto& binding : vb_bindings) { + xenos::xe_gpu_vertex_fetch_t vfetch = + regs.GetVertexFetch(binding.fetch_constant); + uint32_t buffer_offset = vfetch.address << 2; + uint32_t buffer_length = vfetch.size << 2; + VertexBindingRange range; + range.binding_index = static_cast(binding.binding_index); + range.offset = buffer_offset; + range.length = buffer_length; + range.stride = binding.stride_words * 4; + assert_true(vertex_range_count < vertex_ranges.size()); + vertex_ranges[vertex_range_count++] = range; } } - // Viewport info for system constants (same as the MSC path). + const bool current_draw_has_invalid_shared_memory = + shared_memory_ && !current_draw_shared_memory_ranges.empty() && + AnySharedMemoryRangeInvalid( + current_draw_shared_memory_ranges.data(), + static_cast(current_draw_shared_memory_ranges.size())); + if (shared_memory_ && !current_draw_shared_memory_ranges.empty()) { + ++backend_telemetry_.draw_materialization_per_draw_requests; + if (current_draw_has_invalid_shared_memory) { + ++backend_telemetry_.draw_materialization_per_draw_invalid_requests; + } else { + ++backend_telemetry_.draw_materialization_per_draw_resident_skips; + } + } + + if (has_texture_request_work && !textures_requested_for_draw) { + const bool texture_request_started_with_active_encoder = + current_render_encoder_ != nullptr; + uint64_t& texture_request_counter = + texture_request_started_with_active_encoder + ? backend_telemetry_.texture_requests_after_encoder_begin + : backend_telemetry_.texture_requests_before_encoder; + if (!request_textures_for_draw(texture_request_counter)) { + return false; + } + } + + UniformBufferInfo uniforms; + DrawDynamicState draw_dynamic_state; + const bool prepare_uniforms = constant_buffer_pool_ && shared_memory_; + if (prepare_uniforms) { + if (!draw_pass_descriptor) { + XELOGE("IssueDraw: no render pass descriptor for draw constants"); + return false; + } + if (!EnsureCommandBuffer()) { + return false; + } + if (!PrepareDrawConstants(regs, vertex_shader, pixel_shader, + metal_vertex_shader, metal_pixel_shader, + shared_memory_is_uav, is_rasterization_done, + primitive_processing_result, used_texture_mask, + normalized_color_mask, draw_pass_descriptor, + uniforms, draw_dynamic_state)) { + return false; + } + } + + PreparedIndexBuffer prepared_guest_dma_index_buffer; + if (memexport_used && + primitive_processing_result.index_buffer_type == + PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA) { + if (!PrepareGuestDMAIndexBufferForMemexport( + primitive_processing_result, prepared_guest_dma_index_buffer)) { + return false; + } + } + + std::array shared_memory_hazard_ranges = {}; + uint32_t shared_memory_hazard_range_count = 0; + auto add_shared_memory_hazard_range = [&](uint32_t start, uint32_t length) { + if (!length || shared_memory_hazard_range_count >= + shared_memory_hazard_ranges.size()) { + return; + } + shared_memory_hazard_ranges[shared_memory_hazard_range_count++] = + SharedMemoryRange{start, length}; + }; + for (uint32_t i = 0; i < vertex_range_count; ++i) { + add_shared_memory_hazard_range(vertex_ranges[i].offset, + vertex_ranges[i].length); + } + if (guest_dma_index_buffer_read || shader_primitive_index_load) { + const xenos::IndexFormat index_format = + shader_primitive_index_load + ? regs.Get().index_size + : primitive_processing_result.host_index_format; + uint32_t index_stride = index_format == xenos::IndexFormat::kInt16 + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint32_t index_count = + shader_primitive_index_load + ? primitive_processing_result.guest_draw_vertex_count + : primitive_processing_result.host_draw_vertex_count; + uint64_t index_length = uint64_t(index_count) * index_stride; + if (index_length <= SharedMemory::kBufferSize) { + add_shared_memory_hazard_range( + static_cast(primitive_processing_result.guest_index_base), + static_cast(index_length)); + } + } + if (NS::UInteger(memexport_write_stages)) { + for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { + add_shared_memory_hazard_range(memexport_range.base_address_dwords << 2, + memexport_range.size_bytes); + } + } + MTL::RenderStages shared_memory_consumer_stages = MTL::RenderStages(0); + if (vertex_range_count || guest_dma_index_buffer_read || + shader_primitive_index_load) { + shared_memory_consumer_stages = + (use_geometry_emulation || use_tessellation_emulation) + ? MTL::RenderStages(MTL::RenderStageObject | MTL::RenderStageMesh) + : MTL::RenderStageVertex; + } + shared_memory_consumer_stages = + MTL::RenderStages(NS::UInteger(shared_memory_consumer_stages) | + NS::UInteger(memexport_write_stages)); + + PreparedDraw prepared_draw; + prepared_draw.pipeline = pipeline; + prepared_draw.tessellation_pipeline_state = tessellation_pipeline_state; + prepared_draw.geometry_pipeline_state = geometry_pipeline_state; + prepared_draw.primitive_processing_result = primitive_processing_result; + prepared_draw.uniforms = uniforms; + prepared_draw.dynamic_state = draw_dynamic_state; + prepared_draw.prepared_guest_dma_index_buffer = + prepared_guest_dma_index_buffer; + if (index_buffer_info) { + prepared_draw.index_buffer_info = *index_buffer_info; + prepared_draw.has_index_buffer_info = true; + } + prepared_draw.vertex_bindings = vb_bindings; + prepared_draw.vertex_ranges = vertex_ranges; + prepared_draw.vertex_range_count = vertex_range_count; + prepared_draw.materialization_ranges = + std::move(current_draw_shared_memory_ranges); + prepared_draw.has_invalid_shared_memory = + current_draw_has_invalid_shared_memory; + prepared_draw.shared_memory_hazard_ranges = shared_memory_hazard_ranges; + prepared_draw.shared_memory_hazard_range_count = + shared_memory_hazard_range_count; + prepared_draw.shared_memory_consumer_stages = shared_memory_consumer_stages; + prepared_draw.memexport_ranges = memexport_ranges_; + prepared_draw.memexport_write_stages = memexport_write_stages; + prepared_draw.shared_memory_usage = shared_memory_usage; + prepared_draw.render_target_key = render_target_key; + prepared_draw.texture_resource_set = current_bindless_texture_resource_set_; + prepared_draw.texture_upload_needed = + texture_materialization_plan.NeedsTextureUpload(); + prepared_draw.use_tessellation_emulation = use_tessellation_emulation; + prepared_draw.use_geometry_emulation = use_geometry_emulation; + prepared_draw.shared_memory_is_uav = shared_memory_is_uav; + prepared_draw.memexport_used = memexport_used; + prepared_draw.uses_vertex_fetch = uses_vertex_fetch; + prepared_draw.prepare_uniforms = prepare_uniforms; + prepared_draw.fallback_depth_attachment_required = + fallback_depth_attachment_required; + prepared_draw.may_texture_request_load_data = may_texture_request_load_data; + prepared_draw.texture_materialization_plan = + std::move(texture_materialization_plan); + prepared_draw.has_pending_draw_pass_transfers = + render_target_cache_ && + render_target_cache_->HasPendingDrawPassTransfers(); + return SubmitPreparedDraw(std::move(prepared_draw)); +} + +bool MetalCommandProcessor::PrepareDrawConstants( + const RegisterFile& regs, Shader* vertex_shader, Shader* pixel_shader, + MetalShader* metal_vertex_shader, MetalShader* metal_pixel_shader, + bool shared_memory_is_uav, bool is_rasterization_done, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + uint32_t used_texture_mask, uint32_t normalized_color_mask, + MTL::RenderPassDescriptor* render_pass_descriptor, + UniformBufferInfo& uniforms_out, DrawDynamicState& dynamic_state_out) { + // Determine primitive type characteristics + bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs); + + // Get viewport info for NDC transform. Use the actual RT0 dimensions + // when available so system constants match the current render target. + uint32_t vp_width = 1; + uint32_t vp_height = 1; + GetActiveRenderTargetSize(render_pass_descriptor, render_target_cache_.get(), + 1280, 720, vp_width, vp_height); draw_util::ViewportInfo viewport_info; + auto depth_control = draw_util::GetNormalizedDepthControl(regs); + auto dynamic_depth_control = depth_control; + if (!is_rasterization_done) { + dynamic_depth_control.value = 0; + } constexpr uint32_t kViewportBoundsMax = 32767; - bool convert_z_to_float24 = ::cvars::depth_float24_convert_in_pixel_shader; + bool host_render_targets_used = true; + bool convert_z_to_float24 = host_render_targets_used && + ::cvars::depth_float24_convert_in_pixel_shader; uint32_t draw_resolution_scale_x = texture_cache_ ? texture_cache_->draw_resolution_scale_x() : 1; uint32_t draw_resolution_scale_y = @@ -2427,202 +3369,458 @@ bool MetalCommandProcessor::IssueDrawMsl( : divisors::MagicDiv(1), texture_cache_ ? texture_cache_->draw_resolution_scale_y_divisor() : divisors::MagicDiv(1), - true, kViewportBoundsMax, kViewportBoundsMax, false, - normalized_depth_control, convert_z_to_float24, true, - msl_pixel_shader && msl_pixel_shader->writes_depth()); + true, kViewportBoundsMax, kViewportBoundsMax, false, depth_control, + convert_z_to_float24, host_render_targets_used, + pixel_shader && pixel_shader->writes_depth()); gviargs.SetupRegisterValues(regs); draw_util::GetHostViewportInfo(&gviargs, viewport_info); - // Apply viewport and scissor (matching the MSC path). - { - draw_util::Scissor scissor; - draw_util::GetScissor(regs, scissor); - scissor.offset[0] *= draw_resolution_scale_x; - scissor.offset[1] *= draw_resolution_scale_y; - scissor.extent[0] *= draw_resolution_scale_x; - scissor.extent[1] *= draw_resolution_scale_y; + // Apply per-draw viewport and scissor so the Metal viewport + // matches the guest viewport computed by draw_util. + draw_util::Scissor scissor; + draw_util::GetScissor(regs, scissor); + // draw_resolution_scale_x/y already computed above for viewport. + scissor.offset[0] *= draw_resolution_scale_x; + scissor.offset[1] *= draw_resolution_scale_y; + scissor.extent[0] *= draw_resolution_scale_x; + scissor.extent[1] *= draw_resolution_scale_y; - // Clamp scissor to actual render target bounds (Metal requires this). - uint32_t rt_width = 1; - uint32_t rt_height = 1; - GetBoundRenderTargetSize(render_target_cache_.get(), render_target_width_, - render_target_height_, rt_width, rt_height); - ClampScissorToBounds(scissor, rt_width, rt_height); + // Clamp scissor to actual render target bounds (Metal requires this). + ClampScissorToBounds(scissor, vp_width, vp_height); - MTL::Viewport mtl_viewport; - mtl_viewport.originX = static_cast(viewport_info.xy_offset[0]); - mtl_viewport.originY = static_cast(viewport_info.xy_offset[1]); - mtl_viewport.width = static_cast(viewport_info.xy_extent[0]); - mtl_viewport.height = static_cast(viewport_info.xy_extent[1]); - mtl_viewport.znear = viewport_info.z_min; - mtl_viewport.zfar = viewport_info.z_max; - current_render_encoder_->setViewport(mtl_viewport); + MTL::Viewport mtl_viewport; + mtl_viewport.originX = static_cast(viewport_info.xy_offset[0]); + mtl_viewport.originY = static_cast(viewport_info.xy_offset[1]); + mtl_viewport.width = static_cast(viewport_info.xy_extent[0]); + mtl_viewport.height = static_cast(viewport_info.xy_extent[1]); + mtl_viewport.znear = viewport_info.z_min; + mtl_viewport.zfar = viewport_info.z_max; - MTL::ScissorRect mtl_scissor; - mtl_scissor.x = scissor.offset[0]; - mtl_scissor.y = scissor.offset[1]; - mtl_scissor.width = scissor.extent[0]; - mtl_scissor.height = scissor.extent[1]; - if (!msl_scissor_valid_ || msl_scissor_.x != mtl_scissor.x || - msl_scissor_.y != mtl_scissor.y || - msl_scissor_.width != mtl_scissor.width || - msl_scissor_.height != mtl_scissor.height) { - current_render_encoder_->setScissorRect(mtl_scissor); - msl_scissor_ = mtl_scissor; - msl_scissor_valid_ = true; - } - } + MTL::ScissorRect mtl_scissor; + mtl_scissor.x = scissor.offset[0]; + mtl_scissor.y = scissor.offset[1]; + mtl_scissor.width = scissor.extent[0]; + mtl_scissor.height = scissor.extent[1]; + dynamic_state_out.viewport = mtl_viewport; + dynamic_state_out.scissor = mtl_scissor; + dynamic_state_out.viewport_info = viewport_info; + dynamic_state_out.depth_control = dynamic_depth_control; + dynamic_state_out.primitive_polygonal = primitive_polygonal; + dynamic_state_out.rasterization_enabled = is_rasterization_done; - // Apply fixed-function state. - if (msl_bound_pipeline_state_ != pipeline) { - current_render_encoder_->setRenderPipelineState(pipeline); - msl_bound_pipeline_state_ = pipeline; - } - ApplyRasterizerState(primitive_polygonal); - ApplyDepthStencilState(primitive_polygonal, normalized_depth_control); - - // Update SPIRV system constants. - UpdateSpirvSystemConstantValues( - primitive_processing_result, primitive_polygonal, + // Update full system constants from GPU registers. + // normalized_color_mask was already computed above for render target update. + UpdateSystemConstantValues( + shared_memory_is_uav, primitive_polygonal, primitive_processing_result.line_loop_closing_index, primitive_processing_result.host_shader_index_endian, viewport_info, - used_texture_mask, normalized_depth_control, normalized_color_mask); + used_texture_mask, depth_control, normalized_color_mask); - // Blend constants (fixed-function, same as MSC path). - float blend_constants[] = { + float blend_constants[4] = { regs.Get(XE_GPU_REG_RB_BLEND_RED), regs.Get(XE_GPU_REG_RB_BLEND_GREEN), regs.Get(XE_GPU_REG_RB_BLEND_BLUE), regs.Get(XE_GPU_REG_RB_BLEND_ALPHA), }; - if (!ff_blend_factor_valid_ || - std::memcmp(ff_blend_factor_, blend_constants, sizeof(float) * 4) != 0) { - std::memcpy(ff_blend_factor_, blend_constants, sizeof(float) * 4); - ff_blend_factor_valid_ = true; - current_render_encoder_->setBlendColor( - blend_constants[0], blend_constants[1], blend_constants[2], - blend_constants[3]); - } + std::memcpy(dynamic_state_out.blend_constants, blend_constants, + sizeof(blend_constants)); - // ===================================================================== - // Resource binding — direct Metal encoder calls, no IRDescriptorTable. - // ===================================================================== - constexpr uint32_t kCBVSize = MslBufferIndex::kCbvSizeBytes; - uint32_t ring_index = current_draw_index_ % uint32_t(draw_ring_count_); - constexpr size_t kStageVertex = 0; - constexpr size_t kStagePixel = 1; - size_t table_index_vertex = size_t(ring_index) * kStageCount + kStageVertex; - size_t table_index_pixel = size_t(ring_index) * kStageCount + kStagePixel; + dynamic_state_out.stencil_ref_mask_front = regs.Get(); + dynamic_state_out.stencil_ref_mask_back = + regs.Get(XE_GPU_REG_RB_STENCILREFMASK_BF); + dynamic_state_out.pa_su_sc_mode_cntl = regs.Get(); + auto pa_cl_clip_cntl = regs.Get(); - uint8_t* uniforms_base = static_cast(uniforms_buffer_->contents()); - uint8_t* uniforms_vertex = - uniforms_base + table_index_vertex * kUniformsBytesPerTable; - uint8_t* uniforms_pixel = - uniforms_base + table_index_pixel * kUniformsBytesPerTable; - if (msl_constants_versioned_uniform_buffer_ != uniforms_buffer_) { - msl_constants_versioned_uniform_buffer_ = uniforms_buffer_; - std::fill(msl_system_constants_written_vertex_versions_.begin(), - msl_system_constants_written_vertex_versions_.end(), uint64_t(0)); - std::fill(msl_system_constants_written_pixel_versions_.begin(), - msl_system_constants_written_pixel_versions_.end(), uint64_t(0)); - std::fill(msl_clip_plane_constants_written_vertex_versions_.begin(), - msl_clip_plane_constants_written_vertex_versions_.end(), - uint64_t(0)); - std::fill(msl_tessellation_constants_written_vertex_versions_.begin(), - msl_tessellation_constants_written_vertex_versions_.end(), - uint64_t(0)); - std::fill(msl_tessellation_constants_written_pixel_versions_.begin(), - msl_tessellation_constants_written_pixel_versions_.end(), - uint64_t(0)); - } - auto ensure_uniform_versions_size = [&](std::vector& versions) { - if (versions.size() != draw_ring_count_) { - versions.assign(draw_ring_count_, 0); - } - }; - ensure_uniform_versions_size(msl_system_constants_written_vertex_versions_); - ensure_uniform_versions_size(msl_system_constants_written_pixel_versions_); - ensure_uniform_versions_size( - msl_clip_plane_constants_written_vertex_versions_); - ensure_uniform_versions_size( - msl_tessellation_constants_written_vertex_versions_); - ensure_uniform_versions_size( - msl_tessellation_constants_written_pixel_versions_); - const size_t ring_index_size = size_t(ring_index); - auto copy_uniform_block_if_stale = - [&](uint8_t* dst, const void* src, size_t size, - std::vector& written_versions, uint64_t source_version) { - if (ring_index_size >= written_versions.size()) { - return; - } - if (written_versions[ring_index_size] != source_version) { - std::memcpy(dst, src, size); - written_versions[ring_index_size] = source_version; - } - }; - - // b0 (msl_buffer 1): System constants. - copy_uniform_block_if_stale(uniforms_vertex, &spirv_system_constants_, - sizeof(SpirvShaderTranslator::SystemConstants), - msl_system_constants_written_vertex_versions_, - msl_system_constants_version_); - copy_uniform_block_if_stale(uniforms_pixel, &spirv_system_constants_, - sizeof(SpirvShaderTranslator::SystemConstants), - msl_system_constants_written_pixel_versions_, - msl_system_constants_version_); - - // b1 (msl_buffer 2/3): Float constants. - // SpirvShaderTranslator uses packed float constants like Vulkan. - const size_t kFloatConstantOffset = 1 * kCBVSize; - const Shader::ConstantRegisterMap& float_constant_map_vertex = - msl_vertex_shader->constant_register_map(); - for (uint32_t i = 0; i < 4; ++i) { - if (msl_current_float_constant_map_vertex_[i] != - float_constant_map_vertex.float_bitmap[i]) { - msl_current_float_constant_map_vertex_[i] = - float_constant_map_vertex.float_bitmap[i]; - msl_float_constants_dirty_vertex_ = true; + MTL::CullMode cull_mode = MTL::CullModeNone; + if (primitive_polygonal) { + bool cull_front = dynamic_state_out.pa_su_sc_mode_cntl.cull_front; + bool cull_back = dynamic_state_out.pa_su_sc_mode_cntl.cull_back; + if (cull_front && !cull_back) { + cull_mode = MTL::CullModeFront; + } else if (cull_back && !cull_front) { + cull_mode = MTL::CullModeBack; } } - if (msl_pixel_shader) { - const Shader::ConstantRegisterMap& float_constant_map_pixel = - msl_pixel_shader->constant_register_map(); + dynamic_state_out.cull_mode = cull_mode; + dynamic_state_out.front_facing_winding = + dynamic_state_out.pa_su_sc_mode_cntl.face ? MTL::WindingClockwise + : MTL::WindingCounterClockwise; + dynamic_state_out.triangle_fill_mode = MTL::TriangleFillModeFill; + if (primitive_polygonal && dynamic_state_out.pa_su_sc_mode_cntl.poly_mode == + xenos::PolygonModeEnable::kDualMode) { + xenos::PolygonType polygon_type = xenos::PolygonType::kTriangles; + if (!dynamic_state_out.pa_su_sc_mode_cntl.cull_front) { + polygon_type = + std::min(polygon_type, + dynamic_state_out.pa_su_sc_mode_cntl.polymode_front_ptype); + } + if (!dynamic_state_out.pa_su_sc_mode_cntl.cull_back) { + polygon_type = + std::min(polygon_type, + dynamic_state_out.pa_su_sc_mode_cntl.polymode_back_ptype); + } + if (polygon_type != xenos::PolygonType::kTriangles) { + dynamic_state_out.triangle_fill_mode = MTL::TriangleFillModeLines; + } + } + + float polygon_offset_scale = 0.0f; + float polygon_offset = 0.0f; + draw_util::GetPreferredFacePolygonOffset( + regs, primitive_polygonal, polygon_offset_scale, polygon_offset); + dynamic_state_out.depth_bias_constant = + static_cast(draw_util::GetD3D10IntegerPolygonOffset( + regs.Get().depth_format, polygon_offset)); + uint32_t rasterizer_resolution_scale = 1; + if (render_target_cache_) { + rasterizer_resolution_scale = + std::max(render_target_cache_->draw_resolution_scale_x(), + render_target_cache_->draw_resolution_scale_y()); + } + dynamic_state_out.depth_bias_slope = polygon_offset_scale * + xenos::kPolygonOffsetScaleSubpixelUnit * + float(rasterizer_resolution_scale); + dynamic_state_out.depth_clip_mode = pa_cl_clip_cntl.clip_disable + ? MTL::DepthClipModeClamp + : MTL::DepthClipModeClip; + + // CBV descriptor table layout: + // b0: System constants + // b1: Packed float constants + // b2: Bool/loop constants + // b3: Fetch constants + // b4: Descriptor indices + constexpr size_t kBoolLoopConstantsSize = (8 + 32) * sizeof(uint32_t); + const size_t kFetchConstantCount = + xenos::kTextureFetchConstantCount * 6; // 192 DWORDs = 768 bytes + + const MetalShader::DrawConstantMetadata& vertex_draw_metadata = + metal_vertex_shader->GetDrawConstantMetadata(); + const MetalShader::DrawConstantMetadata* pixel_draw_metadata = + metal_pixel_shader ? &metal_pixel_shader->GetDrawConstantMetadata() + : nullptr; + std::array active_cbv_masks = { + vertex_draw_metadata.active_cbv_mask, + pixel_draw_metadata ? pixel_draw_metadata->active_cbv_mask : 0}; + std::array + fetch_constant_dword_masks = { + metal_vertex_shader + ? metal_vertex_shader->GetFetchConstantDwordMaskAfterTranslation() + : DxbcShader::FetchConstantDwordMask(), + metal_pixel_shader + ? metal_pixel_shader->GetFetchConstantDwordMaskAfterTranslation() + : DxbcShader::FetchConstantDwordMask()}; + MergeFetchConstantDwordMask( + fetch_constant_dword_masks[kStageVertex], + vertex_draw_metadata.shader_fetch_constant_dword_mask); + if (pixel_draw_metadata) { + MergeFetchConstantDwordMask( + fetch_constant_dword_masks[kStagePixel], + pixel_draw_metadata->shader_fetch_constant_dword_mask); + } + for (size_t stage = 0; stage < kStageCount; ++stage) { + if ((active_cbv_masks[stage] & (uint32_t(1) << kCbvSlotFetch)) && + FetchConstantDwordMaskEmpty(fetch_constant_dword_masks[stage])) { + MarkAllFetchConstantDwords(fetch_constant_dword_masks[stage]); + } + } + + // --------------------------------------------------------------- + // Allocate dirty constant buffers individually from the upload buffer pool. + // Clean CBVs keep their previous upload allocation and are re-referenced from + // the new per-draw descriptor table. + // --------------------------------------------------------------- + + // Check if float constant layout changed (different shader bound). + // Matches D3D12 d3d12_command_processor.cc:4910-4943. + { + const Shader::ConstantRegisterMap& float_map_vs = + vertex_shader->constant_register_map(); for (uint32_t i = 0; i < 4; ++i) { - if (msl_current_float_constant_map_pixel_[i] != - float_constant_map_pixel.float_bitmap[i]) { - msl_current_float_constant_map_pixel_[i] = - float_constant_map_pixel.float_bitmap[i]; - msl_float_constants_dirty_pixel_ = true; + if (current_float_constant_map_vertex_[i] != + float_map_vs.float_bitmap[i]) { + current_float_constant_map_vertex_[i] = float_map_vs.float_bitmap[i]; + if (float_map_vs.float_count) { + cbuffer_binding_float_vertex_.up_to_date = false; + } } } + if (pixel_shader) { + const Shader::ConstantRegisterMap& float_map_ps = + pixel_shader->constant_register_map(); + for (uint32_t i = 0; i < 4; ++i) { + if (current_float_constant_map_pixel_[i] != + float_map_ps.float_bitmap[i]) { + current_float_constant_map_pixel_[i] = float_map_ps.float_bitmap[i]; + if (float_map_ps.float_count) { + cbuffer_binding_float_pixel_.up_to_date = false; + } + } + } + } else { + std::memset(current_float_constant_map_pixel_, 0, + sizeof(current_float_constant_map_pixel_)); + } + } + + const auto& texture_bindings_vertex = + metal_vertex_shader->GetTextureBindingsAfterTranslation(); + const auto& sampler_bindings_vertex = + metal_vertex_shader->GetSamplerBindingsAfterTranslation(); + const size_t texture_count_vertex = texture_bindings_vertex.size(); + const size_t sampler_count_vertex = sampler_bindings_vertex.size(); + size_t texture_layout_uid_vertex = + metal_vertex_shader->GetTextureBindingLayoutUserUID(); + size_t sampler_layout_uid_vertex = + metal_vertex_shader->GetSamplerBindingLayoutUserUID(); + auto& next_texture_bindless_indices_vertex = + scratch_texture_bindless_indices_vertex_; + auto& next_texture_bindless_resources_vertex = + scratch_texture_bindless_resources_vertex_; + auto& next_sampler_bindless_indices_vertex = + scratch_sampler_bindless_indices_vertex_; + next_texture_bindless_indices_vertex.clear(); + next_texture_bindless_resources_vertex.clear(); + next_sampler_bindless_indices_vertex.clear(); + scratch_descriptor_indices_vertex_.clear(); + if (sampler_count_vertex) { + if (current_sampler_layout_uid_vertex_ != sampler_layout_uid_vertex) { + current_sampler_layout_uid_vertex_ = sampler_layout_uid_vertex; + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + } + current_samplers_vertex_.resize( + std::max(current_samplers_vertex_.size(), sampler_count_vertex)); + for (size_t i = 0; i < sampler_count_vertex; ++i) { + auto parameters = + texture_cache_->GetSamplerParameters(sampler_bindings_vertex[i]); + if (current_samplers_vertex_[i] != parameters) { + current_samplers_vertex_[i] = parameters; + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + } + } + } else if (current_sampler_layout_uid_vertex_ != sampler_layout_uid_vertex) { + current_sampler_layout_uid_vertex_ = sampler_layout_uid_vertex; + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + } + bool vertex_texture_layout_changed = + current_texture_layout_uid_vertex_ != texture_layout_uid_vertex; + if (vertex_texture_layout_changed && !texture_count_vertex) { + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + } else if (texture_count_vertex && + cbuffer_binding_descriptor_indices_vertex_.up_to_date) { + bool vertex_texture_srv_changed = + !vertex_texture_layout_changed && + !texture_cache_->AreActiveTextureSRVKeysUpToDate( + current_texture_srv_keys_vertex_.data(), + texture_bindings_vertex.data(), texture_count_vertex); + if (vertex_texture_layout_changed || vertex_texture_srv_changed) { + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + } + } + if (!cbuffer_binding_descriptor_indices_vertex_.up_to_date) { + next_texture_bindless_indices_vertex.reserve(texture_count_vertex); + next_texture_bindless_resources_vertex.reserve(texture_count_vertex); + for (const auto& binding : texture_bindings_vertex) { + MTL::Texture* texture_for_encoder = nullptr; + next_texture_bindless_indices_vertex.push_back( + texture_cache_->GetBindlessSRVIndexForBinding( + binding.fetch_constant, binding.dimension, binding.is_signed, + &texture_for_encoder)); + next_texture_bindless_resources_vertex.push_back(texture_for_encoder); + } + next_sampler_bindless_indices_vertex.reserve(sampler_count_vertex); + for (const auto& binding : sampler_bindings_vertex) { + next_sampler_bindless_indices_vertex.push_back( + texture_cache_->GetBindlessSamplerIndexForBinding(binding)); + } + } + + size_t texture_layout_uid_pixel = 0; + size_t sampler_layout_uid_pixel = 0; + const std::vector* texture_bindings_pixel_ptr = + nullptr; + auto& next_texture_bindless_indices_pixel = + scratch_texture_bindless_indices_pixel_; + auto& next_texture_bindless_resources_pixel = + scratch_texture_bindless_resources_pixel_; + auto& next_sampler_bindless_indices_pixel = + scratch_sampler_bindless_indices_pixel_; + next_texture_bindless_indices_pixel.clear(); + next_texture_bindless_resources_pixel.clear(); + next_sampler_bindless_indices_pixel.clear(); + scratch_descriptor_indices_pixel_.clear(); + if (metal_pixel_shader) { + const auto& texture_bindings_pixel = + metal_pixel_shader->GetTextureBindingsAfterTranslation(); + const auto& sampler_bindings_pixel = + metal_pixel_shader->GetSamplerBindingsAfterTranslation(); + const size_t texture_count_pixel = texture_bindings_pixel.size(); + const size_t sampler_count_pixel = sampler_bindings_pixel.size(); + texture_bindings_pixel_ptr = &texture_bindings_pixel; + texture_layout_uid_pixel = + metal_pixel_shader->GetTextureBindingLayoutUserUID(); + sampler_layout_uid_pixel = + metal_pixel_shader->GetSamplerBindingLayoutUserUID(); + if (sampler_count_pixel) { + if (current_sampler_layout_uid_pixel_ != sampler_layout_uid_pixel) { + current_sampler_layout_uid_pixel_ = sampler_layout_uid_pixel; + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + } + current_samplers_pixel_.resize( + std::max(current_samplers_pixel_.size(), sampler_count_pixel)); + for (size_t i = 0; i < sampler_count_pixel; ++i) { + auto parameters = + texture_cache_->GetSamplerParameters(sampler_bindings_pixel[i]); + if (current_samplers_pixel_[i] != parameters) { + current_samplers_pixel_[i] = parameters; + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + } + } + } else if (current_sampler_layout_uid_pixel_ != sampler_layout_uid_pixel) { + current_sampler_layout_uid_pixel_ = sampler_layout_uid_pixel; + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + } + bool pixel_texture_layout_changed = + current_texture_layout_uid_pixel_ != texture_layout_uid_pixel; + if (pixel_texture_layout_changed && !texture_count_pixel) { + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + } else if (texture_count_pixel && + cbuffer_binding_descriptor_indices_pixel_.up_to_date) { + bool pixel_texture_srv_changed = + !pixel_texture_layout_changed && + !texture_cache_->AreActiveTextureSRVKeysUpToDate( + current_texture_srv_keys_pixel_.data(), + texture_bindings_pixel.data(), texture_count_pixel); + if (pixel_texture_layout_changed || pixel_texture_srv_changed) { + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + } + } + if (!cbuffer_binding_descriptor_indices_pixel_.up_to_date) { + next_texture_bindless_indices_pixel.reserve(texture_count_pixel); + next_texture_bindless_resources_pixel.reserve(texture_count_pixel); + for (const auto& binding : texture_bindings_pixel) { + MTL::Texture* texture_for_encoder = nullptr; + next_texture_bindless_indices_pixel.push_back( + texture_cache_->GetBindlessSRVIndexForBinding( + binding.fetch_constant, binding.dimension, binding.is_signed, + &texture_for_encoder)); + next_texture_bindless_resources_pixel.push_back(texture_for_encoder); + } + next_sampler_bindless_indices_pixel.reserve(sampler_count_pixel); + for (const auto& binding : sampler_bindings_pixel) { + next_sampler_bindless_indices_pixel.push_back( + texture_cache_->GetBindlessSamplerIndexForBinding(binding)); + } + } + } + + bool descriptor_indices_vertex_written = false; + bool descriptor_indices_pixel_written = false; + + auto upload_binding = [&](ConstantBufferBinding& binding, size_t cbv_slot, + size_t size, const char* name, + auto&& writer) -> bool { + constexpr size_t kConstantBufferAlignment = 256; + size = std::max(size, size_t(16)); + MTL::Buffer* buffer = nullptr; + size_t offset = 0; + uint64_t gpu_address = 0; + uint8_t* data = constant_buffer_pool_->Request( + frame_current_, size, kConstantBufferAlignment, &buffer, offset, + gpu_address); + if (!data) { + XELOGE("IssueDraw: {} constant buffer pool allocation failed", name); + return false; + } + writer(data, size); + binding.buffer = buffer; + binding.offset = static_cast(offset); + binding.gpu_address = gpu_address; + binding.size = size; + binding.upload_frame = frame_current_; + binding.up_to_date = true; + if (cbv_slot < backend_telemetry_.cbv_uploads.size()) { + ++backend_telemetry_.cbv_uploads[cbv_slot]; + } + return true; + }; + // Pack-then-compare wrapper around upload_binding. Honours the existing + // "register write dirties live constant range" semantic (the caller still + // gates on binding.up_to_date == false), but if the freshly packed payload + // is byte-identical to the previous upload we reuse the prior pool slice + // instead of allocating a new one. See the descriptor-indices precedent + // below for the same pattern applied to the descriptor-indices CBV. + auto try_reuse_or_upload = + [&](ConstantBufferBinding& binding, size_t cbv_slot, size_t stage_index, + std::vector& current_payload, + std::vector& scratch_payload, size_t logical_size, + const char* name, auto&& writer) -> bool { + const size_t aligned_size = std::max(logical_size, size_t(16)); + scratch_payload.assign(aligned_size, 0); + writer(scratch_payload.data(), aligned_size); + // Reuse the prior slice only if it was allocated in this frame. Cross-frame + // reuse is unsafe because frame-open reclamation may recycle older slices. + if (binding.buffer && binding.upload_frame == frame_current_ && + current_payload.size() == aligned_size && + std::memcmp(current_payload.data(), scratch_payload.data(), + aligned_size) == 0) { + binding.up_to_date = true; + if (cbv_slot < backend_telemetry_.cbv_reuse_hits.size()) { + ++backend_telemetry_.cbv_reuse_hits[cbv_slot]; + } + return true; + } + if (TryReuseConstantPayloadFromCache(binding, cbv_slot, stage_index, + scratch_payload.data(), + scratch_payload.size())) { + current_payload = scratch_payload; + return true; + } + if (!upload_binding(binding, cbv_slot, logical_size, name, + [&](uint8_t* data, size_t out_size) { + std::memcpy( + data, scratch_payload.data(), + std::min(out_size, scratch_payload.size())); + })) { + return false; + } + StoreConstantPayloadInCache(binding, cbv_slot, stage_index, + scratch_payload.data(), scratch_payload.size()); + current_payload.swap(scratch_payload); + return true; + }; + + if (!cbuffer_binding_system_.up_to_date) { + if (!try_reuse_or_upload( + cbuffer_binding_system_, kCbvSlotSystem, + kConstantPayloadSharedStage, current_payload_system_, + scratch_payload_system_, + sizeof(DxbcShaderTranslator::SystemConstants), "system", + [&](uint8_t* data, size_t) { + std::memcpy(data, &system_constants_, + sizeof(DxbcShaderTranslator::SystemConstants)); + })) { + return false; + } } else { - for (uint32_t i = 0; i < 4; ++i) { - if (msl_current_float_constant_map_pixel_[i] != 0) { - msl_current_float_constant_map_pixel_[i] = 0; - msl_float_constants_dirty_pixel_ = true; - } - } + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotSystem]; } - auto rebuild_packed_float_constants = - [&](std::array& dst, const Shader* shader, + auto write_packed_float_constants = + [&](uint8_t* dst, size_t dst_size, const Shader::ConstantRegisterMap* map, uint32_t regs_base) { - std::memset(dst.data(), 0, kCBVSize); - if (!shader) { + std::memset(dst, 0, dst_size); + if (!map || !map->float_count) { return; } - const Shader::ConstantRegisterMap& map = - shader->constant_register_map(); - if (!map.float_count) { - return; - } - uint8_t* out = dst.data(); + uint8_t* out = dst; + uint8_t* end = dst + dst_size; for (uint32_t i = 0; i < 4; ++i) { - uint64_t bits = map.float_bitmap[i]; + uint64_t bits = map->float_bitmap[i]; uint32_t constant_index; while (xe::bit_scan_forward(bits, &constant_index)) { bits &= ~(uint64_t(1) << constant_index); - if (out + 4 * sizeof(uint32_t) > dst.data() + kCBVSize) { + if (out + 4 * sizeof(uint32_t) > end) { return; } std::memcpy( @@ -2632,825 +3830,1701 @@ bool MetalCommandProcessor::IssueDrawMsl( } } }; - if (msl_float_constants_dirty_vertex_) { - rebuild_packed_float_constants(msl_cached_float_constants_vertex_, - msl_vertex_shader, - XE_GPU_REG_SHADER_CONSTANT_000_X); - msl_float_constants_dirty_vertex_ = false; - } - if (msl_float_constants_dirty_pixel_) { - rebuild_packed_float_constants(msl_cached_float_constants_pixel_, - msl_pixel_shader, - XE_GPU_REG_SHADER_CONSTANT_256_X); - msl_float_constants_dirty_pixel_ = false; - } - std::memcpy(uniforms_vertex + kFloatConstantOffset, - msl_cached_float_constants_vertex_.data(), kCBVSize); - std::memcpy(uniforms_pixel + kFloatConstantOffset, - msl_cached_float_constants_pixel_.data(), kCBVSize); - // b2 (msl_buffer 4): Bool/loop constants. - const size_t kBoolLoopConstantOffset = 2 * kCBVSize; - constexpr size_t kBoolLoopConstantsSize = (8 + 32) * sizeof(uint32_t); - if (msl_bool_loop_constants_dirty_) { - std::memcpy(msl_cached_bool_loop_constants_.data(), - ®s.values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031], - kBoolLoopConstantsSize); - msl_bool_loop_constants_dirty_ = false; - } - std::memcpy(uniforms_vertex + kBoolLoopConstantOffset, - msl_cached_bool_loop_constants_.data(), kBoolLoopConstantsSize); - std::memcpy(uniforms_pixel + kBoolLoopConstantOffset, - msl_cached_bool_loop_constants_.data(), kBoolLoopConstantsSize); - - // b3 (msl_buffer 5): Fetch constants. - const size_t kFetchConstantOffset = 3 * kCBVSize; - const size_t kFetchConstantCount = xenos::kTextureFetchConstantCount * 6; - const size_t kFetchConstantsSize = kFetchConstantCount * sizeof(uint32_t); - if (msl_fetch_constants_dirty_) { - std::memcpy(msl_cached_fetch_constants_.data(), - ®s.values[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0], - kFetchConstantsSize); - msl_fetch_constants_dirty_ = false; - } - std::memcpy(uniforms_vertex + kFetchConstantOffset, - msl_cached_fetch_constants_.data(), kFetchConstantsSize); - std::memcpy(uniforms_pixel + kFetchConstantOffset, - msl_cached_fetch_constants_.data(), kFetchConstantsSize); - - // b5 (msl_buffer 6): Clip plane constants (separate buffer for SPIR-V path). - const size_t kClipPlaneConstantOffset = 4 * kCBVSize; - copy_uniform_block_if_stale(uniforms_vertex + kClipPlaneConstantOffset, - &spirv_clip_plane_constants_, - sizeof(SpirvShaderTranslator::ClipPlaneConstants), - msl_clip_plane_constants_written_vertex_versions_, - msl_clip_plane_constants_version_); - - // b6 (msl_buffer 7): Tessellation constants. - const size_t kTessellationConstantOffset = 5 * kCBVSize; - copy_uniform_block_if_stale( - uniforms_vertex + kTessellationConstantOffset, - &spirv_tessellation_constants_, - sizeof(SpirvShaderTranslator::TessellationConstants), - msl_tessellation_constants_written_vertex_versions_, - msl_tessellation_constants_version_); - copy_uniform_block_if_stale( - uniforms_pixel + kTessellationConstantOffset, - &spirv_tessellation_constants_, - sizeof(SpirvShaderTranslator::TessellationConstants), - msl_tessellation_constants_written_pixel_versions_, - msl_tessellation_constants_version_); - - // Keep binding behavior conservative while using per-encoder dedupe caches. - const bool msl_bind_dedupe = true; - - // Bind shared memory buffer at msl_buffer 0. - MTL::Buffer* shared_mem_buffer = - shared_memory_ ? shared_memory_->GetBuffer() : nullptr; - MTL::ResourceUsage shared_memory_usage = MTL::ResourceUsageRead; - if (memexport_used) { - shared_memory_usage |= MTL::ResourceUsageWrite; - } - if (!msl_bind_dedupe || - msl_bound_shared_memory_buffer_ != shared_mem_buffer) { - current_render_encoder_->setVertexBuffer(shared_mem_buffer, 0, - MslBufferIndex::kSharedMemory); - current_render_encoder_->setFragmentBuffer(shared_mem_buffer, 0, - MslBufferIndex::kSharedMemory); - msl_bound_shared_memory_buffer_ = shared_mem_buffer; - } - if (shared_mem_buffer) { - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - } - - // Bind a null buffer at the EDRAM slot (msl_buffer 30) as a safety measure. - // FSI/EDRAM is disabled on this path (fragment_shader_sample_interlock = - // false), so no shader should reference it, but binding a dummy prevents - // GPU faults if any code path unexpectedly accesses buffer(30). - if (!msl_bind_dedupe || msl_bound_null_buffer_ != null_buffer_) { - current_render_encoder_->setFragmentBuffer(null_buffer_, 0, 30); - msl_bound_null_buffer_ = null_buffer_; - } - - // Bind uniforms buffer at the appropriate indices. - NS::UInteger vs_base_offset = table_index_vertex * kUniformsBytesPerTable; - NS::UInteger ps_base_offset = table_index_pixel * kUniformsBytesPerTable; - if (!msl_bind_dedupe || !msl_bound_uniforms_offsets_valid_ || - msl_bound_uniforms_buffer_ != uniforms_buffer_ || - msl_bound_uniforms_vs_base_offset_ != vs_base_offset || - msl_bound_uniforms_ps_base_offset_ != ps_base_offset) { - // System constants (msl_buffer 1). - current_render_encoder_->setVertexBuffer(uniforms_buffer_, - vs_base_offset + 0 * kCBVSize, - MslBufferIndex::kSystemConstants); - current_render_encoder_->setFragmentBuffer( - uniforms_buffer_, ps_base_offset + 0 * kCBVSize, - MslBufferIndex::kSystemConstants); - - // Float constants vertex (msl_buffer 2). - current_render_encoder_->setVertexBuffer( - uniforms_buffer_, vs_base_offset + 1 * kCBVSize, - MslBufferIndex::kFloatConstantsVertex); - // Float constants pixel (msl_buffer 3). - current_render_encoder_->setFragmentBuffer( - uniforms_buffer_, ps_base_offset + 1 * kCBVSize, - MslBufferIndex::kFloatConstantsPixel); - - // Bool/loop constants (msl_buffer 4). - current_render_encoder_->setVertexBuffer( - uniforms_buffer_, vs_base_offset + 2 * kCBVSize, - MslBufferIndex::kBoolLoopConstants); - current_render_encoder_->setFragmentBuffer( - uniforms_buffer_, ps_base_offset + 2 * kCBVSize, - MslBufferIndex::kBoolLoopConstants); - - // Fetch constants (msl_buffer 5). - current_render_encoder_->setVertexBuffer(uniforms_buffer_, - vs_base_offset + 3 * kCBVSize, - MslBufferIndex::kFetchConstants); - current_render_encoder_->setFragmentBuffer(uniforms_buffer_, - ps_base_offset + 3 * kCBVSize, - MslBufferIndex::kFetchConstants); - - // Clip plane constants (msl_buffer 6) — vertex shader only. - // The SPIR-V translator uses a separate constant buffer for clip planes. - current_render_encoder_->setVertexBuffer( - uniforms_buffer_, vs_base_offset + 4 * kCBVSize, - MslBufferIndex::kClipPlaneConstants); - - // Tessellation constants (msl_buffer 7). - current_render_encoder_->setVertexBuffer( - uniforms_buffer_, vs_base_offset + 5 * kCBVSize, - MslBufferIndex::kTessellationConstants); - current_render_encoder_->setFragmentBuffer( - uniforms_buffer_, ps_base_offset + 5 * kCBVSize, - MslBufferIndex::kTessellationConstants); - - msl_bound_uniforms_buffer_ = uniforms_buffer_; - msl_bound_uniforms_vs_base_offset_ = vs_base_offset; - msl_bound_uniforms_ps_base_offset_ = ps_base_offset; - msl_bound_uniforms_offsets_valid_ = true; - } - - UseRenderEncoderResource(uniforms_buffer_, MTL::ResourceUsageRead); - - const bool vertex_uses_argbuf = - vertex_translation && vertex_translation->uses_argument_buffers(); - const bool pixel_uses_argbuf = - pixel_translation && pixel_translation->uses_argument_buffers(); - auto get_msl_binding_layout_uid = - [](const MslShader::MslTranslation* translation) -> uint64_t { - if (!translation) { - return 0; - } - const auto& texture_binding_indices = - translation->texture_binding_indices_for_msl_slots(); - const auto& sampler_binding_indices = - translation->sampler_binding_indices_for_msl_slots(); - uint64_t uid = XXH3_64bits( - texture_binding_indices.data(), - texture_binding_indices.size() * sizeof(texture_binding_indices[0])); - uid = XXH3_64bits_withSeed( - sampler_binding_indices.data(), - sampler_binding_indices.size() * sizeof(sampler_binding_indices[0]), - uid); - return uid; - }; - - auto bind_msl_argument_buffer = [&](MslShader* shader, - MslShader::MslTranslation* translation, - bool is_pixel_stage) -> bool { - if (!shader || !translation || !translation->uses_argument_buffers() || - !texture_cache_) { - return true; - } - - MTL::ArgumentEncoder* arg_encoder = translation->argument_encoder(); - uint32_t encoded_length = translation->argument_encoder_encoded_length(); - if (!arg_encoder || encoded_length == 0) { - return true; - } - - const auto& texture_bindings = shader->GetTextureBindingsAfterTranslation(); - const auto& texture_binding_indices = - translation->texture_binding_indices_for_msl_slots(); - uint32_t texture_count = std::min(uint32_t(texture_binding_indices.size()), - MslTextureIndex::kMaxPerStage); - std::array textures = - {}; - - MetalTextureCache* metal_texture_cache = texture_cache_.get(); - for (uint32_t slot = 0; slot < texture_count; ++slot) { - MTL::Texture* texture = nullptr; - int32_t texture_binding_index = texture_binding_indices[slot]; - if (texture_binding_index >= 0 && - size_t(texture_binding_index) < texture_bindings.size()) { - const auto& binding = texture_bindings[size_t(texture_binding_index)]; - texture = texture_cache_->GetTextureForBinding( - binding.fetch_constant, binding.dimension, binding.is_signed); - if (!texture) { - switch (binding.dimension) { - case xenos::FetchOpDimension::k3DOrStacked: - texture = metal_texture_cache->GetNullTexture3D(); - break; - case xenos::FetchOpDimension::kCube: - texture = metal_texture_cache->GetNullTextureCube(); - break; - default: - texture = metal_texture_cache->GetNullTexture2D(); - break; - } - } - } else { - texture = metal_texture_cache->GetNullTexture2D(); - } - textures[slot] = texture; - // UseRenderEncoderResource must always be called for hazard tracking, - // even when we skip re-encoding. The dedup map handles per-encoder - // deduplication. - if (texture) { - UseRenderEncoderResource(texture, MTL::ResourceUsageRead); - } - } - - const auto& sampler_bindings = shader->GetSamplerBindingsAfterTranslation(); - const auto& sampler_binding_indices = - translation->sampler_binding_indices_for_msl_slots(); - uint32_t sampler_count = std::min(uint32_t(sampler_binding_indices.size()), - MslSamplerIndex::kMaxPerStage); - std::array - samplers = {}; - for (uint32_t smp_index = 0; smp_index < sampler_count; ++smp_index) { - MTL::SamplerState* sampler_state = null_sampler_; - uint32_t sampler_binding_index = sampler_binding_indices[smp_index]; - if (sampler_binding_index < sampler_bindings.size()) { - auto parameters = texture_cache_->GetSamplerParameters( - sampler_bindings[sampler_binding_index]); - sampler_state = texture_cache_->GetOrCreateSampler(parameters); - if (!sampler_state) { - sampler_state = null_sampler_; - } - } - samplers[smp_index] = sampler_state; - } - - // Check if textures and samplers match the cached content from the last - // encoding. If so, skip the expensive acquire+encode and reuse the previous - // argument buffer slice. - auto& cached_textures = is_pixel_stage ? msl_last_argbuf_pixel_textures_ - : msl_last_argbuf_vertex_textures_; - auto& cached_texture_count = is_pixel_stage - ? msl_last_argbuf_pixel_texture_count_ - : msl_last_argbuf_vertex_texture_count_; - auto& cached_samplers = is_pixel_stage ? msl_last_argbuf_pixel_samplers_ - : msl_last_argbuf_vertex_samplers_; - auto& cached_sampler_count = is_pixel_stage - ? msl_last_argbuf_pixel_sampler_count_ - : msl_last_argbuf_vertex_sampler_count_; - auto& cached_argbuf_buffer = is_pixel_stage - ? msl_last_argbuf_pixel_buffer_ - : msl_last_argbuf_vertex_buffer_; - auto& cached_argbuf_offset = is_pixel_stage - ? msl_last_argbuf_pixel_offset_ - : msl_last_argbuf_vertex_offset_; - auto& cached_translation = is_pixel_stage - ? msl_last_argbuf_pixel_translation_ - : msl_last_argbuf_vertex_translation_; - auto& cached_encoded_length = is_pixel_stage - ? msl_last_argbuf_pixel_encoded_length_ - : msl_last_argbuf_vertex_encoded_length_; - auto& cached_layout_uid = is_pixel_stage - ? msl_last_argbuf_pixel_layout_uid_ - : msl_last_argbuf_vertex_layout_uid_; - const uint64_t layout_uid = get_msl_binding_layout_uid(translation); - - bool content_changed = cached_translation != translation || - cached_encoded_length != encoded_length || - layout_uid != cached_layout_uid || - texture_count != cached_texture_count || - sampler_count != cached_sampler_count || - !cached_argbuf_buffer; - if (!content_changed && texture_count > 0) { - content_changed = std::memcmp(textures.data(), cached_textures.data(), - texture_count * sizeof(textures[0])) != 0; - } - if (!content_changed && sampler_count > 0) { - content_changed = std::memcmp(samplers.data(), cached_samplers.data(), - sampler_count * sizeof(samplers[0])) != 0; - } - - MTL::Buffer* argbuf_buffer; - NS::UInteger argbuf_offset; - - if (content_changed) { - // Content changed — acquire a new slice and re-encode. - argbuf_buffer = nullptr; - argbuf_offset = 0; - if (!AcquireSpirvArgumentBufferSlice( - encoded_length, translation->argument_encoder_alignment(), - &argbuf_buffer, &argbuf_offset)) { - XELOGE( - "SPIRV-Cross: Failed to allocate argument buffer slice ({} bytes)", - encoded_length); - return false; - } - - arg_encoder->setArgumentBuffer(argbuf_buffer, argbuf_offset); - if (texture_count) { - arg_encoder->setTextures( - textures.data(), - NS::Range::Make(MslTextureIndex::kBase, texture_count)); - } - if (sampler_count) { - arg_encoder->setSamplerStates( - samplers.data(), - NS::Range::Make(MslArgumentBufferId::kSamplerBase, sampler_count)); - } - - // Update the cache. - std::memcpy(cached_textures.data(), textures.data(), - texture_count * sizeof(textures[0])); - if (texture_count < cached_texture_count) { - std::memset(&cached_textures[texture_count], 0, - (cached_texture_count - texture_count) * - sizeof(cached_textures[0])); - } - cached_texture_count = texture_count; - std::memcpy(cached_samplers.data(), samplers.data(), - sampler_count * sizeof(samplers[0])); - if (sampler_count < cached_sampler_count) { - std::memset(&cached_samplers[sampler_count], 0, - (cached_sampler_count - sampler_count) * - sizeof(cached_samplers[0])); - } - cached_sampler_count = sampler_count; - cached_argbuf_buffer = argbuf_buffer; - cached_argbuf_offset = argbuf_offset; - cached_translation = translation; - cached_encoded_length = encoded_length; - cached_layout_uid = layout_uid; - } else { - // Content unchanged — reuse the previous argument buffer slice. - argbuf_buffer = cached_argbuf_buffer; - argbuf_offset = cached_argbuf_offset; - } - - if (is_pixel_stage) { - if (argbuf_buffer != msl_bound_pixel_argument_buffer_) { - current_render_encoder_->setFragmentBuffer( - argbuf_buffer, 0, MslBufferIndex::kArgumentBufferTexturesSamplers); - msl_bound_pixel_argument_buffer_ = argbuf_buffer; - msl_bound_pixel_argument_buffer_offset_valid_ = false; - } - if (!msl_bound_pixel_argument_buffer_offset_valid_ || - msl_bound_pixel_argument_buffer_offset_ != argbuf_offset) { - current_render_encoder_->setFragmentBufferOffset( - argbuf_offset, MslBufferIndex::kArgumentBufferTexturesSamplers); - msl_bound_pixel_argument_buffer_offset_ = argbuf_offset; - msl_bound_pixel_argument_buffer_offset_valid_ = true; - } - } else { - if (argbuf_buffer != msl_bound_vertex_argument_buffer_) { - current_render_encoder_->setVertexBuffer( - argbuf_buffer, 0, MslBufferIndex::kArgumentBufferTexturesSamplers); - msl_bound_vertex_argument_buffer_ = argbuf_buffer; - msl_bound_vertex_argument_buffer_offset_valid_ = false; - } - if (!msl_bound_vertex_argument_buffer_offset_valid_ || - msl_bound_vertex_argument_buffer_offset_ != argbuf_offset) { - current_render_encoder_->setVertexBufferOffset( - argbuf_offset, MslBufferIndex::kArgumentBufferTexturesSamplers); - msl_bound_vertex_argument_buffer_offset_ = argbuf_offset; - msl_bound_vertex_argument_buffer_offset_valid_ = true; - } - } - - UseRenderEncoderResource(argbuf_buffer, MTL::ResourceUsageRead); - return true; - }; - if (vertex_uses_argbuf && - !bind_msl_argument_buffer(msl_vertex_shader, vertex_translation, false)) { - return false; - } - if (pixel_uses_argbuf && - !bind_msl_argument_buffer(msl_pixel_shader, pixel_translation, true)) { - return false; - } - - // Bind textures and samplers directly. - auto bind_msl_textures = [&](MslShader* shader, - MslShader::MslTranslation* translation, - bool is_pixel_stage) { - auto bind_texture_slot = [&](uint32_t slot, MTL::Texture* texture) { - if (is_pixel_stage) { - current_render_encoder_->setFragmentTexture(texture, slot); - } else { - current_render_encoder_->setVertexTexture(texture, slot); - } - }; - uint32_t* previous_bound_count = is_pixel_stage - ? &msl_bound_pixel_texture_count_ - : &msl_bound_vertex_texture_count_; - uint64_t* cached_binding_uid = is_pixel_stage - ? &msl_bound_pixel_texture_binding_uid_ - : &msl_bound_vertex_texture_binding_uid_; - auto* bound_textures = is_pixel_stage ? &msl_bound_pixel_textures_ - : &msl_bound_vertex_textures_; - const uint64_t layout_uid = get_msl_binding_layout_uid(translation); - const uint64_t binding_uid = - (shader && translation && texture_cache_) - ? (uint64_t(reinterpret_cast(pipeline)) * - UINT64_C(11400714819323198485) ^ - layout_uid) - : 0; - const bool force_rebind = *cached_binding_uid != binding_uid; - auto clear_slots_from = [&](uint32_t start, uint32_t end_exclusive) { - for (uint32_t slot = start; slot < end_exclusive; ++slot) { - if (force_rebind || (*bound_textures)[slot] != nullptr) { - bind_texture_slot(slot, nullptr); - (*bound_textures)[slot] = nullptr; - } - } - }; - - if (!shader || !translation || !texture_cache_) { - clear_slots_from(0, *previous_bound_count); - *previous_bound_count = 0; - *cached_binding_uid = binding_uid; - return; - } - - const auto& texture_bindings = shader->GetTextureBindingsAfterTranslation(); - const auto& texture_binding_indices = - translation->texture_binding_indices_for_msl_slots(); - uint32_t bound_count = std::min(uint32_t(texture_binding_indices.size()), - MslTextureIndex::kMaxPerStage); - if (*previous_bound_count > bound_count) { - clear_slots_from(bound_count, *previous_bound_count); - } - - MetalTextureCache* metal_texture_cache = texture_cache_.get(); - for (uint32_t slot = 0; slot < bound_count; ++slot) { - uint32_t tex_index = MslTextureIndex::kBase + slot; - MTL::Texture* texture = nullptr; - int32_t texture_binding_index = texture_binding_indices[slot]; - if (texture_binding_index >= 0 && - size_t(texture_binding_index) < texture_bindings.size()) { - const auto& binding = texture_bindings[size_t(texture_binding_index)]; - texture = texture_cache_->GetTextureForBinding( - binding.fetch_constant, binding.dimension, binding.is_signed); - if (!texture) { - switch (binding.dimension) { - case xenos::FetchOpDimension::k3DOrStacked: - texture = metal_texture_cache->GetNullTexture3D(); - break; - case xenos::FetchOpDimension::kCube: - texture = metal_texture_cache->GetNullTextureCube(); - break; - default: - texture = metal_texture_cache->GetNullTexture2D(); - break; - } - } - } else { - texture = metal_texture_cache->GetNullTexture2D(); - } - if (force_rebind || (*bound_textures)[tex_index] != texture) { - bind_texture_slot(tex_index, texture); - (*bound_textures)[tex_index] = texture; - } - if (texture) { - UseRenderEncoderResource(texture, MTL::ResourceUsageRead); - } - } - *previous_bound_count = bound_count; - *cached_binding_uid = binding_uid; - }; - - auto bind_msl_samplers = [&](MslShader* shader, - MslShader::MslTranslation* translation, - bool is_pixel_stage) { - auto bind_sampler_slot = [&](uint32_t slot, MTL::SamplerState* sampler) { - if (is_pixel_stage) { - current_render_encoder_->setFragmentSamplerState(sampler, slot); - } else { - current_render_encoder_->setVertexSamplerState(sampler, slot); - } - }; - - uint32_t* previous_bound_count = is_pixel_stage - ? &msl_bound_pixel_sampler_count_ - : &msl_bound_vertex_sampler_count_; - uint64_t* cached_binding_uid = is_pixel_stage - ? &msl_bound_pixel_sampler_binding_uid_ - : &msl_bound_vertex_sampler_binding_uid_; - auto* bound_samplers = is_pixel_stage ? &msl_bound_pixel_samplers_ - : &msl_bound_vertex_samplers_; - const uint64_t layout_uid = get_msl_binding_layout_uid(translation); - const uint64_t binding_uid = - (shader && translation && texture_cache_) - ? (uint64_t(reinterpret_cast(pipeline)) * - UINT64_C(11400714819323198485) ^ - layout_uid) - : 0; - const bool force_rebind = *cached_binding_uid != binding_uid; - - if (!shader || !translation || !texture_cache_) { - for (uint32_t slot = 0; slot < *previous_bound_count; ++slot) { - if (force_rebind || (*bound_samplers)[slot] != null_sampler_) { - bind_sampler_slot(slot, null_sampler_); - (*bound_samplers)[slot] = null_sampler_; - } - } - *previous_bound_count = 0; - *cached_binding_uid = binding_uid; - return; - } - // Samplers are remapped to compact Metal indices 0..M-1 in - // MslShader::AddResourceBindings. Use the reflected SPIR-V remap order - // captured in the translation, not the raw translator array order, because - // SPIRV-Cross may drop/reorder separate samplers. - const auto& sampler_bindings = shader->GetSamplerBindingsAfterTranslation(); - const auto& sampler_binding_indices = - translation->sampler_binding_indices_for_msl_slots(); - uint32_t bound_count = std::min(uint32_t(sampler_binding_indices.size()), - MslSamplerIndex::kMaxPerStage); - if (*previous_bound_count > bound_count) { - for (uint32_t slot = bound_count; slot < *previous_bound_count; ++slot) { - if (force_rebind || (*bound_samplers)[slot] != null_sampler_) { - bind_sampler_slot(slot, null_sampler_); - (*bound_samplers)[slot] = null_sampler_; - } - } - } - - for (uint32_t smp_index = 0; smp_index < bound_count; ++smp_index) { - MTL::SamplerState* sampler_state = null_sampler_; - uint32_t sampler_binding_index = sampler_binding_indices[smp_index]; - if (sampler_binding_index < sampler_bindings.size()) { - auto parameters = texture_cache_->GetSamplerParameters( - sampler_bindings[sampler_binding_index]); - sampler_state = texture_cache_->GetOrCreateSampler(parameters); - if (!sampler_state) { - sampler_state = null_sampler_; - } - } - if (force_rebind || (*bound_samplers)[smp_index] != sampler_state) { - bind_sampler_slot(smp_index, sampler_state); - (*bound_samplers)[smp_index] = sampler_state; - } - } - *previous_bound_count = bound_count; - *cached_binding_uid = binding_uid; - }; - - if (!vertex_uses_argbuf) { - bind_msl_textures(msl_vertex_shader, vertex_translation, false); - bind_msl_samplers(msl_vertex_shader, vertex_translation, false); - } - if (!pixel_uses_argbuf) { - bind_msl_textures(msl_pixel_shader, pixel_translation, true); - bind_msl_samplers(msl_pixel_shader, pixel_translation, true); - } - - // ===================================================================== - // Draw dispatch — native Metal encoder calls (no IRRuntime). - // ===================================================================== - if (is_tessellated) { - // --------------------------------------------------------------- - // Tessellated draw: fill tessellation factor buffer, then drawPatches. - // --------------------------------------------------------------- - // Xenos tess levels are 0-based; add 1 to match other backends. - float max_tess = std::max( - 1.0f, regs.Get(XE_GPU_REG_VGT_HOS_MAX_TESS_LEVEL) + 1.0f); - - // Determine control points per patch and patch count from the draw. - // The primitive processor passes patch count in host_draw_vertex_count - // for tessellated draws (vertex count = cp_per_patch * patch_count). - uint32_t cp_per_patch = 1; - bool is_quad_domain = false; - switch (host_vertex_shader_type) { - case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: - case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: - cp_per_patch = 3; - break; - case Shader::HostVertexShaderType::kQuadDomainCPIndexed: - case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: - cp_per_patch = 4; - is_quad_domain = true; - break; - case Shader::HostVertexShaderType::kLineDomainCPIndexed: - case Shader::HostVertexShaderType::kLineDomainPatchIndexed: - cp_per_patch = 2; - break; - default: - break; - } - uint32_t vertex_count = primitive_processing_result.host_draw_vertex_count; - uint32_t patch_count = cp_per_patch > 0 ? vertex_count / cp_per_patch : 0; - if (patch_count == 0) { - return true; // Nothing to draw. - } - - // Ensure tessellation factor buffer is large enough. - if (!EnsureTessFactorBuffer(patch_count)) { - XELOGE( - "SPIRV-Cross: Failed to allocate tess factor buffer for {} " - "patches", - patch_count); + const Shader::ConstantRegisterMap& float_map_vertex = + vertex_shader->constant_register_map(); + if (!cbuffer_binding_float_vertex_.up_to_date) { + const size_t float_size = + sizeof(float) * 4 * std::max(float_map_vertex.float_count, uint32_t(1)); + if (!try_reuse_or_upload( + cbuffer_binding_float_vertex_, kCbvSlotFloat, kStageVertex, + current_payload_float_vertex_, scratch_payload_float_vertex_, + float_size, "vertex float", [&](uint8_t* data, size_t size) { + write_packed_float_constants(data, size, &float_map_vertex, + XE_GPU_REG_SHADER_CONSTANT_000_X); + })) { return false; } + } else { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotFloat]; + } - // IEEE 754 float32 → float16 conversion for tessellation factors. - // Uses round-to-nearest-even and handles subnormals for accuracy - // near tessellation factor boundaries. - auto f32_to_f16 = [](float v) -> uint16_t { - uint32_t b; - std::memcpy(&b, &v, 4); - uint16_t s = (b >> 16) & 0x8000u; - int e = int((b >> 23) & 0xFFu) - 127 + 15; - uint32_t m = b & 0x7FFFFFu; - if (e <= 0) { - // Subnormal or zero in half precision. - if (e < -10) { - return s; // Too small, flush to signed zero. - } - // Subnormal half: shift mantissa (with implicit leading 1) right. - m = (m | 0x800000u) >> (1 - e); - // Round to nearest even. - if ((m & 0x1FFFu) > 0x1000u || - ((m & 0x1FFFu) == 0x1000u && (m & 0x2000u))) { - m += 0x2000u; - } - return uint16_t(s | (m >> 13)); + if (!cbuffer_binding_float_pixel_.up_to_date) { + const Shader::ConstantRegisterMap* float_map_pixel = + pixel_shader ? &pixel_shader->constant_register_map() : nullptr; + const size_t float_size = + sizeof(float) * 4 * + std::max(float_map_pixel ? float_map_pixel->float_count : uint32_t(0), + uint32_t(1)); + if (!try_reuse_or_upload( + cbuffer_binding_float_pixel_, kCbvSlotFloat, kStagePixel, + current_payload_float_pixel_, scratch_payload_float_pixel_, + float_size, "pixel float", [&](uint8_t* data, size_t size) { + write_packed_float_constants(data, size, float_map_pixel, + XE_GPU_REG_SHADER_CONSTANT_256_X); + })) { + return false; + } + } else if (pixel_shader) { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotFloat]; + } + + if (!cbuffer_binding_bool_loop_.up_to_date) { + if (!try_reuse_or_upload( + cbuffer_binding_bool_loop_, kCbvSlotBoolLoop, + kConstantPayloadSharedStage, current_payload_bool_loop_, + scratch_payload_bool_loop_, kBoolLoopConstantsSize, "bool loop", + [&](uint8_t* data, size_t) { + std::memcpy(data, + ®s.values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031], + kBoolLoopConstantsSize); + })) { + return false; + } + } else { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotBoolLoop]; + } + + std::array fetch_binding_active = {}; + for (size_t stage = 0; stage < kStageCount; ++stage) { + if (!(active_cbv_masks[stage] & (uint32_t(1) << kCbvSlotFetch))) { + continue; + } + fetch_binding_active[stage] = true; + if (FetchConstantDwordMasksOverlap(fetch_constant_dirty_masks_[stage], + fetch_constant_dword_masks[stage])) { + cbuffer_binding_fetch_[stage].up_to_date = false; + } + } + const size_t fetch_size = kFetchConstantCount * sizeof(uint32_t); + for (size_t stage = 0; stage < kStageCount; ++stage) { + if (!fetch_binding_active[stage]) { + continue; + } + if (!cbuffer_binding_fetch_[stage].up_to_date) { + if (!try_reuse_or_upload( + cbuffer_binding_fetch_[stage], kCbvSlotFetch, + kConstantPayloadSharedStage, current_payload_fetch_[stage], + scratch_payload_fetch_[stage], fetch_size, "fetch", + [&](uint8_t* data, size_t) { + std::memcpy(data, + ®s.values[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0], + fetch_size); + })) { + return false; } - if (e >= 31) { - // Overflow → infinity (or NaN passthrough). - if (e == 31 && m != 0) { - // NaN: preserve at least one mantissa bit. - return uint16_t(s | 0x7C00u | std::max(m >> 13, uint32_t(1))); + fetch_constant_dirty_masks_[stage].fill(0); + } else { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotFetch]; + } + } + + auto build_descriptor_indices = + [&](MetalShader* shader, std::vector& words, + const MetalShader::DrawConstantMetadata* metadata, + const std::vector& texture_indices, + const std::vector& sampler_indices) { + words.assign(metadata ? metadata->descriptor_indices_word_count : 1, 0); + if (!shader || !texture_cache_) { + return; } - return uint16_t(s | 0x7C00u); - } - // Round to nearest even: check the 13 bits being truncated. - if ((m & 0x1FFFu) > 0x1000u || - ((m & 0x1FFFu) == 0x1000u && (m & 0x2000u))) { - m += 0x2000u; - if (m & 0x800000u) { - m = 0; - e++; - if (e >= 31) { - return uint16_t(s | 0x7C00u); + const auto& tex_bindings = shader->GetTextureBindingsAfterTranslation(); + for (size_t i = 0; + i < tex_bindings.size() && i < texture_indices.size(); ++i) { + uint32_t d = tex_bindings[i].bindless_descriptor_index; + assert_true(d < words.size()); + if (d >= words.size()) { + continue; } + words[d] = texture_indices[i]; } - } - return uint16_t(s | (e << 10) | (m >> 13)); - }; - - // Determine tessellation mode from the register file. - auto tess_mode = regs.Get().tess_mode; - - uint8_t* factor_data = - static_cast(tess_factor_buffer_->contents()); - - if (tess_mode == xenos::TessellationMode::kAdaptive && shared_memory_) { - // ------------------------------------------------------------------ - // Adaptive tessellation: per-edge factors from shared memory. - // The guest "index buffer" is repurposed as a factor buffer - // containing big-endian float32 edge factors. - // ------------------------------------------------------------------ - xenos::Endian index_endian = - primitive_processing_result.host_shader_index_endian; - uint32_t factor_base = primitive_processing_result.guest_index_base; - const uint8_t* xbox_ram = shared_memory_->GetXboxRamBase(); - // Minimum factor from VGT_HOS_MIN_TESS_LEVEL + 1.0 (Xbox 360 - // convention). For fractional_even partitioning, must be >= 2.0. - float factor_min = std::max( - 2.0f, regs.Get(XE_GPU_REG_VGT_HOS_MIN_TESS_LEVEL) + 1.0f); - float factor_max = max_tess; - - if (is_quad_domain) { - // Quad: 4 edge factors per patch. - struct QuadFactors { - uint16_t edge[4]; - uint16_t inside[2]; - }; - static_assert(sizeof(QuadFactors) == 12); - auto* factors = reinterpret_cast(factor_data); - for (uint32_t i = 0; i < patch_count; ++i) { - // Read 4 edge factors from guest memory. - float ef[4]; - for (uint32_t j = 0; j < 4; ++j) { - uint32_t addr = factor_base + (i * 4 + j) * sizeof(float); - float raw; - std::memcpy(&raw, xbox_ram + addr, sizeof(float)); - // Endian-swap per the index endian mode. - raw = xenos::GpuSwap(raw, index_endian); - // Add 1.0 per Xbox 360 convention, clamp. - ef[j] = std::clamp(raw + 1.0f, factor_min, factor_max); + const auto& smp_bindings = shader->GetSamplerBindingsAfterTranslation(); + for (size_t i = 0; + i < smp_bindings.size() && i < sampler_indices.size(); ++i) { + uint32_t d = smp_bindings[i].bindless_descriptor_index; + assert_true(d < words.size()); + if (d >= words.size()) { + continue; } - // Map Xbox 360 edge order to Metal: - // edge[i] = input[(i+3) & 3] - // (from adaptive_quad.hs.glsl) - factors[i].edge[0] = f32_to_f16(ef[3]); - factors[i].edge[1] = f32_to_f16(ef[0]); - factors[i].edge[2] = f32_to_f16(ef[1]); - factors[i].edge[3] = f32_to_f16(ef[2]); - // Inside factors: minimum of opposing edges. - // inside[0] along U = min(mapped_edge[1], mapped_edge[3]) - // inside[1] along V = min(mapped_edge[0], mapped_edge[2]) - factors[i].inside[0] = f32_to_f16(std::min(ef[0], ef[2])); - factors[i].inside[1] = f32_to_f16(std::min(ef[3], ef[1])); + words[d] = sampler_indices[i]; + } + }; + + auto upload_descriptor_indices = [&](ConstantBufferBinding& binding, + const std::vector& words, + size_t stage, const char* name) -> bool { + const size_t descriptor_indices_bytes = words.size() * sizeof(uint32_t); + const size_t descriptor_payload_size = + std::max(descriptor_indices_bytes, size_t(16)); + std::vector descriptor_payload(descriptor_payload_size, 0); + if (descriptor_indices_bytes) { + std::memcpy(descriptor_payload.data(), words.data(), + descriptor_indices_bytes); + } + if (TryReuseConstantPayloadFromCache(binding, kCbvSlotDescriptorIndices, + stage, descriptor_payload.data(), + descriptor_payload.size())) { + return true; + } + if (stage < backend_telemetry_.descriptor_index_uploads.size()) { + ++backend_telemetry_.descriptor_index_uploads[stage]; + } + if (!upload_binding( + binding, kCbvSlotDescriptorIndices, descriptor_indices_bytes, name, + [&](uint8_t* data, size_t size) { + std::memcpy(data, descriptor_payload.data(), + std::min(size, descriptor_payload.size())); + })) { + return false; + } + StoreConstantPayloadInCache(binding, kCbvSlotDescriptorIndices, stage, + descriptor_payload.data(), + descriptor_payload.size()); + return true; + }; + + if (!cbuffer_binding_descriptor_indices_vertex_.up_to_date) { + build_descriptor_indices( + metal_vertex_shader, scratch_descriptor_indices_vertex_, + &vertex_draw_metadata, next_texture_bindless_indices_vertex, + next_sampler_bindless_indices_vertex); + const bool can_reuse_descriptor_indices = + cbuffer_binding_descriptor_indices_vertex_.buffer && + cbuffer_binding_descriptor_indices_vertex_.upload_frame == + frame_current_ && + current_descriptor_indices_vertex_ == + scratch_descriptor_indices_vertex_; + if (can_reuse_descriptor_indices) { + cbuffer_binding_descriptor_indices_vertex_.up_to_date = true; + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotDescriptorIndices]; + } else { + if (!upload_descriptor_indices(cbuffer_binding_descriptor_indices_vertex_, + scratch_descriptor_indices_vertex_, + kStageVertex, + "vertex descriptor indices")) { + return false; + } + current_descriptor_indices_vertex_.swap( + scratch_descriptor_indices_vertex_); + } + descriptor_indices_vertex_written = true; + } else { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotDescriptorIndices]; + } + + if (metal_pixel_shader && + !cbuffer_binding_descriptor_indices_pixel_.up_to_date) { + build_descriptor_indices( + metal_pixel_shader, scratch_descriptor_indices_pixel_, + pixel_draw_metadata, next_texture_bindless_indices_pixel, + next_sampler_bindless_indices_pixel); + const bool can_reuse_descriptor_indices = + cbuffer_binding_descriptor_indices_pixel_.buffer && + cbuffer_binding_descriptor_indices_pixel_.upload_frame == + frame_current_ && + current_descriptor_indices_pixel_ == scratch_descriptor_indices_pixel_; + if (can_reuse_descriptor_indices) { + cbuffer_binding_descriptor_indices_pixel_.up_to_date = true; + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotDescriptorIndices]; + } else { + if (!upload_descriptor_indices(cbuffer_binding_descriptor_indices_pixel_, + scratch_descriptor_indices_pixel_, + kStagePixel, "pixel descriptor indices")) { + return false; + } + current_descriptor_indices_pixel_.swap(scratch_descriptor_indices_pixel_); + } + descriptor_indices_pixel_written = true; + } else if (metal_pixel_shader) { + ++backend_telemetry_.cbv_reuse_hits[kCbvSlotDescriptorIndices]; + } + + if (descriptor_indices_vertex_written) { + current_texture_layout_uid_vertex_ = texture_layout_uid_vertex; + current_texture_bindless_resources_vertex_.swap( + next_texture_bindless_resources_vertex); + if (texture_count_vertex) { + current_texture_srv_keys_vertex_.resize(std::max( + current_texture_srv_keys_vertex_.size(), texture_count_vertex)); + texture_cache_->WriteActiveTextureSRVKeys( + current_texture_srv_keys_vertex_.data(), + texture_bindings_vertex.data(), texture_count_vertex); + } + } + if (descriptor_indices_pixel_written) { + current_texture_layout_uid_pixel_ = texture_layout_uid_pixel; + current_texture_bindless_resources_pixel_.swap( + next_texture_bindless_resources_pixel); + if (texture_bindings_pixel_ptr && !texture_bindings_pixel_ptr->empty()) { + current_texture_srv_keys_pixel_.resize( + std::max(current_texture_srv_keys_pixel_.size(), + texture_bindings_pixel_ptr->size())); + texture_cache_->WriteActiveTextureSRVKeys( + current_texture_srv_keys_pixel_.data(), + texture_bindings_pixel_ptr->data(), + texture_bindings_pixel_ptr->size()); + } + } + if (descriptor_indices_vertex_written || descriptor_indices_pixel_written) { + PublishBindlessTextureResourceSet(); + } + + uniforms_out = {}; + auto set_uniform_cbv = [&](UniformBufferInfo::Cbv& cbv, + const ConstantBufferBinding& binding, + bool active) { + cbv.active = active; + if (!active) { + return; + } + assert_true(binding.up_to_date); + assert_true(binding.upload_frame == frame_current_); + cbv.buffer = binding.buffer; + cbv.offset = binding.offset; + cbv.gpu_address = binding.gpu_address; + cbv.size = binding.size; + }; + uniforms_out.active_cbv_masks = active_cbv_masks; + set_uniform_cbv(uniforms_out.cbvs[kStageVertex][kCbvSlotSystem], + cbuffer_binding_system_, + uniforms_out.active_cbv_masks[kStageVertex] & + (uint32_t(1) << kCbvSlotSystem)); + set_uniform_cbv(uniforms_out.cbvs[kStageVertex][kCbvSlotFloat], + cbuffer_binding_float_vertex_, + uniforms_out.active_cbv_masks[kStageVertex] & + (uint32_t(1) << kCbvSlotFloat)); + set_uniform_cbv(uniforms_out.cbvs[kStageVertex][kCbvSlotBoolLoop], + cbuffer_binding_bool_loop_, + uniforms_out.active_cbv_masks[kStageVertex] & + (uint32_t(1) << kCbvSlotBoolLoop)); + set_uniform_cbv(uniforms_out.cbvs[kStageVertex][kCbvSlotFetch], + cbuffer_binding_fetch_[kStageVertex], + uniforms_out.active_cbv_masks[kStageVertex] & + (uint32_t(1) << kCbvSlotFetch)); + set_uniform_cbv(uniforms_out.cbvs[kStageVertex][kCbvSlotDescriptorIndices], + cbuffer_binding_descriptor_indices_vertex_, + uniforms_out.active_cbv_masks[kStageVertex] & + (uint32_t(1) << kCbvSlotDescriptorIndices)); + set_uniform_cbv(uniforms_out.cbvs[kStagePixel][kCbvSlotSystem], + cbuffer_binding_system_, + uniforms_out.active_cbv_masks[kStagePixel] & + (uint32_t(1) << kCbvSlotSystem)); + set_uniform_cbv(uniforms_out.cbvs[kStagePixel][kCbvSlotFloat], + cbuffer_binding_float_pixel_, + uniforms_out.active_cbv_masks[kStagePixel] & + (uint32_t(1) << kCbvSlotFloat)); + set_uniform_cbv(uniforms_out.cbvs[kStagePixel][kCbvSlotBoolLoop], + cbuffer_binding_bool_loop_, + uniforms_out.active_cbv_masks[kStagePixel] & + (uint32_t(1) << kCbvSlotBoolLoop)); + set_uniform_cbv(uniforms_out.cbvs[kStagePixel][kCbvSlotFetch], + cbuffer_binding_fetch_[kStagePixel], + uniforms_out.active_cbv_masks[kStagePixel] & + (uint32_t(1) << kCbvSlotFetch)); + set_uniform_cbv(uniforms_out.cbvs[kStagePixel][kCbvSlotDescriptorIndices], + cbuffer_binding_descriptor_indices_pixel_, + uniforms_out.active_cbv_masks[kStagePixel] & + (uint32_t(1) << kCbvSlotDescriptorIndices)); + uniforms_out.fetch_constant_dword_masks = fetch_constant_dword_masks; + current_fetch_constant_dword_masks_ = uniforms_out.fetch_constant_dword_masks; + return true; +} + +void MetalCommandProcessor::ApplyDrawDynamicState( + const DrawDynamicState& dynamic_state) { + if (viewport_dirty_ || std::memcmp(&dynamic_state.viewport, &cached_viewport_, + sizeof(MTL::Viewport)) != 0) { + current_render_encoder_->setViewport(dynamic_state.viewport); + cached_viewport_ = dynamic_state.viewport; + viewport_dirty_ = false; + } + + if (scissor_dirty_ || std::memcmp(&dynamic_state.scissor, &cached_scissor_, + sizeof(MTL::ScissorRect)) != 0) { + current_render_encoder_->setScissorRect(dynamic_state.scissor); + cached_scissor_ = dynamic_state.scissor; + scissor_dirty_ = false; + } + + if (dynamic_state.rasterization_enabled) { + ApplyRasterizerState(dynamic_state); + } + + // Fixed-function depth/stencil state is not part of the pipeline state in + // Metal, so update it per draw. + ApplyDepthStencilState(dynamic_state); + + bool blend_factor_update_needed = + !ff_blend_factor_valid_ || + std::memcmp(ff_blend_factor_, dynamic_state.blend_constants, + sizeof(float) * 4) != 0; + if (blend_factor_update_needed) { + std::memcpy(ff_blend_factor_, dynamic_state.blend_constants, + sizeof(float) * 4); + ff_blend_factor_valid_ = true; + current_render_encoder_->setBlendColor( + dynamic_state.blend_constants[0], dynamic_state.blend_constants[1], + dynamic_state.blend_constants[2], dynamic_state.blend_constants[3]); + } +} + +MetalCommandProcessor::StageRootArgumentEntries +MetalCommandProcessor::BuildGraphicsRootArgumentEntries( + const UniformBufferInfo& uniforms, bool shared_memory_is_uav, + bool use_tessellation_emulation) const { + StageRootArgumentEntries entries = {}; + constexpr uint64_t kDescriptorEntrySize = sizeof(IRDescriptorTableEntry); + uint64_t view_heap_gpu = view_bindless_heap_->gpuAddress(); + uint64_t sampler_heap_gpu = sampler_bindless_heap_->gpuAddress(); + uint64_t system_view_gpu = system_view_tables_->gpuAddress(); + uint64_t srv_space0_gpu = + system_view_gpu + (shared_memory_is_uav + ? kSystemViewTableSRVNull + : kSystemViewTableSRVSharedMemory) * + kDescriptorEntrySize; + uint64_t uav_space0_gpu = + system_view_gpu + (shared_memory_is_uav + ? kSystemViewTableUAVSharedMemoryStart + : kSystemViewTableUAVNullStart) * + kDescriptorEntrySize; + uint64_t null_uav_gpu = + system_view_gpu + kSystemViewTableUAVNullStart * kDescriptorEntrySize; + const uint64_t null_gpu = null_buffer_->gpuAddress(); + + entries[kGraphicsRootABSlotSRVSpace0] = srv_space0_gpu; + entries[kGraphicsRootABSlotSRVSpace1] = view_heap_gpu; + entries[kGraphicsRootABSlotSRVSpace2] = view_heap_gpu; + entries[kGraphicsRootABSlotSRVSpace3] = view_heap_gpu; + entries[kGraphicsRootABSlotSRVSpace10] = view_heap_gpu; + entries[kGraphicsRootABSlotUAVSpace0] = uav_space0_gpu; + entries[kGraphicsRootABSlotUAVSpace1] = null_uav_gpu; + entries[kGraphicsRootABSlotUAVSpace2] = null_uav_gpu; + entries[kGraphicsRootABSlotUAVSpace3] = null_uav_gpu; + entries[kGraphicsRootABSlotSamplerSpace0] = sampler_heap_gpu; + + auto cbv_address = [&](size_t stage, size_t cbv) -> uint64_t { + const uint32_t cbv_bit = uint32_t(1) << cbv; + if (!(uniforms.active_cbv_masks[stage] & cbv_bit)) { + return null_gpu; + } + const UniformBufferInfo::Cbv& uniform_cbv = uniforms.cbvs[stage][cbv]; + return uniform_cbv.gpu_address ? uniform_cbv.gpu_address : null_gpu; + }; + auto common_cbv_address = [&](size_t cbv) -> uint64_t { + uint64_t vertex_address = cbv_address(kStageVertex, cbv); + if (vertex_address != null_gpu) { + return vertex_address; + } + return cbv_address(kStagePixel, cbv); + }; + auto set_stage_local_cbvs = [&](uint32_t float_slot, uint32_t fetch_slot, + uint32_t descriptor_slot, size_t stage) { + entries[float_slot] = cbv_address(stage, kCbvSlotFloat); + entries[fetch_slot] = cbv_address(stage, kCbvSlotFetch); + entries[descriptor_slot] = cbv_address(stage, kCbvSlotDescriptorIndices); + }; + + entries[kGraphicsRootABSlotCBVSystem] = common_cbv_address(kCbvSlotSystem); + entries[kGraphicsRootABSlotCBVBoolLoop] = + common_cbv_address(kCbvSlotBoolLoop); + set_stage_local_cbvs( + kGraphicsRootABSlotCBVVertexFloat, kGraphicsRootABSlotCBVVertexFetch, + kGraphicsRootABSlotCBVVertexDescriptorIndices, kStageVertex); + if (use_tessellation_emulation) { + // Hull and domain generated stages consume the same guest constant state as + // the vertex/domain translation they expand. Keep these slots null on + // ordinary draws so normal vertex root churn doesn't patch inactive stages. + set_stage_local_cbvs( + kGraphicsRootABSlotCBVHullFloat, kGraphicsRootABSlotCBVHullFetch, + kGraphicsRootABSlotCBVHullDescriptorIndices, kStageVertex); + set_stage_local_cbvs( + kGraphicsRootABSlotCBVDomainFloat, kGraphicsRootABSlotCBVDomainFetch, + kGraphicsRootABSlotCBVDomainDescriptorIndices, kStageVertex); + } else { + entries[kGraphicsRootABSlotCBVHullFloat] = null_gpu; + entries[kGraphicsRootABSlotCBVHullFetch] = null_gpu; + entries[kGraphicsRootABSlotCBVHullDescriptorIndices] = null_gpu; + entries[kGraphicsRootABSlotCBVDomainFloat] = null_gpu; + entries[kGraphicsRootABSlotCBVDomainFetch] = null_gpu; + entries[kGraphicsRootABSlotCBVDomainDescriptorIndices] = null_gpu; + } + set_stage_local_cbvs( + kGraphicsRootABSlotCBVPixelFloat, kGraphicsRootABSlotCBVPixelFetch, + kGraphicsRootABSlotCBVPixelDescriptorIndices, kStagePixel); + return entries; +} + +bool MetalCommandProcessor::AllocateStageRootArgument( + size_t /* stage_index */, const StageRootArgumentEntries& entries, + StageRootArgumentAllocation& allocation_out) { + MTL::Buffer* top_level_buffer = nullptr; + size_t top_level_offset = 0; + uint64_t top_level_gpu_address = 0; + auto* top_level_entries = + reinterpret_cast(constant_buffer_pool_->Request( + frame_current_, kTopLevelABBytesPerTable, kTopLevelABBytesPerTable, + &top_level_buffer, top_level_offset, top_level_gpu_address)); + if (!top_level_entries) { + XELOGE("IssueDraw: bindless table allocation failed"); + return false; + } + + std::memcpy(top_level_entries, entries.data(), kTopLevelABBytesPerTable); + + allocation_out = {top_level_buffer, + static_cast(top_level_offset), + top_level_gpu_address, frame_current_, true}; + return true; +} + +bool MetalCommandProcessor::TryReuseConstantPayloadFromCache( + ConstantBufferBinding& binding, size_t cbv_slot, size_t stage_index, + const uint8_t* payload, size_t size) { + if (!cvars::metal_constant_payload_cache) { + return false; + } + if (!payload || !size) { + return false; + } + const uint64_t hash = XXH3_64bits(payload, size); + auto it = constant_payload_cache_.find(hash); + if (it != constant_payload_cache_.end()) { + for (const ConstantPayloadCacheEntry& entry : it->second) { + if (entry.cbv_slot != cbv_slot || entry.stage_index != stage_index || + entry.size != size || entry.binding.upload_frame != frame_current_) { + continue; + } + if (entry.bytes.size() != size || + std::memcmp(entry.bytes.data(), payload, size) != 0) { + continue; + } + binding = entry.binding; + binding.up_to_date = true; + if (cbv_slot < backend_telemetry_.constant_payload_cache_hits.size()) { + ++backend_telemetry_.constant_payload_cache_hits[cbv_slot]; + backend_telemetry_.constant_payload_cache_bytes_saved[cbv_slot] += size; + ++backend_telemetry_.cbv_reuse_hits[cbv_slot]; + } + if (cbv_slot == kCbvSlotDescriptorIndices && stage_index < kStageCount) { + ++backend_telemetry_.descriptor_index_payload_cache_hits[stage_index]; + backend_telemetry_ + .descriptor_index_payload_cache_bytes_saved[stage_index] += size; + } + return true; + } + } + if (cbv_slot < backend_telemetry_.constant_payload_cache_misses.size()) { + ++backend_telemetry_.constant_payload_cache_misses[cbv_slot]; + } + if (cbv_slot == kCbvSlotDescriptorIndices && stage_index < kStageCount) { + ++backend_telemetry_.descriptor_index_payload_cache_misses[stage_index]; + } + return false; +} + +void MetalCommandProcessor::StoreConstantPayloadInCache( + const ConstantBufferBinding& binding, size_t cbv_slot, size_t stage_index, + const uint8_t* payload, size_t size) { + if (!cvars::metal_constant_payload_cache) { + return; + } + if (!binding.buffer || !payload || !size || + binding.upload_frame != frame_current_ || + constant_payload_cache_bytes_ + size > kConstantPayloadCacheMaxBytes) { + return; + } + const uint64_t hash = XXH3_64bits(payload, size); + auto& entries = constant_payload_cache_[hash]; + for (const ConstantPayloadCacheEntry& entry : entries) { + if (entry.cbv_slot == cbv_slot && entry.stage_index == stage_index && + entry.size == size && entry.bytes.size() == size && + std::memcmp(entry.bytes.data(), payload, size) == 0) { + return; + } + } + ConstantPayloadCacheEntry entry; + entry.cbv_slot = cbv_slot; + entry.stage_index = stage_index; + entry.size = size; + entry.binding = binding; + entry.bytes.assign(payload, payload + size); + entries.push_back(std::move(entry)); + constant_payload_cache_bytes_ += size; +} + +bool MetalCommandProcessor::PopulateBindlessTables( + bool shared_memory_is_uav, MTL::ResourceUsage shared_memory_usage, + bool use_geometry_emulation, bool use_tessellation_emulation, + const UniformBufferInfo& uniforms) { + for (size_t stage = 0; stage < kStageCount; ++stage) { + if (current_bindless_stage_root_valid_[stage]) { + assert_true(current_bindless_stage_root_arguments_[stage].upload_frame == + frame_current_); + } + } + + constexpr size_t kGraphicsRootStage = kStageVertex; + const bool bindless_shared_memory_uav_mismatch = + current_bindless_shared_memory_is_uav_ != shared_memory_is_uav; + StageRootArgumentEntries entries = BuildGraphicsRootArgumentEntries( + uniforms, shared_memory_is_uav, use_tessellation_emulation); + const bool entries_were_valid = + current_bindless_stage_root_entries_valid_[kGraphicsRootStage]; + const bool root_rebuild_detail_telemetry = + cvars::metal_root_rebuild_detail_telemetry; + const uint64_t null_gpu = root_rebuild_detail_telemetry && null_buffer_ + ? null_buffer_->gpuAddress() + : 0; + struct RootCbvIdentity { + bool active = false; + MTL::Buffer* buffer = nullptr; + NS::UInteger offset = 0; + }; + auto root_slot_to_cbv = [&](size_t slot, size_t& stage, size_t& cbv, + bool& common) -> bool { + common = false; + switch (slot) { + case kGraphicsRootABSlotCBVSystem: + common = true; + cbv = kCbvSlotSystem; + return true; + case kGraphicsRootABSlotCBVBoolLoop: + common = true; + cbv = kCbvSlotBoolLoop; + return true; + case kGraphicsRootABSlotCBVVertexFloat: + case kGraphicsRootABSlotCBVHullFloat: + case kGraphicsRootABSlotCBVDomainFloat: + stage = kStageVertex; + cbv = kCbvSlotFloat; + return true; + case kGraphicsRootABSlotCBVVertexFetch: + case kGraphicsRootABSlotCBVHullFetch: + case kGraphicsRootABSlotCBVDomainFetch: + stage = kStageVertex; + cbv = kCbvSlotFetch; + return true; + case kGraphicsRootABSlotCBVVertexDescriptorIndices: + case kGraphicsRootABSlotCBVHullDescriptorIndices: + case kGraphicsRootABSlotCBVDomainDescriptorIndices: + stage = kStageVertex; + cbv = kCbvSlotDescriptorIndices; + return true; + case kGraphicsRootABSlotCBVPixelFloat: + stage = kStagePixel; + cbv = kCbvSlotFloat; + return true; + case kGraphicsRootABSlotCBVPixelFetch: + stage = kStagePixel; + cbv = kCbvSlotFetch; + return true; + case kGraphicsRootABSlotCBVPixelDescriptorIndices: + stage = kStagePixel; + cbv = kCbvSlotDescriptorIndices; + return true; + default: + return false; + } + }; + auto uniform_cbv_identity = [&](size_t stage, size_t cbv) -> RootCbvIdentity { + const uint32_t cbv_bit = uint32_t(1) << cbv; + if (!(uniforms.active_cbv_masks[stage] & cbv_bit)) { + return {}; + } + const UniformBufferInfo::Cbv& uniform_cbv = uniforms.cbvs[stage][cbv]; + return {true, uniform_cbv.buffer, uniform_cbv.offset}; + }; + auto current_cbv_identity = [&](size_t stage, size_t cbv) -> RootCbvIdentity { + const uint32_t cbv_bit = uint32_t(1) << cbv; + if (!(current_bindless_active_cbv_masks_[stage] & cbv_bit)) { + return {}; + } + return {true, current_bindless_cbv_buffers_[stage][cbv], + current_bindless_cbv_offsets_[stage][cbv]}; + }; + auto select_common_uniform_cbv_identity = [&](size_t cbv) -> RootCbvIdentity { + RootCbvIdentity vertex_identity = uniform_cbv_identity(kStageVertex, cbv); + if (vertex_identity.active) { + return vertex_identity; + } + return uniform_cbv_identity(kStagePixel, cbv); + }; + auto select_common_current_cbv_identity = [&](size_t cbv) -> RootCbvIdentity { + RootCbvIdentity vertex_identity = current_cbv_identity(kStageVertex, cbv); + if (vertex_identity.active) { + return vertex_identity; + } + return current_cbv_identity(kStagePixel, cbv); + }; + size_t slots_patched = 0; + bool descriptor_indices_pointer_mismatch = false; + bool other_cbv_pointer_mismatch = false; + bool root_resource_identity_changed = false; + bool root_resource_identity_same = false; + for (size_t slot = 0; slot < entries.size(); ++slot) { + const bool slot_changed = + entries_were_valid + ? current_bindless_stage_root_entries_[kGraphicsRootStage][slot] != + entries[slot] + : entries[slot] != 0; + if (!slot_changed) { + continue; + } + ++slots_patched; + if (slot < backend_telemetry_.bindless_root_arg_slot_patches.size()) { + ++backend_telemetry_.bindless_root_arg_slot_patches[slot]; + } + if (root_rebuild_detail_telemetry) { + const bool previous_slot_active = + entries_were_valid && + current_bindless_stage_root_entries_[kGraphicsRootStage][slot] != + null_gpu; + const bool new_slot_active = entries[slot] != null_gpu; + size_t cbv_stage = kStageVertex; + size_t cbv_slot = 0; + bool common_cbv = false; + const bool root_slot_is_cbv = + root_slot_to_cbv(slot, cbv_stage, cbv_slot, common_cbv); + if (root_slot_is_cbv) { + RootCbvIdentity previous_identity = + common_cbv ? select_common_current_cbv_identity(cbv_slot) + : current_cbv_identity(cbv_stage, cbv_slot); + RootCbvIdentity new_identity = + common_cbv ? select_common_uniform_cbv_identity(cbv_slot) + : uniform_cbv_identity(cbv_stage, cbv_slot); + previous_identity.active &= previous_slot_active; + new_identity.active &= new_slot_active; + previous_identity.buffer = + previous_identity.active ? previous_identity.buffer : nullptr; + new_identity.buffer = + new_identity.active ? new_identity.buffer : nullptr; + if (previous_identity.buffer == new_identity.buffer && + previous_identity.buffer && + previous_identity.offset != new_identity.offset) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailSameBufferOffsetChanged]; + root_resource_identity_same = true; + } else if (previous_identity.buffer != new_identity.buffer) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailDifferentBuffer]; + root_resource_identity_changed = true; + } else if (previous_identity.buffer) { + root_resource_identity_same = true; } } else { - // Triangle: 3 edge factors per patch. - struct TriFactors { - uint16_t edge[3]; - uint16_t inside; - }; - static_assert(sizeof(TriFactors) == 8); - auto* factors = reinterpret_cast(factor_data); - for (uint32_t i = 0; i < patch_count; ++i) { - // Read 3 edge factors from guest memory. - float ef[3]; - for (uint32_t j = 0; j < 3; ++j) { - uint32_t addr = factor_base + (i * 3 + j) * sizeof(float); - float raw; - std::memcpy(&raw, xbox_ram + addr, sizeof(float)); - raw = xenos::GpuSwap(raw, index_endian); - ef[j] = std::clamp(raw + 1.0f, factor_min, factor_max); - } - // Map Xbox 360 edge order to Metal: - // Metal edge[0] = U0 (v1->v2) = ef[1] - // Metal edge[1] = V0 (v2->v0) = ef[2] - // Metal edge[2] = W0 (v0->v1) = ef[0] - // (from adaptive_triangle.hs.glsl) - factors[i].edge[0] = f32_to_f16(ef[1]); - factors[i].edge[1] = f32_to_f16(ef[2]); - factors[i].edge[2] = f32_to_f16(ef[0]); - // Inside factor = minimum of all edge factors. - factors[i].inside = - f32_to_f16(std::min(std::min(ef[0], ef[1]), ef[2])); + root_resource_identity_changed = true; + } + } + if (entries_were_valid) { + switch (slot) { + case kGraphicsRootABSlotCBVVertexDescriptorIndices: + case kGraphicsRootABSlotCBVHullDescriptorIndices: + case kGraphicsRootABSlotCBVDomainDescriptorIndices: + case kGraphicsRootABSlotCBVPixelDescriptorIndices: + descriptor_indices_pointer_mismatch = true; + break; + case kGraphicsRootABSlotCBVSystem: + case kGraphicsRootABSlotCBVVertexFloat: + case kGraphicsRootABSlotCBVBoolLoop: + case kGraphicsRootABSlotCBVVertexFetch: + case kGraphicsRootABSlotCBVHullFloat: + case kGraphicsRootABSlotCBVHullFetch: + case kGraphicsRootABSlotCBVDomainFloat: + case kGraphicsRootABSlotCBVDomainFetch: + case kGraphicsRootABSlotCBVPixelFloat: + case kGraphicsRootABSlotCBVPixelFetch: + other_cbv_pointer_mismatch = true; + break; + default: + break; + } + } + } + + const bool root_valid = + current_bindless_stage_root_valid_[kGraphicsRootStage] && + current_bindless_stage_root_arguments_[kGraphicsRootStage].valid; + const bool root_update_needed = !root_valid || !entries_were_valid || + slots_patched || + bindless_shared_memory_uav_mismatch; + if (root_update_needed) { + if (root_rebuild_detail_telemetry) { + const size_t slots_changed_bin = + std::min(slots_patched, + backend_telemetry_.bindless_root_slots_changed.size() - 1); + ++backend_telemetry_.bindless_root_slots_changed[slots_changed_bin]; + if (descriptor_indices_pointer_mismatch && !other_cbv_pointer_mismatch) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailDescriptorIndicesOnly]; + } else if (!descriptor_indices_pointer_mismatch && + other_cbv_pointer_mismatch) { + ++backend_telemetry_ + .bindless_root_rebuild_details[kBindlessRootDetailOtherCbvOnly]; + } else if (descriptor_indices_pointer_mismatch && + other_cbv_pointer_mismatch) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailMixedDescriptorAndOther]; + } + if (root_resource_identity_changed) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailResourceIdentityChanged]; + } else if (root_resource_identity_same || slots_patched) { + ++backend_telemetry_.bindless_root_rebuild_details + [kBindlessRootDetailResourceIdentitySame]; + } + } + const bool frame_open_rebuild = + current_bindless_stage_root_frame_open_rebuild_pending_ + [kGraphicsRootStage]; + if (frame_open_rebuild) { + ++backend_telemetry_ + .bindless_root_rebuild_reasons[kBindlessRootRebuildFrameOpen]; + } + if (!frame_open_rebuild && descriptor_indices_pointer_mismatch) { + ++backend_telemetry_.bindless_root_rebuild_reasons + [kBindlessRootRebuildDescriptorIndicesPointerChange]; + } + if (!frame_open_rebuild && other_cbv_pointer_mismatch) { + ++backend_telemetry_.bindless_root_rebuild_reasons + [kBindlessRootRebuildOtherCbvPointerChange]; + } + if (bindless_shared_memory_uav_mismatch) { + ++backend_telemetry_.bindless_root_rebuild_reasons + [kBindlessRootRebuildSharedMemoryUavChange]; + } + if (entries_were_valid && !slots_patched && root_valid) { + ++backend_telemetry_.bindless_root_arg_noop_updates[kGraphicsRootStage]; + } else { + StageRootArgumentAllocation allocation; + if (!AllocateStageRootArgument(kGraphicsRootStage, entries, allocation)) { + return false; + } + current_bindless_stage_root_arguments_[kGraphicsRootStage] = allocation; + ++backend_telemetry_.bindless_root_allocations[kGraphicsRootStage]; + backend_telemetry_.bindless_root_arg_slots_patched[kGraphicsRootStage] += + slots_patched; + backend_telemetry_.bindless_root_arg_bytes_copied[kGraphicsRootStage] += + kTopLevelABBytesPerTable; + ++current_bindless_stage_root_serials_[kGraphicsRootStage]; + if (!current_bindless_stage_root_serials_[kGraphicsRootStage]) { + current_bindless_stage_root_serials_[kGraphicsRootStage] = 1; + } + } + current_bindless_stage_root_entries_[kGraphicsRootStage] = entries; + current_bindless_stage_root_entries_valid_[kGraphicsRootStage] = true; + current_bindless_stage_root_valid_[kGraphicsRootStage] = true; + current_bindless_stage_root_frame_open_rebuild_pending_ + [kGraphicsRootStage] = false; + } else { + ++backend_telemetry_.bindless_root_reuse_hits[kGraphicsRootStage]; + } + + for (size_t stage = 0; stage < kStageCount; ++stage) { + for (size_t cbv = 0; cbv < kCbvSlotCount; ++cbv) { + const UniformBufferInfo::Cbv& uniform_cbv = uniforms.cbvs[stage][cbv]; + const bool cbv_active = + uniforms.active_cbv_masks[stage] & (uint32_t(1) << cbv); + current_bindless_cbv_gpu_addresses_[stage][cbv] = + cbv_active ? (uniform_cbv.gpu_address ? uniform_cbv.gpu_address + : null_buffer_->gpuAddress()) + : null_buffer_->gpuAddress(); + current_bindless_cbv_buffers_[stage][cbv] = + cbv_active ? uniform_cbv.buffer : nullptr; + current_bindless_cbv_offsets_[stage][cbv] = + cbv_active ? uniform_cbv.offset : 0; + } + current_bindless_active_cbv_masks_[stage] = + uniforms.active_cbv_masks[stage]; + } + current_bindless_stage_root_valid_[kStagePixel] = false; + current_bindless_stage_root_frame_open_rebuild_pending_[kStagePixel] = false; + current_bindless_stage_root_serials_[kStagePixel] = 0; + current_bindless_shared_memory_is_uav_ = shared_memory_is_uav; + + PublishBindlessFixedResourceSet(shared_memory_usage); + PublishBindlessRootResourceSet(uniforms); + ApplyRenderEncoderResourceSets(); + + const bool use_mesh_path = + use_geometry_emulation || use_tessellation_emulation; + const bool root_argument_path_needs_update = + render_encoder_bindless_table_bind_mesh_path_ != use_mesh_path || + render_encoder_bindless_table_bind_tessellation_ != + use_tessellation_emulation; + { + constexpr size_t kGraphicsRootStage = kStageVertex; + const StageRootArgumentAllocation& graphics_root_arguments = + current_bindless_stage_root_arguments_[kGraphicsRootStage]; + const uint64_t graphics_root_serial = + current_bindless_stage_root_serials_[kGraphicsRootStage]; + assert_true(graphics_root_arguments.valid); + assert_true(graphics_root_arguments.upload_frame == frame_current_); + const bool vertex_root_argument_binding_needs_update = + render_encoder_bindless_stage_root_bind_serials_[kStageVertex] != + graphics_root_serial || + root_argument_path_needs_update; + const bool pixel_root_argument_binding_needs_update = + render_encoder_bindless_stage_root_bind_serials_[kStagePixel] != + graphics_root_serial; + const bool root_argument_bindings_need_update = + vertex_root_argument_binding_needs_update || + pixel_root_argument_binding_needs_update; + if (use_mesh_path) { + if (vertex_root_argument_binding_needs_update) { + SetRenderEncoderObjectBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferBindPoint); + SetRenderEncoderMeshBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferBindPoint); + + if (use_tessellation_emulation) { + SetRenderEncoderObjectBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferHullDomainBindPoint); + SetRenderEncoderMeshBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferHullDomainBindPoint); } } + if (pixel_root_argument_binding_needs_update) { + SetRenderEncoderFragmentBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferBindPoint); + } + + if (!heap_binds_set_on_encoder_) { + SetRenderEncoderObjectBuffer(view_bindless_heap_, 0, + kIRDescriptorHeapBindPoint); + SetRenderEncoderMeshBuffer(view_bindless_heap_, 0, + kIRDescriptorHeapBindPoint); + SetRenderEncoderFragmentBuffer(view_bindless_heap_, 0, + kIRDescriptorHeapBindPoint); + SetRenderEncoderObjectBuffer(sampler_bindless_heap_, 0, + kIRSamplerHeapBindPoint); + SetRenderEncoderMeshBuffer(sampler_bindless_heap_, 0, + kIRSamplerHeapBindPoint); + SetRenderEncoderFragmentBuffer(sampler_bindless_heap_, 0, + kIRSamplerHeapBindPoint); + heap_binds_set_on_encoder_ = true; + } } else { - // ------------------------------------------------------------------ - // Uniform tessellation (discrete / continuous modes). - // All patches get the same factor from VGT_HOS_MAX_TESS_LEVEL. - // ------------------------------------------------------------------ - uint16_t ef = f32_to_f16(max_tess); - if (is_quad_domain) { - struct QuadFactors { - uint16_t edge[4]; - uint16_t inside[2]; - }; - static_assert(sizeof(QuadFactors) == 12); - auto* factors = reinterpret_cast(factor_data); - for (uint32_t i = 0; i < patch_count; ++i) { - factors[i].edge[0] = ef; - factors[i].edge[1] = ef; - factors[i].edge[2] = ef; - factors[i].edge[3] = ef; - factors[i].inside[0] = ef; - factors[i].inside[1] = ef; - } - } else { - struct TriFactors { - uint16_t edge[3]; - uint16_t inside; - }; - static_assert(sizeof(TriFactors) == 8); - auto* factors = reinterpret_cast(factor_data); - for (uint32_t i = 0; i < patch_count; ++i) { - factors[i].edge[0] = ef; - factors[i].edge[1] = ef; - factors[i].edge[2] = ef; - factors[i].inside = ef; + if (vertex_root_argument_binding_needs_update) { + SetRenderEncoderVertexBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferBindPoint); + } + if (pixel_root_argument_binding_needs_update) { + SetRenderEncoderFragmentBuffer(graphics_root_arguments.buffer, + graphics_root_arguments.offset, + kIRArgumentBufferBindPoint); + } + + if (!heap_binds_set_on_encoder_) { + SetRenderEncoderVertexBuffer(view_bindless_heap_, 0, + kIRDescriptorHeapBindPoint); + SetRenderEncoderFragmentBuffer(view_bindless_heap_, 0, + kIRDescriptorHeapBindPoint); + SetRenderEncoderVertexBuffer(sampler_bindless_heap_, 0, + kIRSamplerHeapBindPoint); + SetRenderEncoderFragmentBuffer(sampler_bindless_heap_, 0, + kIRSamplerHeapBindPoint); + heap_binds_set_on_encoder_ = true; + } + } + if (vertex_root_argument_binding_needs_update) { + render_encoder_bindless_stage_root_bind_serials_[kStageVertex] = + graphics_root_serial; + } + if (pixel_root_argument_binding_needs_update) { + render_encoder_bindless_stage_root_bind_serials_[kStagePixel] = + graphics_root_serial; + } + if (root_argument_bindings_need_update) { + render_encoder_bindless_table_bind_mesh_path_ = use_mesh_path; + render_encoder_bindless_table_bind_tessellation_ = + use_tessellation_emulation; + } + } + + return true; +} + +bool MetalCommandProcessor::PrepareGuestDMAIndexBufferForMemexport( + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + PreparedIndexBuffer& prepared_index_buffer_out) { + prepared_index_buffer_out = {}; + if (primitive_processing_result.index_buffer_type != + PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA) { + return true; + } + if (!shared_memory_) { + XELOGE("IssueDraw: shared memory is unavailable for guest DMA index copy"); + return false; + } + + MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer(); + if (!shared_mem_buffer) { + XELOGE("IssueDraw: shared memory buffer is unavailable for index copy"); + return false; + } + + uint32_t index_stride = primitive_processing_result.host_index_format == + xenos::IndexFormat::kInt16 + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint64_t index_bytes = + uint64_t(primitive_processing_result.host_draw_vertex_count) * + index_stride; + uint64_t guest_index_base = primitive_processing_result.guest_index_base; + if (guest_index_base > SharedMemory::kBufferSize || + SharedMemory::kBufferSize - guest_index_base < index_bytes) { + XELOGW("Index buffer range out of bounds (base=0x{:08X} size={} count={})", + static_cast(guest_index_base), index_bytes, + primitive_processing_result.host_draw_vertex_count); + return false; + } + + // Metal's buffer-to-buffer blit requires 4-byte offsets and sizes on macOS. + uint64_t source_copy_offset = guest_index_base & ~uint64_t(3); + uint64_t source_delta = guest_index_base - source_copy_offset; + uint64_t copy_size = xe::align(source_delta + index_bytes, uint64_t(4)); + if (source_copy_offset > SharedMemory::kBufferSize || + SharedMemory::kBufferSize - source_copy_offset < copy_size) { + XELOGW( + "Aligned index buffer copy range out of bounds " + "(base=0x{:08X} size={})", + static_cast(source_copy_offset), copy_size); + return false; + } + if (!RequestSharedMemoryRange(SharedMemoryRequestReason::kIndexCopySource, + static_cast(source_copy_offset), + static_cast(copy_size))) { + XELOGE( + "IssueDraw: failed to request guest DMA index copy range at 0x{:08X} " + "(size {})", + static_cast(source_copy_offset), copy_size); + return false; + } + + MTL::CommandBuffer* command_buffer = + RequestTransferCommandBuffer(TransferRequestSource::kGuestIndexCopy); + if (!command_buffer) { + XELOGE("IssueDraw: failed to get command buffer for index copy"); + return false; + } + shared_memory_->MarkGpuAccess(static_cast(source_copy_offset), + static_cast(copy_size), + GetCurrentSubmission()); + + MTL::Buffer* scratch_buffer = nullptr; + size_t scratch_offset = 0; + uint64_t scratch_gpu_address = 0; + MTL::Buffer* direct_scratch_buffer = nullptr; + if (constant_buffer_pool_ && + copy_size <= ui::GraphicsUploadBufferPool::kDefaultPageSize) { + (void)constant_buffer_pool_->Request( + frame_current_, static_cast(copy_size), 4, &scratch_buffer, + scratch_offset, scratch_gpu_address); + } + if (!scratch_buffer) { + direct_scratch_buffer = device_->newBuffer( + static_cast(copy_size), MTL::ResourceStorageModePrivate); + if (!direct_scratch_buffer) { + XELOGE( + "IssueDraw: failed to allocate scratch index buffer for guest DMA " + "memexport draw"); + return false; + } + scratch_buffer = direct_scratch_buffer; + scratch_offset = 0; + } + + MTL::BlitCommandEncoder* blit_encoder = command_buffer->blitCommandEncoder(); + if (!blit_encoder) { + if (direct_scratch_buffer) { + direct_scratch_buffer->release(); + } + XELOGE("IssueDraw: failed to create blit encoder for index copy"); + return false; + } + blit_encoder->setLabel( + NS::String::string("XeniaGuestDMAIndexCopy", NS::UTF8StringEncoding)); + if (!EncodeSharedMemoryBlitReadDependency( + blit_encoder, static_cast(source_copy_offset), + static_cast(copy_size))) { + blit_encoder->endEncoding(); + if (direct_scratch_buffer) { + direct_scratch_buffer->release(); + } + return false; + } + blit_encoder->copyFromBuffer( + shared_mem_buffer, static_cast(source_copy_offset), + scratch_buffer, static_cast(scratch_offset), + static_cast(copy_size)); + blit_encoder->endEncoding(); + + if (direct_scratch_buffer) { + retired_memexport_index_buffers_.push_back( + {direct_scratch_buffer, GetCurrentSubmission()}); + } + prepared_index_buffer_out.buffer = scratch_buffer; + prepared_index_buffer_out.offset = scratch_offset + source_delta; + return true; +} + +MetalCommandProcessor::PreparedDrawRenderTargetKey +MetalCommandProcessor::BuildPreparedDrawRenderTargetKey( + const RegisterFile& regs, bool is_rasterization_done, + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask) const { + PreparedDrawRenderTargetKey key; + key.rb_surface_info = regs.Get().value; + key.rb_depth_info = regs.Get().value; + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + key.rb_color_info[i] = + regs.Get(reg::RB_COLOR_INFO::rt_register_indices[i]) + .value; + } + key.normalized_depth_control = normalized_depth_control.value; + key.normalized_color_mask = normalized_color_mask; + key.is_rasterization_done = is_rasterization_done; + return key; +} + +bool MetalCommandProcessor::PreparedDrawQueueHasActiveZPD() const { + if (GetZPDMode() == ZPDMode::kFake) { + return false; + } + return zpd_active_segment_.logical_active || + zpd_active_segment_.segment_active || + zpd_active_segment_.segment_pending_begin || + zpd_active_query_.is_open(); +} + +void MetalCommandProcessor::RecordPreparedDrawQueueReject( + PreparedDrawQueueRejectReason reject_reason) { + const size_t reject_index = static_cast(reject_reason); + if (reject_index < + backend_telemetry_.prepared_draw_queue_reject_reasons.size()) { + ++backend_telemetry_.prepared_draw_queue_reject_reasons[reject_index]; + } +} + +bool MetalCommandProcessor::CanQueuePreparedDraw( + const PreparedDraw& draw, + PreparedDrawQueueRejectReason& reject_reason) const { + reject_reason = PreparedDrawQueueRejectReason::kNone; + if (draw.memexport_used) { + reject_reason = PreparedDrawQueueRejectReason::kMemexport; + return false; + } + if (draw.has_pending_draw_pass_transfers) { + reject_reason = PreparedDrawQueueRejectReason::kPendingDrawPassTransfers; + return false; + } + if (PreparedDrawQueueHasActiveZPD()) { + reject_reason = PreparedDrawQueueRejectReason::kZPDActive; + return false; + } + if (draw.materialization_ranges.empty() && !draw.texture_upload_needed) { + reject_reason = PreparedDrawQueueRejectReason::kNoSharedMemoryRanges; + return false; + } + if (!draw.has_invalid_shared_memory && prepared_draw_queue_.empty() && + current_render_encoder_ && !draw.texture_upload_needed) { + reject_reason = PreparedDrawQueueRejectReason::kResidentWithoutActiveQueue; + return false; + } + if (prepared_draw_queue_render_target_key_valid_ && + draw.render_target_key != prepared_draw_queue_render_target_key_) { + reject_reason = PreparedDrawQueueRejectReason::kRenderTargetKeyMismatch; + return false; + } + + size_t range_count = draw.materialization_ranges.size(); + uint64_t byte_count = 0; + for (const SharedMemory::Range& range : draw.materialization_ranges) { + byte_count += range.length; + } + for (const PreparedDraw& queued_draw : prepared_draw_queue_) { + range_count += queued_draw.materialization_ranges.size(); + for (const SharedMemory::Range& range : + queued_draw.materialization_ranges) { + byte_count += range.length; + } + } + if (prepared_draw_queue_.size() + 1 > kPreparedDrawQueueMaxDraws || + range_count > kPreparedDrawQueueMaxRanges || + byte_count > kPreparedDrawQueueMaxBytes) { + reject_reason = PreparedDrawQueueRejectReason::kQueueBudget; + return false; + } + return true; +} + +bool MetalCommandProcessor::EncodePreparedDraw(const PreparedDraw& draw) { + if (!BeginRenderEncoderForDraw(draw.fallback_depth_attachment_required)) { + static bool no_command_buffer_logged = false; + if (!no_command_buffer_logged) { + no_command_buffer_logged = true; + XELOGE( + "IssueDraw: failed to begin Metal command buffer/render encoder; " + "skipping draws until uniforms buffer allocation recovers"); + } + return false; + } + if (render_target_cache_ && + render_target_cache_->HasPendingDrawPassTransfers()) { + MetalRenderTargetCache::DrawPassTransferEncoderMutationMask + transfer_mutations = + MetalRenderTargetCache::kDrawPassTransferEncoderMutationNone; + if (!render_target_cache_->EncodePendingDrawPassTransfers( + current_render_encoder_, current_render_pass_descriptor_, + &transfer_mutations)) { + if (!render_target_cache_->FlushPendingDrawPassTransfers()) { + return false; + } + if (!BeginRenderEncoderForDraw(draw.fallback_depth_attachment_required)) { + return false; + } + transfer_mutations = + MetalRenderTargetCache::kDrawPassTransferEncoderMutationNone; + } + InvalidateRenderEncoderStateAfterDrawPassTransfers(transfer_mutations); + } + + if (draw.shared_memory_hazard_range_count && + PendingSharedMemoryWritesOverlapRanges( + draw.shared_memory_hazard_ranges.data(), + draw.shared_memory_hazard_range_count)) { + if (!EncodeSharedMemoryRenderReadDependencies( + draw.shared_memory_hazard_ranges.data(), + draw.shared_memory_hazard_range_count, + draw.shared_memory_consumer_stages)) { + return false; + } + if (!current_render_encoder_) { + return false; + } + } + if (shared_memory_ && draw.shared_memory_hazard_range_count) { + uint64_t submission = GetCurrentSubmission(); + for (uint32_t i = 0; i < draw.shared_memory_hazard_range_count; ++i) { + const SharedMemoryRange& range = draw.shared_memory_hazard_ranges[i]; + shared_memory_->MarkGpuAccess(range.start, range.length, submission); + } + } + + if (current_render_pipeline_state_ != draw.pipeline) { + current_render_encoder_->setRenderPipelineState(draw.pipeline); + current_render_pipeline_state_ = draw.pipeline; + ++backend_telemetry_.pipeline_sets; + } else { + ++backend_telemetry_.pipeline_set_skips; + } + if (draw.use_tessellation_emulation) { + if (!tessellator_tables_buffer_) { + XELOGE("Tessellation emulation requires tessellator tables buffer"); + return false; + } + SetRenderEncoderObjectBuffer(tessellator_tables_buffer_, 0, + kIRRuntimeTessellatorTablesBindPoint); + SetRenderEncoderMeshBuffer(tessellator_tables_buffer_, 0, + kIRRuntimeTessellatorTablesBindPoint); + UseRenderEncoderResource(tessellator_tables_buffer_, + MTL::ResourceUsageRead); + } + + if (draw.prepare_uniforms) { + ApplyDrawDynamicState(draw.dynamic_state); + + RestoreBindlessTextureResourceSet(draw.texture_resource_set); + + if (!PopulateBindlessTables( + draw.shared_memory_is_uav, draw.shared_memory_usage, + draw.use_geometry_emulation, draw.use_tessellation_emulation, + draw.uniforms)) { + return false; + } + } + + OpenQuerySegment(false); + + return DispatchDraw( + draw.primitive_processing_result, draw.use_tessellation_emulation, + draw.tessellation_pipeline_state, draw.use_geometry_emulation, + draw.geometry_pipeline_state, draw.shared_memory_is_uav, + draw.shared_memory_usage, draw.memexport_used, + draw.memexport_write_stages, draw.uses_vertex_fetch, + draw.prepare_uniforms, + draw.prepared_guest_dma_index_buffer.buffer + ? &draw.prepared_guest_dma_index_buffer + : nullptr, + draw.vertex_bindings, draw.vertex_ranges.data(), draw.vertex_range_count, + draw.has_index_buffer_info ? &draw.index_buffer_info : nullptr, + draw.memexport_ranges); +} + +bool MetalCommandProcessor::FlushPreparedDrawQueue( + PreparedDrawFlushReason reason) { + if (prepared_draw_queue_.empty()) { + return true; + } + const size_t reason_index = static_cast(reason); + if (reason_index < + backend_telemetry_.prepared_draw_queue_flush_reasons.size()) { + ++backend_telemetry_.prepared_draw_queue_flush_reasons[reason_index]; + } + ++backend_telemetry_.prepared_draw_queue_flushes; + if (prepared_draw_queue_.size() == 1) { + ++backend_telemetry_.prepared_draw_queue_single_draw_flushes; + } + + std::vector draws = std::move(prepared_draw_queue_); + prepared_draw_queue_.clear(); + prepared_draw_queue_render_target_key_valid_ = false; + prepared_draw_queue_render_target_key_ = {}; + + std::vector ranges; + size_t range_count = 0; + uint64_t byte_count = 0; + bool has_invalid_shared_memory = false; + bool has_texture_materialization = false; + for (const PreparedDraw& draw : draws) { + range_count += draw.materialization_ranges.size(); + has_invalid_shared_memory = + has_invalid_shared_memory || draw.has_invalid_shared_memory; + has_texture_materialization = + has_texture_materialization || + draw.texture_materialization_plan.NeedsTextureUpload(); + for (const SharedMemory::Range& range : draw.materialization_ranges) { + byte_count += range.length; + } + } + ranges.reserve(range_count); + for (const PreparedDraw& draw : draws) { + ranges.insert(ranges.end(), draw.materialization_ranges.begin(), + draw.materialization_ranges.end()); + } + backend_telemetry_.prepared_draw_queue_draws_flushed += draws.size(); + backend_telemetry_.prepared_draw_queue_ranges_flushed += ranges.size(); + backend_telemetry_.prepared_draw_queue_bytes_flushed += byte_count; + if (has_invalid_shared_memory) { + ++backend_telemetry_.prepared_draw_queue_invalid_flushes; + } + + const bool previous_flushing = flushing_prepared_draw_queue_; + flushing_prepared_draw_queue_ = true; + + if (has_invalid_shared_memory && shared_memory_ && !ranges.empty()) { + PrepareSharedMemoryUploadBeforeDrawPass( + ranges.data(), static_cast(ranges.size())); + if (!RequestSharedMemoryRanges( + SharedMemoryRequestReason::kDrawMaterialization, ranges.data(), + static_cast(ranges.size()))) { + XELOGE("Failed to request {} prepared-draw shared-memory ranges", + ranges.size()); + flushing_prepared_draw_queue_ = previous_flushing; + return false; + } + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kMaterializationDrain); + } + + if (has_texture_materialization && texture_cache_) { + if (current_render_encoder_) { + EndRenderEncoder(RenderEncoderEndReason::kTextureUploadBeforeDrawPass); + } + if (!EnsureCommandBuffer()) { + flushing_prepared_draw_queue_ = previous_flushing; + return false; + } + texture_cache_->BeginTextureUploadBatch(); + bool texture_materialization_succeeded = true; + for (PreparedDraw& draw : draws) { + if (!draw.texture_materialization_plan.NeedsTextureUpload()) { + continue; + } + ++backend_telemetry_.prepared_draw_queue_texture_plans_flushed; + backend_telemetry_.prepared_draw_queue_texture_loads_planned += + draw.texture_materialization_plan.planned_load_count; + if (!texture_cache_->ExecuteTextureMaterialization( + draw.texture_materialization_plan)) { + texture_materialization_succeeded = false; + break; + } + backend_telemetry_.prepared_draw_queue_texture_loads_executed += + draw.texture_materialization_plan.executed_load_count; + } + if (!texture_cache_->EndTextureUploadBatch()) { + texture_materialization_succeeded = false; + } + if (!texture_materialization_succeeded) { + flushing_prepared_draw_queue_ = previous_flushing; + return false; + } + } + + for (const PreparedDraw& draw : draws) { + if (!EncodePreparedDraw(draw)) { + flushing_prepared_draw_queue_ = previous_flushing; + return false; + } + } + + flushing_prepared_draw_queue_ = previous_flushing; + return true; +} + +bool MetalCommandProcessor::SubmitPreparedDraw(PreparedDraw&& draw) { + PreparedDrawQueueRejectReason reject_reason = + PreparedDrawQueueRejectReason::kNone; + if (CanQueuePreparedDraw(draw, reject_reason)) { + if (prepared_draw_queue_.empty()) { + prepared_draw_queue_render_target_key_ = draw.render_target_key; + prepared_draw_queue_render_target_key_valid_ = true; + } + prepared_draw_queue_.push_back(std::move(draw)); + ++backend_telemetry_.prepared_draw_queue_appends; + if (prepared_draw_queue_.size() >= kPreparedDrawQueueMaxDraws) { + return FlushPreparedDrawQueue(PreparedDrawFlushReason::kQueueBudget); + } + return true; + } + + RecordPreparedDrawQueueReject(reject_reason); + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kQueueReject)) { + return false; + } + + if (draw.has_invalid_shared_memory && shared_memory_ && + !draw.materialization_ranges.empty()) { + PrepareSharedMemoryUploadBeforeDrawPass( + draw.materialization_ranges.data(), + static_cast(draw.materialization_ranges.size())); + if (!RequestSharedMemoryRanges( + SharedMemoryRequestReason::kDrawMaterialization, + draw.materialization_ranges.data(), + static_cast(draw.materialization_ranges.size()))) { + XELOGE("Failed to request {} current-draw shared-memory ranges", + draw.materialization_ranges.size()); + return false; + } + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kMaterializationDrain); + } + + if (draw.texture_materialization_plan.NeedsTextureUpload() && + texture_cache_) { + if (current_render_encoder_) { + EndRenderEncoder(RenderEncoderEndReason::kTextureUploadBeforeDrawPass); + } + if (!EnsureCommandBuffer()) { + return false; + } + if (!texture_cache_->ExecuteTextureMaterialization( + draw.texture_materialization_plan)) { + return false; + } + } + + return EncodePreparedDraw(draw); +} + +bool MetalCommandProcessor::DispatchDraw( + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + bool use_tessellation_emulation, + MetalPipelineCache::TessellationPipelineState* tessellation_pipeline_state, + bool use_geometry_emulation, + MetalPipelineCache::GeometryPipelineState* geometry_pipeline_state, + bool shared_memory_is_uav, MTL::ResourceUsage shared_memory_usage, + bool memexport_used, MTL::RenderStages memexport_write_stages, + bool uses_vertex_fetch, bool shared_memory_resource_registered, + const PreparedIndexBuffer* prepared_guest_dma_index_buffer, + const std::vector& vb_bindings, + const VertexBindingRange* vertex_ranges, uint32_t vertex_range_count, + const IndexBufferInfo* index_buffer_info, + const std::vector& memexport_ranges) { + auto use_shared_memory_resource_if_needed = [&](MTL::Buffer* buffer) { + if (!buffer || shared_memory_resource_registered) { + return; + } + UseRenderEncoderResource(buffer, shared_memory_usage); + }; + + // Bind vertex buffers / descriptors. + if (use_geometry_emulation || use_tessellation_emulation) { + IRRuntimeVertexBuffers vertex_buffers = {}; + MTL::Buffer* shared_mem_buffer = + shared_memory_ ? shared_memory_->GetBuffer() : nullptr; + if (shared_mem_buffer) { + use_shared_memory_resource_if_needed(shared_mem_buffer); + for (uint32_t i = 0; i < vertex_range_count; ++i) { + const auto& range = vertex_ranges[i]; + size_t binding_index = range.binding_index; + if (binding_index < + (sizeof(vertex_buffers) / sizeof(vertex_buffers[0]))) { + vertex_buffers[binding_index].addr = + shared_mem_buffer->gpuAddress() + range.offset; + vertex_buffers[binding_index].length = range.length; + vertex_buffers[binding_index].stride = range.stride; } } } - - // Draw with tessellation. - assert_not_null(tess_factor_buffer_); - UseRenderEncoderResource(tess_factor_buffer_, MTL::ResourceUsageRead); - current_render_encoder_->setTessellationFactorBuffer(tess_factor_buffer_, 0, - 0); - // drawPatches signature: - // numberOfPatchControlPoints, patchStart, patchCount, - // patchIndexBuffer, patchIndexBufferOffset, - // instanceCount, baseInstance - // patchIndexBuffer = nullptr since patches are not indexed (the domain - // shader reads control points from shared memory via vertex ID). - current_render_encoder_->drawPatches( - NS::UInteger(cp_per_patch), NS::UInteger(0), NS::UInteger(patch_count), - nullptr, // patchIndexBuffer (non-indexed patches) - 0, // patchIndexBufferOffset - NS::UInteger(1), // instanceCount - NS::UInteger(0)); // baseInstance + // MSC manual: bind IRRuntimeVertexBuffers at kIRVertexBufferBindPoint (6) + // for the object stage when using geometry emulation. + current_render_encoder_->setObjectBytes( + vertex_buffers, sizeof(vertex_buffers), kIRVertexBufferBindPoint); + InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage::kObject, + kIRVertexBufferBindPoint); + } else if (uses_vertex_fetch) { + // Vertex fetch shaders read directly from shared memory via SRV, so avoid + // stage-in bindings that can trigger invalid buffer loads. + if (shared_memory_) { + if (MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer()) { + use_shared_memory_resource_if_needed(shared_mem_buffer); + } + } } else { - // --------------------------------------------------------------- - // Non-tessellated draw: standard primitives. - // --------------------------------------------------------------- + // Bind vertex buffers at kIRVertexBufferBindPoint (index 6+) for stage-in. + // The pipeline's vertex descriptor expects buffers at these indices, + // populated from the vertex fetch constants. The buffer addresses come from + // shared memory. + if (shared_memory_ && !vb_bindings.empty()) { + MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer(); + if (shared_mem_buffer) { + // Mark shared memory as used for reading + use_shared_memory_resource_if_needed(shared_mem_buffer); + + // Bind vertex buffers for each binding + for (uint32_t i = 0; i < vertex_range_count; ++i) { + const auto& range = vertex_ranges[i]; + uint64_t buffer_index = + kIRVertexBufferBindPoint + uint64_t(range.binding_index); + SetRenderEncoderVertexBuffer(shared_mem_buffer, range.offset, + buffer_index); + } + } + } else if (shared_memory_) { + // No vertex bindings, but still mark shared memory as resident + if (MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer()) { + use_shared_memory_resource_if_needed(shared_mem_buffer); + } + } + } + + auto validate_guest_index_range = [&](uint64_t index_base, + uint32_t index_count, + MTL::IndexType index_type) -> bool { + if (!shared_memory_) { + return false; + } + uint32_t index_stride = (index_type == MTL::IndexTypeUInt16) + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint64_t index_length = uint64_t(index_count) * index_stride; + if (index_base > SharedMemory::kBufferSize || + SharedMemory::kBufferSize - index_base < index_length) { + XELOGW( + "Index buffer range out of bounds (base=0x{:08X} size={} count={})", + static_cast(index_base), index_length, index_count); + return false; + } + return true; + }; + auto resolve_guest_dma_index_buffer = + [&](uint64_t guest_index_base, uint32_t index_count, + MTL::IndexType index_type, MTL::Buffer*& index_buffer_out, + uint64_t& index_offset_out) -> bool { + index_buffer_out = nullptr; + index_offset_out = 0; + if (memexport_used) { + if (!prepared_guest_dma_index_buffer || + !prepared_guest_dma_index_buffer->buffer) { + XELOGE( + "IssueDraw: guest DMA memexport index draw was not prepared for " + "GPU copy"); + return false; + } + index_buffer_out = prepared_guest_dma_index_buffer->buffer; + index_offset_out = prepared_guest_dma_index_buffer->offset; + return true; + } + if (!validate_guest_index_range(guest_index_base, index_count, + index_type)) { + return false; + } + MTL::Buffer* shared_mem_buffer = + shared_memory_ ? shared_memory_->GetBuffer() : nullptr; + if (!shared_mem_buffer) { + return false; + } + index_buffer_out = shared_mem_buffer; + index_offset_out = guest_index_base; + return true; + }; + + // Shared index buffer resolution used by tessellation, geometry, and + // standard indexed draw paths. Returns false on fatal error. + auto resolve_index_buffer = [&](MTL::IndexType index_type, + MTL::Buffer*& index_buffer_out, + uint64_t& index_offset_out) -> bool { + index_buffer_out = nullptr; + index_offset_out = 0; + switch (primitive_processing_result.index_buffer_type) { + case PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA: + if (!resolve_guest_dma_index_buffer( + primitive_processing_result.guest_index_base, + primitive_processing_result.host_draw_vertex_count, index_type, + index_buffer_out, index_offset_out)) { + XELOGE("IssueDraw: failed to resolve guest DMA index buffer"); + return false; + } + break; + case PrimitiveProcessor::ProcessedIndexBufferType::kHostConverted: + if (primitive_processor_) { + index_buffer_out = primitive_processor_->GetConvertedIndexBuffer( + primitive_processing_result.host_index_buffer_handle, + index_offset_out); + } + break; + case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForAuto: + case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA: + if (primitive_processor_) { + index_buffer_out = primitive_processor_->GetBuiltinIndexBuffer(); + index_offset_out = + primitive_processing_result.host_index_buffer_handle; + } + break; + default: + XELOGE("Unsupported index buffer type {}", + uint32_t(primitive_processing_result.index_buffer_type)); + return false; + } + if (!index_buffer_out) { + XELOGE("IssueDraw: index buffer is null for type {}", + uint32_t(primitive_processing_result.index_buffer_type)); + return false; + } + UseRenderEncoderResource(index_buffer_out, MTL::ResourceUsageRead); + return true; + }; + + if (use_tessellation_emulation) { + IRRuntimePrimitiveType tess_primitive = IRRuntimePrimitiveTypeTriangle; + switch (primitive_processing_result.host_primitive_type) { + case xenos::PrimitiveType::kTriangleList: + tess_primitive = IRRuntimePrimitiveType3ControlPointPatchlist; + break; + case xenos::PrimitiveType::kQuadList: + tess_primitive = IRRuntimePrimitiveType4ControlPointPatchlist; + break; + case xenos::PrimitiveType::kTrianglePatch: + tess_primitive = (primitive_processing_result.tessellation_mode == + xenos::TessellationMode::kAdaptive) + ? IRRuntimePrimitiveType3ControlPointPatchlist + : IRRuntimePrimitiveType1ControlPointPatchlist; + break; + case xenos::PrimitiveType::kQuadPatch: + tess_primitive = (primitive_processing_result.tessellation_mode == + xenos::TessellationMode::kAdaptive) + ? IRRuntimePrimitiveType4ControlPointPatchlist + : IRRuntimePrimitiveType1ControlPointPatchlist; + break; + default: + XELOGE( + "Host tessellated primitive type {} returned by the primitive " + "processor is not supported by the Metal tessellation path", + uint32_t(primitive_processing_result.host_primitive_type)); + return false; + } + + const IRRuntimeTessellationPipelineConfig& tess_config = + tessellation_pipeline_state->config; + + if (primitive_processing_result.index_buffer_type == + PrimitiveProcessor::ProcessedIndexBufferType::kNone) { + IRRuntimeDrawPatchesTessellationEmulation( + current_render_encoder_, tess_primitive, tess_config, 1, + primitive_processing_result.host_draw_vertex_count, 0, 0); + } else { + MTL::IndexType index_type = + (primitive_processing_result.host_index_format == + xenos::IndexFormat::kInt16) + ? MTL::IndexTypeUInt16 + : MTL::IndexTypeUInt32; + MTL::Buffer* index_buffer = nullptr; + uint64_t index_offset = 0; + if (!resolve_index_buffer(index_type, index_buffer, index_offset)) { + return false; + } + uint32_t index_stride = (index_type == MTL::IndexTypeUInt16) + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint32_t start_index = + index_stride ? uint32_t(index_offset / index_stride) : 0; + IRRuntimeDrawIndexedPatchesTessellationEmulation( + current_render_encoder_, tess_primitive, index_type, index_buffer, + tess_config, 1, primitive_processing_result.host_draw_vertex_count, 0, + 0, start_index); + } + } else if (use_geometry_emulation) { + IRRuntimePrimitiveType geometry_primitive = IRRuntimePrimitiveTypeTriangle; + switch (primitive_processing_result.host_primitive_type) { + case xenos::PrimitiveType::kPointList: + geometry_primitive = IRRuntimePrimitiveTypePoint; + break; + case xenos::PrimitiveType::kRectangleList: + geometry_primitive = IRRuntimePrimitiveTypeTriangle; + break; + case xenos::PrimitiveType::kQuadList: + geometry_primitive = IRRuntimePrimitiveTypeLineWithAdj; + break; + default: + XELOGE( + "Host primitive type {} returned by the primitive processor is not " + "supported by the Metal geometry path", + uint32_t(primitive_processing_result.host_primitive_type)); + return false; + } + + IRRuntimeGeometryPipelineConfig geometry_config = {}; + geometry_config.gsVertexSizeInBytes = + geometry_pipeline_state->gs_vertex_size_in_bytes; + geometry_config.gsMaxInputPrimitivesPerMeshThreadgroup = + geometry_pipeline_state->gs_max_input_primitives_per_mesh_threadgroup; + + if (primitive_processing_result.index_buffer_type == + PrimitiveProcessor::ProcessedIndexBufferType::kNone) { + IRRuntimeDrawPrimitivesGeometryEmulation( + current_render_encoder_, geometry_primitive, geometry_config, 1, + primitive_processing_result.host_draw_vertex_count, 0, 0); + } else { + MTL::IndexType index_type = + (primitive_processing_result.host_index_format == + xenos::IndexFormat::kInt16) + ? MTL::IndexTypeUInt16 + : MTL::IndexTypeUInt32; + MTL::Buffer* index_buffer = nullptr; + uint64_t index_offset = 0; + if (!resolve_index_buffer(index_type, index_buffer, index_offset)) { + return false; + } + uint32_t index_stride = (index_type == MTL::IndexTypeUInt16) + ? sizeof(uint16_t) + : sizeof(uint32_t); + uint32_t start_index = + index_stride ? uint32_t(index_offset / index_stride) : 0; + IRRuntimeDrawIndexedPrimitivesGeometryEmulation( + current_render_encoder_, geometry_primitive, index_type, index_buffer, + geometry_config, 1, + primitive_processing_result.host_draw_vertex_count, start_index, 0, + 0); + } + } else { + // Primitive topology - from primitive processor, like D3D12. MTL::PrimitiveType mtl_primitive = MTL::PrimitiveTypeTriangle; switch (primitive_processing_result.host_primitive_type) { case xenos::PrimitiveType::kPointList: @@ -3470,68 +5544,20 @@ bool MetalCommandProcessor::IssueDrawMsl( mtl_primitive = MTL::PrimitiveTypeTriangleStrip; break; default: - XELOGE("SPIRV-Cross: Unsupported host primitive type {}", - uint32_t(primitive_processing_result.host_primitive_type)); + XELOGE( + "Host primitive type {} returned by the primitive processor is not " + "supported by the Metal command processor", + uint32_t(primitive_processing_result.host_primitive_type)); return false; } - auto request_guest_index_range = [&](uint64_t index_base, - uint32_t index_count, - MTL::IndexType index_type) -> bool { - if (!shared_memory_) { - return false; - } - uint32_t index_stride = (index_type == MTL::IndexTypeUInt16) - ? sizeof(uint16_t) - : sizeof(uint32_t); - uint64_t index_length = uint64_t(index_count) * index_stride; - if (index_base > SharedMemory::kBufferSize || - SharedMemory::kBufferSize - index_base < index_length) { - return false; - } - return shared_memory_->RequestRange(static_cast(index_base), - static_cast(index_length)); - }; - - bool use_expansion_triangle_list_fallback = false; - uint32_t draw_index_count = - primitive_processing_result.host_draw_vertex_count; - if ((host_vertex_shader_type == - Shader::HostVertexShaderType::kPointListAsTriangleStrip || - host_vertex_shader_type == - Shader::HostVertexShaderType::kRectangleListAsTriangleStrip) && - (primitive_processing_result.index_buffer_type == - PrimitiveProcessor::ProcessedIndexBufferType:: - kHostBuiltinForAuto || - primitive_processing_result.index_buffer_type == - PrimitiveProcessor::ProcessedIndexBufferType:: - kHostBuiltinForDMA)) { - // Expansion strips normally rely on primitive restart separators. - // Keep a Metal-local triangle-list fallback to avoid dependence on strip - // restart behavior in this SPIRV-Cross path. - uint32_t strip_index_count = draw_index_count; - uint32_t expanded_primitive_count = - strip_index_count ? (strip_index_count + 1u) / 5u : 0u; - draw_index_count = expanded_primitive_count * 6u; - mtl_primitive = MTL::PrimitiveTypeTriangle; - use_expansion_triangle_list_fallback = true; - static bool logged_expansion_triangle_list_fallback = false; - if (!logged_expansion_triangle_list_fallback) { - logged_expansion_triangle_list_fallback = true; - XELOGW( - "SPIRV-Cross: Using triangle-list fallback for VS primitive " - "expansion draws to avoid strip-restart dependency"); - } - } - + // Draw using primitive processor output. if (primitive_processing_result.index_buffer_type == PrimitiveProcessor::ProcessedIndexBufferType::kNone) { - // Non-indexed draw. - current_render_encoder_->drawPrimitives( - mtl_primitive, NS::UInteger(0), + IRRuntimeDrawPrimitives( + current_render_encoder_, mtl_primitive, NS::UInteger(0), NS::UInteger(primitive_processing_result.host_draw_vertex_count)); } else { - // Indexed draw. MTL::IndexType index_type = (primitive_processing_result.host_index_format == xenos::IndexFormat::kInt16) @@ -3539,165 +5565,72 @@ bool MetalCommandProcessor::IssueDrawMsl( : MTL::IndexTypeUInt32; MTL::Buffer* index_buffer = nullptr; uint64_t index_offset = 0; - switch (primitive_processing_result.index_buffer_type) { - case PrimitiveProcessor::ProcessedIndexBufferType::kGuestDMA: - index_buffer = shared_memory_ ? shared_memory_->GetBuffer() : nullptr; - index_offset = primitive_processing_result.guest_index_base; - if (!request_guest_index_range(index_offset, draw_index_count, - index_type)) { - XELOGE("SPIRV-Cross: Failed to validate guest index buffer range"); - return false; - } - break; - case PrimitiveProcessor::ProcessedIndexBufferType::kHostConverted: - if (primitive_processor_) { - index_buffer = primitive_processor_->GetConvertedIndexBuffer( - primitive_processing_result.host_index_buffer_handle, - index_offset); - } - break; - case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForAuto: - case PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA: - if (primitive_processor_) { - if (use_expansion_triangle_list_fallback) { - index_buffer = - primitive_processor_->GetExpansionTriangleListIndexBuffer(); - index_offset = 0; - index_type = MTL::IndexTypeUInt32; - } else { - index_buffer = primitive_processor_->GetBuiltinIndexBuffer(); - index_offset = - primitive_processing_result.host_index_buffer_handle; - } - } - break; - default: - XELOGE("SPIRV-Cross: Unsupported index buffer type {}", - uint32_t(primitive_processing_result.index_buffer_type)); - return false; - } - if (!index_buffer) { - XELOGE("SPIRV-Cross: Index buffer is null"); + if (!resolve_index_buffer(index_type, index_buffer, index_offset)) { return false; } - UseRenderEncoderResource(index_buffer, MTL::ResourceUsageRead); - current_render_encoder_->drawIndexedPrimitives( - mtl_primitive, NS::UInteger(draw_index_count), index_type, - index_buffer, NS::UInteger(index_offset)); + IRRuntimeDrawIndexedPrimitives( + current_render_encoder_, mtl_primitive, + NS::UInteger(primitive_processing_result.host_draw_vertex_count), + index_type, index_buffer, index_offset, NS::UInteger(1), 0, 0); } } - // Handle memexport. if (memexport_used && shared_memory_) { - for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { + for (const draw_util::MemExportRange& memexport_range : memexport_ranges) { shared_memory_->RangeWrittenByGpu( memexport_range.base_address_dwords << 2, memexport_range.size_bytes); + if (NS::UInteger(memexport_write_stages)) { + active_render_encoder_shared_memory_write_stages_ = MTL::RenderStages( + NS::UInteger(active_render_encoder_shared_memory_write_stages_) | + NS::UInteger(memexport_write_stages)); + MarkSharedMemoryWritePending(memexport_range.base_address_dwords << 2, + memexport_range.size_bytes, + memexport_write_stages, true, false); + } } } - ++current_draw_index_; + submission_has_draws_ = true; + return true; } bool MetalCommandProcessor::IssueCopy() { - // Finish any in-flight rendering so render target contents are visible to - // resolve logic. - EndRenderEncoder(); - MTL::CommandBuffer* copy_command_buffer = EnsureCommandBuffer(); - if (!copy_command_buffer) { - XELOGE("MetalCommandProcessor::IssueCopy: failed to get command buffer"); + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kIssueCopy)) { return false; } - if (!render_target_cache_) { XELOGW("MetalCommandProcessor::IssueCopy - No render target cache"); return true; } - uint32_t written_address = 0; - uint32_t written_length = 0; - - if (!render_target_cache_->Resolve(*memory_, written_address, written_length, - copy_command_buffer)) { - XELOGE("MetalCommandProcessor::IssueCopy - Resolve failed"); + MetalRenderTargetCache::ResolvePlan resolve_plan; + if (!render_target_cache_->PrepareResolvePlan(*memory_, resolve_plan)) { + XELOGE("MetalCommandProcessor::IssueCopy - Resolve planning failed"); return false; } - ReadbackResolveMode readback_mode = GetReadbackResolveMode(); - bool do_readback = (readback_mode != ReadbackResolveMode::kDisabled); - bool readback_scaled = false; - bool readback_scaled_gpu = false; - bool use_gpu_downscale = false; - bool readback_scheduled = false; - uint32_t write_index = 0; - uint32_t read_index = 0; - bool use_delayed_sync = false; - bool wait_for_completion = false; - bool should_copy = false; - bool is_cache_miss = false; - uint32_t source_length = 0; - uint32_t readback_length = 0; - uint32_t tile_count = 0; - uint32_t pixel_size_log2 = 0; - uint32_t scale_x = 1; - uint32_t scale_y = 1; - bool half_pixel_offset = false; - uint32_t source_offset_bytes = 0; - uint64_t scaled_range_offset_bytes = 0; - uint64_t readback_base_offset_bytes = 0; - uint64_t scaled_copy_length = 0; - size_t source_buffer_binding_offset = 0; - uint64_t source_offset_bytes_log = 0; + uint32_t written_address = 0; + uint32_t written_length = 0; - if (do_readback) { - // Early check: if destination memory is not accessible, skip readback. - VirtualHeap* physical_heap = memory_->GetPhysicalHeap(); - bool memory_accessible = false; - if (physical_heap) { - HeapAllocationInfo alloc_info; - if (physical_heap->QueryRegionInfo(written_address, &alloc_info) && - (alloc_info.state & kMemoryAllocationCommit) && - IsWritableProtect(alloc_info.protect)) { - uint32_t end_address = written_address + written_length; - uint32_t region_end = alloc_info.base_address + alloc_info.region_size; - if (end_address <= region_end) { - memory_accessible = true; - } - } - } - if (!memory_accessible) { - do_readback = false; + MTL::CommandBuffer* copy_command_buffer = nullptr; + if (resolve_plan.needs_render_encoder_end) { + // End only the render encoder here. The resolve/transfer work may still + // reuse the current Metal command buffer and submission ordering. + EndRenderEncoder(RenderEncoderEndReason::kResolveNeedsBoundary); + copy_command_buffer = EnsureCommandBuffer(); + if (!copy_command_buffer) { + XELOGE("MetalCommandProcessor::IssueCopy: failed to get command buffer"); + return false; } } - if (!written_length) { - // Keep the submission open for no-op copies and let primary-buffer end, - // swap, or explicit sync points choose the commit boundary. - return true; - } - // Track this resolved region so the trace player can avoid overwriting it - // with stale MemoryRead commands from the trace file. - MarkResolvedMemory(written_address, written_length); - - // Keep copy-only resolve bursts open so multiple resolves can be coalesced, - // but commit draw-containing submissions so subsequent work observes the - // resolved guest memory immediately. - if (current_draw_index_ == 0) { - copy_resolve_writes_pending_ = true; - return true; + if (!render_target_cache_->Resolve(*memory_, written_address, written_length, + copy_command_buffer, &resolve_plan)) { + XELOGE("MetalCommandProcessor::IssueCopy - Resolve failed"); + return false; } - // Resolve touched guest memory in a draw-containing submission; commit now - // so following packets don't observe stale resolve results. - if (UseSpirvCrossPath()) { - ScheduleSpirvUniformBufferRelease(copy_command_buffer); - } - copy_command_buffer->commit(); - copy_command_buffer->release(); - current_command_buffer_ = nullptr; - current_draw_index_ = 0; - copy_resolve_writes_pending_ = false; - return true; } @@ -3709,34 +5642,279 @@ void MetalCommandProcessor::OnGammaRampPWLValueWritten() { gamma_ramp_pwl_up_to_date_ = false; } +namespace { + +bool RegisterRangeContains(uint32_t start, uint32_t end, uint32_t range_start, + uint32_t range_end) { + return start >= range_start && end <= range_end; +} + +bool RegisterRangeOverlaps(uint32_t start, uint32_t end, uint32_t range_start, + uint32_t range_end) { + return start < range_end && range_start < end; +} + +} // namespace + +void MetalCommandProcessor::WriteRegistersFromMem(uint32_t start_index, + uint32_t* base, + uint32_t num_registers) { + if (!num_registers) { + return; + } + if (TryWriteKnownRegisterRangeFromMem(start_index, base, num_registers)) { + return; + } + CommandProcessor::WriteRegistersFromMem(start_index, base, num_registers); +} + +void MetalCommandProcessor::WriteRegisterRangeFromRing(xe::RingBuffer* ring, + uint32_t base, + uint32_t num_registers) { + if (!num_registers) { + return; + } + if (CanFastWriteRegisterRange(base, num_registers)) { + WriteFastRegisterRangeFromRing(ring, base, num_registers); + return; + } + CommandProcessor::WriteRegisterRangeFromRing(ring, base, num_registers); +} + +bool MetalCommandProcessor::CanFastWriteRegisterRange( + uint32_t start_index, uint32_t num_registers) const { + if (!num_registers) { + return true; + } + const uint32_t end = start_index + num_registers; + if (end < start_index || end > RegisterFile::kRegisterCount) { + return false; + } + if (RegisterRangeContains(start_index, end, XE_GPU_REG_SHADER_CONSTANT_000_X, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) || + RegisterRangeContains(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0, + XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5 + 1) || + RegisterRangeContains(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031, + XE_GPU_REG_SHADER_CONSTANT_LOOP_31 + 1)) { + return true; + } + + const bool overlaps_special = + RegisterRangeOverlaps(start_index, end, XE_GPU_REG_SCRATCH_REG0, + XE_GPU_REG_SCRATCH_REG7 + 1) || + RegisterRangeOverlaps(start_index, end, XE_GPU_REG_COHER_STATUS_HOST, + XE_GPU_REG_COHER_STATUS_HOST + 1) || + RegisterRangeOverlaps(start_index, end, XE_GPU_REG_DC_LUT_RW_INDEX, + XE_GPU_REG_DC_LUT_30_COLOR + 1); + const bool overlaps_shader_constants = + RegisterRangeOverlaps(start_index, end, XE_GPU_REG_SHADER_CONSTANT_000_X, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) || + RegisterRangeOverlaps(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0, + XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5 + 1) || + RegisterRangeOverlaps(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031, + XE_GPU_REG_SHADER_CONSTANT_LOOP_31 + 1); + return !overlaps_special && !overlaps_shader_constants; +} + +bool MetalCommandProcessor::TryWriteKnownRegisterRangeFromMem( + uint32_t start_index, uint32_t* base, uint32_t num_registers) { + if (!CanFastWriteRegisterRange(start_index, num_registers)) { + return false; + } + const uint32_t end = start_index + num_registers; + if (RegisterRangeContains(start_index, end, XE_GPU_REG_SHADER_CONSTANT_000_X, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0)) { + WriteShaderConstantsFromMem(start_index, base, num_registers); + return true; + } + if (RegisterRangeContains(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0, + XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5 + 1)) { + WriteFetchConstantsFromMem(start_index, base, num_registers); + return true; + } + if (RegisterRangeContains(start_index, end, + XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031, + XE_GPU_REG_SHADER_CONSTANT_LOOP_31 + 1)) { + WriteBoolLoopConstantsFromMem(start_index, base, num_registers); + return true; + } + + xe::copy_and_swap_32_unaligned(®ister_file_->values[start_index], base, + num_registers); + return true; +} + +void MetalCommandProcessor::WriteFastRegisterRangeFromRing( + xe::RingBuffer* ring, uint32_t base, uint32_t num_registers) { + RingBuffer::ReadRange range = + ring->BeginRead(num_registers * sizeof(uint32_t)); + if (!range.second) { + TryWriteKnownRegisterRangeFromMem( + base, reinterpret_cast(const_cast(range.first)), + num_registers); + ring->EndRead(range); + return; + } + + uint32_t first_registers = + static_cast(range.first_length / sizeof(uint32_t)); + TryWriteKnownRegisterRangeFromMem( + base, reinterpret_cast(const_cast(range.first)), + first_registers); + TryWriteKnownRegisterRangeFromMem( + base + first_registers, + reinterpret_cast(const_cast(range.second)), + num_registers - first_registers); + ring->EndRead(range); +} + +void MetalCommandProcessor::WriteShaderConstantsFromMem( + uint32_t start_index, uint32_t* base, uint32_t num_registers) { + if (!num_registers) { + return; + } + if (cbuffer_binding_float_vertex_.up_to_date && + FloatConstantRangeNeedsDirty(start_index, base, num_registers, + current_float_constant_map_vertex_, 0)) { + cbuffer_binding_float_vertex_.up_to_date = false; + } + if (cbuffer_binding_float_pixel_.up_to_date && + FloatConstantRangeNeedsDirty(start_index, base, num_registers, + current_float_constant_map_pixel_, 256)) { + cbuffer_binding_float_pixel_.up_to_date = false; + } + xe::copy_and_swap_32_unaligned(®ister_file_->values[start_index], base, + num_registers); +} + +void MetalCommandProcessor::WriteBoolLoopConstantsFromMem( + uint32_t start_index, uint32_t* base, uint32_t num_registers) { + xe::copy_and_swap_32_unaligned(®ister_file_->values[start_index], base, + num_registers); + cbuffer_binding_bool_loop_.up_to_date = false; +} + +void MetalCommandProcessor::WriteFetchConstantsFromMem(uint32_t start_index, + uint32_t* base, + uint32_t num_registers) { + if (!num_registers) { + return; + } + DxbcShader::FetchConstantDwordMask written_fetch_dword_mask = {}; + const uint32_t dword_start = + start_index - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; + const uint32_t dword_end = dword_start + num_registers; + for (uint32_t dword = dword_start; dword < dword_end; ++dword) { + MarkFetchConstantDword(written_fetch_dword_mask, dword); + } + + xe::copy_and_swap_32_unaligned(®ister_file_->values[start_index], base, + num_registers); + + DirtyFetchConstantDwords(written_fetch_dword_mask); + if (texture_cache_) { + texture_cache_->TextureFetchConstantsWritten(dword_start / 6, + (dword_end - 1) / 6); + } +} + +bool MetalCommandProcessor::FloatConstantRangeNeedsDirty( + uint32_t start_index, const uint32_t* base, uint32_t num_registers, + const uint64_t* constant_map, uint32_t stage_first_constant) const { + constexpr uint32_t kStageFloatConstantCount = 256; + const uint32_t written_dword_start = + start_index - XE_GPU_REG_SHADER_CONSTANT_000_X; + const uint32_t written_dword_end = written_dword_start + num_registers; + const uint32_t stage_dword_start = stage_first_constant * 4; + const uint32_t stage_dword_end = + (stage_first_constant + kStageFloatConstantCount) * 4; + uint32_t dword = std::max(written_dword_start, stage_dword_start); + const uint32_t dword_end = std::min(written_dword_end, stage_dword_end); + const bool dirty_on_change = ::cvars::metal_float_constants_dirty_on_change; + + while (dword < dword_end) { + const uint32_t relative_constant = (dword - stage_dword_start) >> 2; + const uint64_t live_constant = constant_map[relative_constant >> 6] & + (uint64_t(1) << (relative_constant & 63)); + const uint32_t constant_dword_end = + std::min(dword_end, stage_dword_start + ((relative_constant + 1) << 2)); + if (!live_constant) { + dword = constant_dword_end; + continue; + } + if (!dirty_on_change) { + return true; + } + for (; dword < constant_dword_end; ++dword) { + const uint32_t value = + xe::load_and_swap(base + (dword - written_dword_start)); + if (register_file_->values[XE_GPU_REG_SHADER_CONSTANT_000_X + dword] != + value) { + return true; + } + } + } + return false; +} + +void MetalCommandProcessor::DirtyFetchConstantDwords( + const DxbcShader::FetchConstantDwordMask& dirty_mask) { + for (size_t stage = 0; stage < kStageCount; ++stage) { + MergeFetchConstantDwordMask(fetch_constant_dirty_masks_[stage], dirty_mask); + if (FetchConstantDwordMasksOverlap( + dirty_mask, current_fetch_constant_dword_masks_[stage])) { + cbuffer_binding_fetch_[stage].up_to_date = false; + } + } +} + void MetalCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { + uint32_t old_value = 0; + bool valid_register = index < RegisterFile::kRegisterCount; + if (valid_register) { + old_value = register_file_->values[index]; + } CommandProcessor::WriteRegister(index, value); + if (!valid_register) { + return; + } + bool value_changed = old_value != value; if (index >= XE_GPU_REG_SHADER_CONSTANT_000_X && index <= XE_GPU_REG_SHADER_CONSTANT_511_W) { - const uint32_t float_constant_index = + if (::cvars::metal_float_constants_dirty_on_change && !value_changed) { + return; + } + uint32_t float_constant_index = (index - XE_GPU_REG_SHADER_CONSTANT_000_X) >> 2; - const uint32_t stage_constant_index = float_constant_index & 0xFF; - const uint32_t map_index = stage_constant_index >> 6; - const uint64_t map_bit = uint64_t(1) << (stage_constant_index & 63); if (float_constant_index >= 256) { - if (msl_current_float_constant_map_pixel_[map_index] & map_bit) { - msl_float_constants_dirty_pixel_ = true; + uint32_t rel = float_constant_index - 256; + if (current_float_constant_map_pixel_[rel >> 6] & + (uint64_t(1) << (rel & 63))) { + cbuffer_binding_float_pixel_.up_to_date = false; } } else { - if (msl_current_float_constant_map_vertex_[map_index] & map_bit) { - msl_float_constants_dirty_vertex_ = true; + if (current_float_constant_map_vertex_[float_constant_index >> 6] & + (uint64_t(1) << (float_constant_index & 63))) { + cbuffer_binding_float_vertex_.up_to_date = false; } } } else if (index >= XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031 && index <= XE_GPU_REG_SHADER_CONSTANT_LOOP_31) { - msl_bool_loop_constants_dirty_ = true; + cbuffer_binding_bool_loop_.up_to_date = false; } else if (index >= XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 && index <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5) { - msl_fetch_constants_dirty_ = true; + const uint32_t fetch_dword = index - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0; + DxbcShader::FetchConstantDwordMask changed_fetch_dword_mask = {}; + MarkFetchConstantDword(changed_fetch_dword_mask, fetch_dword); + DirtyFetchConstantDwords(changed_fetch_dword_mask); if (texture_cache_) { - texture_cache_->TextureFetchConstantWritten( - (index - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6); + texture_cache_->TextureFetchConstantWritten(fetch_dword / 6); } } } @@ -3750,6 +5928,10 @@ MTL::CommandBuffer* MetalCommandProcessor::EnsureCommandBuffer() { XELOGE("EnsureCommandBuffer: no command queue"); return nullptr; } + const bool is_opening_frame = !frame_open_; + if (is_opening_frame) { + OpenFrameLifetime(); + } EnsureCommandBufferAutoreleasePool(); @@ -3761,40 +5943,38 @@ MTL::CommandBuffer* MetalCommandProcessor::EnsureCommandBuffer() { return nullptr; } current_command_buffer_->retain(); + + ++submission_current_; + current_command_buffer_->setLabel( NS::String::string("XeniaCommandBuffer", NS::UTF8StringEncoding)); - if (UseSpirvCrossPath() && !EnsureSpirvUniformBuffer()) { - static auto last_ensure_uniforms_fail_log = - std::chrono::steady_clock::time_point{}; - const auto now = std::chrono::steady_clock::now(); - if (now - last_ensure_uniforms_fail_log >= std::chrono::seconds(1)) { - last_ensure_uniforms_fail_log = now; - XELOGE( - "EnsureCommandBuffer: failed to prepare SPIRV-Cross uniforms " - "buffer"); - } - current_command_buffer_->release(); - current_command_buffer_ = nullptr; - DrainCommandBufferAutoreleasePool(); - return nullptr; - } - - ++submission_current_; pending_completion_handlers_.fetch_add(1, std::memory_order_relaxed); - current_command_buffer_->addCompletedHandler([this](MTL::CommandBuffer*) { - completed_command_buffers_.fetch_add(1, std::memory_order_relaxed); - pending_completion_handlers_.fetch_sub(1, std::memory_order_relaxed); - }); + current_command_buffer_->addCompletedHandler( + [this](MTL::CommandBuffer* completed_cmd) { + if (completed_cmd->status() == MTL::CommandBufferStatusError) { + NS::Error* error = completed_cmd->error(); + if (error) { + XELOGE("Metal command buffer error: {}", + error->localizedDescription()->utf8String()); + } + } + { + std::lock_guard lock(completion_mutex_); + completed_command_buffers_.fetch_add(1, std::memory_order_release); + pending_completion_handlers_.fetch_sub(1, std::memory_order_release); + } + completion_cond_.notify_all(); + }); - if (primitive_processor_) { - primitive_processor_->BeginSubmission(); - } if (texture_cache_) { texture_cache_->BeginSubmission(submission_current_); } - if (primitive_processor_ && !frame_open_) { - primitive_processor_->BeginFrame(); + submission_has_draws_ = false; + if (is_opening_frame) { + if (primitive_processor_) { + primitive_processor_->BeginFrame(); + } if (render_target_cache_) { render_target_cache_->BeginFrame(); } @@ -3814,12 +5994,539 @@ void MetalCommandProcessor::ProcessCompletedSubmissions() { return; } submission_completed_processed_ = completed; - if (primitive_processor_) { - primitive_processor_->CompletedSubmissionUpdated(); - } if (texture_cache_) { texture_cache_->CompletedSubmissionUpdated(completed); } + PruneCompletedSharedMemoryWrites(completed); + while (!retired_view_bindless_indices_.empty() && + retired_view_bindless_indices_.front().submission_id <= completed) { + FreeViewBindlessIndexNow(retired_view_bindless_indices_.front().index); + retired_view_bindless_indices_.pop_front(); + } + while (!retired_sampler_bindless_indices_.empty() && + retired_sampler_bindless_indices_.front().submission_id <= completed) { + FreeSamplerBindlessIndexNow( + retired_sampler_bindless_indices_.front().index); + retired_sampler_bindless_indices_.pop_front(); + } + while (!retired_memexport_index_buffers_.empty() && + retired_memexport_index_buffers_.front().submission_id <= completed) { + if (retired_memexport_index_buffers_.front().buffer) { + retired_memexport_index_buffers_.front().buffer->release(); + } + retired_memexport_index_buffers_.pop_front(); + } +} + +void MetalCommandProcessor::WaitForFrameSlotSubmission( + uint64_t awaited_submission) { + if (!awaited_submission) { + return; + } + if (completed_command_buffers_.load(std::memory_order_acquire) >= + awaited_submission) { + return; + } + ++backend_telemetry_.frame_slot_waits; + ++backend_telemetry_.frame_slot_wait_submission_count; + backend_telemetry_.frame_slot_wait_submission_last = awaited_submission; + std::unique_lock lock(completion_mutex_); + completion_cond_.wait(lock, [&]() { + return completed_command_buffers_.load(std::memory_order_acquire) >= + awaited_submission; + }); +} + +void MetalCommandProcessor::OpenFrameLifetime() { + const uint64_t awaited_submission = + closed_frame_submissions_[frame_current_ % kMaxFramesInFlight]; + WaitForFrameSlotSubmission(awaited_submission); + ProcessCompletedSubmissions(); + + const uint64_t completed_submission = + completed_command_buffers_.load(std::memory_order_acquire); + frame_completed_ = std::max(frame_current_, uint64_t(kMaxFramesInFlight)) - + kMaxFramesInFlight; + for (uint64_t frame = frame_completed_ + 1; frame < frame_current_; ++frame) { + if (closed_frame_submissions_[frame % kMaxFramesInFlight] > + completed_submission) { + break; + } + frame_completed_ = frame; + } + + if (constant_buffer_pool_) { + constant_buffer_pool_->Reclaim(frame_completed_); + } + InvalidateFrameTransientBindings(); +} + +void MetalCommandProcessor::InvalidateFrameTransientBindings() { + cbuffer_binding_system_.up_to_date = false; + cbuffer_binding_float_vertex_.up_to_date = false; + cbuffer_binding_float_pixel_.up_to_date = false; + cbuffer_binding_bool_loop_.up_to_date = false; + for (auto& fetch_binding : cbuffer_binding_fetch_) { + fetch_binding.up_to_date = false; + } + cbuffer_binding_descriptor_indices_vertex_.up_to_date = false; + cbuffer_binding_descriptor_indices_pixel_.up_to_date = false; + std::memset(current_float_constant_map_vertex_, 0, + sizeof(current_float_constant_map_vertex_)); + std::memset(current_float_constant_map_pixel_, 0, + sizeof(current_float_constant_map_pixel_)); + current_payload_system_.clear(); + current_payload_float_vertex_.clear(); + current_payload_float_pixel_.clear(); + current_payload_bool_loop_.clear(); + for (auto& payload : current_payload_fetch_) { + payload.clear(); + } + for (auto& payload : scratch_payload_fetch_) { + payload.clear(); + } + constant_payload_cache_.clear(); + constant_payload_cache_bytes_ = 0; + + current_bindless_stage_root_valid_.fill(false); + current_bindless_stage_root_frame_open_rebuild_pending_.fill(true); + current_bindless_stage_root_serials_.fill(0); + current_bindless_cbv_gpu_addresses_ = {}; + current_bindless_cbv_buffers_ = {}; + current_bindless_cbv_offsets_ = {}; + current_bindless_active_cbv_masks_.fill(0); + current_bindless_root_resource_set_ = {}; + current_bindless_root_resource_source_serial_ = 0; + render_encoder_bindless_root_resources_serial_ = 0; + current_bindless_stage_root_arguments_ = {}; + current_bindless_stage_root_entries_ = {}; + current_bindless_stage_root_entries_valid_.fill(false); + current_bindless_shared_memory_is_uav_ = false; +} + +void MetalCommandProcessor::CloseFrameLifetime() { + if (!frame_open_) { + return; + } + if (primitive_processor_) { + primitive_processor_->EndFrame(); + } + frame_open_ = false; + + const uint32_t frame_slot = uint32_t(frame_current_ % kMaxFramesInFlight); + const uint64_t previous_slot_submission = + closed_frame_submissions_[frame_slot]; + assert_true(!previous_slot_submission || + previous_slot_submission <= + completed_command_buffers_.load(std::memory_order_acquire)); + closed_frame_submissions_[frame_slot] = submission_current_; + ++frame_current_; +} + +void MetalCommandProcessor::MaybeDumpBackendTelemetry(const char* reason, + bool force) { + if (!::cvars::metal_backend_telemetry) { + return; + } + int32_t interval_config = ::cvars::metal_backend_telemetry_interval; + uint64_t interval = interval_config > 0 ? uint64_t(interval_config) : 0; + if (!force && + (!interval || backend_telemetry_.swaps < + backend_telemetry_last_dump_swap_ + interval)) { + return; + } + + MetalRenderTargetCache::TelemetryStats rt_stats = {}; + if (render_target_cache_) { + rt_stats = render_target_cache_->GetAndResetTelemetryStats(); + } + + const std::string end_reasons = MetalFormatNamedCounts( + backend_telemetry_.end_reasons, MetalRenderEncoderEndReasonName); + const std::string transfer_request_sources = MetalFormatNamedTriplets( + backend_telemetry_.transfer_request_sources_total, + backend_telemetry_.transfer_request_sources_active, + backend_telemetry_.transfer_request_sources_no_active, + MetalTransferRequestSourceName); + const std::string transfer_request_render_end_sources = + MetalFormatNamedCounts( + backend_telemetry_.transfer_request_render_encoder_ends, + MetalTransferRequestSourceName); + const std::string shared_memory_request_upload_bytes = MetalFormatNamedCounts( + backend_telemetry_.shared_memory_request_upload_bytes, + MetalSharedMemoryRequestReasonName); + const std::string shared_memory_request_failures = + MetalFormatNamedCounts(backend_telemetry_.shared_memory_request_failures, + MetalSharedMemoryRequestReasonName); + const std::string shared_memory_request_outcomes = + MetalFormatNamedCounts(backend_telemetry_.shared_memory_request_outcomes, + MetalSharedMemoryRequestOutcomeName); + const std::string shared_memory_upload_route_counts = MetalFormatNamedCounts( + backend_telemetry_.shared_memory_upload_route_counts, + MetalSharedMemoryUploadRouteName); + const std::string shared_memory_upload_route_bytes = MetalFormatNamedCounts( + backend_telemetry_.shared_memory_upload_route_bytes, + MetalSharedMemoryUploadRouteName); + const std::string shared_memory_direct_write_rejects = MetalFormatNamedCounts( + backend_telemetry_.shared_memory_direct_write_reject_counts, + MetalSharedMemoryDirectWriteRejectReasonName); + const std::string shared_memory_direct_write_reject_bytes = + MetalFormatNamedCounts( + backend_telemetry_.shared_memory_direct_write_reject_bytes, + MetalSharedMemoryDirectWriteRejectReasonName); + const std::string texture_upload_source_route_counts = MetalFormatNamedCounts( + backend_telemetry_.texture_upload_source_route_counts, + MetalTextureUploadSourceRouteName); + const std::string texture_upload_source_route_bytes = MetalFormatNamedCounts( + backend_telemetry_.texture_upload_source_route_bytes, + MetalTextureUploadSourceRouteName); + const std::string texture_upload_source_fallback_reasons = + MetalFormatNamedCounts( + backend_telemetry_.texture_upload_source_fallback_reasons, + MetalTextureUploadSourceFallbackReasonName); + const std::string shared_memory_upload_encoder_end_reasons = + MetalFormatNamedCounts( + backend_telemetry_.shared_memory_upload_encoder_end_reasons, + MetalSharedMemoryUploadEncoderEndReasonName); + const std::string draw_materialization_source_ranges = MetalFormatNamedCounts( + backend_telemetry_.draw_materialization_source_ranges, + MetalTelemetryDrawMaterializationSourceName); + const std::string draw_materialization_source_bytes = MetalFormatNamedCounts( + backend_telemetry_.draw_materialization_source_bytes, + MetalTelemetryDrawMaterializationSourceName); + const std::string draw_materialization_source_invalid_ranges = + MetalFormatNamedCounts( + backend_telemetry_.draw_materialization_source_invalid_ranges, + MetalTelemetryDrawMaterializationSourceName); + const std::string draw_materialization_source_invalid_bytes = + MetalFormatNamedCounts( + backend_telemetry_.draw_materialization_source_invalid_bytes, + MetalTelemetryDrawMaterializationSourceName); + const std::string prepared_draw_queue_flush_reasons = MetalFormatNamedCounts( + backend_telemetry_.prepared_draw_queue_flush_reasons, + MetalPreparedDrawFlushReasonName); + const std::string prepared_draw_queue_reject_reasons = MetalFormatNamedCounts( + backend_telemetry_.prepared_draw_queue_reject_reasons, + MetalPreparedDrawQueueRejectReasonName); + const std::string cbv_uploads = MetalFormatNamedCounts( + backend_telemetry_.cbv_uploads, MetalTelemetryCbvSlotName); + const std::string cbv_reuse_hits = MetalFormatNamedCounts( + backend_telemetry_.cbv_reuse_hits, MetalTelemetryCbvSlotName); + const std::string descriptor_index_uploads = + MetalFormatNamedCounts(backend_telemetry_.descriptor_index_uploads, + MetalTelemetryShaderStageName); + const std::string constant_payload_cache_hits = + MetalFormatNamedCounts(backend_telemetry_.constant_payload_cache_hits, + MetalTelemetryCbvSlotName); + const std::string constant_payload_cache_misses = + MetalFormatNamedCounts(backend_telemetry_.constant_payload_cache_misses, + MetalTelemetryCbvSlotName); + const std::string constant_payload_cache_bytes_saved = MetalFormatNamedCounts( + backend_telemetry_.constant_payload_cache_bytes_saved, + MetalTelemetryCbvSlotName); + const std::string descriptor_index_payload_cache_hits = + MetalFormatNamedCounts( + backend_telemetry_.descriptor_index_payload_cache_hits, + MetalTelemetryShaderStageName); + const std::string descriptor_index_payload_cache_misses = + MetalFormatNamedCounts( + backend_telemetry_.descriptor_index_payload_cache_misses, + MetalTelemetryShaderStageName); + const std::string descriptor_index_payload_cache_bytes_saved = + MetalFormatNamedCounts( + backend_telemetry_.descriptor_index_payload_cache_bytes_saved, + MetalTelemetryShaderStageName); + const std::string bindless_root_allocations = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_allocations, + MetalTelemetryRootScopeName); + const std::string bindless_root_reuse_hits = MetalFormatNamedCounts( + backend_telemetry_.bindless_root_reuse_hits, MetalTelemetryRootScopeName); + const std::string bindless_root_arg_noop_updates = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_arg_noop_updates, + MetalTelemetryRootScopeName); + const std::string bindless_root_arg_slots_patched = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_arg_slots_patched, + MetalTelemetryRootScopeName); + const std::string bindless_root_arg_bytes_copied = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_arg_bytes_copied, + MetalTelemetryRootScopeName); + const std::string bindless_root_arg_slot_patches = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_arg_slot_patches, + MetalTelemetryRootArgSlotName); + const std::string bindless_root_rebuild_reasons = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_rebuild_reasons, + MetalTelemetryRootRebuildReasonName); + const std::string encoder_buffer_full_binds = MetalFormatNamedCounts( + backend_telemetry_.render_encoder_buffer_full_binds, + MetalTelemetryRenderEncoderBufferStageName); + const std::string encoder_buffer_offset_binds = MetalFormatNamedCounts( + backend_telemetry_.render_encoder_buffer_offset_binds, + MetalTelemetryRenderEncoderBufferStageName); + const std::string encoder_buffer_noop_binds = MetalFormatNamedCounts( + backend_telemetry_.render_encoder_buffer_noop_binds, + MetalTelemetryRenderEncoderBufferStageName); + const std::string encoder_buffer_null_binds = MetalFormatNamedCounts( + backend_telemetry_.render_encoder_buffer_null_binds, + MetalTelemetryRenderEncoderBufferStageName); + const std::string encoder_buffer_untracked_binds = MetalFormatNamedCounts( + backend_telemetry_.render_encoder_buffer_untracked_binds, + MetalTelemetryRenderEncoderBufferStageName); + const std::string render_resource_set_applies = + MetalFormatNamedCounts(backend_telemetry_.render_resource_set_applies, + MetalTelemetryRenderResourceSetName); + const std::string render_resource_set_skips = + MetalFormatNamedCounts(backend_telemetry_.render_resource_set_skips, + MetalTelemetryRenderResourceSetName); + const std::string render_resource_set_resources = + MetalFormatNamedCounts(backend_telemetry_.render_resource_set_resources, + MetalTelemetryRenderResourceSetName); + const std::string render_resource_registry_serial_skips = + MetalFormatNamedCounts( + backend_telemetry_.render_resource_registry_serial_skips, + MetalTelemetryRenderResourceSetName); + const std::string render_resource_registry_builds = + MetalFormatNamedCounts(backend_telemetry_.render_resource_registry_builds, + MetalTelemetryRenderResourceSetName); + const std::string render_resource_registry_registers = MetalFormatNamedCounts( + backend_telemetry_.render_resource_registry_registers, + MetalTelemetryRenderResourceSetName); + const auto& direct_host_stats = rt_stats.resolve_direct_host; + MetalStageCompileCacheStats stage_compile_stats = {}; + MetalPipelineRuntimeStats pipeline_runtime_stats = {}; + if (pipeline_cache_) { + stage_compile_stats = pipeline_cache_->GetAndResetStageCompileStats(); + pipeline_runtime_stats = pipeline_cache_->GetAndResetRuntimeStats(); + } + + XELOGI( + "MetalTelemetry[{}]: work swaps={} draws={} pipelines set/skip={}/{} " + "texture_requests before/after_encoder={}/{}", + reason, backend_telemetry_.swaps - backend_telemetry_last_dump_swap_, + backend_telemetry_.draw_calls, backend_telemetry_.pipeline_sets, + backend_telemetry_.pipeline_set_skips, + backend_telemetry_.texture_requests_before_encoder, + backend_telemetry_.texture_requests_after_encoder_begin); + XELOGI( + "MetalTelemetry[{}]: render_encoder begin_calls={} reused={} created={} " + "descriptor_restarts={} resource_resets={} desc_fail={} create_fail={} " + "end active/no_active={}/{} reasons={{ {} }}", + reason, backend_telemetry_.begin_encoder_calls, + backend_telemetry_.begin_encoder_reused_compatible, + backend_telemetry_.begin_encoder_created, + backend_telemetry_.begin_encoder_descriptor_restarts, + backend_telemetry_.begin_encoder_resource_usage_resets, + backend_telemetry_.begin_encoder_descriptor_failures, + backend_telemetry_.begin_encoder_creation_failures, + backend_telemetry_.end_encoder_active, + backend_telemetry_.end_encoder_no_active, end_reasons); + XELOGI( + "MetalTelemetry[{}]: transfer_request sources " + "total/active/no_active={{ {} }} render_end={{ {} }}", + reason, transfer_request_sources, transfer_request_render_end_sources); + XELOGI("MetalTelemetry[{}]: shared_memory request failures={{ {} }}", reason, + shared_memory_request_failures); + XELOGI( + "MetalTelemetry[{}]: shared_memory upload bytes={{ {} }} " + "outcomes={{ {} }}", + reason, shared_memory_request_upload_bytes, + shared_memory_request_outcomes); + XELOGI( + "MetalTelemetry[{}]: shared_memory upload_batches requests={} " + "ranges input/coalesced={}/{} bytes={} upload_encoder " + "acquire/reuse/copies={}/{}/{} closes={{ {} }} routes counts={{ {} }} " + "bytes={{ {} }}", + reason, backend_telemetry_.shared_memory_upload_batches, + backend_telemetry_.shared_memory_upload_batch_input_ranges, + backend_telemetry_.shared_memory_upload_batch_coalesced_ranges, + backend_telemetry_.shared_memory_upload_batch_bytes, + backend_telemetry_.shared_memory_upload_encoder_acquisitions, + backend_telemetry_.shared_memory_upload_encoder_reuses, + backend_telemetry_.shared_memory_upload_encoder_copies, + shared_memory_upload_encoder_end_reasons, + shared_memory_upload_route_counts, shared_memory_upload_route_bytes); + XELOGI( + "MetalTelemetry[{}]: shared_memory direct_write " + "eligible/staged_bytes={}/{} rejects={{ {} }} reject_bytes={{ {} }}", + reason, backend_telemetry_.shared_memory_direct_write_eligible_bytes, + backend_telemetry_.shared_memory_direct_write_staged_required_bytes, + shared_memory_direct_write_rejects, + shared_memory_direct_write_reject_bytes); + XELOGI( + "MetalTelemetry[{}]: shared_memory lazy_upload batches " + "no_upload/direct_only/mixed/staged_only={}/{}/{}/{} " + "render_active direct_only/mixed/staged_only={}/{}/{}", + reason, backend_telemetry_.shared_memory_lazy_upload_no_upload_batches, + backend_telemetry_.shared_memory_lazy_upload_direct_only_batches, + backend_telemetry_.shared_memory_lazy_upload_mixed_batches, + backend_telemetry_.shared_memory_lazy_upload_staged_only_batches, + backend_telemetry_.shared_memory_lazy_upload_direct_only_active, + backend_telemetry_.shared_memory_lazy_upload_mixed_active, + backend_telemetry_.shared_memory_lazy_upload_staged_only_active); + XELOGI( + "MetalTelemetry[{}]: texture_upload_source routes counts={{ {} }} " + "bytes={{ {} }} fallback_reasons={{ {} }}", + reason, texture_upload_source_route_counts, + texture_upload_source_route_bytes, + texture_upload_source_fallback_reasons); + XELOGI( + "MetalTelemetry[{}]: draw_materialization ranges={{ {} }} bytes={{ {} }} " + "invalid_ranges={{ {} }} invalid_bytes={{ {} }} " + "per_draw requests/invalid/resident_skip={}/{}/{}", + reason, draw_materialization_source_ranges, + draw_materialization_source_bytes, + draw_materialization_source_invalid_ranges, + draw_materialization_source_invalid_bytes, + backend_telemetry_.draw_materialization_per_draw_requests, + backend_telemetry_.draw_materialization_per_draw_invalid_requests, + backend_telemetry_.draw_materialization_per_draw_resident_skips); + XELOGI( + "MetalTelemetry[{}]: prepared_draw_queue appends={} flushes={} " + "single_draw_flushes={} draws_flushed={} ranges_flushed={} " + "bytes_flushed={} invalid_flushes={} texture_plans={} " + "texture_loads planned/executed={}/{} flush_reasons={{ {} }} " + "rejects={{ {} }}", + reason, backend_telemetry_.prepared_draw_queue_appends, + backend_telemetry_.prepared_draw_queue_flushes, + backend_telemetry_.prepared_draw_queue_single_draw_flushes, + backend_telemetry_.prepared_draw_queue_draws_flushed, + backend_telemetry_.prepared_draw_queue_ranges_flushed, + backend_telemetry_.prepared_draw_queue_bytes_flushed, + backend_telemetry_.prepared_draw_queue_invalid_flushes, + backend_telemetry_.prepared_draw_queue_texture_plans_flushed, + backend_telemetry_.prepared_draw_queue_texture_loads_planned, + backend_telemetry_.prepared_draw_queue_texture_loads_executed, + prepared_draw_queue_flush_reasons, prepared_draw_queue_reject_reasons); + XELOGI( + "MetalTelemetry[{}]: constants cbv_uploads={{ {} }} " + "cbv_reuse={{ {} }} descriptor_index_uploads={{ {} }} " + "root_allocations={{ {} }} root_reuse={{ {} }} " + "root_rebuild_reasons={{ {} }} frame_slot_waits={} " + "waited_submissions={} last_wait_submission={}", + reason, cbv_uploads, cbv_reuse_hits, descriptor_index_uploads, + bindless_root_allocations, bindless_root_reuse_hits, + bindless_root_rebuild_reasons, backend_telemetry_.frame_slot_waits, + backend_telemetry_.frame_slot_wait_submission_count, + backend_telemetry_.frame_slot_wait_submission_last); + XELOGI( + "MetalTelemetry[{}]: binding_cache constant_payload hits={{ {} }} " + "misses={{ {} }} bytes_saved={{ {} }} descriptor_payload hits={{ {} }} " + "misses={{ {} }} bytes_saved={{ {} }}", + reason, constant_payload_cache_hits, constant_payload_cache_misses, + constant_payload_cache_bytes_saved, descriptor_index_payload_cache_hits, + descriptor_index_payload_cache_misses, + descriptor_index_payload_cache_bytes_saved); + XELOGI( + "MetalTelemetry[{}]: root_args allocs={{ {} }} reuse={{ {} }} " + "noop={{ {} }} slots_patched={{ {} }} bytes_copied={{ {} }} " + "slot_patches={{ {} }} rebuild_reasons={{ {} }}", + reason, bindless_root_allocations, bindless_root_reuse_hits, + bindless_root_arg_noop_updates, bindless_root_arg_slots_patched, + bindless_root_arg_bytes_copied, bindless_root_arg_slot_patches, + bindless_root_rebuild_reasons); + if (cvars::metal_root_rebuild_detail_telemetry) { + const std::string bindless_root_slots_changed = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_slots_changed, + MetalTelemetryRootSlotsChangedName); + const std::string bindless_root_rebuild_details = + MetalFormatNamedCounts(backend_telemetry_.bindless_root_rebuild_details, + MetalTelemetryRootRebuildDetailName); + XELOGI( + "MetalTelemetry[{}]: root_rebuild_detail slots_changed={{ {} }} " + "details={{ {} }}", + reason, bindless_root_slots_changed, bindless_root_rebuild_details); + } + XELOGI( + "MetalTelemetry[{}]: encoder_bindings full={{ {} }} offset={{ {} }} " + "noop={{ {} }} null={{ {} }} untracked={{ {} }} resource_use " + "calls/skips/upgrades={}/{}/{} batches/resources/skips={}/{}/{} " + "resource_sets apply={{ {} }} skip={{ {} }} resources={{ {} }}", + reason, encoder_buffer_full_binds, encoder_buffer_offset_binds, + encoder_buffer_noop_binds, encoder_buffer_null_binds, + encoder_buffer_untracked_binds, + backend_telemetry_.render_encoder_use_resource_calls, + backend_telemetry_.render_encoder_use_resource_skips, + backend_telemetry_.render_encoder_use_resource_upgrades, + backend_telemetry_.render_encoder_use_resources_batches, + backend_telemetry_.render_encoder_use_resources_requested, + backend_telemetry_.render_encoder_use_resources_skips, + render_resource_set_applies, render_resource_set_skips, + render_resource_set_resources); + XELOGI( + "MetalTelemetry[{}]: resource_registry serial_skip={{ {} }} " + "build={{ {} }} register={{ {} }}", + reason, render_resource_registry_serial_skips, + render_resource_registry_builds, render_resource_registry_registers); + XELOGI( + "MetalTelemetry[{}]: residency_set supported/enabled/attached={}/{}/{} " + "allocations added/duplicates/commits={}/{}/{} " + "resource_refs covered/fallback={}/{} " + "use_resource covered/fallback={}/{} " + "use_heap covered/fallback={}/{} live_allocations={}", + reason, residency_set_supported_ ? 1 : 0, residency_set_enabled_ ? 1 : 0, + residency_set_attached_ ? 1 : 0, + backend_telemetry_.residency_set_allocations_added, + backend_telemetry_.residency_set_allocation_duplicates, + backend_telemetry_.residency_set_commits, + backend_telemetry_.residency_set_resource_refs_covered, + backend_telemetry_.residency_set_resource_refs_fallback, + backend_telemetry_.residency_set_use_resources_covered, + backend_telemetry_.residency_set_use_resources_fallback, + backend_telemetry_.residency_set_use_heaps_covered, + backend_telemetry_.residency_set_use_heaps_fallback, + residency_set_ ? uint64_t(residency_set_->allocationCount()) : 0); + XELOGI( + "MetalTelemetry[{}]: resolve_direct_host attempt/success={}/{} " + "reject gamma/exp_bias/format/sample/depth_no_fast={}/{}/{}/{}/{}", + reason, direct_host_stats.direct_host_attempt, + direct_host_stats.direct_host_success, + direct_host_stats.direct_host_reject_gamma, + direct_host_stats.direct_host_reject_exp_bias, + direct_host_stats.direct_host_reject_format_mismatch, + direct_host_stats.direct_host_reject_sample_select, + direct_host_stats.direct_host_reject_depth_no_fast); + XELOGI( + "MetalTelemetry[{}]: stage_compile requests={} hits/misses={}/{} " + "waits={} failures={} persistent hits/misses={}/{} " + "bytes dxil/metallib={}/{} owner_ms total/max={}/{} " + "wait_ms total/max={}/{}", + reason, stage_compile_stats.requests, stage_compile_stats.memory_hits, + stage_compile_stats.memory_misses, stage_compile_stats.waits, + stage_compile_stats.failures, stage_compile_stats.persistent_hits, + stage_compile_stats.persistent_misses, stage_compile_stats.dxil_bytes, + stage_compile_stats.metallib_bytes, + stage_compile_stats.owner_compile_ms_total, + stage_compile_stats.owner_compile_ms_max, + stage_compile_stats.wait_ms_total, stage_compile_stats.wait_ms_max); + XELOGI( + "MetalTelemetry[{}]: shader_prep dxil requests/hits/misses/failures=" + "{}/{}/{}/{} " + "bytes dxbc/dxil={}/{} ms total/max={}/{} libraries " + "requests/failures={}/{} bytes={} ms total/max={}/{} render_pso " + "requests/failures={}/{} ms total/max={}/{}", + reason, pipeline_runtime_stats.dxil_convert_requests, + pipeline_runtime_stats.dxil_cache_hits, + pipeline_runtime_stats.dxil_cache_misses, + pipeline_runtime_stats.dxil_convert_failures, + pipeline_runtime_stats.dxil_convert_dxbc_bytes, + pipeline_runtime_stats.dxil_convert_dxil_bytes, + pipeline_runtime_stats.dxil_convert_ms_total, + pipeline_runtime_stats.dxil_convert_ms_max, + pipeline_runtime_stats.library_requests, + pipeline_runtime_stats.library_failures, + pipeline_runtime_stats.library_bytes, + pipeline_runtime_stats.library_ms_total, + pipeline_runtime_stats.library_ms_max, + pipeline_runtime_stats.render_pipeline_requests, + pipeline_runtime_stats.render_pipeline_failures, + pipeline_runtime_stats.render_pipeline_ms_total, + pipeline_runtime_stats.render_pipeline_ms_max); + ResetBackendTelemetry(); +} + +void MetalCommandProcessor::ResetBackendTelemetry() { + backend_telemetry_last_dump_swap_ = backend_telemetry_.swaps; + backend_telemetry_ = BackendTelemetryStats(); + backend_telemetry_.swaps = backend_telemetry_last_dump_swap_; } void MetalCommandProcessor::EnsureCommandBufferAutoreleasePool() { @@ -3837,105 +6544,1185 @@ void MetalCommandProcessor::DrainCommandBufferAutoreleasePool() { command_buffer_autorelease_pool_ = nullptr; } -void MetalCommandProcessor::ResetMslRenderEncoderStateCache() { - msl_bound_vertex_texture_count_ = 0; - msl_bound_pixel_texture_count_ = 0; - msl_bound_vertex_sampler_count_ = 0; - msl_bound_pixel_sampler_count_ = 0; - msl_bound_vertex_texture_binding_uid_ = 0; - msl_bound_pixel_texture_binding_uid_ = 0; - msl_bound_vertex_sampler_binding_uid_ = 0; - msl_bound_pixel_sampler_binding_uid_ = 0; - msl_bound_vertex_textures_.fill(nullptr); - msl_bound_pixel_textures_.fill(nullptr); - msl_bound_vertex_samplers_.fill(nullptr); - msl_bound_pixel_samplers_.fill(nullptr); - msl_bound_shared_memory_buffer_ = nullptr; - msl_bound_vertex_argument_buffer_ = nullptr; - msl_bound_pixel_argument_buffer_ = nullptr; - msl_bound_vertex_argument_buffer_offset_ = 0; - msl_bound_pixel_argument_buffer_offset_ = 0; - msl_bound_vertex_argument_buffer_offset_valid_ = false; - msl_bound_pixel_argument_buffer_offset_valid_ = false; - msl_bound_null_buffer_ = nullptr; - msl_bound_uniforms_buffer_ = nullptr; - msl_bound_uniforms_vs_base_offset_ = 0; - msl_bound_uniforms_ps_base_offset_ = 0; - msl_bound_uniforms_offsets_valid_ = false; - msl_bound_pipeline_state_ = nullptr; - msl_viewport_valid_ = false; - msl_scissor_valid_ = false; - msl_rasterizer_state_valid_ = false; - msl_depth_stencil_state_ = nullptr; - msl_stencil_reference_valid_ = false; - msl_stencil_reference_ = 0; - ff_blend_factor_valid_ = false; - ResetRenderEncoderResourceUsage(); -} - -void MetalCommandProcessor::ResetMslCrossEncoderReuseCaches() { - msl_last_argbuf_vertex_textures_.fill(nullptr); - msl_last_argbuf_vertex_texture_count_ = 0; - msl_last_argbuf_vertex_samplers_.fill(nullptr); - msl_last_argbuf_vertex_sampler_count_ = 0; - msl_last_argbuf_vertex_buffer_ = nullptr; - msl_last_argbuf_vertex_offset_ = 0; - msl_last_argbuf_vertex_translation_ = nullptr; - msl_last_argbuf_vertex_encoded_length_ = 0; - msl_last_argbuf_vertex_layout_uid_ = 0; - msl_last_argbuf_pixel_textures_.fill(nullptr); - msl_last_argbuf_pixel_texture_count_ = 0; - msl_last_argbuf_pixel_samplers_.fill(nullptr); - msl_last_argbuf_pixel_sampler_count_ = 0; - msl_last_argbuf_pixel_buffer_ = nullptr; - msl_last_argbuf_pixel_offset_ = 0; - msl_last_argbuf_pixel_translation_ = nullptr; - msl_last_argbuf_pixel_encoded_length_ = 0; - msl_last_argbuf_pixel_layout_uid_ = 0; -} - void MetalCommandProcessor::EndRenderEncoder() { + EndRenderEncoder(RenderEncoderEndReason::kUnknown); +} + +void MetalCommandProcessor::EndRenderEncoder(RenderEncoderEndReason reason) { + if (!flushing_prepared_draw_queue_ && !prepared_draw_queue_.empty()) { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kRenderEncoderEnd)) { + XELOGE("Metal EndRenderEncoder: failed to flush prepared draw queue"); + } + } + size_t reason_index = static_cast(reason); + if (reason_index < backend_telemetry_.end_reasons.size()) { + ++backend_telemetry_.end_reasons[reason_index]; + } if (!current_render_encoder_) { + ++backend_telemetry_.end_encoder_no_active; + current_render_encoder_has_zpd_visibility_ = false; + if (current_render_pass_descriptor_) { + current_render_pass_descriptor_->release(); + current_render_pass_descriptor_ = nullptr; + } + ResetRenderEncoderBufferBindings(); + ResetRenderEncoderResourceUsage(); return; } + ++backend_telemetry_.end_encoder_active; + // Close any active ZPD query segment before ending the encoder. + // Metal visibility results are scoped to the render encoder; the offset + // cannot be reused after the encoder is ended. + if (GetZPDMode() != ZPDMode::kFake) { + CloseQuerySegment(); + } + UpdateSharedMemoryFenceForActiveRenderEncoder(); current_render_encoder_->endEncoding(); current_render_encoder_->release(); current_render_encoder_ = nullptr; - current_render_pass_descriptor_ = nullptr; - ResetMslRenderEncoderStateCache(); + current_render_encoder_has_zpd_visibility_ = false; + ResetRenderEncoderBufferBindings(); + ResetRenderEncoderResourceUsage(); + if (current_render_pass_descriptor_) { + current_render_pass_descriptor_->release(); + current_render_pass_descriptor_ = nullptr; + } + current_render_pipeline_state_ = nullptr; + rasterizer_state_valid_ = false; + current_depth_stencil_state_ = nullptr; + stencil_reference_valid_ = false; + heap_binds_set_on_encoder_ = false; + ResetRenderEncoderBufferBindings(); +} + +void MetalCommandProcessor::InvalidateRenderEncoderStateAfterDrawPassTransfers( + MetalRenderTargetCache::DrawPassTransferEncoderMutationMask mutations) { + if (!mutations) { + return; + } + using RTC = MetalRenderTargetCache; + if (mutations & RTC::kDrawPassTransferEncoderMutationPipeline) { + current_render_pipeline_state_ = nullptr; + } + if (mutations & RTC::kDrawPassTransferEncoderMutationDepthStencil) { + current_depth_stencil_state_ = nullptr; + } + if (mutations & RTC::kDrawPassTransferEncoderMutationStencilReference) { + stencil_reference_valid_ = false; + } + if (mutations & RTC::kDrawPassTransferEncoderMutationViewport) { + viewport_dirty_ = true; + } + if (mutations & RTC::kDrawPassTransferEncoderMutationScissor) { + scissor_dirty_ = true; + } + + constexpr RTC::DrawPassTransferEncoderMutationMask kTransferBufferMutations = + RTC::kDrawPassTransferEncoderMutationVertexSlot0 | + RTC::kDrawPassTransferEncoderMutationVertexSlot1 | + RTC::kDrawPassTransferEncoderMutationFragmentSlot0 | + RTC::kDrawPassTransferEncoderMutationFragmentSlot1; + if (mutations & RTC::kDrawPassTransferEncoderMutationVertexSlot0) { + InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage::kVertex, 0); + } + if (mutations & RTC::kDrawPassTransferEncoderMutationVertexSlot1) { + InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage::kVertex, 1); + } + if (mutations & RTC::kDrawPassTransferEncoderMutationFragmentSlot0) { + InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage::kFragment, + 0); + } + if (mutations & RTC::kDrawPassTransferEncoderMutationFragmentSlot1) { + InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage::kFragment, + 1); + } + if (mutations & kTransferBufferMutations) { + heap_binds_set_on_encoder_ = false; + } +} + +bool MetalCommandProcessor::RequestSharedMemoryRange( + SharedMemoryRequestReason reason, uint32_t start, uint32_t length) { + SharedMemory::Range range = {start, length}; + return RequestSharedMemoryRanges(reason, &range, 1); +} + +bool MetalCommandProcessor::RequestSharedMemoryRangeBeforeDrawPass( + SharedMemoryRequestReason reason, uint32_t start, uint32_t length) { + SharedMemory::Range range = {start, length}; + PrepareSharedMemoryUploadBeforeDrawPass(&range, 1); + return RequestSharedMemoryRanges(reason, &range, 1); +} + +bool MetalCommandProcessor::RequestSharedMemoryRanges( + SharedMemoryRequestReason reason, const SharedMemory::Range* ranges, + uint32_t range_count) { + const bool was_active = current_render_encoder_ != nullptr; + const size_t reason_index = static_cast(reason); + const bool reason_valid = reason_index < kSharedMemoryRequestReasonCount; + if (!shared_memory_) { + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kNoSharedMemory); + if (reason_valid) { + ++backend_telemetry_.shared_memory_request_failures[reason_index]; + } + return false; + } + if (range_count && !ranges) { + if (reason_valid) { + ++backend_telemetry_.shared_memory_request_failures[reason_index]; + } + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kRequestFailed); + return false; + } + + std::vector coalesced_ranges; + const SharedMemory::Range* request_ranges = ranges; + uint32_t request_range_count = range_count; + if (ranges && range_count) { + coalesced_ranges.reserve(range_count); + for (uint32_t i = 0; i < range_count; ++i) { + const SharedMemory::Range& range = ranges[i]; + if (!range.length) { + continue; + } + coalesced_ranges.push_back(range); + } + if (!coalesced_ranges.empty()) { + std::sort(coalesced_ranges.begin(), coalesced_ranges.end(), + [](const SharedMemory::Range& a, const SharedMemory::Range& b) { + return a.start < b.start; + }); + size_t coalesced_count = 0; + for (SharedMemory::Range range : coalesced_ranges) { + uint64_t range_end = uint64_t(range.start) + range.length; + if (range_end > SharedMemory::kBufferSize) { + coalesced_ranges[coalesced_count++] = range; + continue; + } + if (!coalesced_count) { + coalesced_ranges[coalesced_count++] = range; + continue; + } + SharedMemory::Range& previous = coalesced_ranges[coalesced_count - 1]; + uint64_t previous_end = uint64_t(previous.start) + previous.length; + if (range.start <= previous_end) { + uint64_t merged_end = std::max(previous_end, range_end); + previous.length = static_cast(merged_end - previous.start); + } else { + coalesced_ranges[coalesced_count++] = range; + } + } + coalesced_ranges.resize(coalesced_count); + } + request_ranges = coalesced_ranges.data(); + request_range_count = static_cast(coalesced_ranges.size()); + if (request_range_count) { + ++backend_telemetry_.shared_memory_upload_batches; + backend_telemetry_.shared_memory_upload_batch_input_ranges += range_count; + backend_telemetry_.shared_memory_upload_batch_coalesced_ranges += + request_range_count; + } + } + + SharedMemory::RequestRangeStats stats; + SharedMemoryRequestReason previous_upload_reason = + current_shared_memory_upload_reason_; + current_shared_memory_upload_reason_ = reason; + const bool success = shared_memory_->RequestRanges( + request_ranges, request_range_count, &stats); + current_shared_memory_upload_reason_ = previous_upload_reason; + backend_telemetry_.shared_memory_upload_batch_bytes += stats.upload_bytes; + if (reason_valid) { + backend_telemetry_.shared_memory_request_upload_bytes[reason_index] += + stats.upload_bytes; + if (!success) { + ++backend_telemetry_.shared_memory_request_failures[reason_index]; + } + } + + if (!success) { + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kRequestFailed); + } else if (!stats.upload_bytes) { + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kAlreadyResident); + } else if (was_active) { + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kUploadInsideRenderEncoder); + } else { + RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome::kUploadBeforeRenderEncoder); + } + + return success; +} + +void MetalCommandProcessor::RecordSharedMemoryRequestOutcome( + SharedMemoryRequestOutcome outcome) { + const size_t outcome_index = static_cast(outcome); + if (outcome_index < kSharedMemoryRequestOutcomeCount) { + ++backend_telemetry_.shared_memory_request_outcomes[outcome_index]; + } +} + +bool MetalCommandProcessor::AnySharedMemoryRangeInvalid( + const SharedMemory::Range* ranges, uint32_t range_count) const { + if (!shared_memory_ || !ranges || !range_count) { + return false; + } + for (uint32_t i = 0; i < range_count; ++i) { + if (!shared_memory_->IsRangeValid(ranges[i].start, ranges[i].length)) { + return true; + } + } + return false; +} + +void MetalCommandProcessor::RecordSharedMemoryLazyUploadRoute( + const MetalSharedMemory::UploadRouteInfo& route_info, + bool render_encoder_active) { + if (!route_info.upload_bytes) { + ++backend_telemetry_.shared_memory_lazy_upload_no_upload_batches; + return; + } + if (route_info.direct_bytes && !route_info.staged_bytes) { + ++backend_telemetry_.shared_memory_lazy_upload_direct_only_batches; + if (render_encoder_active) { + ++backend_telemetry_.shared_memory_lazy_upload_direct_only_active; + } + return; + } + if (route_info.direct_bytes && route_info.staged_bytes) { + ++backend_telemetry_.shared_memory_lazy_upload_mixed_batches; + if (render_encoder_active) { + ++backend_telemetry_.shared_memory_lazy_upload_mixed_active; + } + return; + } + ++backend_telemetry_.shared_memory_lazy_upload_staged_only_batches; + if (render_encoder_active) { + ++backend_telemetry_.shared_memory_lazy_upload_staged_only_active; + } +} + +void MetalCommandProcessor::PrepareSharedMemoryUploadBeforeDrawPass( + const SharedMemory::Range* ranges, uint32_t range_count) { + if (!shared_memory_ || !ranges || !range_count) { + return; + } + const bool render_encoder_active = current_render_encoder_ != nullptr; + MetalSharedMemory::UploadRouteInfo route_info = + shared_memory_->GetUploadRouteInfo(ranges, range_count); + RecordSharedMemoryLazyUploadRoute(route_info, render_encoder_active); + if (render_encoder_active && route_info.staged_bytes) { + EndRenderEncoder(RenderEncoderEndReason::kSharedMemoryUploadBeforeDrawPass); + } +} + +bool MetalCommandProcessor::HasActiveSharedMemoryWritePending() const { + if (NS::UInteger(active_render_encoder_shared_memory_write_stages_)) { + return true; + } + for (const PendingSharedMemoryWrite& pending : + pending_shared_memory_writes_) { + if (pending.active_render_encoder) { + return true; + } + } + return false; +} + +MTL::CommandBuffer* MetalCommandProcessor::RequestTransferCommandBuffer( + TransferRequestSource source) { + if (!flushing_prepared_draw_queue_ && !prepared_draw_queue_.empty()) { + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kTransferRequest)) { + return nullptr; + } + } + const size_t source_index = static_cast(source); + const bool ends_render_encoder = current_render_encoder_ != nullptr; + if (source_index < kTransferRequestSourceCount) { + ++backend_telemetry_.transfer_request_sources_total[source_index]; + if (ends_render_encoder) { + ++backend_telemetry_.transfer_request_sources_active[source_index]; + ++backend_telemetry_.transfer_request_render_encoder_ends[source_index]; + } else { + ++backend_telemetry_.transfer_request_sources_no_active[source_index]; + } + } + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kTransferRequest); + if (current_render_encoder_) { + EndRenderEncoder(RenderEncoderEndReason::kRequestTransferCommandBuffer); + } + if (texture_cache_ && + !texture_cache_->FlushPendingUploadEncodersForCommandEncoderBoundary()) { + return nullptr; + } + return EnsureCommandBuffer(); +} + +void MetalCommandProcessor::RecordSharedMemoryUploadRoute( + SharedMemoryUploadRoute route, uint64_t bytes) { + const size_t route_index = static_cast(route); + if (route_index >= kSharedMemoryUploadRouteCount) { + return; + } + ++backend_telemetry_.shared_memory_upload_route_counts[route_index]; + backend_telemetry_.shared_memory_upload_route_bytes[route_index] += bytes; +} + +void MetalCommandProcessor::RecordSharedMemoryDirectWriteEligibility( + uint64_t direct_bytes, uint64_t staged_bytes) { + backend_telemetry_.shared_memory_direct_write_eligible_bytes += direct_bytes; + backend_telemetry_.shared_memory_direct_write_staged_required_bytes += + staged_bytes; +} + +void MetalCommandProcessor::RecordSharedMemoryDirectWriteReject( + SharedMemoryDirectWriteRejectReason reason, uint64_t bytes) { + const size_t reason_index = static_cast(reason); + if (reason_index >= kSharedMemoryDirectWriteRejectReasonCount) { + return; + } + ++backend_telemetry_.shared_memory_direct_write_reject_counts[reason_index]; + backend_telemetry_.shared_memory_direct_write_reject_bytes[reason_index] += + bytes; +} + +void MetalCommandProcessor::RecordTextureUploadSourceRoute( + TextureUploadSourceRoute route, uint64_t bytes) { + const size_t route_index = static_cast(route); + if (route_index >= kTextureUploadSourceRouteCount) { + return; + } + ++backend_telemetry_.texture_upload_source_route_counts[route_index]; + backend_telemetry_.texture_upload_source_route_bytes[route_index] += bytes; +} + +void MetalCommandProcessor::RecordTextureUploadSourceFallback( + TextureUploadSourceFallbackReason reason) { + const size_t reason_index = static_cast(reason); + if (reason_index < kTextureUploadSourceFallbackReasonCount) { + ++backend_telemetry_.texture_upload_source_fallback_reasons[reason_index]; + } +} + +void MetalCommandProcessor::RecordSharedMemoryUploadEncoderCopy() { + ++backend_telemetry_.shared_memory_upload_encoder_copies; +} + +MTL::BlitCommandEncoder* +MetalCommandProcessor::GetSharedMemoryUploadBlitEncoder() { + if (shared_memory_upload_blit_encoder_) { + ++backend_telemetry_.shared_memory_upload_encoder_reuses; + return shared_memory_upload_blit_encoder_; + } + + MTL::CommandBuffer* command_buffer = + RequestTransferCommandBuffer(TransferRequestSource::kSharedMemoryUpload); + if (!command_buffer) { + return nullptr; + } + + shared_memory_upload_blit_encoder_ = command_buffer->blitCommandEncoder(); + if (!shared_memory_upload_blit_encoder_) { + XELOGE("Metal: failed to create shared-memory upload blit encoder"); + return nullptr; + } + ++backend_telemetry_.shared_memory_upload_encoder_acquisitions; + shared_memory_upload_blit_encoder_->retain(); + shared_memory_upload_blit_encoder_->setLabel( + NS::String::string("XeniaSharedMemoryUpload", NS::UTF8StringEncoding)); + return shared_memory_upload_blit_encoder_; +} + +void MetalCommandProcessor::EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason reason) { + if (!shared_memory_upload_blit_encoder_) { + return; + } + const size_t reason_index = static_cast(reason); + if (reason_index < kSharedMemoryUploadEncoderEndReasonCount) { + ++backend_telemetry_.shared_memory_upload_encoder_end_reasons[reason_index]; + } + shared_memory_upload_blit_encoder_->endEncoding(); + shared_memory_upload_blit_encoder_->release(); + shared_memory_upload_blit_encoder_ = nullptr; +} + +MTL::CommandBuffer* +MetalCommandProcessor::CreateStandaloneTransferCommandBuffer( + const char* label) { + if (!command_queue_) { + return nullptr; + } + MTL::CommandBuffer* cmd = command_queue_->commandBuffer(); + if (!cmd) { + return nullptr; + } + cmd->retain(); + if (label) { + cmd->setLabel(NS::String::string(label, NS::UTF8StringEncoding)); + } + return cmd; +} + +void MetalCommandProcessor::CommitStandaloneAsync(MTL::CommandBuffer* cmd) { + if (!cmd) { + return; + } + cmd->addCompletedHandler(^(MTL::CommandBuffer* completed_cmd) { + completed_cmd->release(); + }); + cmd->commit(); +} + +void MetalCommandProcessor::CommitStandaloneAndWait(MTL::CommandBuffer* cmd) { + if (!cmd) { + return; + } + cmd->commit(); + cmd->waitUntilCompleted(); + cmd->release(); } void MetalCommandProcessor::ResetRenderEncoderResourceUsage() { - render_encoder_resource_usage_.clear(); + for (EncoderResourceUsageTableEntry& entry : + render_encoder_resource_usage_table_) { + entry = {}; + } + render_encoder_resource_usage_count_ = 0; render_encoder_heap_usage_.clear(); + render_encoder_bindless_fixed_resources_serial_ = 0; + render_encoder_bindless_texture_resources_serial_ = 0; + render_encoder_bindless_root_resources_serial_ = 0; +} + +void MetalCommandProcessor::AddRenderResourceRef(RenderResourceSet& set, + MTL::Resource* resource, + MTL::ResourceUsage usage, + MTL::RenderStages stages) { + if (!resource) { + return; + } + if (IsResidencySetResourceCovered(resource)) { + ++backend_telemetry_.residency_set_resource_refs_covered; + return; + } + ++backend_telemetry_.residency_set_resource_refs_fallback; + uint32_t stage_bits = MetalRenderStageBits(stages); + if (!stage_bits) { + stages = MetalAllGraphicsRenderStages(); + stage_bits = MetalRenderStageBits(stages); + } + const uint32_t usage_bits = MetalResourceUsageBits(usage); + for (RenderResourceRef& existing : set.resources) { + if (existing.resource != resource) { + continue; + } + existing.usage = + MTL::ResourceUsage(MetalResourceUsageBits(existing.usage) | usage_bits); + existing.stages = + MTL::RenderStages(MetalRenderStageBits(existing.stages) | stage_bits); + return; + } + set.resources.push_back({resource, usage, stages}); +} + +void MetalCommandProcessor::RestoreRenderResourceSet( + RenderResourceSetKind kind, RenderResourceSet& current, + const RenderResourceSet& snapshot) { + const size_t kind_index = static_cast(kind); + if (snapshot.source_serial && + current.source_serial == snapshot.source_serial) { + if (kind_index < + backend_telemetry_.render_resource_registry_serial_skips.size()) { + ++backend_telemetry_.render_resource_registry_serial_skips[kind_index]; + } + return; + } + + bool same = current.resources.size() == snapshot.resources.size(); + if (same) { + for (size_t i = 0; i < current.resources.size(); ++i) { + const RenderResourceRef& current_ref = current.resources[i]; + const RenderResourceRef& snapshot_ref = snapshot.resources[i]; + if (current_ref.resource != snapshot_ref.resource || + MetalResourceUsageBits(current_ref.usage) != + MetalResourceUsageBits(snapshot_ref.usage) || + MetalRenderStageBits(current_ref.stages) != + MetalRenderStageBits(snapshot_ref.stages)) { + same = false; + break; + } + } + } + if (same) { + current.source_serial = snapshot.source_serial; + return; + } + + if (kind_index < backend_telemetry_.render_resource_registry_builds.size()) { + ++backend_telemetry_.render_resource_registry_builds[kind_index]; + } + uint64_t next_serial = current.serial + 1; + if (!next_serial) { + next_serial = 1; + } + current.resources = snapshot.resources; + current.serial = next_serial; + current.source_serial = snapshot.source_serial; +} + +void MetalCommandProcessor::PublishRenderResourceSet(RenderResourceSet& current, + RenderResourceSet&& next) { + if (next.source_serial && current.source_serial == next.source_serial) { + return; + } + bool same = current.resources.size() == next.resources.size(); + if (same) { + for (size_t i = 0; i < current.resources.size(); ++i) { + const RenderResourceRef& current_ref = current.resources[i]; + const RenderResourceRef& next_ref = next.resources[i]; + if (current_ref.resource != next_ref.resource || + MetalResourceUsageBits(current_ref.usage) != + MetalResourceUsageBits(next_ref.usage) || + MetalRenderStageBits(current_ref.stages) != + MetalRenderStageBits(next_ref.stages)) { + same = false; + break; + } + } + } + if (same) { + current.source_serial = next.source_serial; + return; + } + uint64_t next_serial = current.serial + 1; + if (!next_serial) { + next_serial = 1; + } + current.resources = std::move(next.resources); + current.serial = next_serial; + current.source_serial = next.source_serial; +} + +uint64_t MetalCommandProcessor::GetBindlessFixedResourceSourceSerial( + MTL::ResourceUsage shared_memory_usage) const { + const uint64_t shared_memory_usage_bits = + uint64_t(MetalResourceUsageBits(shared_memory_usage)); + const uint64_t rt_serial = + render_target_cache_ ? render_target_cache_->GetBindlessResourcesSerial() + : 0; + return (rt_serial << 8) ^ shared_memory_usage_bits; +} + +uint64_t MetalCommandProcessor::GetBindlessTextureResourceSourceSerial() const { + uint64_t hash = 1469598103934665603ull; + auto hash_value = [&](uint64_t value) { + hash ^= value; + hash *= 1099511628211ull; + }; + hash_value(current_texture_bindless_resources_vertex_.size()); + for (MTL::Texture* texture : current_texture_bindless_resources_vertex_) { + hash_value(reinterpret_cast(texture)); + } + hash_value(0x9e3779b97f4a7c15ull); + hash_value(current_texture_bindless_resources_pixel_.size()); + for (MTL::Texture* texture : current_texture_bindless_resources_pixel_) { + hash_value(reinterpret_cast(texture)); + } + return hash ? hash : 1; +} + +uint64_t MetalCommandProcessor::GetBindlessRootResourceSourceSerial( + const UniformBufferInfo& uniforms) const { + uint64_t hash = 1469598103934665603ull; + auto hash_value = [&](uint64_t value) { + hash ^= value; + hash *= 1099511628211ull; + }; + constexpr size_t kGraphicsRootStage = kStageVertex; + const StageRootArgumentAllocation& allocation = + current_bindless_stage_root_arguments_[kGraphicsRootStage]; + hash_value(reinterpret_cast(allocation.valid ? allocation.buffer + : nullptr)); + for (size_t stage = 0; stage < kStageCount; ++stage) { + hash_value(uniforms.active_cbv_masks[stage]); + for (size_t cbv = 0; cbv < kCbvSlotCount; ++cbv) { + const bool active = + uniforms.active_cbv_masks[stage] & (uint32_t(1) << cbv); + hash_value(reinterpret_cast( + active ? uniforms.cbvs[stage][cbv].buffer : nullptr)); + } + } + return hash ? hash : 1; +} + +void MetalCommandProcessor::PublishBindlessFixedResourceSet( + MTL::ResourceUsage shared_memory_usage) { + constexpr size_t kFixedKindIndex = size_t(RenderResourceSetKind::kFixed); + const uint64_t source_serial = + GetBindlessFixedResourceSourceSerial(shared_memory_usage); + if (source_serial && + current_bindless_fixed_resource_source_serial_ == source_serial) { + ++backend_telemetry_.render_resource_registry_serial_skips[kFixedKindIndex]; + return; + } + ++backend_telemetry_.render_resource_registry_builds[kFixedKindIndex]; + RenderResourceSet next; + next.source_serial = source_serial; + const MTL::RenderStages stages = MetalAllGraphicsRenderStages(); + AddRenderResourceRef(next, + shared_memory_ ? shared_memory_->GetBuffer() : nullptr, + shared_memory_usage, stages); + std::vector render_target_resources; + if (render_target_cache_) { + render_target_cache_->CollectBindlessResources(render_target_resources); + } + for (MTL::Resource* resource : render_target_resources) { + AddRenderResourceRef(next, resource, + MTL::ResourceUsageRead | MTL::ResourceUsageWrite, + stages); + } + AddRenderResourceRef(next, null_buffer_, MTL::ResourceUsageRead, stages); + AddRenderResourceRef(next, view_bindless_heap_, MTL::ResourceUsageRead, + stages); + AddRenderResourceRef(next, sampler_bindless_heap_, MTL::ResourceUsageRead, + stages); + AddRenderResourceRef(next, system_view_tables_, MTL::ResourceUsageRead, + stages); + PublishRenderResourceSet(current_bindless_fixed_resource_set_, + std::move(next)); + current_bindless_fixed_resource_source_serial_ = + current_bindless_fixed_resource_set_.source_serial; +} + +void MetalCommandProcessor::RestoreBindlessTextureResourceSet( + const RenderResourceSet& set) { + RestoreRenderResourceSet(RenderResourceSetKind::kTexture, + current_bindless_texture_resource_set_, set); +} + +void MetalCommandProcessor::PublishBindlessTextureResourceSet() { + constexpr size_t kTextureKindIndex = size_t(RenderResourceSetKind::kTexture); + const uint64_t source_serial = GetBindlessTextureResourceSourceSerial(); + if (source_serial && + current_bindless_texture_resource_source_serial_ == source_serial) { + ++backend_telemetry_ + .render_resource_registry_serial_skips[kTextureKindIndex]; + return; + } + ++backend_telemetry_.render_resource_registry_builds[kTextureKindIndex]; + RenderResourceSet next; + next.source_serial = source_serial; + const MTL::RenderStages stages = MetalAllGraphicsRenderStages(); + for (MTL::Texture* texture : current_texture_bindless_resources_vertex_) { + AddRenderResourceRef(next, texture, MTL::ResourceUsageRead, stages); + } + for (MTL::Texture* texture : current_texture_bindless_resources_pixel_) { + AddRenderResourceRef(next, texture, MTL::ResourceUsageRead, stages); + } + PublishRenderResourceSet(current_bindless_texture_resource_set_, + std::move(next)); + current_bindless_texture_resource_source_serial_ = + current_bindless_texture_resource_set_.source_serial; +} + +void MetalCommandProcessor::PublishBindlessRootResourceSet( + const UniformBufferInfo& uniforms) { + constexpr size_t kRootKindIndex = size_t(RenderResourceSetKind::kRoot); + const uint64_t root_source_serial = + GetBindlessRootResourceSourceSerial(uniforms); + if (root_source_serial && + current_bindless_root_resource_source_serial_ == root_source_serial) { + ++backend_telemetry_.render_resource_registry_serial_skips[kRootKindIndex]; + return; + } + ++backend_telemetry_.render_resource_registry_builds[kRootKindIndex]; + RenderResourceSet next; + next.source_serial = root_source_serial; + const MTL::RenderStages stages = MetalAllGraphicsRenderStages(); + constexpr size_t kGraphicsRootStage = kStageVertex; + const StageRootArgumentAllocation& allocation = + current_bindless_stage_root_arguments_[kGraphicsRootStage]; + if (allocation.valid) { + AddRenderResourceRef(next, allocation.buffer, MTL::ResourceUsageRead, + stages); + } + for (size_t stage = 0; stage < kStageCount; ++stage) { + for (size_t cbv = 0; cbv < kCbvSlotCount; ++cbv) { + if (uniforms.cbvs[stage][cbv].active) { + AddRenderResourceRef(next, uniforms.cbvs[stage][cbv].buffer, + MTL::ResourceUsageRead, stages); + } + } + } + PublishRenderResourceSet(current_bindless_root_resource_set_, + std::move(next)); + current_bindless_root_resource_source_serial_ = + current_bindless_root_resource_set_.source_serial; +} + +void MetalCommandProcessor::ApplyRenderEncoderResourceSets() { + ApplyRenderEncoderResourceSet( + RenderResourceSetKind::kFixed, current_bindless_fixed_resource_set_, + render_encoder_bindless_fixed_resources_serial_); + ApplyRenderEncoderResourceSet( + RenderResourceSetKind::kTexture, current_bindless_texture_resource_set_, + render_encoder_bindless_texture_resources_serial_); + ApplyRenderEncoderResourceSet(RenderResourceSetKind::kRoot, + current_bindless_root_resource_set_, + render_encoder_bindless_root_resources_serial_); +} + +void MetalCommandProcessor::ApplyRenderEncoderResourceSet( + RenderResourceSetKind kind, const RenderResourceSet& set, + uint64_t& applied_serial) { + const size_t kind_index = static_cast(kind); + if (!current_render_encoder_) { + return; + } + if (applied_serial == set.serial) { + if (kind_index < backend_telemetry_.render_resource_set_skips.size()) { + ++backend_telemetry_.render_resource_set_skips[kind_index]; + } + return; + } + if (kind_index < backend_telemetry_.render_resource_set_applies.size()) { + ++backend_telemetry_.render_resource_set_applies[kind_index]; + backend_telemetry_.render_resource_set_resources[kind_index] += + set.resources.size(); + ++backend_telemetry_.render_resource_registry_registers[kind_index]; + } + constexpr size_t kResourceBatchSize = 128; + std::array batch; + uint32_t batch_count = 0; + MTL::ResourceUsage batch_usage = MTL::ResourceUsageRead; + MTL::RenderStages batch_stages = MTL::RenderStages(0); + auto flush_batch = [&]() { + if (!batch_count) { + return; + } + UseRenderEncoderResources(batch.data(), batch_count, batch_usage, + batch_stages); + batch_count = 0; + }; + for (const RenderResourceRef& ref : set.resources) { + if (!ref.resource) { + continue; + } + if (!batch_count) { + batch_usage = ref.usage; + batch_stages = ref.stages; + } else if (MetalResourceUsageBits(batch_usage) != + MetalResourceUsageBits(ref.usage) || + MetalRenderStageBits(batch_stages) != + MetalRenderStageBits(ref.stages)) { + flush_batch(); + batch_usage = ref.usage; + batch_stages = ref.stages; + } + batch[batch_count++] = ref.resource; + if (batch_count == batch.size()) { + flush_batch(); + } + } + flush_batch(); + applied_serial = set.serial; +} + +void MetalCommandProcessor::GrowRenderEncoderResourceUsageTable( + size_t min_capacity) { + size_t capacity = render_encoder_resource_usage_table_.size(); + if (!capacity) { + capacity = 256; + } + while (capacity < min_capacity) { + capacity <<= 1; + } + + std::vector old_entries = + std::move(render_encoder_resource_usage_table_); + render_encoder_resource_usage_table_.assign(capacity, {}); + render_encoder_resource_usage_count_ = 0; + for (const EncoderResourceUsageTableEntry& old_entry : old_entries) { + if (!old_entry.resource) { + continue; + } + bool inserted = false; + EncoderResourceUsageState* state = + FindOrInsertRenderEncoderResourceUsage(old_entry.resource, inserted); + if (state) { + *state = old_entry.state; + } + } +} + +MetalCommandProcessor::EncoderResourceUsageState* +MetalCommandProcessor::FindOrInsertRenderEncoderResourceUsage( + MTL::Resource* resource, bool& inserted) { + inserted = false; + if (!resource) { + return nullptr; + } + if (render_encoder_resource_usage_table_.empty() || + (render_encoder_resource_usage_count_ + 1) * 4 >= + render_encoder_resource_usage_table_.size() * 3) { + GrowRenderEncoderResourceUsageTable( + std::max(render_encoder_resource_usage_table_.size() * 2, 256)); + } + + const size_t mask = render_encoder_resource_usage_table_.size() - 1; + size_t index = + ((reinterpret_cast(resource) >> 4) * 11400714819323198485ull) & + mask; + for (;;) { + EncoderResourceUsageTableEntry& entry = + render_encoder_resource_usage_table_[index]; + if (!entry.resource) { + entry.resource = resource; + entry.state = {}; + ++render_encoder_resource_usage_count_; + inserted = true; + return &entry.state; + } + if (entry.resource == resource) { + return &entry.state; + } + index = (index + 1) & mask; + } +} + +void MetalCommandProcessor::ResetRenderEncoderBufferBindings() { + for (auto& stage_bindings : render_encoder_buffer_bindings_) { + for (auto& binding : stage_bindings) { + binding = {}; + } + } + render_encoder_bindless_stage_root_bind_serials_.fill(0); + render_encoder_bindless_table_bind_mesh_path_ = false; + render_encoder_bindless_table_bind_tessellation_ = false; +} + +void MetalCommandProcessor::InvalidateRenderEncoderBufferBinding( + RenderEncoderBufferStage stage, NS::UInteger index) { + const size_t stage_index = size_t(stage); + if (stage_index >= render_encoder_buffer_bindings_.size() || + index >= kTrackedRenderEncoderBufferBindingCount) { + return; + } + render_encoder_buffer_bindings_[stage_index][index] = {}; + if (index == kIRArgumentBufferBindPoint || + index == kIRArgumentBufferHullDomainBindPoint) { + if (stage == RenderEncoderBufferStage::kFragment) { + render_encoder_bindless_stage_root_bind_serials_[kStagePixel] = 0; + } else { + render_encoder_bindless_stage_root_bind_serials_[kStageVertex] = 0; + render_encoder_bindless_table_bind_mesh_path_ = false; + render_encoder_bindless_table_bind_tessellation_ = false; + } + } +} + +void MetalCommandProcessor::SetRenderEncoderBuffer( + RenderEncoderBufferStage stage, MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index) { + if (!current_render_encoder_ || stage == RenderEncoderBufferStage::kCount) { + return; + } + size_t stage_index = static_cast(stage); + auto set_buffer = [&](MTL::Buffer* buffer_to_set, + NS::UInteger offset_to_set) { + switch (stage) { + case RenderEncoderBufferStage::kVertex: + current_render_encoder_->setVertexBuffer(buffer_to_set, offset_to_set, + index); + break; + case RenderEncoderBufferStage::kFragment: + current_render_encoder_->setFragmentBuffer(buffer_to_set, offset_to_set, + index); + break; + case RenderEncoderBufferStage::kObject: + current_render_encoder_->setObjectBuffer(buffer_to_set, offset_to_set, + index); + break; + case RenderEncoderBufferStage::kMesh: + current_render_encoder_->setMeshBuffer(buffer_to_set, offset_to_set, + index); + break; + case RenderEncoderBufferStage::kCount: + break; + } + }; + auto set_buffer_offset = [&]() { + switch (stage) { + case RenderEncoderBufferStage::kVertex: + current_render_encoder_->setVertexBufferOffset(offset, index); + break; + case RenderEncoderBufferStage::kFragment: + current_render_encoder_->setFragmentBufferOffset(offset, index); + break; + case RenderEncoderBufferStage::kObject: + current_render_encoder_->setObjectBufferOffset(offset, index); + break; + case RenderEncoderBufferStage::kMesh: + current_render_encoder_->setMeshBufferOffset(offset, index); + break; + case RenderEncoderBufferStage::kCount: + break; + } + }; + if (!buffer) { + if (stage_index < + backend_telemetry_.render_encoder_buffer_null_binds.size()) { + ++backend_telemetry_.render_encoder_buffer_null_binds[stage_index]; + } + set_buffer(nullptr, 0); + InvalidateRenderEncoderBufferBinding(stage, index); + return; + } + if (index >= kTrackedRenderEncoderBufferBindingCount) { + if (stage_index < + backend_telemetry_.render_encoder_buffer_untracked_binds.size()) { + ++backend_telemetry_.render_encoder_buffer_untracked_binds[stage_index]; + } + set_buffer(buffer, offset); + return; + } + auto& binding = render_encoder_buffer_bindings_[stage_index][index]; + if (binding.valid && binding.buffer == buffer) { + if (binding.offset != offset) { + if (stage_index < + backend_telemetry_.render_encoder_buffer_offset_binds.size()) { + ++backend_telemetry_.render_encoder_buffer_offset_binds[stage_index]; + } + set_buffer_offset(); + binding.offset = offset; + } else if (stage_index < + backend_telemetry_.render_encoder_buffer_noop_binds.size()) { + ++backend_telemetry_.render_encoder_buffer_noop_binds[stage_index]; + } + return; + } + if (stage_index < + backend_telemetry_.render_encoder_buffer_full_binds.size()) { + ++backend_telemetry_.render_encoder_buffer_full_binds[stage_index]; + } + set_buffer(buffer, offset); + binding = {buffer, offset, true}; +} + +void MetalCommandProcessor::SetRenderEncoderVertexBuffer(MTL::Buffer* buffer, + NS::UInteger offset, + NS::UInteger index) { + SetRenderEncoderBuffer(RenderEncoderBufferStage::kVertex, buffer, offset, + index); +} + +void MetalCommandProcessor::SetRenderEncoderFragmentBuffer(MTL::Buffer* buffer, + NS::UInteger offset, + NS::UInteger index) { + SetRenderEncoderBuffer(RenderEncoderBufferStage::kFragment, buffer, offset, + index); +} + +void MetalCommandProcessor::SetRenderEncoderObjectBuffer(MTL::Buffer* buffer, + NS::UInteger offset, + NS::UInteger index) { + SetRenderEncoderBuffer(RenderEncoderBufferStage::kObject, buffer, offset, + index); +} + +void MetalCommandProcessor::SetRenderEncoderMeshBuffer(MTL::Buffer* buffer, + NS::UInteger offset, + NS::UInteger index) { + SetRenderEncoderBuffer(RenderEncoderBufferStage::kMesh, buffer, offset, + index); } void MetalCommandProcessor::UseRenderEncoderResource(MTL::Resource* resource, MTL::ResourceUsage usage) { + UseRenderEncoderResource(resource, usage, MetalAllGraphicsRenderStages()); +} + +void MetalCommandProcessor::UseRenderEncoderResource(MTL::Resource* resource, + MTL::ResourceUsage usage, + MTL::RenderStages stages) { if (!current_render_encoder_ || !resource) { return; } - UseRenderEncoderHeap(resource->heap()); - uint32_t usage_bits = static_cast(usage); - auto it = render_encoder_resource_usage_.find(resource); - if (it != render_encoder_resource_usage_.end()) { - if ((it->second & usage_bits) == usage_bits) { + if (IsResidencySetResourceCovered(resource)) { + ++backend_telemetry_.residency_set_use_resources_covered; + return; + } + ++backend_telemetry_.residency_set_use_resources_fallback; + ++backend_telemetry_.render_encoder_use_resource_calls; + + const uint32_t usage_bits = MetalResourceUsageBits(usage); + uint32_t stage_bits = MetalRenderStageBits(stages); + if (!stage_bits) { + stages = MetalAllGraphicsRenderStages(); + stage_bits = MetalRenderStageBits(stages); + } + + auto usage_state_covers = [&](const EncoderResourceUsageState& state) { + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageRead)) && + ((state.read_stage_bits & stage_bits) != stage_bits)) { + return false; + } + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageWrite)) && + ((state.write_stage_bits & stage_bits) != stage_bits)) { + return false; + } + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageSample)) && + ((state.sample_stage_bits & stage_bits) != stage_bits)) { + return false; + } + return (state.usage_bits & usage_bits) == usage_bits; + }; + auto add_usage_to_state = [&](EncoderResourceUsageState& state) { + state.usage_bits |= usage_bits; + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageRead)) { + state.read_stage_bits |= stage_bits; + } + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageWrite)) { + state.write_stage_bits |= stage_bits; + } + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageSample)) { + state.sample_stage_bits |= stage_bits; + } + }; + + bool inserted = false; + EncoderResourceUsageState* state = + FindOrInsertRenderEncoderResourceUsage(resource, inserted); + if (!state) { + return; + } + if (!inserted) { + if (usage_state_covers(*state)) { + ++backend_telemetry_.render_encoder_use_resource_skips; return; } - it->second |= usage_bits; - } else { - render_encoder_resource_usage_.emplace(resource, usage_bits); + ++backend_telemetry_.render_encoder_use_resource_upgrades; } - current_render_encoder_->useResource(resource, usage); + + add_usage_to_state(*state); + current_render_encoder_->useResource(resource, usage, stages); +} + +void MetalCommandProcessor::UseRenderEncoderResources( + const MTL::Resource* const resources[], uint32_t count, + MTL::ResourceUsage usage) { + UseRenderEncoderResources(resources, count, usage, + MetalAllGraphicsRenderStages()); +} + +void MetalCommandProcessor::UseRenderEncoderResources( + const MTL::Resource* const resources[], uint32_t count, + MTL::ResourceUsage usage, MTL::RenderStages stages) { + if (!current_render_encoder_ || !resources || !count) { + return; + } + const uint32_t usage_bits = MetalResourceUsageBits(usage); + uint32_t stage_bits = MetalRenderStageBits(stages); + if (!stage_bits) { + stages = MetalAllGraphicsRenderStages(); + stage_bits = MetalRenderStageBits(stages); + } + auto usage_state_covers = [&](const EncoderResourceUsageState& state) { + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageRead)) && + ((state.read_stage_bits & stage_bits) != stage_bits)) { + return false; + } + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageWrite)) && + ((state.write_stage_bits & stage_bits) != stage_bits)) { + return false; + } + if ((usage_bits & MetalResourceUsageBits(MTL::ResourceUsageSample)) && + ((state.sample_stage_bits & stage_bits) != stage_bits)) { + return false; + } + return (state.usage_bits & usage_bits) == usage_bits; + }; + auto add_usage_to_state = [&](EncoderResourceUsageState& state) { + state.usage_bits |= usage_bits; + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageRead)) { + state.read_stage_bits |= stage_bits; + } + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageWrite)) { + state.write_stage_bits |= stage_bits; + } + if (usage_bits & MetalResourceUsageBits(MTL::ResourceUsageSample)) { + state.sample_stage_bits |= stage_bits; + } + }; + + constexpr size_t kResourceBatchSize = 128; + std::array resource_batch; + uint32_t resource_batch_count = 0; + auto flush_batch = [&]() { + if (!resource_batch_count) { + return; + } + current_render_encoder_->useResources(resource_batch.data(), + resource_batch_count, usage, stages); + ++backend_telemetry_.render_encoder_use_resources_batches; + resource_batch_count = 0; + }; + for (uint32_t i = 0; i < count; ++i) { + const MTL::Resource* const_resource = resources[i]; + if (!const_resource) { + continue; + } + MTL::Resource* resource = const_cast(const_resource); + if (IsResidencySetResourceCovered(resource)) { + ++backend_telemetry_.residency_set_use_resources_covered; + continue; + } + ++backend_telemetry_.residency_set_use_resources_fallback; + ++backend_telemetry_.render_encoder_use_resources_requested; + bool inserted = false; + EncoderResourceUsageState* state = + FindOrInsertRenderEncoderResourceUsage(resource, inserted); + if (!state) { + continue; + } + if (!inserted) { + if (usage_state_covers(*state)) { + ++backend_telemetry_.render_encoder_use_resources_skips; + continue; + } + ++backend_telemetry_.render_encoder_use_resource_upgrades; + } + + add_usage_to_state(*state); + resource_batch[resource_batch_count++] = const_resource; + if (resource_batch_count == resource_batch.size()) { + flush_batch(); + } + } + flush_batch(); } void MetalCommandProcessor::UseRenderEncoderHeap(MTL::Heap* heap) { if (!current_render_encoder_ || !heap) { return; } - if (!render_encoder_heap_usage_.insert(heap).second) { - return; + // See IsResidencySetResourceCovered: heap-backed textures and render targets + // still need this usage declaration until texture / RT hazards are tracked + // explicitly by the backend. + ++backend_telemetry_.residency_set_use_heaps_fallback; + for (MTL::Heap* used_heap : render_encoder_heap_usage_) { + if (used_heap == heap) { + return; + } } + render_encoder_heap_usage_.push_back(heap); current_render_encoder_->useHeap(heap); } @@ -3965,44 +7752,84 @@ void MetalCommandProcessor::UseRenderEncoderAttachmentHeaps( } } -void MetalCommandProcessor::BeginCommandBuffer() { +MTL::RenderPassDescriptor* MetalCommandProcessor::GetDrawRenderPassDescriptor( + bool fallback_depth_attachment_required) { + if (render_target_cache_) { + if (MTL::RenderPassDescriptor* cache_desc = + render_target_cache_->GetRenderPassDescriptor( + 1, fallback_depth_attachment_required)) { + // Attach the ZPD visibility buffer so occlusion queries can write results + // directly into shared memory without an explicit resolve step. + if (GetZPDMode() != ZPDMode::kFake && zpd_visibility_pool_ && + zpd_visibility_pool_->is_initialized()) { + cache_desc->setVisibilityResultBuffer( + zpd_visibility_pool_->visibility_buffer()); + cache_desc->setVisibilityResultType(MTL::VisibilityResultTypeReset); + } else { + cache_desc->setVisibilityResultBuffer(nullptr); + } + return cache_desc; + } + } + return nullptr; +} + +bool MetalCommandProcessor::BeginRenderEncoderForDraw( + bool fallback_depth_attachment_required) { + ++backend_telemetry_.begin_encoder_calls; if (!EnsureCommandBuffer()) { - return; + return false; + } + const bool zpd_segment_pending = GetZPDMode() != ZPDMode::kFake && + zpd_active_segment_.logical_active && + zpd_active_segment_.segment_pending_begin; + if (zpd_segment_pending) { + EnsureZPDQueryResources(); + } + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kRenderBegin); + if (texture_cache_ && + !texture_cache_->FlushPendingUploadEncodersForCommandEncoderBoundary()) { + return false; } - if (!current_render_encoder_ && (!render_encoder_resource_usage_.empty() || + if (!current_render_encoder_ && (render_encoder_resource_usage_count_ || !render_encoder_heap_usage_.empty())) { + ++backend_telemetry_.begin_encoder_resource_usage_resets; ResetRenderEncoderResourceUsage(); } - // Obtain the render pass descriptor. Prefer the one provided by - // MetalRenderTargetCache (host render-target path), falling back to the - // legacy descriptor if needed. - MTL::RenderPassDescriptor* pass_descriptor = render_pass_descriptor_; - if (render_target_cache_) { - if (MTL::RenderPassDescriptor* cache_desc = - render_target_cache_->GetRenderPassDescriptor(1)) { - pass_descriptor = cache_desc; - } - } - if (!pass_descriptor) { - XELOGE("BeginCommandBuffer: No render pass descriptor available"); - return; + if (current_render_encoder_ && zpd_segment_pending && IsZPDQueryPoolReady() && + !current_render_encoder_has_zpd_visibility_) { + EndRenderEncoder(RenderEncoderEndReason::kUnknown); } - // Detect Reverse-Z usage and update clear depth. - if (register_file_) { - auto depth_control = register_file_->Get(); - bool reverse_z = - depth_control.z_enable && - (depth_control.zfunc == xenos::CompareFunction::kGreater || - depth_control.zfunc == xenos::CompareFunction::kGreaterEqual); + // Obtain the render pass descriptor from MetalRenderTargetCache (host + // render-target path). If an encoder is already active, keep using its + // descriptor while the attachment textures still match the current binding. + // The cache may be dirty only because a clear load action was consumed and + // the next new pass needs a refreshed descriptor. + if (current_render_encoder_ && render_target_cache_ && + render_target_cache_->IsRenderPassDescriptorCompatible( + current_render_pass_descriptor_, 1, + fallback_depth_attachment_required)) { + ++backend_telemetry_.begin_encoder_reused_compatible; + return true; + } + + MTL::RenderPassDescriptor* pass_descriptor = + GetDrawRenderPassDescriptor(fallback_depth_attachment_required); + if (!pass_descriptor) { + ++backend_telemetry_.begin_encoder_descriptor_failures; + XELOGE("BeginRenderEncoderForDraw: No render pass descriptor available"); + return false; + } + + // Keep first-use depth clears tied to the guest depth format. D24FS8 uses + // 0.0 as the far value, unlike fixed-point D24S8. + if (render_target_cache_) { if (auto* da = pass_descriptor->depthAttachment()) { - if (reverse_z) { - da->setClearDepth(0.0); - } else { - da->setClearDepth(1.0); - } + da->setClearDepth(render_target_cache_->GetDepthTargetClearDepth()); } } @@ -4011,551 +7838,111 @@ void MetalCommandProcessor::BeginCommandBuffer() { // restart the render encoder with the updated descriptor. if (current_render_encoder_ && current_render_pass_descriptor_ != pass_descriptor) { - EndRenderEncoder(); + ++backend_telemetry_.begin_encoder_descriptor_restarts; + EndRenderEncoder( + RenderEncoderEndReason::kBeginRenderEncoderDescriptorChanged); } if (!current_render_encoder_) { + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kRenderBegin); // If some path cleared the encoder without going through EndRenderEncoder, // avoid leaking cached binding state into the new encoder. - ResetMslRenderEncoderStateCache(); // Note: renderCommandEncoder() returns an autoreleased object, we must // retain it. current_render_encoder_ = current_command_buffer_->renderCommandEncoder(pass_descriptor); if (!current_render_encoder_) { + ++backend_telemetry_.begin_encoder_creation_failures; XELOGE("Failed to create render command encoder"); - return; + return false; } + ++backend_telemetry_.begin_encoder_created; current_render_encoder_->retain(); + current_render_encoder_has_zpd_visibility_ = + zpd_visibility_pool_ && zpd_visibility_pool_->is_initialized() && + pass_descriptor->visibilityResultBuffer() == + zpd_visibility_pool_->visibility_buffer(); + ResetRenderEncoderBufferBindings(); current_render_encoder_->setLabel( NS::String::string("XeniaRenderEncoder", NS::UTF8StringEncoding)); + current_render_pipeline_state_ = nullptr; ff_blend_factor_valid_ = false; - current_render_pass_descriptor_ = pass_descriptor; + rasterizer_state_valid_ = false; + viewport_dirty_ = true; + scissor_dirty_ = true; + current_depth_stencil_state_ = nullptr; + stencil_reference_valid_ = false; + heap_binds_set_on_encoder_ = false; + if (current_render_pass_descriptor_ != pass_descriptor) { + if (current_render_pass_descriptor_) { + current_render_pass_descriptor_->release(); + } + current_render_pass_descriptor_ = pass_descriptor; + current_render_pass_descriptor_->retain(); + } UseRenderEncoderAttachmentHeaps(pass_descriptor); - } - // Derive viewport/scissor from the actual bound render target rather than - // a hard-coded 1280x720. Prefer color RT 0 from the MetalRenderTargetCache, - // falling back to depth (depth-only passes) and then legacy - // render_target_width_/height_ when needed. - uint32_t rt_width = 1; - uint32_t rt_height = 1; - GetBoundRenderTargetSize(render_target_cache_.get(), render_target_width_, - render_target_height_, rt_width, rt_height); + // Derive the initial viewport/scissor from the active render pass texture. + // Metal validates scissor rectangles against the descriptor attachments, + // not against the cache's logical RT0 binding. + uint32_t rt_width = 1; + uint32_t rt_height = 1; + GetActiveRenderTargetSize(pass_descriptor, render_target_cache_.get(), 1280, + 720, rt_width, rt_height); - // Set viewport - MTL::Viewport viewport = { - 0.0, 0.0, static_cast(rt_width), static_cast(rt_height), - 0.0, 1.0}; - current_render_encoder_->setViewport(viewport); + MTL::Viewport viewport = { + 0.0, 0.0, static_cast(rt_width), static_cast(rt_height), + 0.0, 1.0}; + current_render_encoder_->setViewport(viewport); - // Set scissor (must not exceed render pass dimensions) - MTL::ScissorRect scissor = {0, 0, rt_width, rt_height}; - current_render_encoder_->setScissorRect(scissor); -} + MTL::ScissorRect scissor = {0, 0, rt_width, rt_height}; + current_render_encoder_->setScissorRect(scissor); -bool MetalCommandProcessor::EnsureSpirvUniformBuffer() { - if (uniforms_buffer_) { - return true; - } - if (!device_) { - XELOGE("EnsureSpirvUniformBuffer: Metal device is null"); - return false; - } - - // Keep this aligned with the SPIRV-Cross descriptor table layout used by - // IssueDrawMsl (6 x 4KB CBVs + texture/sampler descriptor blocks). - constexpr size_t kUniformsBytesPerTable = 24576; - constexpr size_t kStageCount = 2; - - if (!draw_ring_count_) { - XELOGW("SPIRV-Cross: draw ring count was zero, forcing to 1"); - draw_ring_count_ = 1; - } - - // Keep a slightly larger initial pool on iOS to reduce early-frame pressure. -#if XE_PLATFORM_IOS - constexpr size_t kUniformsBuffersInFlightInitial = 6; - // iOS commonly needs extra headroom to avoid command-buffer churn when - // submissions retire later than the CPU draw cadence. - constexpr size_t kUniformsBuffersInFlightMax = 24; -#else - constexpr size_t kUniformsBuffersInFlightInitial = 4; - constexpr size_t kUniformsBuffersInFlightMax = 12; -#endif - - if (!spirv_uniforms_pool_initialized_) { - size_t requested_ring_count = std::max(1, draw_ring_count_); - while (requested_ring_count >= 1) { - const size_t descriptor_table_count = kStageCount * requested_ring_count; - const size_t uniforms_buffer_size = - kUniformsBytesPerTable * descriptor_table_count; - - std::vector new_pool; - new_pool.reserve(kUniformsBuffersInFlightInitial); - bool allocation_failed = false; - for (size_t i = 0; i < kUniformsBuffersInFlightInitial; ++i) { - MTL::Buffer* buffer = device_->newBuffer( - uniforms_buffer_size, MTL::ResourceStorageModeShared); - if (!buffer) { - allocation_failed = true; - break; - } - buffer->setLabel( - NS::String::string("MslUniformsBuffer", NS::UTF8StringEncoding)); - std::memset(buffer->contents(), 0, uniforms_buffer_size); - new_pool.push_back(buffer); - } - - if (!allocation_failed && - new_pool.size() == kUniformsBuffersInFlightInitial) { - { - std::lock_guard lock(spirv_uniforms_mutex_); - for (MTL::Buffer* old_buffer : spirv_uniforms_pool_) { - if (old_buffer) { - old_buffer->release(); - } - } - spirv_uniforms_pool_ = std::move(new_pool); - spirv_uniforms_available_.clear(); - spirv_uniforms_available_.insert(spirv_uniforms_available_.end(), - spirv_uniforms_pool_.begin(), - spirv_uniforms_pool_.end()); - } - - if (spirv_uniforms_available_semaphore_) { -#if !OS_OBJECT_USE_OBJC - dispatch_release(spirv_uniforms_available_semaphore_); -#endif - spirv_uniforms_available_semaphore_ = nullptr; - } - spirv_uniforms_available_semaphore_ = dispatch_semaphore_create( - static_cast(kUniformsBuffersInFlightInitial)); - if (!spirv_uniforms_available_semaphore_) { - XELOGE( - "SPIRV-Cross: failed to create uniforms availability semaphore"); - return false; - } - - if (requested_ring_count != draw_ring_count_) { - XELOGW( - "SPIRV-Cross: reduced uniforms ring from {} to {} pages after " - "allocation pressure", - draw_ring_count_, requested_ring_count); - draw_ring_count_ = requested_ring_count; - } - spirv_uniforms_pool_initialized_ = true; - break; - } - - for (MTL::Buffer* buffer : new_pool) { - if (buffer) { - buffer->release(); - } - } - if (requested_ring_count == 1) { - break; - } - const size_t fallback_ring_count = - std::max(1, requested_ring_count / 2); - XELOGW( - "SPIRV-Cross: failed to allocate uniforms pool with {} ring pages, " - "retrying with {}", - requested_ring_count, fallback_ring_count); - requested_ring_count = fallback_ring_count; - } - - if (!spirv_uniforms_pool_initialized_) { - XELOGE( - "Failed to create uniforms buffer pool for SPIRV-Cross path (ring " - "pages={}, bytes per table={})", - draw_ring_count_, kUniformsBytesPerTable); - return false; - } - } - - if (!spirv_uniforms_available_semaphore_) { - XELOGE("SPIRV-Cross: uniforms pool semaphore is not initialized"); - return false; - } - - const auto try_grow_uniforms_pool = [&]() -> bool { - size_t pool_size = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - pool_size = spirv_uniforms_pool_.size(); - if (pool_size >= kUniformsBuffersInFlightMax) { - return false; - } - } - const size_t descriptor_table_count = - kStageCount * std::max(size_t(1), draw_ring_count_); - const size_t uniforms_buffer_size = - kUniformsBytesPerTable * descriptor_table_count; - MTL::Buffer* buffer = device_->newBuffer(uniforms_buffer_size, - MTL::ResourceStorageModeShared); - if (!buffer) { - return false; - } - buffer->setLabel( - NS::String::string("MslUniformsBufferGrow", NS::UTF8StringEncoding)); - std::memset(buffer->contents(), 0, uniforms_buffer_size); - size_t new_pool_size = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - spirv_uniforms_pool_.push_back(buffer); - spirv_uniforms_available_.push_back(buffer); - new_pool_size = spirv_uniforms_pool_.size(); - } - dispatch_semaphore_signal(spirv_uniforms_available_semaphore_); - XELOGW("SPIRV-Cross: grew uniforms pool to {} buffers under load", - new_pool_size); - return true; - }; - - if (dispatch_semaphore_wait(spirv_uniforms_available_semaphore_, - DISPATCH_TIME_NOW) != 0) { - bool acquired_after_grow = false; - if (try_grow_uniforms_pool()) { - acquired_after_grow = - dispatch_semaphore_wait(spirv_uniforms_available_semaphore_, - DISPATCH_TIME_NOW) == 0; - } - if (!acquired_after_grow) { - // Last resort: block until one in-flight command buffer retires. - // D3D12-style behavior is to avoid this in common paths by growing first. - static auto last_wait_log = std::chrono::steady_clock::time_point{}; - static uint32_t suppressed_wait_logs = 0; - const auto now = std::chrono::steady_clock::now(); - if (now - last_wait_log >= std::chrono::seconds(10)) { - last_wait_log = now; - size_t pool_size = 0; - size_t available_size = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - pool_size = spirv_uniforms_pool_.size(); - available_size = spirv_uniforms_available_.size(); - } - XELOGW( - "SPIRV-Cross: uniforms pool busy; waiting for an in-flight command " - "buffer to retire (in-use={}, total={}, available={}, ring " - "pages={}, suppressed_wait_logs={})", - pool_size - available_size, pool_size, available_size, - draw_ring_count_, suppressed_wait_logs); - suppressed_wait_logs = 0; - } else { - ++suppressed_wait_logs; - } - dispatch_semaphore_wait(spirv_uniforms_available_semaphore_, - DISPATCH_TIME_FOREVER); - } - } - - { - std::lock_guard lock(spirv_uniforms_mutex_); - if (spirv_uniforms_available_.empty()) { - XELOGE( - "SPIRV-Cross: uniforms semaphore signaled but no reusable buffer is " - "available"); - return false; - } - uniforms_buffer_ = spirv_uniforms_available_.back(); - spirv_uniforms_available_.pop_back(); - } - if (uniforms_buffer_) { - command_buffer_spirv_uniform_buffers_.push_back(uniforms_buffer_); - } - - return uniforms_buffer_ != nullptr; -} - -bool MetalCommandProcessor::EnsureSpirvUniformBufferCapacity() { - if (!draw_ring_count_) { - draw_ring_count_ = 1; - } - if (!uniforms_buffer_) { - return EnsureSpirvUniformBuffer(); - } - if (current_draw_index_ == 0) { - return true; - } - const uint32_t ring_index = - current_draw_index_ % uint32_t(std::max(1, draw_ring_count_)); - if (ring_index != 0) { - return true; - } - - // Try to rotate to another uniforms buffer in the current command buffer to - // avoid forcing a split at every ring wrap. - uniforms_buffer_ = nullptr; - const auto try_acquire_uniforms_buffer = [&]() -> bool { - if (!spirv_uniforms_available_semaphore_ || - dispatch_semaphore_wait(spirv_uniforms_available_semaphore_, - DISPATCH_TIME_NOW) != 0) { - return false; - } - std::lock_guard lock(spirv_uniforms_mutex_); - if (!spirv_uniforms_available_.empty()) { - uniforms_buffer_ = spirv_uniforms_available_.back(); - spirv_uniforms_available_.pop_back(); - command_buffer_spirv_uniform_buffers_.push_back(uniforms_buffer_); - return true; - } - // Keep semaphore state consistent if availability changed concurrently. - dispatch_semaphore_signal(spirv_uniforms_available_semaphore_); - return false; - }; - -#if XE_PLATFORM_IOS - constexpr size_t kUniformsBuffersInFlightMax = 24; -#else - constexpr size_t kUniformsBuffersInFlightMax = 12; -#endif - const auto try_grow_uniforms_pool = [&]() -> bool { - if (!device_) { - return false; - } - size_t pool_size = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - pool_size = spirv_uniforms_pool_.size(); - if (pool_size >= kUniformsBuffersInFlightMax) { - return false; - } - } - const size_t descriptor_table_count = - kStageCount * std::max(size_t(1), draw_ring_count_); - const size_t uniforms_buffer_size = - kUniformsBytesPerTable * descriptor_table_count; - MTL::Buffer* buffer = device_->newBuffer(uniforms_buffer_size, - MTL::ResourceStorageModeShared); - if (!buffer) { - return false; - } - buffer->setLabel( - NS::String::string("MslUniformsBufferGrow", NS::UTF8StringEncoding)); - std::memset(buffer->contents(), 0, uniforms_buffer_size); - size_t new_pool_size = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - spirv_uniforms_pool_.push_back(buffer); - spirv_uniforms_available_.push_back(buffer); - new_pool_size = spirv_uniforms_pool_.size(); - } - dispatch_semaphore_signal(spirv_uniforms_available_semaphore_); - XELOGW( - "SPIRV-Cross: grew uniforms pool to {} buffers at ring-wrap pressure", - new_pool_size); - return true; - }; - - if (try_acquire_uniforms_buffer()) { - return true; - } - if (try_grow_uniforms_pool() && try_acquire_uniforms_buffer()) { - return true; - } - - static bool rollover_logged = false; - if (!rollover_logged) { - rollover_logged = true; - XELOGW( - "SPIRV-Cross: uniforms ring exhausted; rotating Metal command buffer"); - } - - EndCommandBuffer(); - BeginCommandBuffer(); - if (!current_command_buffer_ || !current_render_encoder_ || - !uniforms_buffer_) { - XELOGE( - "SPIRV-Cross: failed to restart command buffer after uniforms ring " - "rollover"); - return false; + // IssueDraw applies the guest viewport/scissor before dispatch. + viewport_dirty_ = true; + scissor_dirty_ = true; } return true; } -void MetalCommandProcessor::ScheduleSpirvUniformBufferRelease( - MTL::CommandBuffer* command_buffer) { - if (!command_buffer) { - return; - } - - std::vector submitted_uniforms; - if (!command_buffer_spirv_uniform_buffers_.empty()) { - submitted_uniforms.swap(command_buffer_spirv_uniform_buffers_); - } else if (uniforms_buffer_) { - submitted_uniforms.reserve(1); - submitted_uniforms.push_back(uniforms_buffer_); - } - uniforms_buffer_ = nullptr; - - if (submitted_uniforms.empty()) { - return; - } - - pending_completion_handlers_.fetch_add(1, std::memory_order_relaxed); - command_buffer->addCompletedHandler( - [this, submitted_uniforms = - std::move(submitted_uniforms)](MTL::CommandBuffer*) mutable { - size_t returned_count = 0; - { - std::lock_guard lock(spirv_uniforms_mutex_); - for (MTL::Buffer* uniforms : submitted_uniforms) { - if (!uniforms) { - continue; - } - spirv_uniforms_available_.push_back(uniforms); - ++returned_count; - } - } - if (spirv_uniforms_available_semaphore_) { - for (size_t i = 0; i < returned_count; ++i) { - dispatch_semaphore_signal(spirv_uniforms_available_semaphore_); - } - } - pending_completion_handlers_.fetch_sub(1, std::memory_order_relaxed); - }); -} - -bool MetalCommandProcessor::AcquireSpirvArgumentBufferSlice( - uint32_t bytes, uint32_t alignment, MTL::Buffer** buffer_out, - NS::UInteger* offset_out) { - if (!buffer_out || !offset_out) { - return false; - } - *buffer_out = nullptr; - *offset_out = 0; - if (!device_ || !current_command_buffer_ || bytes == 0) { - return false; - } - - const size_t align = std::max(1, size_t(alignment)); - auto align_up = [](size_t value, size_t alignment) -> size_t { - return ((value + alignment - 1) / alignment) * alignment; - }; - - if (!command_buffer_spirv_argbuf_pages_.empty()) { - auto& page = command_buffer_spirv_argbuf_pages_.back(); - const size_t aligned_offset = align_up(page->offset, align); - if (aligned_offset + bytes <= page->bytes) { - page->offset = aligned_offset + bytes; - *buffer_out = page->buffer; - *offset_out = NS::UInteger(aligned_offset); - return *buffer_out != nullptr; - } - } - - constexpr size_t kDefaultSpirvArgumentBufferPageBytes = 1024 * 1024; - const size_t required_page_bytes = align_up(bytes, align); - const size_t page_bytes = - std::max(kDefaultSpirvArgumentBufferPageBytes, required_page_bytes); - - std::shared_ptr page; - { - std::lock_guard lock(spirv_argbuf_mutex_); - for (auto it = spirv_argbuf_pool_.begin(); it != spirv_argbuf_pool_.end(); - ++it) { - if ((*it) && (*it)->bytes >= page_bytes) { - page = *it; - spirv_argbuf_pool_.erase(it); - break; - } - } - } - if (!page) { - page = std::make_shared(); - page->bytes = page_bytes; - page->buffer = - device_->newBuffer(page_bytes, MTL::ResourceStorageModeShared); - if (!page->buffer) { - return false; - } - } - page->offset = 0; - command_buffer_spirv_argbuf_pages_.push_back(page); - - const size_t aligned_offset = align_up(page->offset, align); - if (aligned_offset + bytes > page->bytes) { - return false; - } - page->offset = aligned_offset + bytes; - *buffer_out = page->buffer; - *offset_out = NS::UInteger(aligned_offset); - return *buffer_out != nullptr; -} - -void MetalCommandProcessor::ScheduleSpirvArgumentBufferRelease( - MTL::CommandBuffer* command_buffer) { - if (!command_buffer || command_buffer_spirv_argbuf_pages_.empty()) { - return; - } - - std::vector> pages; - pages.swap(command_buffer_spirv_argbuf_pages_); - - bool add_handler = false; - { - std::lock_guard lock(spirv_argbuf_mutex_); - auto& pending = pending_spirv_argbuf_releases_[command_buffer]; - add_handler = pending.empty(); - pending.reserve(pending.size() + pages.size()); - for (auto& page : pages) { - pending.push_back(std::move(page)); - } - } - - if (add_handler) { - pending_completion_handlers_.fetch_add(1, std::memory_order_relaxed); - command_buffer->addCompletedHandler( - [this](MTL::CommandBuffer* completed_cmd) { - { - std::lock_guard lock(spirv_argbuf_mutex_); - auto it = pending_spirv_argbuf_releases_.find(completed_cmd); - if (it != pending_spirv_argbuf_releases_.end()) { - for (auto& page : it->second) { - if (page) { - page->offset = 0; - spirv_argbuf_pool_.push_back(std::move(page)); - } - } - pending_spirv_argbuf_releases_.erase(it); - } - } - pending_completion_handlers_.fetch_sub(1, std::memory_order_relaxed); - }); - } -} - void MetalCommandProcessor::EndCommandBuffer() { - EndRenderEncoder(); - ResetMslCrossEncoderReuseCaches(); + if (!FlushPreparedDrawQueue(PreparedDrawFlushReason::kCommandBufferEnd)) { + XELOGE("Metal EndCommandBuffer: failed to flush prepared draw queue"); + } + EndRenderEncoder(RenderEncoderEndReason::kCommandBufferEnd); + EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason::kCommandBufferEnd); + if (texture_cache_ && + !texture_cache_->FlushPendingUploadEncodersForCommandEncoderBoundary()) { + XELOGE( + "Metal: failed to flush texture upload encoder before command buffer " + "end"); + } + if (pipeline_cache_) { + pipeline_cache_->EndSubmission(); + } if (current_command_buffer_) { - if (UseSpirvCrossPath()) { - ScheduleSpirvUniformBufferRelease(current_command_buffer_); - } - ScheduleSpirvArgumentBufferRelease(current_command_buffer_); current_command_buffer_->commit(); current_command_buffer_->release(); current_command_buffer_ = nullptr; - current_draw_index_ = 0; + submission_has_draws_ = false; } - copy_resolve_writes_pending_ = false; DrainCommandBufferAutoreleasePool(); } void MetalCommandProcessor::ApplyDepthStencilState( - bool primitive_polygonal, reg::RB_DEPTHCONTROL normalized_depth_control) { + const DrawDynamicState& dynamic_state) { if (!current_render_encoder_ || !device_) { return; } - const RegisterFile& regs = *register_file_; - auto stencil_ref_mask_front = regs.Get(); - auto stencil_ref_mask_back = - regs.Get(XE_GPU_REG_RB_STENCILREFMASK_BF); - auto depth_control = normalized_depth_control; + bool primitive_polygonal = + dynamic_state.rasterization_enabled && dynamic_state.primitive_polygonal; + auto stencil_ref_mask_front = dynamic_state.stencil_ref_mask_front; + auto stencil_ref_mask_back = dynamic_state.stencil_ref_mask_back; + auto depth_control = dynamic_state.depth_control; bool has_stencil_attachment = false; if (current_render_pass_descriptor_) { @@ -4656,15 +8043,18 @@ void MetalCommandProcessor::ApplyDepthStencilState( depth_stencil_state_cache_.emplace(key, state); } - current_render_encoder_->setDepthStencilState(state); + if (current_depth_stencil_state_ != state) { + current_render_encoder_->setDepthStencilState(state); + current_depth_stencil_state_ = state; + } if (depth_control.stencil_enable) { uint32_t ref_front = stencil_ref_mask_front.stencilref; uint32_t ref_back = stencil_ref_mask_back.stencilref; - auto pa_su_sc_mode_cntl = regs.Get(); uint32_t ref = ref_front; if (primitive_polygonal && depth_control.backface_enable && - pa_su_sc_mode_cntl.cull_front && !pa_su_sc_mode_cntl.cull_back) { + dynamic_state.pa_su_sc_mode_cntl.cull_front && + !dynamic_state.pa_su_sc_mode_cntl.cull_back) { ref = ref_back; } else if (primitive_polygonal && depth_control.backface_enable && ref_front != ref_back) { @@ -4677,1072 +8067,358 @@ void MetalCommandProcessor::ApplyDepthStencilState( ref_front, ref_back); } } - current_render_encoder_->setStencilReferenceValue(ref); + if (!stencil_reference_valid_ || current_stencil_reference_ != ref) { + current_render_encoder_->setStencilReferenceValue(ref); + current_stencil_reference_ = ref; + stencil_reference_valid_ = true; + } } } -void MetalCommandProcessor::ApplyRasterizerState(bool primitive_polygonal) { +void MetalCommandProcessor::ApplyRasterizerState( + const DrawDynamicState& dynamic_state) { if (!current_render_encoder_ || !render_target_cache_) { return; } - const RegisterFile& regs = *register_file_; - auto pa_su_sc_mode_cntl = regs.Get(); - auto pa_cl_clip_cntl = regs.Get(); - - MTL::CullMode cull_mode = MTL::CullModeNone; - if (primitive_polygonal) { - bool cull_front = pa_su_sc_mode_cntl.cull_front; - bool cull_back = pa_su_sc_mode_cntl.cull_back; - if (cull_front && !cull_back) { - cull_mode = MTL::CullModeFront; - } else if (cull_back && !cull_front) { - cull_mode = MTL::CullModeBack; - } + if (!rasterizer_state_valid_ || + current_cull_mode_ != dynamic_state.cull_mode) { + current_render_encoder_->setCullMode(dynamic_state.cull_mode); + current_cull_mode_ = dynamic_state.cull_mode; } - current_render_encoder_->setCullMode(cull_mode); - current_render_encoder_->setFrontFacingWinding( - pa_su_sc_mode_cntl.face ? MTL::WindingClockwise - : MTL::WindingCounterClockwise); - - MTL::TriangleFillMode fill_mode = MTL::TriangleFillModeFill; - if (primitive_polygonal && - pa_su_sc_mode_cntl.poly_mode == xenos::PolygonModeEnable::kDualMode) { - xenos::PolygonType polygon_type = xenos::PolygonType::kTriangles; - if (!pa_su_sc_mode_cntl.cull_front) { - polygon_type = - std::min(polygon_type, pa_su_sc_mode_cntl.polymode_front_ptype); - } - if (!pa_su_sc_mode_cntl.cull_back) { - polygon_type = - std::min(polygon_type, pa_su_sc_mode_cntl.polymode_back_ptype); - } - if (polygon_type != xenos::PolygonType::kTriangles) { - fill_mode = MTL::TriangleFillModeLines; - } + if (!rasterizer_state_valid_ || + current_front_facing_winding_ != dynamic_state.front_facing_winding) { + current_render_encoder_->setFrontFacingWinding( + dynamic_state.front_facing_winding); + current_front_facing_winding_ = dynamic_state.front_facing_winding; } - current_render_encoder_->setTriangleFillMode(fill_mode); - float polygon_offset_scale = 0.0f; - float polygon_offset = 0.0f; - draw_util::GetPreferredFacePolygonOffset( - regs, primitive_polygonal, polygon_offset_scale, polygon_offset); - float depth_bias_factor = regs.Get().depth_format == - xenos::DepthRenderTargetFormat::kD24S8 - ? draw_util::kD3D10PolygonOffsetFactorUnorm24 - : draw_util::kD3D10PolygonOffsetFactorFloat24; - float depth_bias_constant = polygon_offset * depth_bias_factor; - float depth_bias_slope = - polygon_offset_scale * xenos::kPolygonOffsetScaleSubpixelUnit * - float(std::max(render_target_cache_->draw_resolution_scale_x(), - render_target_cache_->draw_resolution_scale_y())); - current_render_encoder_->setDepthBias(depth_bias_constant, depth_bias_slope, - 0.0f); + if (!rasterizer_state_valid_ || + current_triangle_fill_mode_ != dynamic_state.triangle_fill_mode) { + current_render_encoder_->setTriangleFillMode( + dynamic_state.triangle_fill_mode); + current_triangle_fill_mode_ = dynamic_state.triangle_fill_mode; + } - current_render_encoder_->setDepthClipMode(pa_cl_clip_cntl.clip_disable - ? MTL::DepthClipModeClamp - : MTL::DepthClipModeClip); + float depth_bias_values[] = {dynamic_state.depth_bias_constant, + dynamic_state.depth_bias_slope, 0.0f}; + if (!rasterizer_state_valid_ || + std::memcmp(current_depth_bias_values_, depth_bias_values, + sizeof(depth_bias_values)) != 0) { + current_render_encoder_->setDepthBias(dynamic_state.depth_bias_constant, + dynamic_state.depth_bias_slope, 0.0f); + std::memcpy(current_depth_bias_values_, depth_bias_values, + sizeof(depth_bias_values)); + } + + if (!rasterizer_state_valid_ || + current_depth_clip_mode_ != dynamic_state.depth_clip_mode) { + current_render_encoder_->setDepthClipMode(dynamic_state.depth_clip_mode); + current_depth_clip_mode_ = dynamic_state.depth_clip_mode; + } + rasterizer_state_valid_ = true; } -MTL::RenderPassDescriptor* -MetalCommandProcessor::GetCurrentRenderPassDescriptor() { - return render_pass_descriptor_; -} - -// ========================================================================== -// SPIRV-Cross tessellation support. -// ========================================================================== - -// Tessellation factor compute kernels are defined in msl_tess_factor_kernels.h. -#include "xenia/gpu/metal/msl_tess_factor_kernels.h" - -bool MetalCommandProcessor::InitializeMslTessellation() { - if (!device_) { - return false; - } - - auto compile_kernel = - [&](const char* source, - const char* function_name) -> MTL::ComputePipelineState* { - NS::Error* error = nullptr; - auto* src = NS::String::string(source, NS::UTF8StringEncoding); - auto* opts = MTL::CompileOptions::alloc()->init(); - opts->setFastMathEnabled(true); - MTL::Library* lib = device_->newLibrary(src, opts, &error); - opts->release(); - if (!lib) { - if (error) { - XELOGE("Tessellation kernel compile error: {}", - error->localizedDescription()->utf8String()); - } - return nullptr; - } - auto* fn_name = NS::String::string(function_name, NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - lib->release(); - if (!fn) { - XELOGE("Tessellation kernel: function '{}' not found", function_name); - return nullptr; - } - MTL::ComputePipelineState* pso = - device_->newComputePipelineState(fn, &error); - fn->release(); - if (!pso && error) { - XELOGE("Tessellation kernel PSO error: {}", - error->localizedDescription()->utf8String()); - } - return pso; - }; - - // Uniform factor kernels (discrete / continuous modes). - tess_factor_pipeline_tri_ = - compile_kernel(kMslTessFactorUniformTriangle, "tess_factor_triangle"); - tess_factor_pipeline_quad_ = - compile_kernel(kMslTessFactorUniformQuad, "tess_factor_quad"); - - if (!tess_factor_pipeline_tri_ || !tess_factor_pipeline_quad_) { - XELOGW( - "SPIRV-Cross: Failed to create uniform tessellation factor " - "pipelines"); - return false; - } - - // Adaptive factor kernels (per-edge factors from shared memory). - tess_factor_pipeline_adaptive_tri_ = compile_kernel( - kMslTessFactorAdaptiveTriangle, "tess_factor_adaptive_triangle"); - tess_factor_pipeline_adaptive_quad_ = - compile_kernel(kMslTessFactorAdaptiveQuad, "tess_factor_adaptive_quad"); - - if (!tess_factor_pipeline_adaptive_tri_ || - !tess_factor_pipeline_adaptive_quad_) { - XELOGW( - "SPIRV-Cross: Failed to create adaptive tessellation factor " - "pipelines (adaptive tessellation will fall back to uniform)"); - // Non-fatal — adaptive tessellation will degrade to uniform factors. - } - - XELOGI("SPIRV-Cross: Tessellation factor pipelines initialized"); - return true; -} - -void MetalCommandProcessor::ShutdownMslTessellation() { - for (auto& [key, pso] : msl_tess_pipeline_cache_) { - if (pso) { - pso->release(); - } - } - msl_tess_pipeline_cache_.clear(); - if (tess_factor_buffer_) { - tess_factor_buffer_->release(); - tess_factor_buffer_ = nullptr; - tess_factor_buffer_patch_capacity_ = 0; - } - if (tess_factor_pipeline_tri_) { - tess_factor_pipeline_tri_->release(); - tess_factor_pipeline_tri_ = nullptr; - } - if (tess_factor_pipeline_quad_) { - tess_factor_pipeline_quad_->release(); - tess_factor_pipeline_quad_ = nullptr; - } - if (tess_factor_pipeline_adaptive_tri_) { - tess_factor_pipeline_adaptive_tri_->release(); - tess_factor_pipeline_adaptive_tri_ = nullptr; - } - if (tess_factor_pipeline_adaptive_quad_) { - tess_factor_pipeline_adaptive_quad_->release(); - tess_factor_pipeline_adaptive_quad_ = nullptr; - } -} - -bool MetalCommandProcessor::EnsureTessFactorBuffer(uint32_t patch_count) { - // MTLQuadTessellationFactorsHalf is the larger of the two (12 bytes vs 8). - constexpr size_t kMaxFactorSize = 12; - size_t needed = size_t(patch_count) * kMaxFactorSize; - if (tess_factor_buffer_ && tess_factor_buffer_->length() >= needed) { - // Buffer already large enough; don't reduce the tracked capacity. - return true; - } - if (tess_factor_buffer_) { - tess_factor_buffer_->release(); - } - // Round up and over-allocate for future growth. - size_t alloc_size = std::max(needed, size_t(4096)); - alloc_size = (alloc_size + 4095) & ~size_t(4095); - // Use Shared storage so the CPU can fill tessellation factors directly - // (avoids needing a separate compute encoder for uniform factors). - tess_factor_buffer_ = - device_->newBuffer(alloc_size, MTL::ResourceStorageModeShared); - if (!tess_factor_buffer_) { - XELOGE("Failed to allocate tessellation factor buffer ({} bytes)", - alloc_size); - return false; - } - tess_factor_buffer_->setLabel( - NS::String::string("Xenia Tess Factor Buffer", NS::UTF8StringEncoding)); - tess_factor_buffer_patch_capacity_ = uint32_t(alloc_size / kMaxFactorSize); - return true; -} - -MTL::RenderPipelineState* -MetalCommandProcessor::GetOrCreateMslTessPipelineState( - MslShader::MslTranslation* domain_translation, - MslShader::MslTranslation* pixel_translation, - Shader::HostVertexShaderType host_vertex_shader_type, - const RegisterFile& regs) { - if (!domain_translation || !domain_translation->metal_function()) { - XELOGE("SPIRV-Cross tess: No domain shader function"); - return nullptr; - } - - // Determine attachment formats from render target cache (same pattern as - // GetOrCreateMslPipelineState). - uint32_t sample_count = 1; - MTL::PixelFormat color_formats[4] = { - MTL::PixelFormatInvalid, MTL::PixelFormatInvalid, MTL::PixelFormatInvalid, - MTL::PixelFormatInvalid}; - MTL::PixelFormat depth_format = MTL::PixelFormatInvalid; - MTL::PixelFormat stencil_format = MTL::PixelFormatInvalid; - if (render_target_cache_) { - for (uint32_t i = 0; i < 4; ++i) { - if (MTL::Texture* rt = render_target_cache_->GetColorTargetForDraw(i)) { - color_formats[i] = rt->pixelFormat(); - if (rt->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(rt->sampleCount())); - } - } - } - if (color_formats[0] == MTL::PixelFormatInvalid) { - if (MTL::Texture* dummy = - render_target_cache_->GetDummyColorTargetForDraw()) { - color_formats[0] = dummy->pixelFormat(); - if (dummy->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(dummy->sampleCount())); - } - } - } - if (MTL::Texture* depth_tex = - render_target_cache_->GetDepthTargetForDraw()) { - depth_format = depth_tex->pixelFormat(); - switch (depth_format) { - case MTL::PixelFormatDepth32Float_Stencil8: - case MTL::PixelFormatDepth24Unorm_Stencil8: - case MTL::PixelFormatX32_Stencil8: - stencil_format = depth_format; - break; - default: - stencil_format = MTL::PixelFormatInvalid; - break; - } - if (depth_tex->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(depth_tex->sampleCount())); - } - } - } - - // Keep tessellation PSO attachment formats/sample count in sync with the - // active render pass descriptor to satisfy Metal validation. - MTL::RenderPassDescriptor* pass_descriptor = current_render_pass_descriptor_; - if (!pass_descriptor) { - pass_descriptor = render_pass_descriptor_; - } - if (pass_descriptor) { - sample_count = 1; - for (uint32_t i = 0; i < 4; ++i) { - color_formats[i] = MTL::PixelFormatInvalid; - } - depth_format = MTL::PixelFormatInvalid; - stencil_format = MTL::PixelFormatInvalid; - PopulatePipelineFormatsFromRenderPassDescriptor( - pass_descriptor, color_formats, 4, &depth_format, &stencil_format, - &sample_count); - } - bool fragment_writes_depth = - (pixel_translation && pixel_translation->shader().writes_depth()) || - (!pixel_translation && depth_only_pixel_library_ && - !depth_only_pixel_function_name_.empty()); - EnsureDepthFormatForDepthWritingFragment( - "SPIRV-Cross tess pipeline", fragment_writes_depth, &depth_format); - - // Build cache key incorporating RT formats, tessellation mode, and blend - // state (same blend fields as GetOrCreateMslPipelineState). - auto tess_mode = regs.Get().tess_mode; - uint32_t pixel_shader_writes_color_targets = - pixel_translation ? pixel_translation->shader().writes_color_targets() - : 0; - uint32_t normalized_color_mask = 0; - if (pixel_shader_writes_color_targets) { - normalized_color_mask = draw_util::GetNormalizedColorMask( - regs, pixel_shader_writes_color_targets); - } - auto rb_colorcontrol = regs.Get(); - struct TessPipelineKey { - const void* ds; - const void* ps; - uint32_t host_vertex_shader_type; - uint32_t tess_mode; - uint32_t sample_count; - uint32_t depth_format; - uint32_t stencil_format; - uint32_t color_formats[4]; - uint32_t normalized_color_mask; - uint32_t alpha_to_mask_enable; - uint32_t blendcontrol[4]; - } key_data = {}; - key_data.ds = domain_translation; - key_data.ps = pixel_translation; - key_data.host_vertex_shader_type = uint32_t(host_vertex_shader_type); - key_data.tess_mode = uint32_t(tess_mode); - key_data.sample_count = sample_count; - key_data.depth_format = uint32_t(depth_format); - key_data.stencil_format = uint32_t(stencil_format); - for (uint32_t i = 0; i < 4; ++i) { - key_data.color_formats[i] = uint32_t(color_formats[i]); - } - key_data.normalized_color_mask = normalized_color_mask; - key_data.alpha_to_mask_enable = rb_colorcontrol.alpha_to_mask_enable ? 1 : 0; - for (uint32_t i = 0; i < 4; ++i) { - key_data.blendcontrol[i] = - regs.Get( - reg::RB_BLENDCONTROL::rt_register_indices[i]) - .value; - } - uint64_t key = XXH3_64bits(&key_data, sizeof(key_data)); - auto it = msl_tess_pipeline_cache_.find(key); - if (it != msl_tess_pipeline_cache_.end()) { - return it->second; - } - - auto* desc = MTL::RenderPipelineDescriptor::alloc()->init(); - // The post-tessellation vertex function IS the domain shader. - desc->setVertexFunction(domain_translation->metal_function()); - - if (pixel_translation && pixel_translation->metal_function()) { - desc->setFragmentFunction(pixel_translation->metal_function()); - } else if (depth_only_pixel_function_name_.size() && - depth_only_pixel_library_) { - auto* fn_name = NS::String::string(depth_only_pixel_function_name_.c_str(), - NS::UTF8StringEncoding); - MTL::Function* depth_fn = depth_only_pixel_library_->newFunction(fn_name); - if (depth_fn) { - desc->setFragmentFunction(depth_fn); - depth_fn->release(); - } - } - - // Tessellation configuration. - desc->setMaxTessellationFactor(64); - desc->setTessellationFactorStepFunction( - MTL::TessellationFactorStepFunctionPerPatch); - - switch (tess_mode) { - case xenos::TessellationMode::kDiscrete: - desc->setTessellationPartitionMode(MTL::TessellationPartitionModeInteger); - break; - case xenos::TessellationMode::kContinuous: - desc->setTessellationPartitionMode( - MTL::TessellationPartitionModeFractionalEven); - break; - case xenos::TessellationMode::kAdaptive: - desc->setTessellationPartitionMode( - MTL::TessellationPartitionModeFractionalEven); - break; - } - - // Control point index type (not needed for our use case since the - // domain shader reads control points from shared memory). - desc->setTessellationControlPointIndexType( - MTL::TessellationControlPointIndexTypeNone); - - // Render target attachments with blend state (same as non-tess pipeline). - desc->setAlphaToCoverageEnabled(key_data.alpha_to_mask_enable != 0); - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - color_attachment->setPixelFormat(color_formats[i]); - if (color_formats[i] == MTL::PixelFormatInvalid) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - uint32_t rt_write_mask = (normalized_color_mask >> (i * 4)) & 0xF; - color_attachment->setWriteMask(ToMetalColorWriteMask(rt_write_mask)); - if (!rt_write_mask) { - color_attachment->setBlendingEnabled(false); - continue; - } - auto blendcontrol = regs.Get( - reg::RB_BLENDCONTROL::rt_register_indices[i]); - MTL::BlendFactor src_rgb = - ToMetalBlendFactorRgb(blendcontrol.color_srcblend); - MTL::BlendFactor dst_rgb = - ToMetalBlendFactorRgb(blendcontrol.color_destblend); - MTL::BlendOperation op_rgb = - ToMetalBlendOperation(blendcontrol.color_comb_fcn); - MTL::BlendFactor src_alpha = - ToMetalBlendFactorAlpha(blendcontrol.alpha_srcblend); - MTL::BlendFactor dst_alpha = - ToMetalBlendFactorAlpha(blendcontrol.alpha_destblend); - MTL::BlendOperation op_alpha = - ToMetalBlendOperation(blendcontrol.alpha_comb_fcn); - bool blending_enabled = - src_rgb != MTL::BlendFactorOne || dst_rgb != MTL::BlendFactorZero || - op_rgb != MTL::BlendOperationAdd || src_alpha != MTL::BlendFactorOne || - dst_alpha != MTL::BlendFactorZero || op_alpha != MTL::BlendOperationAdd; - color_attachment->setBlendingEnabled(blending_enabled); - if (blending_enabled) { - color_attachment->setSourceRGBBlendFactor(src_rgb); - color_attachment->setDestinationRGBBlendFactor(dst_rgb); - color_attachment->setRgbBlendOperation(op_rgb); - color_attachment->setSourceAlphaBlendFactor(src_alpha); - color_attachment->setDestinationAlphaBlendFactor(dst_alpha); - color_attachment->setAlphaBlendOperation(op_alpha); - } - } - desc->setDepthAttachmentPixelFormat(depth_format); - desc->setStencilAttachmentPixelFormat(stencil_format); - desc->setSampleCount(sample_count); - - NS::Error* error = nullptr; - MTL::RenderPipelineState* pso = device_->newRenderPipelineState(desc, &error); - desc->release(); - - if (!pso) { - if (error) { - XELOGE("SPIRV-Cross tess pipeline error: {}", - error->localizedDescription()->utf8String()); - } - return nullptr; - } - - msl_tess_pipeline_cache_[key] = pso; - return pso; -} - -// ========================================================================== -// SPIRV-Cross (MSL) path — shader modification + system constants helpers. -// ========================================================================== - -SpirvShaderTranslator::Modification -MetalCommandProcessor::GetCurrentSpirvVertexShaderModification( - const Shader& shader, Shader::HostVertexShaderType host_vertex_shader_type, - uint32_t interpolator_mask) const { - const auto& regs = *register_file_; - - SpirvShaderTranslator::Modification modification( - spirv_shader_translator_->GetDefaultVertexShaderModification( - shader.GetDynamicAddressableRegisterCount( - regs.Get().vs_num_reg), - host_vertex_shader_type)); - - modification.vertex.interpolator_mask = interpolator_mask; - - auto pa_cl_clip_cntl = regs.Get(); - uint32_t user_clip_planes = - pa_cl_clip_cntl.clip_disable ? 0 : pa_cl_clip_cntl.ucp_ena; - modification.vertex.user_clip_plane_count = xe::bit_count(user_clip_planes); - modification.vertex.user_clip_plane_cull = - uint32_t(user_clip_planes && pa_cl_clip_cntl.ucp_cull_only_ena); - - modification.vertex.output_point_parameters = - uint32_t((shader.writes_point_size_edge_flag_kill_vertex() & 0b001) && - regs.Get().prim_type == - xenos::PrimitiveType::kPointList); - - return modification; -} - -SpirvShaderTranslator::Modification -MetalCommandProcessor::GetCurrentSpirvPixelShaderModification( - const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, +void MetalCommandProcessor::UpdateSystemConstantValues( + bool shared_memory_is_uav, bool primitive_polygonal, + uint32_t line_loop_closing_index, xenos::Endian index_endian, + const draw_util::ViewportInfo& viewport_info, uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control, - uint32_t normalized_color_mask) const { - const auto& regs = *register_file_; - - SpirvShaderTranslator::Modification modification( - spirv_shader_translator_->GetDefaultPixelShaderModification( - shader.GetDynamicAddressableRegisterCount( - regs.Get().ps_num_reg))); - - modification.pixel.interpolator_mask = interpolator_mask; - modification.pixel.interpolators_centroid = - interpolator_mask & - ~xenos::GetInterpolatorSamplingPattern( - regs.Get().msaa_samples, - regs.Get().sc_sample_cntl, - regs.Get().sampling_pattern); - - if (param_gen_pos < xenos::kMaxInterpolators) { - modification.pixel.param_gen_enable = 1; - modification.pixel.param_gen_interpolator = param_gen_pos; - modification.pixel.param_gen_point = - uint32_t(regs.Get().prim_type == - xenos::PrimitiveType::kPointList); - } else { - modification.pixel.param_gen_enable = 0; - modification.pixel.param_gen_interpolator = 0; - modification.pixel.param_gen_point = 0; - } - - using DepthStencilMode = - SpirvShaderTranslator::Modification::DepthStencilMode; - if (shader.implicit_early_z_write_allowed() && - (!shader.writes_color_target(0) || - !draw_util::DoesCoverageDependOnAlpha( - regs.Get()))) { - modification.pixel.depth_stencil_mode = DepthStencilMode::kEarlyHint; - } else { - modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers; - } - - // Initialize MIN/MAX blend pre-multiply factors to kOne (no pre-multiply). - modification.pixel.rt0_blend_rgb_factor_for_premult = - xenos::BlendFactor::kOne; - modification.pixel.rt0_blend_a_factor_for_premult = xenos::BlendFactor::kOne; - - bool rt0_minmax_premult_rgb_expected = false; - bool rt0_minmax_premult_a_expected = false; - if (shader.writes_color_target(0)) { - auto blend_control = regs.Get( - reg::RB_BLENDCONTROL::rt_register_indices[0]); - rt0_minmax_premult_rgb_expected = - (blend_control.color_comb_fcn == xenos::BlendOp::kMin || - blend_control.color_comb_fcn == xenos::BlendOp::kMax) && - blend_control.color_srcblend == xenos::BlendFactor::kSrcAlpha && - blend_control.color_destblend == xenos::BlendFactor::kOne; - rt0_minmax_premult_a_expected = - (blend_control.alpha_comb_fcn == xenos::BlendOp::kMin || - blend_control.alpha_comb_fcn == xenos::BlendOp::kMax) && - blend_control.alpha_srcblend == xenos::BlendFactor::kSrcAlpha && - blend_control.alpha_destblend == xenos::BlendFactor::kOne; - if (rt0_minmax_premult_rgb_expected) { - modification.pixel.rt0_blend_rgb_factor_for_premult = - xenos::BlendFactor::kSrcAlpha; - } - if (rt0_minmax_premult_a_expected) { - modification.pixel.rt0_blend_a_factor_for_premult = - xenos::BlendFactor::kSrcAlpha; - } - } - if (rt0_minmax_premult_rgb_expected || rt0_minmax_premult_a_expected) { - XELOGD( - "SPIRV-Cross PS mod diagnostic: shader={:016X} expected_premult(rgb={}," - "a={}) selected(rgb={},a={})", - shader.ucode_data_hash(), rt0_minmax_premult_rgb_expected ? 1 : 0, - rt0_minmax_premult_a_expected ? 1 : 0, - uint32_t(modification.pixel.rt0_blend_rgb_factor_for_premult), - uint32_t(modification.pixel.rt0_blend_a_factor_for_premult)); - } - - // Extract 1 bit per RT from the 4-bits-per-RT normalized_color_mask. - // Without this, color_targets_used defaults to 0 and the SPIR-V translator - // declares NO fragment color outputs, producing black/transparent rendering. - modification.pixel.color_targets_used = - (((normalized_color_mask >> 0) & 0xF) ? 1 : 0) | - (((normalized_color_mask >> 4) & 0xF) ? 2 : 0) | - (((normalized_color_mask >> 8) & 0xF) ? 4 : 0) | - (((normalized_color_mask >> 12) & 0xF) ? 8 : 0); - - return modification; -} - -MTL::RenderPipelineState* MetalCommandProcessor::GetOrCreateMslPipelineState( - MslShader::MslTranslation* vertex_translation, - MslShader::MslTranslation* pixel_translation, const RegisterFile& regs, - MslPipelineCompileStatus* compile_status_out) { - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kFailed; - } - if (!vertex_translation || !vertex_translation->metal_function()) { - XELOGE("SPIRV-Cross: No valid vertex shader function"); - return nullptr; - } - - // Determine attachment formats from render target cache. - uint32_t sample_count = 1; - MTL::PixelFormat color_formats[4] = { - MTL::PixelFormatInvalid, MTL::PixelFormatInvalid, MTL::PixelFormatInvalid, - MTL::PixelFormatInvalid}; - MTL::PixelFormat depth_format = MTL::PixelFormatInvalid; - MTL::PixelFormat stencil_format = MTL::PixelFormatInvalid; - if (render_target_cache_) { - for (uint32_t i = 0; i < 4; ++i) { - if (MTL::Texture* rt = render_target_cache_->GetColorTargetForDraw(i)) { - color_formats[i] = rt->pixelFormat(); - if (rt->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(rt->sampleCount())); - } - } - } - if (color_formats[0] == MTL::PixelFormatInvalid) { - if (MTL::Texture* dummy = - render_target_cache_->GetDummyColorTargetForDraw()) { - color_formats[0] = dummy->pixelFormat(); - if (dummy->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(dummy->sampleCount())); - } - } - } - if (MTL::Texture* depth_tex = - render_target_cache_->GetDepthTargetForDraw()) { - depth_format = depth_tex->pixelFormat(); - switch (depth_format) { - case MTL::PixelFormatDepth32Float_Stencil8: - case MTL::PixelFormatDepth24Unorm_Stencil8: - case MTL::PixelFormatX32_Stencil8: - stencil_format = depth_format; - break; - default: - stencil_format = MTL::PixelFormatInvalid; - break; - } - if (depth_tex->sampleCount() > 0) { - sample_count = std::max( - sample_count, static_cast(depth_tex->sampleCount())); - } - } - } - - // Match the active render pass attachments exactly to avoid - // setRenderPipelineState validation failures when the pass descriptor differs - // from the cache snapshot (for instance, after attachment reconfiguration). - MTL::RenderPassDescriptor* pass_descriptor = current_render_pass_descriptor_; - if (!pass_descriptor) { - pass_descriptor = render_pass_descriptor_; - } - if (pass_descriptor) { - sample_count = 1; - for (uint32_t i = 0; i < 4; ++i) { - color_formats[i] = MTL::PixelFormatInvalid; - } - depth_format = MTL::PixelFormatInvalid; - stencil_format = MTL::PixelFormatInvalid; - PopulatePipelineFormatsFromRenderPassDescriptor( - pass_descriptor, color_formats, 4, &depth_format, &stencil_format, - &sample_count); - } - bool pixel_shader_writes_depth = - pixel_translation && pixel_translation->shader().writes_depth(); - EnsureDepthFormatForDepthWritingFragment( - "SPIRV-Cross pipeline", pixel_shader_writes_depth, &depth_format); - - // Build pipeline cache key. - struct MslPipelineKey { - const void* vs; - const void* ps; - uint32_t sample_count; - uint32_t depth_format; - uint32_t stencil_format; - uint32_t color_formats[4]; - uint32_t normalized_color_mask; - uint32_t alpha_to_mask_enable; - uint32_t blendcontrol[4]; - } key_data = {}; - key_data.vs = vertex_translation; - key_data.ps = pixel_translation; - key_data.sample_count = sample_count; - key_data.depth_format = uint32_t(depth_format); - key_data.stencil_format = uint32_t(stencil_format); - for (uint32_t i = 0; i < 4; ++i) { - key_data.color_formats[i] = uint32_t(color_formats[i]); - } - uint32_t pixel_shader_writes_color_targets = - pixel_translation ? pixel_translation->shader().writes_color_targets() - : 0; - key_data.normalized_color_mask = 0; - if (pixel_shader_writes_color_targets) { - key_data.normalized_color_mask = draw_util::GetNormalizedColorMask( - regs, pixel_shader_writes_color_targets); - } - auto rb_colorcontrol = regs.Get(); - key_data.alpha_to_mask_enable = rb_colorcontrol.alpha_to_mask_enable ? 1 : 0; - for (uint32_t i = 0; i < 4; ++i) { - key_data.blendcontrol[i] = - regs.Get( - reg::RB_BLENDCONTROL::rt_register_indices[i]) - .value; - } - uint64_t key = XXH3_64bits(&key_data, sizeof(key_data)); - - bool use_async = cvars::async_shader_compilation && - !msl_shader_compile_threads_.empty() && - pixel_translation != nullptr; - - { - std::lock_guard lock(msl_shader_compile_mutex_); - auto it = msl_pipeline_cache_.find(key); - if (it != msl_pipeline_cache_.end()) { - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kReady; - } - return it->second; - } - if (msl_pipeline_compile_pending_.find(key) != - msl_pipeline_compile_pending_.end()) { - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kPending; - } - return nullptr; - } - if (msl_pipeline_compile_failed_.find(key) != - msl_pipeline_compile_failed_.end()) { - return nullptr; - } - } - - MslPipelineCompileRequest request = {}; - request.pipeline_key = key; - request.vertex_shader_hash = vertex_translation->shader().ucode_data_hash(); - request.vertex_modification = vertex_translation->modification(); - if (pixel_translation) { - request.pixel_shader_hash = pixel_translation->shader().ucode_data_hash(); - request.pixel_modification = pixel_translation->modification(); - } - request.vertex_function = vertex_translation->metal_function(); - request.fragment_function = - pixel_translation ? pixel_translation->metal_function() : nullptr; - request.sample_count = sample_count; - request.depth_format = depth_format; - request.stencil_format = stencil_format; - request.normalized_color_mask = key_data.normalized_color_mask; - request.alpha_to_mask_enable = key_data.alpha_to_mask_enable; - request.priority = pixel_translation ? 2 : 1; - for (uint32_t i = 0; i < 4; ++i) { - request.color_formats[i] = color_formats[i]; - request.blendcontrol[i] = key_data.blendcontrol[i]; - } - - if (use_async) { - if (EnqueueMslPipelineCompilation(request)) { - std::lock_guard lock(msl_shader_compile_mutex_); - auto it = msl_pipeline_cache_.find(key); - if (it != msl_pipeline_cache_.end()) { - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kReady; - } - return it->second; - } - if (msl_pipeline_compile_failed_.find(key) != - msl_pipeline_compile_failed_.end()) { - return nullptr; - } - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kPending; - } - return nullptr; - } - std::lock_guard lock(msl_shader_compile_mutex_); - if (msl_pipeline_compile_failed_.find(key) != - msl_pipeline_compile_failed_.end()) { - return nullptr; - } - } - - std::string error_message; - MTL::RenderPipelineState* pipeline = - CreateMslPipelineState(request, &error_message); - if (!pipeline) { - if (!error_message.empty()) { - XELOGE("SPIRV-Cross: Failed to create pipeline: {}", error_message); - } else { - XELOGE("SPIRV-Cross: Failed to create pipeline (unknown error)"); - } - std::lock_guard lock(msl_shader_compile_mutex_); - msl_pipeline_compile_failed_.insert(key); - return nullptr; - } - - { - std::lock_guard lock(msl_shader_compile_mutex_); - auto [it, inserted] = msl_pipeline_cache_.emplace(key, pipeline); - if (!inserted) { - pipeline->release(); - pipeline = it->second; - } - msl_pipeline_compile_failed_.erase(key); - } - if (compile_status_out) { - *compile_status_out = MslPipelineCompileStatus::kReady; - } - return pipeline; -} - -void MetalCommandProcessor::UpdateSpirvSystemConstantValues( - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - bool primitive_polygonal, uint32_t line_loop_closing_index, - xenos::Endian index_endian, const draw_util::ViewportInfo& viewport_info, - uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask) { - const SpirvShaderTranslator::SystemConstants previous_system_constants = - spirv_system_constants_; - const SpirvShaderTranslator::ClipPlaneConstants - previous_clip_plane_constants = spirv_clip_plane_constants_; - const SpirvShaderTranslator::TessellationConstants - previous_tessellation_constants = spirv_tessellation_constants_; - const RegisterFile& regs = *register_file_; + auto pa_cl_clip_cntl = regs.Get(); auto pa_cl_vte_cntl = regs.Get(); auto rb_alpha_ref = regs.Get(XE_GPU_REG_RB_ALPHA_REF); auto rb_colorcontrol = regs.Get(); auto rb_depth_info = regs.Get(); auto rb_surface_info = regs.Get(); auto vgt_draw_initiator = regs.Get(); + uint32_t vgt_indx_offset = regs.Get().indx_offset; + uint32_t vgt_max_vtx_indx = regs.Get().max_indx; + uint32_t vgt_min_vtx_indx = regs.Get().min_indx; - auto& consts = spirv_system_constants_; - std::memset(&consts, 0, sizeof(consts)); + uint32_t dirty = 0u; + ArchFloatMask dirty_float_mask = floatmask_zero; - // Build flags (matching Vulkan backend's SpirvShaderTranslator kSysFlag_*). - uint32_t flags = 0; + auto update_dirty_floatmask = [&dirty_float_mask](float x, float y) { + dirty_float_mask = + ArchORFloatMask(dirty_float_mask, ArchCmpneqFloatMask(x, y)); + }; + auto update_dirty_uint32_cmp = [&dirty](uint32_t x, uint32_t y) { + dirty |= (x ^ y); + }; - // Coordinate format. - if (pa_cl_vte_cntl.vtx_xy_fmt) { - flags |= SpirvShaderTranslator::kSysFlag_XYDividedByW; - } - if (pa_cl_vte_cntl.vtx_z_fmt) { - flags |= SpirvShaderTranslator::kSysFlag_ZDividedByW; - } - if (pa_cl_vte_cntl.vtx_w0_fmt) { - flags |= SpirvShaderTranslator::kSysFlag_WNotReciprocal; - } - - // Primitive type. - if (primitive_polygonal) { - flags |= SpirvShaderTranslator::kSysFlag_PrimitivePolygonal; - } - if (vgt_draw_initiator.prim_type == xenos::PrimitiveType::kLineList || - vgt_draw_initiator.prim_type == xenos::PrimitiveType::kLineStrip || - vgt_draw_initiator.prim_type == xenos::PrimitiveType::kLineLoop || - vgt_draw_initiator.prim_type == xenos::PrimitiveType::k2DLineStrip) { - flags |= SpirvShaderTranslator::kSysFlag_PrimitiveLine; - } - - // MSAA sample count. - flags |= uint32_t(rb_surface_info.msaa_samples) - << SpirvShaderTranslator::kSysFlag_MsaaSamples_Shift; - - // Depth format. - if (rb_depth_info.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) { - flags |= SpirvShaderTranslator::kSysFlag_DepthFloat24; - } - - // Alpha test — pack the CompareFunction value directly into the flag bits - // (matching Vulkan backend behavior). - xenos::CompareFunction alpha_test_function = - rb_colorcontrol.alpha_test_enable ? rb_colorcontrol.alpha_func - : xenos::CompareFunction::kAlways; - flags |= uint32_t(alpha_test_function) - << SpirvShaderTranslator::kSysFlag_AlphaPassIfLess_Shift; - - // Gamma correction for render targets. + // Get color info for each render target reg::RB_COLOR_INFO color_infos[4]; for (uint32_t i = 0; i < 4; ++i) { color_infos[i] = regs.Get( reg::RB_COLOR_INFO::rt_register_indices[i]); } - for (uint32_t i = 0; i < 4; ++i) { - if (color_infos[i].color_format == - xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { - flags |= SpirvShaderTranslator::kSysFlag_ConvertColor0ToGamma << i; - } + + // Build flags + uint32_t flags = 0; + + // Shared memory mode - determines whether shaders read from SRV (T0) or UAV + // (U0) + if (shared_memory_is_uav) { + flags |= DxbcShaderTranslator::kSysFlag_SharedMemoryIsUAV; } - // Vertex index loading for VS-based primitive expansion (point sprites, - // rectangle lists). When the primitive processor builds a host-side - // index buffer for DMA-based VS expansion the shader must load the - // original guest vertex index from shared memory. - if (primitive_processing_result.index_buffer_type == - PrimitiveProcessor::ProcessedIndexBufferType::kHostBuiltinForDMA) { - flags |= SpirvShaderTranslator::kSysFlag_ComputeOrPrimitiveVertexIndexLoad; - if (vgt_draw_initiator.index_size == xenos::IndexFormat::kInt32) { - flags |= SpirvShaderTranslator:: - kSysFlag_ComputeOrPrimitiveVertexIndexLoad32Bit; - } + // W0 division control from PA_CL_VTE_CNTL + if (pa_cl_vte_cntl.vtx_xy_fmt) { + flags |= DxbcShaderTranslator::kSysFlag_XYDividedByW; + } + if (pa_cl_vte_cntl.vtx_z_fmt) { + flags |= DxbcShaderTranslator::kSysFlag_ZDividedByW; + } + if (pa_cl_vte_cntl.vtx_w0_fmt) { + flags |= DxbcShaderTranslator::kSysFlag_WNotReciprocal; } - consts.flags = flags; - - // Vertex index. - consts.vertex_index_endian = index_endian; - consts.vertex_base_index = regs.Get().indx_offset; - const bool is_vs_expansion_draw = - primitive_processing_result.host_vertex_shader_type == - Shader::HostVertexShaderType::kPointListAsTriangleStrip || - primitive_processing_result.host_vertex_shader_type == - Shader::HostVertexShaderType::kRectangleListAsTriangleStrip; - consts.vertex_index_count = - is_vs_expansion_draw ? primitive_processing_result.guest_draw_vertex_count - : primitive_processing_result.host_draw_vertex_count; - - // Vertex index load address (for VS-based primitive expansion). - if (flags & - (SpirvShaderTranslator::kSysFlag_VertexIndexLoad | - SpirvShaderTranslator::kSysFlag_ComputeOrPrimitiveVertexIndexLoad)) { - consts.vertex_index_load_address = - primitive_processing_result.guest_index_base; + // Primitive type flags + if (primitive_polygonal) { + flags |= DxbcShaderTranslator::kSysFlag_PrimitivePolygonal; + } + if (draw_util::IsPrimitiveLine(regs)) { + flags |= DxbcShaderTranslator::kSysFlag_PrimitiveLine; } - // NDC scale/offset. - for (uint32_t i = 0; i < 3; ++i) { - consts.ndc_scale[i] = viewport_info.ndc_scale[i]; - consts.ndc_offset[i] = viewport_info.ndc_offset[i]; + // Depth format + if (rb_depth_info.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) { + flags |= DxbcShaderTranslator::kSysFlag_DepthFloat24; } - // Point rendering (matching Vulkan backend). - auto pa_su_point_size = regs.Get(); - auto pa_su_point_minmax = regs.Get(); - consts.point_vertex_diameter_min = - float(pa_su_point_minmax.min_size) * (2.0f / 16.0f); - consts.point_vertex_diameter_max = - float(pa_su_point_minmax.max_size) * (2.0f / 16.0f); - consts.point_constant_diameter[0] = - float(pa_su_point_size.width) * (2.0f / 16.0f); - consts.point_constant_diameter[1] = - float(pa_su_point_size.height) * (2.0f / 16.0f); - // 2 because 1 in the NDC is half of the viewport's axis, 0.5 for diameter - // to radius conversion — matching the Vulkan backend formula. - uint32_t draw_resolution_scale_x = - texture_cache_ ? texture_cache_->draw_resolution_scale_x() : 1; - uint32_t draw_resolution_scale_y = - texture_cache_ ? texture_cache_->draw_resolution_scale_y() : 1; - consts.point_screen_diameter_to_ndc_radius[0] = - float(draw_resolution_scale_x) / - float(std::max(viewport_info.xy_extent[0], uint32_t(1))); - consts.point_screen_diameter_to_ndc_radius[1] = - float(draw_resolution_scale_y) / - float(std::max(viewport_info.xy_extent[1], uint32_t(1))); + // Alpha test - encode compare function in flags + xenos::CompareFunction alpha_test_function = + rb_colorcontrol.alpha_test_enable ? rb_colorcontrol.alpha_func + : xenos::CompareFunction::kAlways; + flags |= uint32_t(alpha_test_function) + << DxbcShaderTranslator::kSysFlag_AlphaPassIfLess_Shift; - // Texture swizzled signs and swizzles — retrieved from the texture cache - // (matching Vulkan backend behavior). - if (texture_cache_) { - for (uint32_t i = 0; i < 32; ++i) { - if (!(used_texture_mask & (uint32_t(1) << i))) { - continue; + // Gamma conversion flags for render targets + if (!render_target_cache_->gamma_render_target_as_unorm16()) { + for (uint32_t i = 0; i < 4; ++i) { + if (color_infos[i].color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + flags |= DxbcShaderTranslator::kSysFlag_ConvertColor0ToGamma << i; } - // Swizzled signs: 8 bits per texture, 4 textures per uint32. - uint8_t texture_signs = texture_cache_->GetActiveTextureSwizzledSigns(i); - uint32_t signs_shift = 8 * (i & 3); - consts.texture_swizzled_signs[i >> 2] |= uint32_t(texture_signs) - << signs_shift; - - // Host swizzles: 12 bits per texture, 2 textures per uint32. - uint32_t texture_swizzle = texture_cache_->GetActiveTextureHostSwizzle(i); - uint32_t swizzle_shift = 12 * (i & 1); - consts.texture_swizzles[i >> 1] |= (texture_swizzle & 0xFFF) - << swizzle_shift; } } - // Textures resolved — which textures are from scaled resolve operations - // (matching Vulkan backend). - if (texture_cache_) { - uint32_t textures_resolved = 0; - uint32_t textures_remaining = used_texture_mask; - uint32_t texture_index; - while (xe::bit_scan_forward(textures_remaining, &texture_index)) { - textures_remaining &= ~(UINT32_C(1) << texture_index); - textures_resolved |= + update_dirty_uint32_cmp(system_constants_.flags, flags); + system_constants_.flags = flags; + + // Tessellation factor range + float tessellation_factor_min = + regs.Get(XE_GPU_REG_VGT_HOS_MIN_TESS_LEVEL) + 1.0f; + float tessellation_factor_max = + regs.Get(XE_GPU_REG_VGT_HOS_MAX_TESS_LEVEL) + 1.0f; + update_dirty_floatmask(system_constants_.tessellation_factor_range_min, + tessellation_factor_min); + update_dirty_floatmask(system_constants_.tessellation_factor_range_max, + tessellation_factor_max); + system_constants_.tessellation_factor_range_min = tessellation_factor_min; + system_constants_.tessellation_factor_range_max = tessellation_factor_max; + + // Line loop closing index + update_dirty_uint32_cmp(system_constants_.line_loop_closing_index, + line_loop_closing_index); + system_constants_.line_loop_closing_index = line_loop_closing_index; + + // Vertex index configuration + update_dirty_uint32_cmp( + static_cast(system_constants_.vertex_index_endian), + static_cast(index_endian)); + update_dirty_uint32_cmp(system_constants_.vertex_index_offset, + vgt_indx_offset); + update_dirty_uint32_cmp(system_constants_.vertex_index_min, vgt_min_vtx_indx); + update_dirty_uint32_cmp(system_constants_.vertex_index_max, vgt_max_vtx_indx); + system_constants_.vertex_index_endian = index_endian; + system_constants_.vertex_index_offset = vgt_indx_offset; + system_constants_.vertex_index_min = vgt_min_vtx_indx; + system_constants_.vertex_index_max = vgt_max_vtx_indx; + + // User clip planes (when not CLIP_DISABLE) + if (!pa_cl_clip_cntl.clip_disable) { + float* user_clip_plane_write_ptr = system_constants_.user_clip_planes[0]; + uint32_t user_clip_planes_remaining = pa_cl_clip_cntl.ucp_ena; + uint32_t user_clip_plane_index; + while (xe::bit_scan_forward(user_clip_planes_remaining, + &user_clip_plane_index)) { + user_clip_planes_remaining &= ~(UINT32_C(1) << user_clip_plane_index); + const float* user_clip_plane_regs = reinterpret_cast( + ®s.values[XE_GPU_REG_PA_CL_UCP_0_X + user_clip_plane_index * 4]); + if (std::memcmp(user_clip_plane_write_ptr, user_clip_plane_regs, + 4 * sizeof(float)) != 0) { + dirty = true; + std::memcpy(user_clip_plane_write_ptr, user_clip_plane_regs, + 4 * sizeof(float)); + } + user_clip_plane_write_ptr += 4; + } + } + + // NDC scale and offset from viewport info + for (uint32_t i = 0; i < 3; ++i) { + update_dirty_floatmask(system_constants_.ndc_scale[i], + viewport_info.ndc_scale[i]); + update_dirty_floatmask(system_constants_.ndc_offset[i], + viewport_info.ndc_offset[i]); + system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i]; + system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i]; + } + + // Point size parameters + if (vgt_draw_initiator.prim_type == xenos::PrimitiveType::kPointList) { + auto pa_su_point_minmax = regs.Get(); + auto pa_su_point_size = regs.Get(); + float point_vertex_diameter_min = + float(pa_su_point_minmax.min_size) * (2.0f / 16.0f); + float point_vertex_diameter_max = + float(pa_su_point_minmax.max_size) * (2.0f / 16.0f); + float point_constant_diameter_x = + float(pa_su_point_size.width) * (2.0f / 16.0f); + float point_constant_diameter_y = + float(pa_su_point_size.height) * (2.0f / 16.0f); + update_dirty_floatmask(system_constants_.point_vertex_diameter_min, + point_vertex_diameter_min); + update_dirty_floatmask(system_constants_.point_vertex_diameter_max, + point_vertex_diameter_max); + update_dirty_floatmask(system_constants_.point_constant_diameter[0], + point_constant_diameter_x); + update_dirty_floatmask(system_constants_.point_constant_diameter[1], + point_constant_diameter_y); + system_constants_.point_vertex_diameter_min = point_vertex_diameter_min; + system_constants_.point_vertex_diameter_max = point_vertex_diameter_max; + system_constants_.point_constant_diameter[0] = point_constant_diameter_x; + system_constants_.point_constant_diameter[1] = point_constant_diameter_y; + // Screen to NDC radius conversion. + // 2 because 1 in the NDC is half of the viewport's axis, 0.5 for diameter + // to radius conversion to avoid multiplying the per-vertex diameter by an + // additional constant in the shader. Include draw_resolution_scale to + // match D3D12 behavior. + uint32_t point_draw_resolution_scale_x = + render_target_cache_ ? render_target_cache_->draw_resolution_scale_x() + : 1; + uint32_t point_draw_resolution_scale_y = + render_target_cache_ ? render_target_cache_->draw_resolution_scale_y() + : 1; + float point_screen_diameter_to_ndc_radius_x = + (/* 0.5f * 2.0f * */ float(point_draw_resolution_scale_x)) / + std::max(viewport_info.xy_extent[0], uint32_t(1)); + float point_screen_diameter_to_ndc_radius_y = + (/* 0.5f * 2.0f * */ float(point_draw_resolution_scale_y)) / + std::max(viewport_info.xy_extent[1], uint32_t(1)); + update_dirty_floatmask( + system_constants_.point_screen_diameter_to_ndc_radius[0], + point_screen_diameter_to_ndc_radius_x); + update_dirty_floatmask( + system_constants_.point_screen_diameter_to_ndc_radius[1], + point_screen_diameter_to_ndc_radius_y); + system_constants_.point_screen_diameter_to_ndc_radius[0] = + point_screen_diameter_to_ndc_radius_x; + system_constants_.point_screen_diameter_to_ndc_radius[1] = + point_screen_diameter_to_ndc_radius_y; + } + + // Texture signedness / resolution scaling (mirror D3D12 logic). + // Always update textures_resolution_scaled, even when used_texture_mask is 0, + // to avoid stale values from previous draws. + uint32_t textures_resolution_scaled = 0; + uint32_t textures_remaining = used_texture_mask; + uint32_t texture_index; + while (xe::bit_scan_forward(textures_remaining, &texture_index)) { + textures_remaining &= ~(uint32_t(1) << texture_index); + if (texture_cache_) { + uint32_t& texture_signs_uint = + system_constants_.texture_swizzled_signs[texture_index >> 2]; + uint32_t texture_signs_shift = (texture_index & 3) * 8; + uint8_t texture_signs = + texture_cache_->GetActiveTextureSwizzledSigns(texture_index); + uint32_t texture_signs_shifted = uint32_t(texture_signs) + << texture_signs_shift; + uint32_t texture_signs_mask = uint32_t(0xFF) << texture_signs_shift; + update_dirty_uint32_cmp((texture_signs_uint & texture_signs_mask), + texture_signs_shifted); + texture_signs_uint = + (texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted; + textures_resolution_scaled |= uint32_t( texture_cache_->IsActiveTextureResolutionScaled(texture_index)) << texture_index; } - consts.textures_resolved = textures_resolved; } + update_dirty_uint32_cmp(system_constants_.textures_resolution_scaled, + textures_resolution_scaled); + system_constants_.textures_resolution_scaled = textures_resolution_scaled; - // Alpha test reference. - consts.alpha_test_reference = rb_alpha_ref; + // Sample count log2 for alpha to mask + uint32_t sample_count_log2_x = + rb_surface_info.msaa_samples >= xenos::MsaaSamples::k4X ? 1 : 0; + uint32_t sample_count_log2_y = + rb_surface_info.msaa_samples >= xenos::MsaaSamples::k2X ? 1 : 0; + update_dirty_uint32_cmp(system_constants_.sample_count_log2[0], + sample_count_log2_x); + update_dirty_uint32_cmp(system_constants_.sample_count_log2[1], + sample_count_log2_y); + system_constants_.sample_count_log2[0] = sample_count_log2_x; + system_constants_.sample_count_log2[1] = sample_count_log2_y; - // Alpha to mask — if enabled, bits 0:7 are sample offsets, bit 8 = 1. - // (matching Vulkan backend / MSC path). - if (rb_colorcontrol.alpha_to_mask_enable) { - consts.alpha_to_mask = (rb_colorcontrol.value >> 24) | (1 << 8); - } + // Alpha test reference + update_dirty_floatmask(system_constants_.alpha_test_reference, rb_alpha_ref); + system_constants_.alpha_test_reference = rb_alpha_ref; - // Color exponent bias (matching Vulkan backend). + // Alpha to mask + uint32_t alpha_to_mask = rb_colorcontrol.alpha_to_mask_enable + ? (rb_colorcontrol.value >> 24) | (1 << 8) + : 0; + update_dirty_uint32_cmp(system_constants_.alpha_to_mask, alpha_to_mask); + system_constants_.alpha_to_mask = alpha_to_mask; + + // Color exponent bias for (uint32_t i = 0; i < 4; ++i) { int32_t color_exp_bias = color_infos[i].color_exp_bias; - if (render_target_cache_->GetPath() == - RenderTargetCache::Path::kHostRenderTargets && - ((color_infos[i].color_format == - xenos::ColorRenderTargetFormat::k_16_16 && - !render_target_cache_->IsFixedRG16TruncatedToMinus1To1()) || - (color_infos[i].color_format == - xenos::ColorRenderTargetFormat::k_16_16_16_16 && - !render_target_cache_->IsFixedRGBA16TruncatedToMinus1To1()))) { - color_exp_bias -= 5; + // Fixed-point render targets (k_16_16 / k_16_16_16_16) are backed by + // *_SNORM in the host render targets path. If full-range emulation is + // requested, remap from -32...32 to -1...1 by dividing the output values + // by 32. + if (color_infos[i].color_format == + xenos::ColorRenderTargetFormat::k_16_16) { + if (!render_target_cache_->IsFixedRG16TruncatedToMinus1To1()) { + color_exp_bias -= 5; + } + } else if (color_infos[i].color_format == + xenos::ColorRenderTargetFormat::k_16_16_16_16) { + if (!render_target_cache_->IsFixedRGBA16TruncatedToMinus1To1()) { + color_exp_bias -= 5; + } } - float color_exp_bias_scale; - *reinterpret_cast(&color_exp_bias_scale) = - UINT32_C(0x3F800000) + (color_exp_bias << 23); - consts.color_exp_bias[i] = color_exp_bias_scale; + auto color_exp_bias_scale = xe::memory::Reinterpret( + int32_t(0x3F800000 + (color_exp_bias << 23))); + update_dirty_floatmask(system_constants_.color_exp_bias[i], + color_exp_bias_scale); + system_constants_.color_exp_bias[i] = color_exp_bias_scale; } - // Clip plane constants (separate buffer for the SPIR-V translator). - // The SPIR-V translator reads clip planes from a dedicated uniform buffer - // (kConstantBufferClipPlanes), not from system constants. - auto pa_cl_clip_cntl = regs.Get(); - std::memset(&spirv_clip_plane_constants_, 0, - sizeof(spirv_clip_plane_constants_)); - if (!pa_cl_clip_cntl.clip_disable && pa_cl_clip_cntl.ucp_ena) { - float* clip_plane_write_ptr = - spirv_clip_plane_constants_.user_clip_planes[0]; - uint32_t clip_planes_remaining = pa_cl_clip_cntl.ucp_ena; - uint32_t clip_plane_index; - while (xe::bit_scan_forward(clip_planes_remaining, &clip_plane_index)) { - clip_planes_remaining &= ~(UINT32_C(1) << clip_plane_index); - const float* clip_plane_regs = reinterpret_cast( - ®s.values[XE_GPU_REG_PA_CL_UCP_0_X + clip_plane_index * 4]); - std::memcpy(clip_plane_write_ptr, clip_plane_regs, 4 * sizeof(float)); - clip_plane_write_ptr += 4; - } - } + // Blend constants (used by EDRAM and for host blending) + float blend_red = regs.Get(XE_GPU_REG_RB_BLEND_RED); + float blend_green = regs.Get(XE_GPU_REG_RB_BLEND_GREEN); + float blend_blue = regs.Get(XE_GPU_REG_RB_BLEND_BLUE); + float blend_alpha = regs.Get(XE_GPU_REG_RB_BLEND_ALPHA); + update_dirty_floatmask(system_constants_.edram_blend_constant[0], blend_red); + update_dirty_floatmask(system_constants_.edram_blend_constant[1], + blend_green); + update_dirty_floatmask(system_constants_.edram_blend_constant[2], blend_blue); + update_dirty_floatmask(system_constants_.edram_blend_constant[3], + blend_alpha); + system_constants_.edram_blend_constant[0] = blend_red; + system_constants_.edram_blend_constant[1] = blend_green; + system_constants_.edram_blend_constant[2] = blend_blue; + system_constants_.edram_blend_constant[3] = blend_alpha; - // Tessellation constants (separate buffer for the SPIR-V translator). - // Mirror Vulkan constant buffer population to keep shader inputs identical. - std::memset(&spirv_tessellation_constants_, 0, - sizeof(spirv_tessellation_constants_)); - float tess_factor_min = - regs.Get(XE_GPU_REG_VGT_HOS_MIN_TESS_LEVEL) + 1.0f; - float tess_factor_max = - regs.Get(XE_GPU_REG_VGT_HOS_MAX_TESS_LEVEL) + 1.0f; - spirv_tessellation_constants_.tessellation_factor_range[0] = tess_factor_min; - spirv_tessellation_constants_.tessellation_factor_range[1] = tess_factor_max; - auto vgt_dma_size = regs.Get(); - spirv_tessellation_constants_.vertex_index_endian = - static_cast(vgt_dma_size.swap_mode); - spirv_tessellation_constants_.vertex_index_offset = - regs[XE_GPU_REG_VGT_INDX_OFFSET]; - spirv_tessellation_constants_.vertex_index_min_max[0] = - regs[XE_GPU_REG_VGT_MIN_VTX_INDX]; - spirv_tessellation_constants_.vertex_index_min_max[1] = - regs[XE_GPU_REG_VGT_MAX_VTX_INDX]; - - if (std::memcmp(&previous_system_constants, &spirv_system_constants_, - sizeof(spirv_system_constants_)) != 0) { - ++msl_system_constants_version_; - if (msl_system_constants_version_ == 0) { - msl_system_constants_version_ = 1; - } - } - if (std::memcmp(&previous_clip_plane_constants, &spirv_clip_plane_constants_, - sizeof(spirv_clip_plane_constants_)) != 0) { - ++msl_clip_plane_constants_version_; - if (msl_clip_plane_constants_version_ == 0) { - msl_clip_plane_constants_version_ = 1; - } - } - if (std::memcmp(&previous_tessellation_constants, - &spirv_tessellation_constants_, - sizeof(spirv_tessellation_constants_)) != 0) { - ++msl_tessellation_constants_version_; - if (msl_tessellation_constants_version_ == 0) { - msl_tessellation_constants_version_ = 1; - } - } + dirty |= ArchFloatMaskSignbit(dirty_float_mask); + cbuffer_binding_system_.up_to_date &= !dirty; } #define COMMAND_PROCESSOR MetalCommandProcessor diff --git a/src/xenia/gpu/metal/metal_command_processor.h b/src/xenia/gpu/metal/metal_command_processor.h index 9022642dd..39f0b6492 100644 --- a/src/xenia/gpu/metal/metal_command_processor.h +++ b/src/xenia/gpu/metal/metal_command_processor.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -29,18 +30,31 @@ #include "xenia/base/string_buffer.h" #include "xenia/gpu/command_processor.h" #include "xenia/gpu/draw_util.h" +#include "xenia/gpu/dxbc_shader_translator.h" +#include "xenia/gpu/metal/dxbc_to_dxil_converter.h" +#include "xenia/gpu/metal/metal_geometry_shader.h" +#include "xenia/gpu/metal/metal_pipeline_cache.h" #include "xenia/gpu/metal/metal_primitive_processor.h" #include "xenia/gpu/metal/metal_render_target_cache.h" +#include "xenia/gpu/metal/metal_shader.h" +#include "xenia/gpu/metal/metal_shader_converter.h" #include "xenia/gpu/metal/metal_shared_memory.h" #include "xenia/gpu/metal/metal_texture_cache.h" -#include "xenia/gpu/metal/msl_bindings.h" -#include "xenia/gpu/metal/msl_shader.h" -#include "xenia/gpu/spirv_shader_translator.h" +#include "xenia/gpu/metal/metal_upload_buffer_pool.h" +#include "xenia/gpu/metal/metal_zpd_visibility_pool.h" +// clang-format off +// Must come after metal_texture_cache.h which includes Metal.hpp +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" +// clang-format on #include "xenia/ui/metal/metal_api.h" #include "xenia/ui/metal/metal_provider.h" namespace MTL { +class BlitCommandEncoder; +class ComputeCommandEncoder; +class Fence; class Heap; +class ResidencySet; class SharedEvent; } // namespace MTL @@ -50,7 +64,7 @@ namespace metal { class MetalGraphicsSystem; -class MetalCommandProcessor : public CommandProcessor { +class MetalCommandProcessor final : public CommandProcessor { protected: #define OVERRIDING_BASE_CMDPROCESSOR #include "../pm4_command_processor_declare.h" @@ -67,14 +81,6 @@ class MetalCommandProcessor : public CommandProcessor { void InvalidateGpuMemory() override; void ClearReadbackBuffers() override; - std::string GetTitleStateSuffix() const override; - - // Track memory regions written by IssueCopy (resolve) so trace playback - // can skip overwriting them with stale data from the trace file. - void MarkResolvedMemory(uint32_t base_ptr, uint32_t length); - bool IsResolvedMemory(uint32_t base_ptr, uint32_t length) const; - void ClearResolvedMemory(); - ui::metal::MetalProvider& GetMetalProvider() const; // Get the Metal device and command queue @@ -83,29 +89,213 @@ class MetalCommandProcessor : public CommandProcessor { MTL::CommandBuffer* GetCurrentCommandBuffer() const { return current_command_buffer_; } - bool HasActiveRenderEncoder() const { - return current_render_encoder_ != nullptr; + + // Submission coordination helpers — callers use these to query or obtain + // command buffers for transfer/upload work without reaching into internal + // command-processor state. + bool HasActiveSubmission() const { + return current_command_buffer_ != nullptr; } - uint32_t current_draw_index() const { return current_draw_index_; } + // Returns true when upload/transfer work can join the current submission's + // command buffer. This is the case when a command buffer exists but no render + // encoder is open. + bool CanJoinActiveSubmissionForTransfer() const { + return current_command_buffer_ != nullptr && + current_render_encoder_ == nullptr; + } + enum class TransferRequestSource : uint32_t { + kUnknown, + kSharedMemoryUpload, + kGuestIndexCopy, + kRenderTargetTransfer, + kCount, + }; + static constexpr size_t kTransferRequestSourceCount = + static_cast(TransferRequestSource::kCount); + enum class SharedMemoryRequestReason : uint32_t { + kUnknown, + kVertexFetch, + kMemexportStream, + kGuestIndex, + kShaderPrimitiveIndex, + kIndexCopySource, + kTextureBase, + kTextureMips, + kTextureBaseAndMips, + kResolveCopyDest, + kDrawMaterialization, + kCount, + }; + static constexpr size_t kSharedMemoryRequestReasonCount = + static_cast(SharedMemoryRequestReason::kCount); + enum class SharedMemoryRequestOutcome : uint32_t { + kAlreadyResident, + kUploadBeforeRenderEncoder, + kUploadInsideRenderEncoder, + kRequestFailed, + kNoSharedMemory, + kTextureDeferredUploadFlush, + kCount, + }; + static constexpr size_t kSharedMemoryRequestOutcomeCount = + static_cast(SharedMemoryRequestOutcome::kCount); + enum class SharedMemoryUploadRoute : uint32_t { + kStagedBlit, + kDirectWrite, + kCount, + }; + static constexpr size_t kSharedMemoryUploadRouteCount = + static_cast(SharedMemoryUploadRoute::kCount); + enum class SharedMemoryDirectWriteRejectReason : uint32_t { + kMainGpuAccessInFlight, + kStandaloneAccessInFlight, + kNoSharedBufferContents, + kMixedRangeSplit, + kCount, + }; + static constexpr size_t kSharedMemoryDirectWriteRejectReasonCount = + static_cast(SharedMemoryDirectWriteRejectReason::kCount); + enum class TextureUploadSourceRoute : uint32_t { + kCpuGuestMemory, + kResidentSharedMemory, + kScaledResolve, + kCount, + }; + static constexpr size_t kTextureUploadSourceRouteCount = + static_cast(TextureUploadSourceRoute::kCount); + enum class TextureUploadSourceFallbackReason : uint32_t { + kMixedValidity, + kScaledResolve, + kSourceAlreadyResident, + kCpuSourceLoadFailed, + kUnknown, + kCount, + }; + static constexpr size_t kTextureUploadSourceFallbackReasonCount = + static_cast(TextureUploadSourceFallbackReason::kCount); + enum class SharedMemoryUploadEncoderEndReason : uint32_t { + kUnknown, + kRenderBegin, + kTransferRequest, + kTextureCompute, + kTextureBlit, + kTextureDeferredBlit, + kScaledResolveBlit, + kCommandBufferEnd, + kSwap, + kUploadFailure, + kShutdown, + kMaterializationDrain, + kCount, + }; + static constexpr size_t kSharedMemoryUploadEncoderEndReasonCount = + static_cast(SharedMemoryUploadEncoderEndReason::kCount); + // Returns a command buffer suitable for transfer (blit/compute) work. + // If a render encoder is active it is ended first; if no command buffer + // exists one is created. This is an encoder-lifetime break, not necessarily + // a command-buffer submission break. Returns nullptr on failure. + MTL::CommandBuffer* RequestTransferCommandBuffer( + TransferRequestSource source = TransferRequestSource::kUnknown); + void RecordSharedMemoryUploadRoute(SharedMemoryUploadRoute route, + uint64_t bytes); + void RecordSharedMemoryDirectWriteEligibility(uint64_t direct_bytes, + uint64_t staged_bytes); + void RecordSharedMemoryDirectWriteReject( + SharedMemoryDirectWriteRejectReason reason, uint64_t bytes); + void RecordTextureUploadSourceRoute(TextureUploadSourceRoute route, + uint64_t bytes); + void RecordTextureUploadSourceFallback( + TextureUploadSourceFallbackReason reason); + void RecordSharedMemoryUploadEncoderCopy(); + bool RequestSharedMemoryRange(SharedMemoryRequestReason reason, + uint32_t start, uint32_t length); + bool RequestSharedMemoryRangeBeforeDrawPass(SharedMemoryRequestReason reason, + uint32_t start, uint32_t length); + bool RequestSharedMemoryRanges(SharedMemoryRequestReason reason, + const SharedMemory::Range* ranges, + uint32_t range_count); + void RecordSharedMemoryRequestOutcome(SharedMemoryRequestOutcome outcome); + MTL::BlitCommandEncoder* GetSharedMemoryUploadBlitEncoder(); + void EndSharedMemoryUploadBlitEncoder( + SharedMemoryUploadEncoderEndReason reason = + SharedMemoryUploadEncoderEndReason::kUnknown); + + // Standalone (detached) transfer command-buffer helpers. + // These create command buffers that are independent of the active submission + // and are used by caches for upload/transfer work that cannot join the + // current command buffer. The returned CB is retained; ownership transfers + // back via CommitStandaloneAsync or CommitStandaloneAndWait. + MTL::CommandBuffer* CreateStandaloneTransferCommandBuffer(const char* label); + // Commit a standalone command buffer asynchronously (fire-and-forget). + // The CB is released via a completion handler. + void CommitStandaloneAsync(MTL::CommandBuffer* cmd); + // Commit a standalone command buffer synchronously and wait for completion. + // The CB is released before returning. + void CommitStandaloneAndWait(MTL::CommandBuffer* cmd); + uint64_t GetCurrentSubmission() const; uint64_t GetCompletedSubmission() const override; + uint64_t GetLatestSubmissionStarted() const { return submission_current_; } MTL::CommandBuffer* EnsureCommandBuffer(); void EndRenderEncoder(); + void InvalidateRenderEncoderStateAfterDrawPassTransfers( + MetalRenderTargetCache::DrawPassTransferEncoderMutationMask mutations); void ResetRenderEncoderResourceUsage(); void UseRenderEncoderResource(MTL::Resource* resource, MTL::ResourceUsage usage); + void UseRenderEncoderResource(MTL::Resource* resource, + MTL::ResourceUsage usage, + MTL::RenderStages stages); + bool AddResidencySetHeap(MTL::Heap* heap); void EnsureCommandBufferAutoreleasePool(); void DrainCommandBufferAutoreleasePool(); - // Get current render pass descriptor (for render target binding) - MTL::RenderPassDescriptor* GetCurrentRenderPassDescriptor(); - // Force issue a swap to push render target to presenter (for trace dumps) void ForceIssueSwap(); bool HasSeenSwap() const { return saw_swap_; } void SetSwapDestSwap(uint32_t dest_base, bool swap); bool ConsumeSwapDestSwap(uint32_t dest_base, bool* swap_out); + MetalSharedMemory* shared_memory() const { return shared_memory_.get(); } + MetalRenderTargetCache* render_target_cache() const { + return render_target_cache_.get(); + } + MetalTextureCache* texture_cache() const { return texture_cache_.get(); } + + // D3D12-style shared-memory hazard tracking for GPU writes produced by + // resolves and memexport draws, expressed with Metal fences/barriers. + struct SharedMemoryRange { + uint32_t start = 0; + uint32_t length = 0; + }; + + struct SharedMemoryReadDependency { + bool needs_fence_wait = false; + }; + + void MarkSharedMemoryComputeWritePending(uint32_t address, uint32_t length, + MTL::ComputeCommandEncoder* encoder); + void MarkSharedMemoryRenderWritePending(uint32_t address, uint32_t length, + MTL::RenderStages stages); + bool PrepareSharedMemoryComputeReadDependency( + const SharedMemoryRange* ranges, uint32_t range_count, + bool consumer_can_join_current_submission, + SharedMemoryReadDependency* dependency_out); + bool EncodeSharedMemoryComputeReadDependency( + MTL::ComputeCommandEncoder* encoder, + const SharedMemoryReadDependency& dependency, + const SharedMemoryRange* ranges, uint32_t range_count); + + // Persistent bindless descriptor heap allocators. + uint32_t AllocateViewBindlessIndex(); + void ReleaseViewBindlessIndex(uint32_t index); + void RetireViewBindlessIndex(uint32_t index); + uint32_t GetViewBindlessHeapAvailableCount() const; + uint32_t AllocateSamplerBindlessIndex(); + void ReleaseSamplerBindlessIndex(uint32_t index); + IRDescriptorTableEntry* GetViewBindlessHeapEntry(uint32_t index); + IRDescriptorTableEntry* GetSamplerBindlessHeapEntry(uint32_t index); + protected: bool SetupContext() override; void ShutdownContext() override; @@ -135,62 +325,663 @@ class MetalCommandProcessor : public CommandProcessor { bool IssueDraw(xenos::PrimitiveType primitive_type, uint32_t index_count, IndexBufferInfo* index_buffer_info, bool major_mode_explicit) override; - // SPIRV-Cross draw path — called from IssueDraw. Handles shader translation, - // pipeline creation, resource binding, and draw dispatch using native Metal - // encoder calls. - bool IssueDrawMsl( - Shader* vertex_shader, Shader* pixel_shader, - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - bool primitive_polygonal, bool is_rasterization_done, bool memexport_used, - uint32_t normalized_color_mask, const RegisterFile& regs); bool IssueCopy() override; + + // ZPD occlusion query backend overrides. + void PollCompletedSubmission() override; + void EnsureZPDQueryResources() override; + void ShutdownZPDQueryResources() override; + bool IsZPDQueryPoolReady() const override; + bool CanOpenZPDQuery() const override; + QueryOpenResult OpenZPDQuery(ReportHandle report_handle, + bool can_close_submission) override; + bool CloseZPDQuery(ReportHandle report_handle, + uint64_t& out_submission) override; + bool DiscardZPDQuery() override; + void PumpQueryResolves() override; + bool AwaitQueryResolve(ReportHandle report_handle, + uint64_t wait_for_submission) override; + void WriteRegister(uint32_t index, uint32_t value) override; + void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, + uint32_t num_registers) override; + void WriteRegisterRangeFromRing(xe::RingBuffer* ring, uint32_t base, + uint32_t num_registers) override; + + bool CanFastWriteRegisterRange(uint32_t start_index, + uint32_t num_registers) const; + bool TryWriteKnownRegisterRangeFromMem(uint32_t start_index, uint32_t* base, + uint32_t num_registers); + void WriteFastRegisterRangeFromRing(xe::RingBuffer* ring, uint32_t base, + uint32_t num_registers); + void WriteShaderConstantsFromMem(uint32_t start_index, uint32_t* base, + uint32_t num_registers); + void WriteBoolLoopConstantsFromMem(uint32_t start_index, uint32_t* base, + uint32_t num_registers); + void WriteFetchConstantsFromMem(uint32_t start_index, uint32_t* base, + uint32_t num_registers); + bool FloatConstantRangeNeedsDirty(uint32_t start_index, const uint32_t* base, + uint32_t num_registers, + const uint64_t* constant_map, + uint32_t stage_first_constant) const; + void DirtyFetchConstantDwords( + const DxbcShader::FetchConstantDwordMask& dirty_mask); + + static constexpr size_t kStageVertex = 0; + static constexpr size_t kStagePixel = 1; + static constexpr size_t kStageCount = 2; // Vertex + pixel. + static constexpr size_t kCbvSlotCount = 5; + enum CbvSlot : size_t { + kCbvSlotSystem, + kCbvSlotFloat, + kCbvSlotBoolLoop, + kCbvSlotFetch, + kCbvSlotDescriptorIndices, + }; + static constexpr size_t kBindlessRootRebuildReasonCount = 4; + enum BindlessRootRebuildReason : size_t { + kBindlessRootRebuildFrameOpen, + kBindlessRootRebuildDescriptorIndicesPointerChange, + kBindlessRootRebuildOtherCbvPointerChange, + kBindlessRootRebuildSharedMemoryUavChange, + }; + static constexpr size_t kBindlessRootSlotsChangedBinCount = 6; + static constexpr size_t kBindlessRootRebuildDetailCount = 7; + enum BindlessRootRebuildDetail : size_t { + kBindlessRootDetailSameBufferOffsetChanged, + kBindlessRootDetailDifferentBuffer, + kBindlessRootDetailDescriptorIndicesOnly, + kBindlessRootDetailOtherCbvOnly, + kBindlessRootDetailMixedDescriptorAndOther, + kBindlessRootDetailResourceIdentityChanged, + kBindlessRootDetailResourceIdentitySame, + }; + static constexpr size_t kRenderEncoderBufferStageTelemetryCount = 4; + static constexpr size_t kBindlessRootArgTelemetrySlotCount = 32; + + // Per-draw uniform buffer coordinates passed between IssueDraw sub-methods. + struct UniformBufferInfo { + struct Cbv { + MTL::Buffer* buffer = nullptr; + NS::UInteger offset = 0; + uint64_t gpu_address = 0; + size_t size = 0; + // Inactive slots are canonicalized to null in the top-level argument + // table, matching the fixed MSC layout without treating unused CBVs as + // per-draw state. + bool active = false; + }; + + std::array, kStageCount> cbvs = {}; + std::array active_cbv_masks = {}; + std::array + fetch_constant_dword_masks = {}; + }; + + struct DrawDynamicState { + MTL::Viewport viewport = {}; + MTL::ScissorRect scissor = {}; + draw_util::ViewportInfo viewport_info = {}; + reg::RB_DEPTHCONTROL depth_control = {}; + reg::RB_STENCILREFMASK stencil_ref_mask_front = {}; + reg::RB_STENCILREFMASK stencil_ref_mask_back = {}; + reg::PA_SU_SC_MODE_CNTL pa_su_sc_mode_cntl = {}; + MTL::CullMode cull_mode = MTL::CullModeNone; + MTL::Winding front_facing_winding = MTL::WindingCounterClockwise; + MTL::TriangleFillMode triangle_fill_mode = MTL::TriangleFillModeFill; + float depth_bias_constant = 0.0f; + float depth_bias_slope = 0.0f; + MTL::DepthClipMode depth_clip_mode = MTL::DepthClipModeClip; + bool primitive_polygonal = false; + bool rasterization_enabled = true; + float blend_constants[4] = {}; + }; + + // Vertex binding range for stage-in / geometry emulation. + struct VertexBindingRange { + uint32_t binding_index = 0; + uint32_t offset = 0; + uint32_t length = 0; + uint32_t stride = 0; + }; + + struct PreparedIndexBuffer { + MTL::Buffer* buffer = nullptr; + uint64_t offset = 0; + }; + + enum class PreparedDrawFlushReason : uint32_t { + kManual, + kRenderTargetUpdate, + kRenderTargetKeyMismatch, + kQueueBudget, + kQueueReject, + kPrepareForWait, + kSwap, + kIssueCopy, + kTransferRequest, + kRenderEncoderEnd, + kCommandBufferEnd, + kQuery, + kCount, + }; + static constexpr size_t kPreparedDrawFlushReasonCount = + static_cast(PreparedDrawFlushReason::kCount); + + enum class PreparedDrawQueueRejectReason : uint32_t { + kNone, + kResidentWithoutActiveQueue, + kNoSharedMemoryRanges, + kMemexport, + kTextureUpload, + kTextureRequestLoadData, + kPendingDrawPassTransfers, + kZPDActive, + kRenderTargetKeyMismatch, + kQueueBudget, + kCount, + }; + static constexpr size_t kPreparedDrawQueueRejectReasonCount = + static_cast(PreparedDrawQueueRejectReason::kCount); + + struct PreparedDrawRenderTargetKey { + uint32_t rb_surface_info = 0; + uint32_t rb_depth_info = 0; + std::array rb_color_info = {}; + uint32_t normalized_depth_control = 0; + uint32_t normalized_color_mask = 0; + bool is_rasterization_done = false; + + bool operator==(const PreparedDrawRenderTargetKey& other) const { + return rb_surface_info == other.rb_surface_info && + rb_depth_info == other.rb_depth_info && + rb_color_info == other.rb_color_info && + normalized_depth_control == other.normalized_depth_control && + normalized_color_mask == other.normalized_color_mask && + is_rasterization_done == other.is_rasterization_done; + } + + bool operator!=(const PreparedDrawRenderTargetKey& other) const { + return !(*this == other); + } + }; + + struct RenderResourceRef { + MTL::Resource* resource = nullptr; + MTL::ResourceUsage usage = MTL::ResourceUsageRead; + MTL::RenderStages stages = MTL::RenderStages(0); + }; + struct RenderResourceSet { + std::vector resources; + uint64_t serial = 0; + uint64_t source_serial = 0; + }; + struct EncoderResourceUsageState { + uint32_t usage_bits = 0; + uint32_t read_stage_bits = 0; + uint32_t write_stage_bits = 0; + uint32_t sample_stage_bits = 0; + }; + + struct PreparedDraw { + MTL::RenderPipelineState* pipeline = nullptr; + MetalPipelineCache::TessellationPipelineState* tessellation_pipeline_state = + nullptr; + MetalPipelineCache::GeometryPipelineState* geometry_pipeline_state = + nullptr; + + PrimitiveProcessor::ProcessingResult primitive_processing_result = {}; + UniformBufferInfo uniforms = {}; + DrawDynamicState dynamic_state = {}; + PreparedIndexBuffer prepared_guest_dma_index_buffer = {}; + IndexBufferInfo index_buffer_info = {}; + bool has_index_buffer_info = false; + + std::vector vertex_bindings; + std::array vertex_ranges = {}; + uint32_t vertex_range_count = 0; + std::vector materialization_ranges; + bool has_invalid_shared_memory = false; + + std::array shared_memory_hazard_ranges = {}; + uint32_t shared_memory_hazard_range_count = 0; + MTL::RenderStages shared_memory_consumer_stages = MTL::RenderStages(0); + + std::vector memexport_ranges; + MTL::RenderStages memexport_write_stages = MTL::RenderStages(0); + MTL::ResourceUsage shared_memory_usage = MTL::ResourceUsageRead; + PreparedDrawRenderTargetKey render_target_key = {}; + RenderResourceSet texture_resource_set = {}; + MetalTextureCache::TextureMaterializationPlan texture_materialization_plan = + {}; + + bool use_tessellation_emulation = false; + bool use_geometry_emulation = false; + bool shared_memory_is_uav = false; + bool memexport_used = false; + bool uses_vertex_fetch = false; + bool prepare_uniforms = false; + bool fallback_depth_attachment_required = false; + bool texture_upload_needed = false; + bool may_texture_request_load_data = false; + bool has_pending_draw_pass_transfers = false; + }; + + bool PrepareGuestDMAIndexBufferForMemexport( + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + PreparedIndexBuffer& prepared_index_buffer_out); + + // Host draw path — prepare per-draw dynamic state and upload constant buffers + // before entering the Metal render encoder. + bool PrepareDrawConstants( + const RegisterFile& regs, Shader* vertex_shader, Shader* pixel_shader, + MetalShader* metal_vertex_shader, MetalShader* metal_pixel_shader, + bool shared_memory_is_uav, bool is_rasterization_done, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + uint32_t used_texture_mask, uint32_t normalized_color_mask, + MTL::RenderPassDescriptor* render_pass_descriptor, + UniformBufferInfo& uniforms_out, DrawDynamicState& dynamic_state_out); + + void ApplyDrawDynamicState(const DrawDynamicState& dynamic_state); + + PreparedDrawRenderTargetKey BuildPreparedDrawRenderTargetKey( + const RegisterFile& regs, bool is_rasterization_done, + reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t normalized_color_mask) const; + bool SubmitPreparedDraw(PreparedDraw&& draw); + bool EncodePreparedDraw(const PreparedDraw& draw); + bool FlushPreparedDrawQueue(PreparedDrawFlushReason reason); + bool PreparedDrawQueueHasActiveZPD() const; + bool CanQueuePreparedDraw(const PreparedDraw& draw, + PreparedDrawQueueRejectReason& reject_reason) const; + void RecordPreparedDrawQueueReject( + PreparedDrawQueueRejectReason reject_reason); + + // Host draw path — refresh top-level argument buffers when root CBV state + // changes and bind them plus the stable descriptor heap buffers to the + // render encoder when encoder state requires it. + bool PopulateBindlessTables(bool shared_memory_is_uav, + MTL::ResourceUsage shared_memory_usage, + bool use_geometry_emulation, + bool use_tessellation_emulation, + const UniformBufferInfo& uniforms); + + // Host draw path — bind vertex buffers and dispatch the actual draw call + // (tessellation, geometry emulation, or standard path), then track + // memexport writes. + bool DispatchDraw( + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + bool use_tessellation_emulation, + MetalPipelineCache::TessellationPipelineState* + tessellation_pipeline_state, + bool use_geometry_emulation, + MetalPipelineCache::GeometryPipelineState* geometry_pipeline_state, + bool shared_memory_is_uav, MTL::ResourceUsage shared_memory_usage, + bool memexport_used, MTL::RenderStages memexport_write_stages, + bool uses_vertex_fetch, bool shared_memory_resource_registered, + const PreparedIndexBuffer* prepared_guest_dma_index_buffer, + const std::vector& vb_bindings, + const VertexBindingRange* vertex_ranges, uint32_t vertex_range_count, + const IndexBufferInfo* index_buffer_info, + const std::vector& memexport_ranges); private: - // Initialize shader translation pipeline - bool InitializeShaderTranslation(); - // Command buffer management - void BeginCommandBuffer(); + enum class RenderEncoderEndReason : uint32_t { + kUnknown, + kPrepareForWait, + kSwap, + kCommandBufferEnd, + kRequestTransferCommandBuffer, + kSharedMemoryReadDependency, + kRenderTargetUpdateDescriptorDirty, + kPipelineDescriptorIncompatible, + kTextureUploadBeforeDrawPass, + kSharedMemoryUploadBeforeDrawPass, + kResolveNeedsBoundary, + kBeginRenderEncoderDescriptorChanged, + kCount, + }; + + static constexpr size_t kRenderEncoderEndReasonCount = + static_cast(RenderEncoderEndReason::kCount); + + enum class RenderResourceSetKind : uint32_t { + kFixed, + kTexture, + kRoot, + kCount, + }; + static constexpr size_t kRenderResourceSetKindCount = + static_cast(RenderResourceSetKind::kCount); + + enum class DrawMaterializationSource : uint32_t { + kVertexFetch, + kGuestIndex, + kMemexport, + kTextureSource, + kCount, + }; + static constexpr size_t kDrawMaterializationSourceCount = + static_cast(DrawMaterializationSource::kCount); + + struct BackendTelemetryStats { + uint64_t swaps = 0; + uint64_t draw_calls = 0; + uint64_t pipeline_sets = 0; + uint64_t pipeline_set_skips = 0; + uint64_t texture_requests_before_encoder = 0; + uint64_t texture_requests_after_encoder_begin = 0; + + uint64_t begin_encoder_calls = 0; + uint64_t begin_encoder_reused_compatible = 0; + uint64_t begin_encoder_created = 0; + uint64_t begin_encoder_descriptor_restarts = 0; + uint64_t begin_encoder_resource_usage_resets = 0; + uint64_t begin_encoder_descriptor_failures = 0; + uint64_t begin_encoder_creation_failures = 0; + + uint64_t end_encoder_active = 0; + uint64_t end_encoder_no_active = 0; + std::array end_reasons = {}; + std::array + transfer_request_sources_total = {}; + std::array + transfer_request_sources_active = {}; + std::array + transfer_request_sources_no_active = {}; + std::array + transfer_request_render_encoder_ends = {}; + std::array + shared_memory_request_upload_bytes = {}; + std::array + shared_memory_request_failures = {}; + std::array + shared_memory_request_outcomes = {}; + std::array + shared_memory_upload_route_counts = {}; + std::array + shared_memory_upload_route_bytes = {}; + uint64_t shared_memory_direct_write_eligible_bytes = 0; + uint64_t shared_memory_direct_write_staged_required_bytes = 0; + std::array + shared_memory_direct_write_reject_counts = {}; + std::array + shared_memory_direct_write_reject_bytes = {}; + uint64_t shared_memory_lazy_upload_no_upload_batches = 0; + uint64_t shared_memory_lazy_upload_direct_only_batches = 0; + uint64_t shared_memory_lazy_upload_mixed_batches = 0; + uint64_t shared_memory_lazy_upload_staged_only_batches = 0; + uint64_t shared_memory_lazy_upload_direct_only_active = 0; + uint64_t shared_memory_lazy_upload_mixed_active = 0; + uint64_t shared_memory_lazy_upload_staged_only_active = 0; + std::array + texture_upload_source_route_counts = {}; + std::array + texture_upload_source_route_bytes = {}; + std::array + texture_upload_source_fallback_reasons = {}; + uint64_t shared_memory_upload_batches = 0; + uint64_t shared_memory_upload_batch_input_ranges = 0; + uint64_t shared_memory_upload_batch_coalesced_ranges = 0; + uint64_t shared_memory_upload_batch_bytes = 0; + uint64_t shared_memory_upload_encoder_acquisitions = 0; + uint64_t shared_memory_upload_encoder_reuses = 0; + uint64_t shared_memory_upload_encoder_copies = 0; + std::array + shared_memory_upload_encoder_end_reasons = {}; + std::array + draw_materialization_source_ranges = {}; + std::array + draw_materialization_source_bytes = {}; + std::array + draw_materialization_source_invalid_ranges = {}; + std::array + draw_materialization_source_invalid_bytes = {}; + uint64_t draw_materialization_per_draw_requests = 0; + uint64_t draw_materialization_per_draw_invalid_requests = 0; + uint64_t draw_materialization_per_draw_resident_skips = 0; + uint64_t prepared_draw_queue_appends = 0; + uint64_t prepared_draw_queue_flushes = 0; + uint64_t prepared_draw_queue_single_draw_flushes = 0; + uint64_t prepared_draw_queue_draws_flushed = 0; + uint64_t prepared_draw_queue_ranges_flushed = 0; + uint64_t prepared_draw_queue_bytes_flushed = 0; + uint64_t prepared_draw_queue_invalid_flushes = 0; + uint64_t prepared_draw_queue_texture_plans_flushed = 0; + uint64_t prepared_draw_queue_texture_loads_planned = 0; + uint64_t prepared_draw_queue_texture_loads_executed = 0; + std::array + prepared_draw_queue_flush_reasons = {}; + std::array + prepared_draw_queue_reject_reasons = {}; + + std::array cbv_uploads = {}; + std::array cbv_reuse_hits = {}; + std::array descriptor_index_uploads = {}; + std::array constant_payload_cache_hits = {}; + std::array constant_payload_cache_misses = {}; + std::array constant_payload_cache_bytes_saved = {}; + std::array descriptor_index_payload_cache_hits = {}; + std::array descriptor_index_payload_cache_misses = + {}; + std::array + descriptor_index_payload_cache_bytes_saved = {}; + std::array bindless_root_allocations = {}; + std::array bindless_root_reuse_hits = {}; + std::array bindless_root_arg_noop_updates = {}; + std::array bindless_root_arg_slots_patched = {}; + std::array bindless_root_arg_bytes_copied = {}; + std::array + bindless_root_arg_slot_patches = {}; + std::array + bindless_root_rebuild_reasons = {}; + std::array + bindless_root_slots_changed = {}; + std::array + bindless_root_rebuild_details = {}; + std::array + render_encoder_buffer_full_binds = {}; + std::array + render_encoder_buffer_offset_binds = {}; + std::array + render_encoder_buffer_noop_binds = {}; + std::array + render_encoder_buffer_null_binds = {}; + std::array + render_encoder_buffer_untracked_binds = {}; + uint64_t render_encoder_use_resource_calls = 0; + uint64_t render_encoder_use_resource_skips = 0; + uint64_t render_encoder_use_resource_upgrades = 0; + uint64_t render_encoder_use_resources_batches = 0; + uint64_t render_encoder_use_resources_requested = 0; + uint64_t render_encoder_use_resources_skips = 0; + std::array + render_resource_set_applies = {}; + std::array + render_resource_set_skips = {}; + std::array + render_resource_set_resources = {}; + std::array + render_resource_registry_serial_skips = {}; + std::array + render_resource_registry_builds = {}; + std::array + render_resource_registry_registers = {}; + uint64_t residency_set_allocations_added = 0; + uint64_t residency_set_allocation_duplicates = 0; + uint64_t residency_set_commits = 0; + uint64_t residency_set_resource_refs_covered = 0; + uint64_t residency_set_resource_refs_fallback = 0; + uint64_t residency_set_use_resources_covered = 0; + uint64_t residency_set_use_resources_fallback = 0; + uint64_t residency_set_use_heaps_covered = 0; + uint64_t residency_set_use_heaps_fallback = 0; + uint64_t frame_slot_waits = 0; + uint64_t frame_slot_wait_submission_count = 0; + uint64_t frame_slot_wait_submission_last = 0; + }; + + void FlushCommandBufferAndWait(uint64_t timeout_ns, const char* context); + MTL::RenderPassDescriptor* GetDrawRenderPassDescriptor( + bool fallback_depth_attachment_required = false); + bool BeginRenderEncoderForDraw( + bool fallback_depth_attachment_required = false); + void EndRenderEncoder(RenderEncoderEndReason reason); void EndCommandBuffer(); - // Reset per-render-encoder cached bindings/state for the SPIRV-Cross (MSL) - // path. Safe to call when no render encoder is active. - void ResetMslRenderEncoderStateCache(); - // Reset cross-encoder SPIRV-Cross reuse caches at command-buffer boundaries. - void ResetMslCrossEncoderReuseCaches(); bool CanEndSubmissionImmediately(); void WaitForPendingCompletionHandlers(); void ProcessCompletedSubmissions(); - bool EnsureDrawRingCapacity(); + void WaitForFrameSlotSubmission(uint64_t awaited_submission); + void OpenFrameLifetime(); + void InvalidateFrameTransientBindings(); + void CloseFrameLifetime(); + void MaybeDumpBackendTelemetry(const char* reason, bool force = false); + void ResetBackendTelemetry(); + void InitializeResidencySet(); + void ShutdownResidencySet(); + void RegisterInitialResidencySetResources(); + bool AddResidencySetResource(MTL::Resource* resource); + bool IsResidencySetResourceCovered(MTL::Resource* resource) const; + bool IsResidencySetHeapCovered(MTL::Heap* heap) const; + bool AnySharedMemoryRangeInvalid(const SharedMemory::Range* ranges, + uint32_t range_count) const; + void RecordSharedMemoryLazyUploadRoute( + const MetalSharedMemory::UploadRouteInfo& route_info, + bool render_encoder_active); + void PrepareSharedMemoryUploadBeforeDrawPass( + const SharedMemory::Range* ranges, uint32_t range_count); + bool HasActiveSharedMemoryWritePending() const; + void UseRenderEncoderAttachmentHeaps(MTL::RenderPassDescriptor* descriptor); + void AddRenderResourceRef(RenderResourceSet& set, MTL::Resource* resource, + MTL::ResourceUsage usage, MTL::RenderStages stages); + void RestoreRenderResourceSet(RenderResourceSetKind kind, + RenderResourceSet& current, + const RenderResourceSet& snapshot); + void PublishRenderResourceSet(RenderResourceSet& current, + RenderResourceSet&& next); + uint64_t GetBindlessFixedResourceSourceSerial( + MTL::ResourceUsage shared_memory_usage) const; + uint64_t GetBindlessTextureResourceSourceSerial() const; + uint64_t GetBindlessRootResourceSourceSerial( + const UniformBufferInfo& uniforms) const; + void PublishBindlessFixedResourceSet(MTL::ResourceUsage shared_memory_usage); + void RestoreBindlessTextureResourceSet(const RenderResourceSet& set); + void PublishBindlessTextureResourceSet(); + void PublishBindlessRootResourceSet(const UniformBufferInfo& uniforms); + void ApplyRenderEncoderResourceSets(); + void ApplyRenderEncoderResourceSet(RenderResourceSetKind kind, + const RenderResourceSet& set, + uint64_t& applied_serial); + EncoderResourceUsageState* FindOrInsertRenderEncoderResourceUsage( + MTL::Resource* resource, bool& inserted); + void GrowRenderEncoderResourceUsageTable(size_t min_capacity); + void UseRenderEncoderResources(const MTL::Resource* const resources[], + uint32_t count, MTL::ResourceUsage usage); + void UseRenderEncoderResources(const MTL::Resource* const resources[], + uint32_t count, MTL::ResourceUsage usage, + MTL::RenderStages stages); void UseRenderEncoderHeap(MTL::Heap* heap); - // SPIRV-Cross path: uniforms buffer is command-buffer scoped to avoid - // CPU writes racing ahead of in-flight GPU reads. - bool EnsureSpirvUniformBuffer(); - bool EnsureSpirvUniformBufferCapacity(); - void ScheduleSpirvUniformBufferRelease(MTL::CommandBuffer* command_buffer); - bool AcquireSpirvArgumentBufferSlice(uint32_t bytes, uint32_t alignment, - MTL::Buffer** buffer_out, - NS::UInteger* offset_out); - void ScheduleSpirvArgumentBufferRelease(MTL::CommandBuffer* command_buffer); + uint64_t GetBindlessDescriptorRetirementSubmission() const; + void FreeViewBindlessIndexNow(uint32_t index); + void FreeSamplerBindlessIndexNow(uint32_t index); + + struct PendingSharedMemoryWrite { + uint32_t start = 0; + uint32_t end = 0; + uint64_t submission_id = 0; + MTL::RenderStages producer_stages = MTL::RenderStages(0); + bool active_render_encoder = false; + bool fence_updated = false; + }; + + void MarkSharedMemoryWritePending(uint32_t address, uint32_t length, + MTL::RenderStages producer_stages, + bool active_render_encoder, + bool fence_updated); + bool PendingSharedMemoryWritesOverlapRange(uint32_t start, + uint32_t length) const; + bool PendingSharedMemoryWriteOverlapsRanges( + const PendingSharedMemoryWrite& pending, const SharedMemoryRange* ranges, + uint32_t range_count) const; + bool PendingSharedMemoryWritesOverlapRanges(const SharedMemoryRange* ranges, + uint32_t range_count) const; + void UpdateSharedMemoryFenceForActiveRenderEncoder(); + void PruneCompletedSharedMemoryWrites(uint64_t completed_submission); + void RetireFenceWaitedSharedMemoryWrites(const SharedMemoryRange* ranges, + uint32_t range_count); + bool EncodeSharedMemoryRenderReadDependencies( + const SharedMemoryRange* ranges, uint32_t range_count, + MTL::RenderStages consumer_stages); + bool EncodeSharedMemoryBlitReadDependency(MTL::BlitCommandEncoder* encoder, + uint32_t start, uint32_t length); // Fixed-function depth/stencil state (mirrors Vulkan/D3D12 dynamic state). - void ApplyDepthStencilState(bool primitive_polygonal, - reg::RB_DEPTHCONTROL normalized_depth_control); - void ApplyRasterizerState(bool primitive_polygonal); + void ApplyDepthStencilState(const DrawDynamicState& dynamic_state); + void ApplyRasterizerState(const DrawDynamicState& dynamic_state); - // Constants shared between MSC and SPIRV-Cross paths. - static constexpr size_t kStageCount = 2; // Vertex + pixel. + // Constants for the MSC path. static constexpr size_t kNullBufferSize = 4096; static constexpr size_t kCbvSizeBytes = 4096; - static constexpr size_t kUniformsBytesPerTable = 6 * kCbvSizeBytes; - struct SpirvArgumentBufferPage { - MTL::Buffer* buffer = nullptr; - size_t bytes = 0; - size_t offset = 0; - - ~SpirvArgumentBufferPage(); + // Constants for MSC descriptor heap sizes. + static constexpr size_t kResourceHeapSlotsPerTable = 1025 + 2; + static constexpr size_t kSamplerHeapSlotsPerTable = 257 + 2; + static constexpr size_t kTopLevelABSlotsPerTable = 32; + static_assert(kBindlessRootArgTelemetrySlotCount == kTopLevelABSlotsPerTable); + static constexpr size_t kTopLevelABBytesPerTable = + kTopLevelABSlotsPerTable * sizeof(uint64_t); + // MSC explicit root signatures encode descriptor-table pointers and root + // resource pointers as 64-bit entries in the top-level argument buffer. + // Keep these in the same order as MetalShaderConverter's root signature. + enum TopLevelABSlot : uint32_t { + kTopLevelABSlotSRVSpace0, + kTopLevelABSlotSRVSpace1, + kTopLevelABSlotSRVSpace2, + kTopLevelABSlotSRVSpace3, + kTopLevelABSlotSRVSpace10, + kTopLevelABSlotUAVSpace0, + kTopLevelABSlotUAVSpace1, + kTopLevelABSlotUAVSpace2, + kTopLevelABSlotUAVSpace3, + kTopLevelABSlotSamplerSpace0, + kTopLevelABSlotCBVSystem, + kTopLevelABSlotCBVFloat, + kTopLevelABSlotCBVBoolLoop, + kTopLevelABSlotCBVFetch, + kTopLevelABSlotCBVDescriptorIndices, + }; + // Descriptor tables and common CBVs are shared by every graphics stage, so + // one top-level argument table can be rebound across + // vertex/fragment/object/mesh stages. The first stage-local CBV slots are + // kept near the shared slots for telemetry continuity. + enum GraphicsRootABSlot : uint32_t { + kGraphicsRootABSlotSRVSpace0 = kTopLevelABSlotSRVSpace0, + kGraphicsRootABSlotSRVSpace1 = kTopLevelABSlotSRVSpace1, + kGraphicsRootABSlotSRVSpace2 = kTopLevelABSlotSRVSpace2, + kGraphicsRootABSlotSRVSpace3 = kTopLevelABSlotSRVSpace3, + kGraphicsRootABSlotSRVSpace10 = kTopLevelABSlotSRVSpace10, + kGraphicsRootABSlotUAVSpace0 = kTopLevelABSlotUAVSpace0, + kGraphicsRootABSlotUAVSpace1 = kTopLevelABSlotUAVSpace1, + kGraphicsRootABSlotUAVSpace2 = kTopLevelABSlotUAVSpace2, + kGraphicsRootABSlotUAVSpace3 = kTopLevelABSlotUAVSpace3, + kGraphicsRootABSlotSamplerSpace0 = kTopLevelABSlotSamplerSpace0, + kGraphicsRootABSlotCBVSystem = kTopLevelABSlotCBVSystem, + kGraphicsRootABSlotCBVVertexFloat = kTopLevelABSlotCBVFloat, + kGraphicsRootABSlotCBVBoolLoop = kTopLevelABSlotCBVBoolLoop, + kGraphicsRootABSlotCBVVertexFetch = kTopLevelABSlotCBVFetch, + kGraphicsRootABSlotCBVVertexDescriptorIndices = + kTopLevelABSlotCBVDescriptorIndices, + kGraphicsRootABSlotCBVHullFloat, + kGraphicsRootABSlotCBVHullFetch, + kGraphicsRootABSlotCBVHullDescriptorIndices, + kGraphicsRootABSlotCBVDomainFloat, + kGraphicsRootABSlotCBVDomainFetch, + kGraphicsRootABSlotCBVDomainDescriptorIndices, + kGraphicsRootABSlotCBVPixelFloat, + kGraphicsRootABSlotCBVPixelFetch, + kGraphicsRootABSlotCBVPixelDescriptorIndices, }; // System constants population (mirrors D3D12 implementation) @@ -203,71 +994,48 @@ class MetalCommandProcessor : public CommandProcessor { reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask); - // SPIRV-Cross (MSL) path - shader modification and pipeline helpers. - SpirvShaderTranslator::Modification GetCurrentSpirvVertexShaderModification( - const Shader& shader, - Shader::HostVertexShaderType host_vertex_shader_type, - uint32_t interpolator_mask) const; - SpirvShaderTranslator::Modification GetCurrentSpirvPixelShaderModification( - const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, - reg::RB_DEPTHCONTROL normalized_depth_control, - uint32_t normalized_color_mask) const; - void UpdateSpirvSystemConstantValues( - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - bool primitive_polygonal, uint32_t line_loop_closing_index, - xenos::Endian index_endian, const draw_util::ViewportInfo& viewport_info, - uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control, - uint32_t normalized_color_mask); - enum class MslShaderCompileStatus { - kReady, - kPending, - kFailed, - kNotQueued, - }; - enum class MslPipelineCompileStatus { - kReady, - kPending, - kFailed, - }; - struct MslPipelineCompileRequest; - void InitializeMslAsyncCompilation(); - void ShutdownMslAsyncCompilation(); - MslShaderCompileStatus GetMslShaderCompileStatus( - MslShader::MslTranslation* translation); - bool EnqueueMslShaderCompilation(MslShader::MslTranslation* translation, - bool is_ios, uint8_t priority); - bool EnqueueMslPipelineCompilation(const MslPipelineCompileRequest& request); - MTL::RenderPipelineState* CreateMslPipelineState( - const MslPipelineCompileRequest& request, std::string* error_out); - void MslShaderCompileThread(size_t thread_index); - MTL::RenderPipelineState* GetOrCreateMslPipelineState( - MslShader::MslTranslation* vertex_translation, - MslShader::MslTranslation* pixel_translation, const RegisterFile& regs, - MslPipelineCompileStatus* compile_status_out = nullptr); - // Metal device and command queue (from provider) MTL::Device* device_ = nullptr; MTL::CommandQueue* command_queue_ = nullptr; + MTL::ResidencySet* residency_set_ = nullptr; + bool residency_set_supported_ = false; + bool residency_set_enabled_ = false; + bool residency_set_attached_ = false; + std::unordered_set residency_set_resources_; + std::unordered_set residency_set_heaps_; MTL::SharedEvent* wait_shared_event_ = nullptr; uint64_t wait_shared_event_value_ = 0; - - // Render targets - MTL::Texture* render_target_texture_ = nullptr; - MTL::Texture* depth_stencil_texture_ = nullptr; - MTL::RenderPassDescriptor* render_pass_descriptor_ = nullptr; - uint32_t render_target_width_ = 1280; - uint32_t render_target_height_ = 720; - + MTL::Fence* shared_memory_fence_ = nullptr; + SharedMemoryRequestReason current_shared_memory_upload_reason_ = + SharedMemoryRequestReason::kUnknown; // Current command buffer and encoder MTL::CommandBuffer* current_command_buffer_ = nullptr; MTL::RenderCommandEncoder* current_render_encoder_ = nullptr; + MTL::BlitCommandEncoder* shared_memory_upload_blit_encoder_ = nullptr; MTL::RenderPassDescriptor* current_render_pass_descriptor_ = nullptr; + bool current_render_encoder_has_zpd_visibility_ = false; NS::AutoreleasePool* command_buffer_autorelease_pool_ = nullptr; - // Tracks resources marked via useResource for the current render encoder - // to avoid redundant driver calls across draws within the same encoder. - std::unordered_map render_encoder_resource_usage_; - std::unordered_set render_encoder_heap_usage_; + // Tracks resources marked via useResource for the current render encoder to + // avoid redundant driver calls across draws within the same encoder. + struct EncoderResourceUsageTableEntry { + MTL::Resource* resource = nullptr; + EncoderResourceUsageState state = {}; + }; + std::vector + render_encoder_resource_usage_table_; + size_t render_encoder_resource_usage_count_ = 0; + std::vector render_encoder_heap_usage_; + BackendTelemetryStats backend_telemetry_; + uint64_t backend_telemetry_last_dump_swap_ = 0; + + static constexpr size_t kPreparedDrawQueueMaxDraws = 64; + static constexpr size_t kPreparedDrawQueueMaxRanges = 4096; + static constexpr uint64_t kPreparedDrawQueueMaxBytes = 64ull * 1024 * 1024; + std::vector prepared_draw_queue_; + PreparedDrawRenderTargetKey prepared_draw_queue_render_target_key_ = {}; + bool prepared_draw_queue_render_target_key_valid_ = false; + bool flushing_prepared_draw_queue_ = false; // Shared memory for Xbox 360 memory access std::unique_ptr shared_memory_; @@ -280,103 +1048,8 @@ class MetalCommandProcessor : public CommandProcessor { uint32_t last_swap_height_ = 0; std::unordered_map swap_dest_swaps_by_base_; - public: - MetalSharedMemory* shared_memory() const { return shared_memory_.get(); } - MetalRenderTargetCache* render_target_cache() const { - return render_target_cache_.get(); - } - MetalTextureCache* texture_cache() const { return texture_cache_.get(); } - - private: - // Shader ucode disassembly buffer (used by AnalyzeUcode). - StringBuffer ucode_disasm_buffer_; - - // SPIRV-Cross (MSL) path - shader translator and cache. - std::unique_ptr spirv_shader_translator_; - std::unordered_map> msl_shader_cache_; - SpirvShaderTranslator::SystemConstants spirv_system_constants_ = {}; - SpirvShaderTranslator::ClipPlaneConstants spirv_clip_plane_constants_ = {}; - SpirvShaderTranslator::TessellationConstants spirv_tessellation_constants_ = - {}; - struct MslShaderCompileRequest { - MslShader::MslTranslation* translation = nullptr; - uint64_t shader_hash = 0; - uint64_t modification = 0; - bool is_ios = false; - uint8_t priority = 0; - }; - struct MslPipelineCompileRequest { - uint64_t pipeline_key = 0; - uint64_t vertex_shader_hash = 0; - uint64_t vertex_modification = 0; - uint64_t pixel_shader_hash = 0; - uint64_t pixel_modification = 0; - MTL::Function* vertex_function = nullptr; - MTL::Function* fragment_function = nullptr; - uint32_t sample_count = 1; - MTL::PixelFormat color_formats[4] = { - MTL::PixelFormatInvalid, MTL::PixelFormatInvalid, - MTL::PixelFormatInvalid, MTL::PixelFormatInvalid}; - MTL::PixelFormat depth_format = MTL::PixelFormatInvalid; - MTL::PixelFormat stencil_format = MTL::PixelFormatInvalid; - uint32_t normalized_color_mask = 0; - uint32_t alpha_to_mask_enable = 0; - uint32_t blendcontrol[4] = {}; - uint8_t priority = 0; - }; - struct MslShaderCompileRequestCompare { - bool operator()(const MslShaderCompileRequest& a, - const MslShaderCompileRequest& b) const { - return a.priority < b.priority; - } - }; - struct MslPipelineCompileRequestCompare { - bool operator()(const MslPipelineCompileRequest& a, - const MslPipelineCompileRequest& b) const { - return a.priority < b.priority; - } - }; - std::priority_queue, - MslShaderCompileRequestCompare> - msl_shader_compile_queue_; - std::priority_queue, - MslPipelineCompileRequestCompare> - msl_pipeline_compile_queue_; - std::unordered_set msl_shader_compile_pending_; - std::unordered_set msl_shader_compile_failed_; - std::unordered_set msl_pipeline_compile_pending_; - std::unordered_set msl_pipeline_compile_failed_; - std::mutex msl_shader_compile_mutex_; - std::condition_variable msl_shader_compile_cv_; - std::vector msl_shader_compile_threads_; - size_t msl_shader_compile_busy_ = 0; - bool msl_shader_compile_shutdown_ = false; - std::atomic msl_shader_compile_failure_last_log_ns_{0}; - std::atomic msl_pipeline_compile_failure_last_log_ns_{0}; - std::atomic msl_pipeline_pending_last_log_ns_{0}; - std::unordered_map msl_pipeline_cache_; - - // SPIRV-Cross tessellation support. - MTL::ComputePipelineState* tess_factor_pipeline_tri_ = nullptr; - MTL::ComputePipelineState* tess_factor_pipeline_quad_ = nullptr; - // Adaptive tessellation factor pipelines (read per-edge factors from shared - // memory instead of using a uniform value). - MTL::ComputePipelineState* tess_factor_pipeline_adaptive_tri_ = nullptr; - MTL::ComputePipelineState* tess_factor_pipeline_adaptive_quad_ = nullptr; - MTL::Buffer* tess_factor_buffer_ = nullptr; - uint32_t tess_factor_buffer_patch_capacity_ = 0; - std::unordered_map - msl_tess_pipeline_cache_; - bool InitializeMslTessellation(); - void ShutdownMslTessellation(); - MTL::RenderPipelineState* GetOrCreateMslTessPipelineState( - MslShader::MslTranslation* domain_translation, - MslShader::MslTranslation* pixel_translation, - Shader::HostVertexShaderType host_vertex_shader_type, - const RegisterFile& regs); - bool EnsureTessFactorBuffer(uint32_t patch_count); + // Pipeline cache (owns shaders, pipelines, shader translation components). + std::unique_ptr pipeline_cache_; struct DepthStencilStateKey { uint32_t depth_control; @@ -412,165 +1085,303 @@ class MetalCommandProcessor : public CommandProcessor { // Render target cache for framebuffer management std::unique_ptr render_target_cache_; - // Null resources for unbound slots (shared between MSC and SPIRV-Cross) + // Null resources for unbound slots MTL::Buffer* null_buffer_ = nullptr; MTL::Texture* null_texture_ = nullptr; MTL::SamplerState* null_sampler_ = nullptr; - // Uniforms buffer and draw ring count (shared between MSC and SPIRV-Cross) - MTL::Buffer* uniforms_buffer_ = nullptr; - // SPIRV path may rotate through multiple uniforms buffers within a single - // Metal command buffer (at ring wrap boundaries). Track all of them so - // completion handlers can return every buffer to the available pool. - std::vector command_buffer_spirv_uniform_buffers_; - size_t draw_ring_count_ = 0; - // Owning storage for all SPIRV-Cross uniforms buffers allocated for the - // current context. - std::vector spirv_uniforms_pool_; - // Reusable SPIRV-Cross uniforms buffers returned from completed command - // buffers to reduce iOS allocation churn. - std::vector spirv_uniforms_available_; - std::mutex spirv_uniforms_mutex_; - dispatch_semaphore_t spirv_uniforms_available_semaphore_ = nullptr; - bool spirv_uniforms_pool_initialized_ = false; - std::vector> spirv_argbuf_pool_; - std::vector> - command_buffer_spirv_argbuf_pages_; - std::unordered_map>> - pending_spirv_argbuf_releases_; - std::mutex spirv_argbuf_mutex_; + // Persistent bindless descriptor heaps. + // Canonical texture views and samplers get stable slot indices allocated on + // demand and freed on destruction. Non-canonical texture views may use + // submission-lifetime slots from the same heap. The heaps are bound once per + // encoder. + // 1M entries (24 MiB). Metal has no API-side descriptor heap cap — the heap + // is just an MTLBuffer. D3D12 uses 262144 but doesn't need per-swizzle + // texture views; 4x headroom covers the swizzled-view multiplier and avoids + // exhausting the heap before the first submission completes. + static constexpr uint32_t kViewBindlessHeapSize = 1048576; + static constexpr uint32_t kSamplerBindlessHeapSize = 2048; + MTL::Buffer* view_bindless_heap_ = nullptr; + MTL::Buffer* sampler_bindless_heap_ = nullptr; + // Explicit-layout top-level bindings for shared memory / EDRAM use small + // dedicated system tables rather than overlapping the bindless texture heap. + MTL::Buffer* system_view_tables_ = nullptr; + static constexpr uint32_t kSystemViewTableSRVSharedMemory = 0; + static constexpr uint32_t kSystemViewTableSRVNull = 1; + static constexpr uint32_t kSystemViewTableUAVNullStart = 2; + static constexpr uint32_t kSystemViewTableUAVSharedMemoryStart = 4; + static constexpr uint32_t kSystemViewTableEntryCount = 6; - MTL::Library* depth_only_pixel_library_ = nullptr; - std::string depth_only_pixel_function_name_; + // Simple bump allocator with free list for persistent heap slots. + uint32_t view_bindless_heap_next_ = 0; + std::vector view_bindless_heap_free_; + bool view_bindless_heap_exhausted_logged_ = false; + uint32_t sampler_bindless_heap_next_ = 0; + std::vector sampler_bindless_heap_free_; + bool sampler_bindless_heap_exhausted_logged_ = false; + struct RetiredBindlessDescriptor { + uint32_t index; + uint64_t submission_id; + }; + std::deque retired_view_bindless_indices_; + std::deque retired_sampler_bindless_indices_; - bool logged_missing_texture_warning_ = false; - // SPIRV-Cross system/clip/tess constants versioning. - // Each ring-table slot tracks the last source version copied into it so - // draws can skip per-draw memcmp/copy churn for unchanged constants. - uint64_t msl_system_constants_version_ = 1; - uint64_t msl_clip_plane_constants_version_ = 1; - uint64_t msl_tessellation_constants_version_ = 1; - MTL::Buffer* msl_constants_versioned_uniform_buffer_ = nullptr; - std::vector msl_system_constants_written_vertex_versions_; - std::vector msl_system_constants_written_pixel_versions_; - std::vector msl_clip_plane_constants_written_vertex_versions_; - std::vector msl_tessellation_constants_written_vertex_versions_; - std::vector msl_tessellation_constants_written_pixel_versions_; - // SPIRV-Cross path: highest texture/sampler slot counts bound on the current - // render encoder. Used to clear trailing slots when a later draw uses fewer - // resources, preventing stale state leakage between draws. - uint32_t msl_bound_vertex_texture_count_ = 0; - uint32_t msl_bound_pixel_texture_count_ = 0; - uint32_t msl_bound_vertex_sampler_count_ = 0; - uint32_t msl_bound_pixel_sampler_count_ = 0; - uint64_t msl_bound_vertex_texture_binding_uid_ = 0; - uint64_t msl_bound_pixel_texture_binding_uid_ = 0; - uint64_t msl_bound_vertex_sampler_binding_uid_ = 0; - uint64_t msl_bound_pixel_sampler_binding_uid_ = 0; - std::array - msl_bound_vertex_textures_{}; - std::array - msl_bound_pixel_textures_{}; - std::array - msl_bound_vertex_samplers_{}; - std::array - msl_bound_pixel_samplers_{}; - MTL::Buffer* msl_bound_vertex_argument_buffer_ = nullptr; - MTL::Buffer* msl_bound_pixel_argument_buffer_ = nullptr; - NS::UInteger msl_bound_vertex_argument_buffer_offset_ = 0; - NS::UInteger msl_bound_pixel_argument_buffer_offset_ = 0; - bool msl_bound_vertex_argument_buffer_offset_valid_ = false; - bool msl_bound_pixel_argument_buffer_offset_valid_ = false; - MTL::Buffer* msl_bound_shared_memory_buffer_ = nullptr; - MTL::Buffer* msl_bound_null_buffer_ = nullptr; - // Cached argument buffer content for change detection — skip re-encoding - // when textures/samplers haven't changed between draws. - std::array - msl_last_argbuf_vertex_textures_{}; - uint32_t msl_last_argbuf_vertex_texture_count_ = 0; - std::array - msl_last_argbuf_vertex_samplers_{}; - uint32_t msl_last_argbuf_vertex_sampler_count_ = 0; - MTL::Buffer* msl_last_argbuf_vertex_buffer_ = nullptr; - NS::UInteger msl_last_argbuf_vertex_offset_ = 0; - const MslShader::MslTranslation* msl_last_argbuf_vertex_translation_ = - nullptr; - uint32_t msl_last_argbuf_vertex_encoded_length_ = 0; - uint64_t msl_last_argbuf_vertex_layout_uid_ = 0; - std::array - msl_last_argbuf_pixel_textures_{}; - uint32_t msl_last_argbuf_pixel_texture_count_ = 0; - std::array - msl_last_argbuf_pixel_samplers_{}; - uint32_t msl_last_argbuf_pixel_sampler_count_ = 0; - MTL::Buffer* msl_last_argbuf_pixel_buffer_ = nullptr; - NS::UInteger msl_last_argbuf_pixel_offset_ = 0; - const MslShader::MslTranslation* msl_last_argbuf_pixel_translation_ = nullptr; - uint32_t msl_last_argbuf_pixel_encoded_length_ = 0; - uint64_t msl_last_argbuf_pixel_layout_uid_ = 0; - // D3D12-style SPIRV constant cache state. - std::array msl_current_float_constant_map_vertex_{}; - std::array msl_current_float_constant_map_pixel_{}; - bool msl_float_constants_dirty_vertex_ = true; - bool msl_float_constants_dirty_pixel_ = true; - bool msl_bool_loop_constants_dirty_ = true; - bool msl_fetch_constants_dirty_ = true; - std::array msl_cached_float_constants_vertex_{}; - std::array msl_cached_float_constants_pixel_{}; - std::array msl_cached_bool_loop_constants_{}; - std::array msl_cached_fetch_constants_{}; - // Uniforms slot binding dedupe. - MTL::Buffer* msl_bound_uniforms_buffer_ = nullptr; - NS::UInteger msl_bound_uniforms_vs_base_offset_ = 0; - NS::UInteger msl_bound_uniforms_ps_base_offset_ = 0; - bool msl_bound_uniforms_offsets_valid_ = false; - MTL::RenderPipelineState* msl_bound_pipeline_state_ = nullptr; - bool msl_viewport_valid_ = false; - MTL::Viewport msl_viewport_ = {}; - bool msl_scissor_valid_ = false; - MTL::ScissorRect msl_scissor_ = {}; - bool msl_rasterizer_state_valid_ = false; - MTL::CullMode msl_cull_mode_ = MTL::CullModeNone; - MTL::Winding msl_winding_ = MTL::WindingClockwise; - MTL::TriangleFillMode msl_fill_mode_ = MTL::TriangleFillModeFill; - float msl_depth_bias_constant_ = 0.0f; - float msl_depth_bias_slope_ = 0.0f; - float msl_depth_bias_clamp_ = 0.0f; - MTL::DepthClipMode msl_depth_clip_mode_ = MTL::DepthClipModeClip; - MTL::DepthStencilState* msl_depth_stencil_state_ = nullptr; - bool msl_stencil_reference_valid_ = false; - uint32_t msl_stencil_reference_ = 0; + struct RetiredMetalBuffer { + MTL::Buffer* buffer = nullptr; + uint64_t submission_id = 0; + }; + std::deque retired_memexport_index_buffers_; + + MTL::Buffer* tessellator_tables_buffer_ = nullptr; + + // System constants - matches DxbcShaderTranslator::SystemConstants layout + DxbcShaderTranslator::SystemConstants system_constants_; // Fixed-function dynamic state cached per render encoder. + MTL::RenderPipelineState* current_render_pipeline_state_ = nullptr; float ff_blend_factor_[4] = {0.0f, 0.0f, 0.0f, 0.0f}; bool ff_blend_factor_valid_ = false; + bool rasterizer_state_valid_ = false; + MTL::CullMode current_cull_mode_ = MTL::CullModeNone; + MTL::Winding current_front_facing_winding_ = MTL::WindingCounterClockwise; + MTL::TriangleFillMode current_triangle_fill_mode_ = MTL::TriangleFillModeFill; + float current_depth_bias_values_[3] = {0.0f, 0.0f, 0.0f}; + MTL::DepthClipMode current_depth_clip_mode_ = MTL::DepthClipModeClip; + MTL::DepthStencilState* current_depth_stencil_state_ = nullptr; + bool stencil_reference_valid_ = false; + uint32_t current_stencil_reference_ = 0; + bool viewport_dirty_ = true; + MTL::Viewport cached_viewport_ = {}; + bool scissor_dirty_ = true; + MTL::ScissorRect cached_scissor_ = {}; + + // Constant buffer dirty tracking (D3D12 pattern). + // Each binding records the pool-allocated buffer, offset, and GPU address + // for the most recent constant upload. When up_to_date is true, the draw + // reuses that upload through the top-level argument buffer instead of + // gathering and uploading the same CBV again. + struct ConstantBufferBinding { + MTL::Buffer* buffer = nullptr; + NS::UInteger offset = 0; + uint64_t gpu_address = 0; + size_t size = 0; + // Guest frame that originally allocated the pool slice this binding + // references. The upload pool is frame-lifetime here, matching D3D12 and + // Vulkan so command-buffer rollover inside one frame doesn't force a + // re-upload of unchanged constants. + uint64_t upload_frame = 0; + bool up_to_date = false; + }; + static constexpr size_t kConstantPayloadSharedStage = kStageCount; + static constexpr size_t kConstantPayloadCacheMaxBytes = 64 * 1024 * 1024; + struct ConstantPayloadCacheEntry { + size_t cbv_slot = 0; + size_t stage_index = kConstantPayloadSharedStage; + size_t size = 0; + ConstantBufferBinding binding; + std::vector bytes; + }; + using StageRootArgumentEntries = + std::array; + struct StageRootArgumentAllocation { + MTL::Buffer* buffer = nullptr; + NS::UInteger offset = 0; + uint64_t gpu_address = 0; + // Same frame-lifetime rationale as ConstantBufferBinding. + uint64_t upload_frame = 0; + bool valid = false; + }; + // Mirror D3DMetal's model: track the current graphics root argument entries, + // patch the logical slots that changed, and write one fresh frame-lifetime + // table snapshot for Metal to bind. + StageRootArgumentEntries BuildGraphicsRootArgumentEntries( + const UniformBufferInfo& uniforms, bool shared_memory_is_uav, + bool use_tessellation_emulation) const; + bool AllocateStageRootArgument(size_t stage_index, + const StageRootArgumentEntries& entries, + StageRootArgumentAllocation& allocation_out); + bool TryReuseConstantPayloadFromCache(ConstantBufferBinding& binding, + size_t cbv_slot, size_t stage_index, + const uint8_t* payload, size_t size); + void StoreConstantPayloadInCache(const ConstantBufferBinding& binding, + size_t cbv_slot, size_t stage_index, + const uint8_t* payload, size_t size); + ConstantBufferBinding cbuffer_binding_system_; + ConstantBufferBinding cbuffer_binding_float_vertex_; + ConstantBufferBinding cbuffer_binding_float_pixel_; + ConstantBufferBinding cbuffer_binding_bool_loop_; + // Fetch constants are one physical register file, but each shader stage gets + // its own snapshot so one stage's fetch-register churn doesn't force the + // other stage's root CBV pointer to move. + std::array cbuffer_binding_fetch_ = {}; + ConstantBufferBinding cbuffer_binding_descriptor_indices_vertex_; + ConstantBufferBinding cbuffer_binding_descriptor_indices_pixel_; + + // Per-CBV payload caches. We always honour the "register write dirties + // live constant range" semantic from metal_command_processor.cc (matches + // D3D12/Vulkan). But after packing the new payload into scratch, if the + // bytes equal the previous upload we keep the previous pool slice instead + // of allocating a fresh one. Mirrors the descriptor-indices + // current_/scratch_ pattern in PrepareDrawConstants. + std::vector current_payload_system_; + std::vector current_payload_float_vertex_; + std::vector current_payload_float_pixel_; + std::vector current_payload_bool_loop_; + std::array, kStageCount> current_payload_fetch_; + std::vector scratch_payload_system_; + std::vector scratch_payload_float_vertex_; + std::vector scratch_payload_float_pixel_; + std::vector scratch_payload_bool_loop_; + std::array, kStageCount> scratch_payload_fetch_; + std::unordered_map> + constant_payload_cache_; + size_t constant_payload_cache_bytes_ = 0; + std::array + fetch_constant_dirty_masks_ = {}; + + // Float constant usage bitmaps for the current shader pair. + // Used to gate WriteRegister invalidation: only dirty the float CBV + // when the written register is in the current shader's bitmap. + // Matches D3D12's current_float_constant_map_vertex_/pixel_ at + // d3d12_command_processor.h:829-830. + uint64_t current_float_constant_map_vertex_[4] = {}; + uint64_t current_float_constant_map_pixel_[4] = {}; + size_t current_texture_layout_uid_vertex_ = 0; + size_t current_texture_layout_uid_pixel_ = 0; + size_t current_sampler_layout_uid_vertex_ = 0; + size_t current_sampler_layout_uid_pixel_ = 0; + std::vector + current_texture_srv_keys_vertex_; + std::vector current_texture_srv_keys_pixel_; + std::vector current_samplers_vertex_; + std::vector current_samplers_pixel_; + std::vector current_texture_bindless_resources_vertex_; + std::vector current_texture_bindless_resources_pixel_; + std::vector current_descriptor_indices_vertex_; + std::vector current_descriptor_indices_pixel_; + std::vector scratch_texture_bindless_indices_vertex_; + std::vector scratch_texture_bindless_resources_vertex_; + std::vector scratch_sampler_bindless_indices_vertex_; + std::vector scratch_texture_bindless_indices_pixel_; + std::vector scratch_texture_bindless_resources_pixel_; + std::vector scratch_sampler_bindless_indices_pixel_; + std::vector scratch_descriptor_indices_vertex_; + std::vector scratch_descriptor_indices_pixel_; + std::array current_bindless_stage_root_valid_ = {}; + std::array + current_bindless_stage_root_frame_open_rebuild_pending_ = {}; + std::array current_bindless_stage_root_serials_ = {}; + RenderResourceSet current_bindless_fixed_resource_set_; + RenderResourceSet current_bindless_texture_resource_set_; + RenderResourceSet current_bindless_root_resource_set_; + uint64_t current_bindless_fixed_resource_source_serial_ = 0; + uint64_t current_bindless_texture_resource_source_serial_ = 0; + uint64_t current_bindless_root_resource_source_serial_ = 0; + uint64_t render_encoder_bindless_fixed_resources_serial_ = 0; + uint64_t render_encoder_bindless_texture_resources_serial_ = 0; + uint64_t render_encoder_bindless_root_resources_serial_ = 0; + std::array + render_encoder_bindless_stage_root_bind_serials_ = {}; + bool render_encoder_bindless_table_bind_mesh_path_ = false; + bool render_encoder_bindless_table_bind_tessellation_ = false; + std::array + current_bindless_stage_root_arguments_ = {}; + std::array + current_bindless_stage_root_entries_ = {}; + std::array current_bindless_stage_root_entries_valid_ = {}; + std::array, kStageCount> + current_bindless_cbv_gpu_addresses_ = {}; + std::array, kStageCount> + current_bindless_cbv_buffers_ = {}; + std::array, kStageCount> + current_bindless_cbv_offsets_ = {}; + std::array current_bindless_active_cbv_masks_ = {}; + std::array + current_fetch_constant_dword_masks_ = {}; + bool current_bindless_shared_memory_is_uav_ = false; + + // Pool for frame-lifetime constant and top-level argument allocations + // (replaces the ring's uniforms_buffer_ for constant data). + std::unique_ptr constant_buffer_pool_; + enum class RenderEncoderBufferStage : uint32_t { + kVertex, + kFragment, + kObject, + kMesh, + kCount, + }; + struct RenderEncoderBufferBinding { + MTL::Buffer* buffer = nullptr; + NS::UInteger offset = 0; + bool valid = false; + }; + static constexpr size_t kTrackedRenderEncoderBufferBindingCount = 32; + std::array, + size_t(RenderEncoderBufferStage::kCount)> + render_encoder_buffer_bindings_ = {}; + void ResetRenderEncoderBufferBindings(); + void InvalidateRenderEncoderBufferBinding(RenderEncoderBufferStage stage, + NS::UInteger index); + void SetRenderEncoderBuffer(RenderEncoderBufferStage stage, + MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index); + void SetRenderEncoderVertexBuffer(MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index); + void SetRenderEncoderFragmentBuffer(MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index); + void SetRenderEncoderObjectBuffer(MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index); + void SetRenderEncoderMeshBuffer(MTL::Buffer* buffer, NS::UInteger offset, + NS::UInteger index); + // Track which heap buffer binds have been set on the current encoder. + bool heap_binds_set_on_encoder_ = false; std::atomic completed_command_buffers_{0}; std::atomic pending_completion_handlers_{0}; + std::mutex completion_mutex_; + std::condition_variable completion_cond_; uint64_t submission_current_ = 0; uint64_t submission_completed_processed_ = 0; + static constexpr uint32_t kMaxFramesInFlight = 3; + // Guest frame index. Transient constant and MSC root argument allocations are + // tagged with this, not with Metal command-buffer submissions, so they can be + // reused across mid-frame command-buffer rollover. + uint64_t frame_current_ = 1; + uint64_t frame_completed_ = 0; + uint64_t closed_frame_submissions_[kMaxFramesInFlight] = {}; - // 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 - uint32_t current_draw_index_ = 0; - bool copy_resolve_writes_pending_ = false; + bool submission_has_draws_ = false; + + // ZPD visibility query state. Metal has no query pool object; each physical + // query segment gets one fresh 8-byte offset in the visibility buffer. + struct MetalZPDResolve { + uint64_t submission = 0; + uint32_t index = UINT32_MAX; + uint32_t generation = 0; + ReportHandle report_handle = kInvalidReportHandle; + }; + struct MetalZPDActiveQuery { + uint32_t index = UINT32_MAX; + uint32_t generation = 0; + size_t offset = 0; + + bool is_open() const { return index != UINT32_MAX; } + void Reset() { *this = {}; } + }; + std::unique_ptr zpd_visibility_pool_; + MetalZPDActiveQuery zpd_active_query_; + std::deque zpd_resolves_in_flight_; + + std::vector pending_shared_memory_writes_; + MTL::RenderStages active_render_encoder_shared_memory_write_stages_ = + MTL::RenderStages(0); // Memexport tracking for shared memory invalidation. std::vector memexport_ranges_; bool gamma_ramp_256_entry_table_up_to_date_ = false; bool gamma_ramp_pwl_up_to_date_ = false; - - // Track memory regions written by IssueCopy (resolve) during trace playback. - // This prevents the trace player from overwriting resolved data with stale - // data from the trace file. - struct ResolvedRange { - uint32_t base; - uint32_t length; - }; - std::vector resolved_memory_ranges_; }; } // namespace metal diff --git a/src/xenia/gpu/metal/metal_direct_host_resolve.cc b/src/xenia/gpu/metal/metal_direct_host_resolve.cc new file mode 100644 index 000000000..d119702c7 --- /dev/null +++ b/src/xenia/gpu/metal/metal_direct_host_resolve.cc @@ -0,0 +1,1000 @@ +/** + ****************************************************************************** + * 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_render_target_cache.h" + +#include + +#include +#include +#include +#include + +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_32bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_64bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_128bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_16bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_32bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_64bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_8bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_128bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_16bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_32bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_64bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_full_uint_8bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_32bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_color_uint_64bpp_4xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_1xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_1xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_2xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_2xmsaa_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_4xmsaa_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/resolve_host_depth_32bpp_4xmsaa_scaled_cs.h" + +#include "third_party/fmt/include/fmt/format.h" +#include "xenia/base/assert.h" +#include "xenia/base/logging.h" +#include "xenia/gpu/draw_util.h" +#include "xenia/gpu/gpu_flags.h" +#include "xenia/gpu/metal/metal_command_processor.h" +#include "xenia/gpu/metal/metal_texture_cache.h" +#include "xenia/gpu/xenos.h" + +namespace xe { +namespace gpu { +namespace metal { + +namespace { + +class ScopedAutoreleasePool { + public: + ScopedAutoreleasePool() : pool_(NS::AutoreleasePool::alloc()->init()) {} + ~ScopedAutoreleasePool() { + if (pool_) { + pool_->release(); + } + } + + ScopedAutoreleasePool(const ScopedAutoreleasePool&) = delete; + ScopedAutoreleasePool& operator=(const ScopedAutoreleasePool&) = delete; + + private: + NS::AutoreleasePool* pool_; +}; + +MTL::ComputePipelineState* CreateComputePipelineFromEmbeddedLibrary( + MTL::Device* device, const void* metallib_data, size_t metallib_size, + const char* debug_name) { + if (!device || !metallib_data || !metallib_size) { + return nullptr; + } + + NS::Error* error = nullptr; + + dispatch_data_t data = dispatch_data_create( + metallib_data, metallib_size, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + MTL::Library* lib = device->newLibrary(data, &error); + dispatch_release(data); + if (!lib) { + XELOGE("Metal: failed to create {} library: {}", debug_name, + error ? error->localizedDescription()->utf8String() : "unknown"); + return nullptr; + } + + NS::String* fn_name = NS::String::string("entry_xe", NS::UTF8StringEncoding); + MTL::Function* fn = lib->newFunction(fn_name); + if (!fn) { + XELOGE("Metal: {} missing entry_xe", debug_name); + lib->release(); + return nullptr; + } + + MTL::ComputePipelineDescriptor* pipeline_desc = + MTL::ComputePipelineDescriptor::alloc()->init(); + pipeline_desc->setComputeFunction(fn); + if (debug_name) { + pipeline_desc->setLabel( + NS::String::string(debug_name, NS::UTF8StringEncoding)); + } + MTL::ComputePipelineState* pipeline = device->newComputePipelineState( + pipeline_desc, MTL::PipelineOptionNone, + static_cast(nullptr), + &error); + pipeline_desc->release(); + fn->release(); + lib->release(); + + if (!pipeline) { + XELOGE("Metal: failed to create {} pipeline: {}", debug_name, + error ? error->localizedDescription()->utf8String() : "unknown"); + return nullptr; + } + + return pipeline; +} + +bool IsResolveDirectHostRTFastCandidate( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: + return true; + default: + return false; + } +} + +size_t DirectHostResolveFullDestIndex( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return 0; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + return 1; + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 3; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 4; + default: + return 5; + } +} + +bool IsResolveDirectHostRTFullColorCandidate( + draw_util::ResolveCopyShaderIndex shader) { + return DirectHostResolveFullDestIndex(shader) < 5; +} + +bool IsResolveDirectHostRTFullColorSourcePackable( + xenos::ColorRenderTargetFormat format) { + switch (format) { + case xenos::ColorRenderTargetFormat::k_8_8_8_8: + case xenos::ColorRenderTargetFormat::k_2_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_32_FLOAT: + case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: + return true; + default: + return false; + } +} + +uint32_t DirectHostResolvePixelsPerThread( + draw_util::ResolveCopyShaderIndex shader, bool source_is_64bpp, + xenos::MsaaSamples msaa_samples) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return msaa_samples >= xenos::MsaaSamples::k4X ? 4 : 8; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 4; + default: + return source_is_64bpp ? 4 : 8; + } +} + +constexpr char kDirectHostResolveEncoderLabel[] = + "XeniaDirectHostResolveEncoder"; +constexpr uint32_t kDirectHostResolveDepthFlagHasStencil = 1u << 0; +constexpr uint32_t kDirectHostResolveDepthFlagRoundDepth = 1u << 1; + +size_t DirectHostResolveBppIndex(bool is_64bpp) { return is_64bpp ? 1u : 0u; } + +size_t DirectHostResolveMsaaIndex(xenos::MsaaSamples msaa_samples) { + switch (msaa_samples) { + case xenos::MsaaSamples::k1X: + return 0; + case xenos::MsaaSamples::k2X: + return 1; + case xenos::MsaaSamples::k4X: + return 2; + default: + return 3; + } +} + +void SetEncoderLabel(MTL::CommandEncoder* encoder, const char* label) { + if (!encoder || !label) { + return; + } + encoder->setLabel(NS::String::string(label, NS::UTF8StringEncoding)); +} + +void EndSharedMemoryUploadBlitEncoderForCommandBuffer( + MetalCommandProcessor& command_processor, + MTL::CommandBuffer* command_buffer) { + if (command_buffer && + command_buffer == command_processor.GetCurrentCommandBuffer()) { + command_processor.EndSharedMemoryUploadBlitEncoder(); + } +} + +void PushEncoderDebugGroup(MTL::CommandEncoder* encoder, + const std::string& label) { + if (!encoder || label.empty()) { + return; + } + encoder->pushDebugGroup( + NS::String::string(label.c_str(), NS::UTF8StringEncoding)); +} + +} // namespace + +void MetalRenderTargetCache::InitializeDirectHostResolvePipelines( + bool draw_resolution_scaled) { + struct DirectHostResolvePipelineConfig { + const void* metallib_data; + size_t metallib_size; + bool is_64bpp; + xenos::MsaaSamples msaa_samples; + bool scaled; + bool source_is_uint; + const char* debug_name; + }; +#define XE_DIRECT_HOST_RESOLVE_CONFIG(id, is_64bpp, msaa, scaled, source_uint) \ + {id##_metallib, sizeof(id##_metallib), is_64bpp, msaa, \ + scaled, source_uint, #id} +#define XE_DIRECT_HOST_RESOLVE_MSAA(prefix, bpp, is_64bpp, msaa_token, msaa, \ + source_uint) \ + XE_DIRECT_HOST_RESOLVE_CONFIG(prefix##_##bpp##bpp_##msaa_token##xmsaa_cs, \ + is_64bpp, msaa, false, source_uint), \ + XE_DIRECT_HOST_RESOLVE_CONFIG( \ + prefix##_##bpp##bpp_##msaa_token##xmsaa_scaled_cs, is_64bpp, msaa, \ + true, source_uint) +#define XE_DIRECT_HOST_RESOLVE_BPP(prefix, bpp, is_64bpp, source_uint) \ + XE_DIRECT_HOST_RESOLVE_MSAA(prefix, bpp, is_64bpp, 1, \ + xenos::MsaaSamples::k1X, source_uint), \ + XE_DIRECT_HOST_RESOLVE_MSAA(prefix, bpp, is_64bpp, 2, \ + xenos::MsaaSamples::k2X, source_uint), \ + XE_DIRECT_HOST_RESOLVE_MSAA(prefix, bpp, is_64bpp, 4, \ + xenos::MsaaSamples::k4X, source_uint) +#define XE_DIRECT_HOST_RESOLVE_SOURCE(prefix, source_uint) \ + XE_DIRECT_HOST_RESOLVE_BPP(prefix, 32, false, source_uint), \ + XE_DIRECT_HOST_RESOLVE_BPP(prefix, 64, true, source_uint) + static constexpr DirectHostResolvePipelineConfig + kDirectHostResolvePipelineConfigs[] = { + XE_DIRECT_HOST_RESOLVE_SOURCE(resolve_host_color, false), + XE_DIRECT_HOST_RESOLVE_SOURCE(resolve_host_color_uint, true), + }; +#undef XE_DIRECT_HOST_RESOLVE_SOURCE +#undef XE_DIRECT_HOST_RESOLVE_BPP +#undef XE_DIRECT_HOST_RESOLVE_MSAA +#undef XE_DIRECT_HOST_RESOLVE_CONFIG + + for (const DirectHostResolvePipelineConfig& cfg : + kDirectHostResolvePipelineConfigs) { + if (cfg.scaled && !draw_resolution_scaled) { + continue; + } + MTL::ComputePipelineState* pipeline = + CreateComputePipelineFromEmbeddedLibrary( + device_, cfg.metallib_data, cfg.metallib_size, cfg.debug_name); + if (!pipeline) { + XELOGW( + "Metal: failed to initialize optional direct host resolve " + "pipeline {}", + cfg.debug_name); + continue; + } + size_t msaa_index = DirectHostResolveMsaaIndex(cfg.msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + pipeline->release(); + continue; + } + direct_host_resolve_pipelines_[DirectHostResolveBppIndex(cfg.is_64bpp)] + [msaa_index][cfg.scaled ? 1u : 0u] + [cfg.source_is_uint ? 1u : 0u] = pipeline; + } + + struct DirectHostColorFullResolvePipelineConfig { + const void* metallib_data; + size_t metallib_size; + draw_util::ResolveCopyShaderIndex copy_shader; + xenos::MsaaSamples msaa_samples; + bool scaled; + bool source_is_uint; + const char* debug_name; + }; +#define XE_DIRECT_HOST_COLOR_FULL_RESOLVE_CONFIG(id, shader, msaa, scaled, \ + source_uint) \ + {id##_metallib, sizeof(id##_metallib), shader, msaa, scaled, source_uint, #id} +#define XE_DIRECT_HOST_COLOR_FULL_RESOLVE_MSAA(prefix, bpp, shader, \ + msaa_token, msaa, source_uint) \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_CONFIG( \ + prefix##_##bpp##bpp_##msaa_token##xmsaa_cs, shader, msaa, false, \ + source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_CONFIG( \ + prefix##_##bpp##bpp_##msaa_token##xmsaa_scaled_cs, shader, msaa, \ + true, source_uint) +#define XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST(prefix, bpp, shader, \ + source_uint) \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_MSAA( \ + prefix, bpp, shader, 1, xenos::MsaaSamples::k1X, source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_MSAA( \ + prefix, bpp, shader, 2, xenos::MsaaSamples::k2X, source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_MSAA( \ + prefix, bpp, shader, 4, xenos::MsaaSamples::k4X, source_uint) +#define XE_DIRECT_HOST_COLOR_FULL_RESOLVE_SOURCE(prefix, source_uint) \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST( \ + prefix, 8, draw_util::ResolveCopyShaderIndex::kFull8bpp, source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST( \ + prefix, 16, draw_util::ResolveCopyShaderIndex::kFull16bpp, \ + source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST( \ + prefix, 32, draw_util::ResolveCopyShaderIndex::kFull32bpp, \ + source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST( \ + prefix, 64, draw_util::ResolveCopyShaderIndex::kFull64bpp, \ + source_uint), \ + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST( \ + prefix, 128, draw_util::ResolveCopyShaderIndex::kFull128bpp, \ + source_uint) + static constexpr DirectHostColorFullResolvePipelineConfig + kDirectHostColorFullResolvePipelineConfigs[] = { + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_SOURCE(resolve_host_color_full, + false), + XE_DIRECT_HOST_COLOR_FULL_RESOLVE_SOURCE(resolve_host_color_full_uint, + true), + }; +#undef XE_DIRECT_HOST_COLOR_FULL_RESOLVE_SOURCE +#undef XE_DIRECT_HOST_COLOR_FULL_RESOLVE_DEST +#undef XE_DIRECT_HOST_COLOR_FULL_RESOLVE_MSAA +#undef XE_DIRECT_HOST_COLOR_FULL_RESOLVE_CONFIG + + for (const DirectHostColorFullResolvePipelineConfig& cfg : + kDirectHostColorFullResolvePipelineConfigs) { + if (cfg.scaled && !draw_resolution_scaled) { + continue; + } + MTL::ComputePipelineState* pipeline = + CreateComputePipelineFromEmbeddedLibrary( + device_, cfg.metallib_data, cfg.metallib_size, cfg.debug_name); + if (!pipeline) { + XELOGW( + "Metal: failed to initialize optional direct host full color " + "resolve pipeline {}", + cfg.debug_name); + continue; + } + size_t msaa_index = DirectHostResolveMsaaIndex(cfg.msaa_samples); + size_t dest_index = DirectHostResolveFullDestIndex(cfg.copy_shader); + if (msaa_index >= kDirectHostResolveMsaaCount || + dest_index >= kDirectHostResolveFullDestCount) { + pipeline->release(); + continue; + } + direct_host_color_full_resolve_pipelines_[msaa_index][cfg.scaled ? 1u : 0u] + [cfg.source_is_uint ? 1u : 0u] + [dest_index] = pipeline; + } + + struct DirectHostDepthResolvePipelineConfig { + const void* metallib_data; + size_t metallib_size; + xenos::MsaaSamples msaa_samples; + bool scaled; + const char* debug_name; + }; +#define XE_DIRECT_HOST_DEPTH_RESOLVE_CONFIG(id, msaa, scaled) \ + {id##_metallib, sizeof(id##_metallib), msaa, scaled, #id} +#define XE_DIRECT_HOST_DEPTH_RESOLVE_MSAA(msaa_token, msaa) \ + XE_DIRECT_HOST_DEPTH_RESOLVE_CONFIG( \ + resolve_host_depth_32bpp_##msaa_token##xmsaa_cs, msaa, false), \ + XE_DIRECT_HOST_DEPTH_RESOLVE_CONFIG( \ + resolve_host_depth_32bpp_##msaa_token##xmsaa_scaled_cs, msaa, true) + static constexpr DirectHostDepthResolvePipelineConfig + kDirectHostDepthResolvePipelineConfigs[] = { + XE_DIRECT_HOST_DEPTH_RESOLVE_MSAA(1, xenos::MsaaSamples::k1X), + XE_DIRECT_HOST_DEPTH_RESOLVE_MSAA(2, xenos::MsaaSamples::k2X), + XE_DIRECT_HOST_DEPTH_RESOLVE_MSAA(4, xenos::MsaaSamples::k4X), + }; +#undef XE_DIRECT_HOST_DEPTH_RESOLVE_MSAA +#undef XE_DIRECT_HOST_DEPTH_RESOLVE_CONFIG + + for (const DirectHostDepthResolvePipelineConfig& cfg : + kDirectHostDepthResolvePipelineConfigs) { + if (cfg.scaled && !draw_resolution_scaled) { + continue; + } + MTL::ComputePipelineState* pipeline = + CreateComputePipelineFromEmbeddedLibrary( + device_, cfg.metallib_data, cfg.metallib_size, cfg.debug_name); + if (!pipeline) { + XELOGW( + "Metal: failed to initialize optional direct host depth resolve " + "pipeline {}", + cfg.debug_name); + continue; + } + size_t msaa_index = DirectHostResolveMsaaIndex(cfg.msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + pipeline->release(); + continue; + } + direct_host_depth_resolve_pipelines_[msaa_index][cfg.scaled ? 1u : 0u] = + pipeline; + } +} + +namespace { +// Recursively release+null every MTL::ComputePipelineState* in an arbitrarily +// dimensioned C array. Replaces the per-array hand-written nested loops. +template +void ReleaseComputePipelineArray(T& element, bool release_existing) { + if constexpr (std::is_array_v) { + for (auto& sub : element) { + ReleaseComputePipelineArray(sub, release_existing); + } + } else { + if (release_existing && element) { + element->release(); + } + element = nullptr; + } +} +} // namespace + +void MetalRenderTargetCache::ResetDirectHostResolvePipelines( + bool release_existing) { + ReleaseComputePipelineArray(direct_host_resolve_pipelines_, release_existing); + ReleaseComputePipelineArray(direct_host_color_full_resolve_pipelines_, + release_existing); + ReleaseComputePipelineArray(direct_host_depth_resolve_pipelines_, + release_existing); +} + +bool MetalRenderTargetCache::PrepareResolveDestinationBuffer( + const draw_util::ResolveInfo& resolve_info, bool draw_resolution_scaled, + ResolveDestinationBuffer& destination) { + destination = {}; + if (draw_resolution_scaled) { + auto* texture_cache = command_processor_.texture_cache(); + auto* metal_texture_cache = + texture_cache ? static_cast(texture_cache) + : nullptr; + if (!metal_texture_cache) { + return false; + } + const uint32_t range_length = resolve_info.copy_dest_extent_start - + resolve_info.copy_dest_base + + resolve_info.copy_dest_extent_length; + return metal_texture_cache->EnsureScaledResolveMemoryCommitted( + resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length) && + metal_texture_cache->MakeScaledResolveRangeCurrent( + resolve_info.copy_dest_base, range_length) && + metal_texture_cache->GetCurrentScaledResolveBuffer( + destination.buffer, destination.offset, destination.length); + } + + auto* shared = command_processor_.shared_memory(); + destination.buffer = shared ? shared->GetBuffer() : nullptr; + if (!destination.buffer) { + return false; + } + // TODO(xenios-jp): Move resolve/export destination residency into the + // command-processor materialization path before the render encoder is opened. + // Keeping it classified here makes the remaining active shared-memory upload + // breaks visible as resolve_copy_dest telemetry. + return command_processor_.RequestSharedMemoryRange( + MetalCommandProcessor::SharedMemoryRequestReason::kResolveCopyDest, + resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length); +} + +MTL::ComputePipelineState* MetalRenderTargetCache::GetDirectHostResolvePipeline( + bool is_64bpp, xenos::MsaaSamples msaa_samples, bool scaled, + bool source_is_uint) const { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + return nullptr; + } + return direct_host_resolve_pipelines_[DirectHostResolveBppIndex( + is_64bpp)][msaa_index][scaled ? 1u : 0u][source_is_uint ? 1u : 0u]; +} + +MTL::ComputePipelineState* +MetalRenderTargetCache::GetDirectHostColorFullResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled, bool source_is_uint, + draw_util::ResolveCopyShaderIndex copy_shader) const { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + size_t dest_index = DirectHostResolveFullDestIndex(copy_shader); + if (msaa_index >= kDirectHostResolveMsaaCount || + dest_index >= kDirectHostResolveFullDestCount) { + return nullptr; + } + return direct_host_color_full_resolve_pipelines_[msaa_index][scaled ? 1u : 0u] + [source_is_uint ? 1u : 0u] + [dest_index]; +} + +MTL::ComputePipelineState* +MetalRenderTargetCache::GetDirectHostDepthResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled) const { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + return nullptr; + } + return direct_host_depth_resolve_pipelines_[msaa_index][scaled ? 1u : 0u]; +} + +bool MetalRenderTargetCache::TryDirectHostResolveCopy( + const draw_util::ResolveInfo& resolve_info, + const draw_util::ResolveCopyShaderConstants& copy_constants, + draw_util::ResolveCopyShaderIndex copy_shader, uint32_t dump_base, + uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch, + MTL::CommandBuffer* command_buffer, uint32_t& written_address, + uint32_t& written_length) { + TelemetryStats::ResolveDirectHostTelemetry& direct_telemetry = + telemetry_.resolve_direct_host; + ++direct_telemetry.direct_host_attempt; + + auto reject = []() { return false; }; + auto reject_gamma = [&]() { + ++direct_telemetry.direct_host_reject_gamma; + return false; + }; + auto reject_exp_bias = [&]() { + ++direct_telemetry.direct_host_reject_exp_bias; + return false; + }; + auto reject_format_mismatch = [&]() { + ++direct_telemetry.direct_host_reject_format_mismatch; + return false; + }; + auto reject_sample_select = [&]() { + ++direct_telemetry.direct_host_reject_sample_select; + return false; + }; + auto reject_depth_no_fast = [&]() { + ++direct_telemetry.direct_host_reject_depth_no_fast; + return false; + }; + + if (GetPath() != Path::kHostRenderTargets) { + return reject(); + } + const bool resolve_is_depth = resolve_info.IsCopyingDepth(); + const bool copy_shader_is_fast = + IsResolveDirectHostRTFastCandidate(copy_shader); + const bool copy_shader_is_full_color = + !resolve_is_depth && IsResolveDirectHostRTFullColorCandidate(copy_shader); + + xenos::ColorRenderTargetFormat resolve_color_format = + xenos::ColorRenderTargetFormat::k_8_8_8_8; + xenos::DepthRenderTargetFormat resolve_depth_format = + xenos::DepthRenderTargetFormat::kD24S8; + if (resolve_is_depth) { + if (!xenos::IsSingleCopySampleSelected( + resolve_info.copy_dest_coordinate_info.copy_sample_select)) { + return reject_sample_select(); + } + if (!copy_shader_is_fast) { + return reject_depth_no_fast(); + } + resolve_depth_format = + xenos::DepthRenderTargetFormat(resolve_info.depth_edram_info.format); + } else { + resolve_color_format = + xenos::ColorRenderTargetFormat(resolve_info.color_edram_info.format); + if (resolve_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + // TODO(xenios-jp): Support direct host gamma resolves only once the + // shader can mirror the dump path's linear RGBA16Unorm host storage to + // guest PWL-gamma RGBA8 conversion. + return reject_gamma(); + } + if (copy_shader_is_fast) { + if (!xenos::IsSingleCopySampleSelected( + resolve_info.copy_dest_coordinate_info.copy_sample_select)) { + return reject_sample_select(); + } + if (resolve_info.copy_dest_info.copy_dest_exp_bias) { + return reject_exp_bias(); + } + if (!xenos::IsColorResolveFormatBitwiseEquivalent( + resolve_color_format, + xenos::ColorFormat( + resolve_info.copy_dest_info.copy_dest_format))) { + return reject_format_mismatch(); + } + } else if (!copy_shader_is_full_color) { + if (!xenos::IsSingleCopySampleSelected( + resolve_info.copy_dest_coordinate_info.copy_sample_select)) { + return reject_sample_select(); + } + if (resolve_info.copy_dest_info.copy_dest_exp_bias) { + return reject_exp_bias(); + } + if (!xenos::IsColorResolveFormatBitwiseEquivalent( + resolve_color_format, + xenos::ColorFormat( + resolve_info.copy_dest_info.copy_dest_format))) { + return reject_format_mismatch(); + } + return reject(); + } + } + + struct DirectHostResolveConstants { + uint32_t dispatch_offset; + uint32_t dump_base; + uint32_t dump_pitch_tiles; + uint32_t source_base_tiles; + uint32_t source_pitch_tiles; + uint32_t thread_count_x; + uint32_t thread_count_y; + uint32_t height_scaled; + uint32_t msaa_2x_sample_0; + uint32_t msaa_2x_sample_1; + uint32_t flags; + }; + + struct DirectHostResolveSource { + RenderTargetKey key; + MTL::Texture* texture; + MTL::Texture* stencil_texture; + MTL::ComputePipelineState* pipeline; + ResolveCopyDumpRectangle::Dispatch + dispatches[ResolveCopyDumpRectangle::kMaxDispatches]; + uint32_t dispatch_count; + uint32_t flags; + uint32_t pixels_per_thread; + bool is_64bpp; + bool is_depth; + }; + + std::vector rectangles; + GetResolveCopyRectanglesToDump(dump_base, dump_row_length_used, dump_rows, + dump_pitch, rectangles); + if (rectangles.empty()) { + return reject(); + } + + uint64_t covered_tiles = 0; + std::vector sources; + sources.reserve(rectangles.size()); + for (const ResolveCopyDumpRectangle& rect : rectangles) { + if (!rect.rows || rect.row_last_end <= rect.row_first_start) { + return reject(); + } + if (rect.rows == 1) { + covered_tiles += rect.row_last_end - rect.row_first_start; + } else { + covered_tiles += dump_row_length_used - rect.row_first_start; + covered_tiles += uint64_t(rect.rows - 2) * dump_row_length_used; + covered_tiles += rect.row_last_end; + } + + auto* rt = static_cast(rect.render_target); + if (!rt) { + return reject(); + } + RenderTargetKey key = rt->key(); + if (key.is_depth != resolve_is_depth) { + return reject(); + } + + bool source_is_uint = false; + MTL::PixelFormat expected_format = MTL::PixelFormatInvalid; + MTL::Texture* texture = nullptr; + MTL::Texture* stencil_texture = nullptr; + MTL::ComputePipelineState* pipeline = nullptr; + uint32_t source_flags = 0; + bool is_64bpp = false; + if (resolve_is_depth) { + if (key.GetDepthFormat() != resolve_depth_format || + key.msaa_samples != resolve_info.depth_edram_info.msaa_samples) { + return reject_format_mismatch(); + } + texture = rt->texture(); + expected_format = GetDepthPixelFormat(key.GetDepthFormat()); + if (!::cvars::depth_float24_convert_in_pixel_shader && + ::cvars::depth_float24_round) { + source_flags |= kDirectHostResolveDepthFlagRoundDepth; + } + stencil_texture = GetStencilTextureView(rt); + if (stencil_texture) { + source_flags |= kDirectHostResolveDepthFlagHasStencil; + } + pipeline = GetDirectHostDepthResolvePipeline(key.msaa_samples, + IsDrawResolutionScaled()); + } else { + if (key.GetColorFormat() != resolve_color_format || + key.msaa_samples != resolve_info.color_edram_info.msaa_samples) { + return reject_format_mismatch(); + } + if (copy_shader_is_full_color && + !IsResolveDirectHostRTFullColorSourcePackable(resolve_color_format)) { + return reject_format_mismatch(); + } + + MTL::PixelFormat ownership_transfer_format = + GetColorOwnershipTransferPixelFormat(key.GetColorFormat(), + &source_is_uint); + texture = source_is_uint + ? ((key.msaa_samples != xenos::MsaaSamples::k1X && + rt->msaa_texture()) + ? rt->msaa_transfer_texture() + : rt->transfer_texture()) + : rt->texture(); + expected_format = source_is_uint + ? ownership_transfer_format + : GetColorResourcePixelFormat(key.GetColorFormat()); + is_64bpp = key.Is64bpp(); + pipeline = copy_shader_is_full_color + ? GetDirectHostColorFullResolvePipeline( + key.msaa_samples, IsDrawResolutionScaled(), + source_is_uint, copy_shader) + : GetDirectHostResolvePipeline(is_64bpp, key.msaa_samples, + IsDrawResolutionScaled(), + source_is_uint); + } + if (!texture) { + return reject(); + } + if (texture->pixelFormat() != expected_format) { + return reject_format_mismatch(); + } + if (!pipeline) { + return reject(); + } + + DirectHostResolveSource source = {}; + source.key = key; + source.texture = texture; + source.stencil_texture = stencil_texture; + source.pipeline = pipeline; + source.dispatch_count = + rect.GetDispatches(dump_pitch, dump_row_length_used, source.dispatches); + source.flags = source_flags; + source.pixels_per_thread = DirectHostResolvePixelsPerThread( + copy_shader, is_64bpp, key.msaa_samples); + source.is_64bpp = is_64bpp; + source.is_depth = resolve_is_depth; + if (!source.dispatch_count) { + return reject(); + } + const uint32_t tile_size_x = + (source.is_64bpp ? 40u : 80u) * draw_resolution_scale_x(); + const uint32_t tile_pixel_size_x = + tile_size_x >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k4X); + for (uint32_t i = 0; i < source.dispatch_count; ++i) { + uint32_t dispatch_pixel_width = + source.dispatches[i].width_tiles * tile_pixel_size_x; + if (dispatch_pixel_width % source.pixels_per_thread) { + return reject(); + } + } + sources.push_back(source); + } + + const uint64_t required_tiles = + uint64_t(dump_row_length_used) * uint64_t(dump_rows); + if (covered_tiles != required_tiles) { + return reject(); + } + + auto* texture_cache = command_processor_.texture_cache(); + const bool draw_resolution_scaled = IsDrawResolutionScaled(); + ResolveDestinationBuffer destination = {}; + if (!PrepareResolveDestinationBuffer(resolve_info, draw_resolution_scaled, + destination)) { + return reject(); + } + + command_processor_.SetSwapDestSwap( + resolve_info.copy_dest_base, resolve_info.copy_dest_info.copy_dest_swap); + + ScopedAutoreleasePool autorelease_pool; + bool standalone = false; + MTL::CommandBuffer* cmd = command_buffer; + if (!cmd) { + cmd = command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=direct-host-resolve"); + if (!cmd) { + return reject(); + } + standalone = true; + } + + EndSharedMemoryUploadBlitEncoderForCommandBuffer(command_processor_, cmd); + MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); + if (!encoder) { + if (standalone) { + cmd->release(); + } + return reject(); + } + + SetEncoderLabel(encoder, kDirectHostResolveEncoderLabel); + PushEncoderDebugGroup( + encoder, + fmt::format( + "{} rects={} dest={}", kDirectHostResolveEncoderLabel, sources.size(), + draw_resolution_scaled ? "scaled_resolve_memory" : "shared_memory")); + + if (draw_resolution_scaled) { + encoder->setBytes(©_constants.dest_relative, + sizeof(copy_constants.dest_relative), 0); + } else { + encoder->setBytes(©_constants, sizeof(copy_constants), 0); + } + encoder->setBuffer(destination.buffer, destination.offset, 1); + encoder->useResource(destination.buffer, MTL::ResourceUsageWrite); + + const uint32_t scale_x = draw_resolution_scale_x(); + const uint32_t scale_y = draw_resolution_scale_y(); + const uint32_t height_scaled = + (resolve_info.height_div_8 << xenos::kResolveAlignmentPixelsLog2) * + scale_y; + const uint32_t msaa_2x_sample_0 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA(0, msaa_2x_supported_); + const uint32_t msaa_2x_sample_1 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA(1, msaa_2x_supported_); + + for (const DirectHostResolveSource& source : sources) { + encoder->setTexture(source.texture, 0); + if (source.is_depth) { + encoder->setTexture(source.stencil_texture, 1); + } + encoder->useResource(source.texture, MTL::ResourceUsageRead); + if (source.stencil_texture) { + encoder->useResource(source.stencil_texture, MTL::ResourceUsageRead); + } + encoder->setComputePipelineState(source.pipeline); + + const uint32_t tile_size_x = (source.is_64bpp ? 40u : 80u) * scale_x; + const uint32_t tile_size_y = 16u * scale_y; + const uint32_t tile_pixel_size_x = + tile_size_x >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k4X); + const uint32_t tile_pixel_size_y = + tile_size_y >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k2X); + const uint32_t pixels_per_thread = source.pixels_per_thread; + for (uint32_t i = 0; i < source.dispatch_count; ++i) { + const ResolveCopyDumpRectangle::Dispatch& dispatch = source.dispatches[i]; + DirectHostResolveConstants constants = {}; + constants.dispatch_offset = dispatch.offset; + constants.dump_base = dump_base; + constants.dump_pitch_tiles = dump_pitch; + constants.source_base_tiles = source.key.base_tiles; + constants.source_pitch_tiles = source.key.GetPitchTiles(); + constants.thread_count_x = + dispatch.width_tiles * tile_pixel_size_x / pixels_per_thread; + constants.thread_count_y = dispatch.height_tiles * tile_pixel_size_y; + constants.height_scaled = height_scaled; + constants.msaa_2x_sample_0 = msaa_2x_sample_0; + constants.msaa_2x_sample_1 = msaa_2x_sample_1; + constants.flags = source.flags; + encoder->setBytes(&constants, sizeof(constants), 2); + + uint32_t threadgroups_x = (constants.thread_count_x + 7u) >> 3; + uint32_t threadgroups_y = (constants.thread_count_y + 7u) >> 3; + encoder->dispatchThreadgroups( + MTL::Size::Make(threadgroups_x, threadgroups_y, 1), + MTL::Size::Make(8, 8, 1)); + } + } + + if (!draw_resolution_scaled) { + command_processor_.MarkSharedMemoryComputeWritePending( + resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length, encoder); + if (auto* shared_memory = command_processor_.shared_memory()) { + shared_memory->MarkGpuAccess(resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length, + command_processor_.GetCurrentSubmission()); + } + } + + encoder->popDebugGroup(); + encoder->endEncoding(); + if (standalone) { + command_processor_.CommitStandaloneAndWait(cmd); + } + + written_address = resolve_info.copy_dest_extent_start; + written_length = resolve_info.copy_dest_extent_length; + if (texture_cache) { + texture_cache->MarkRangeAsResolved(written_address, written_length); + } + + ++direct_telemetry.direct_host_success; + return true; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_geometry_shader.cc b/src/xenia/gpu/metal/metal_geometry_shader.cc new file mode 100644 index 000000000..3347ce3f9 --- /dev/null +++ b/src/xenia/gpu/metal/metal_geometry_shader.cc @@ -0,0 +1,39 @@ +/** + ****************************************************************************** + * 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_geometry_shader.h" + +#include +#include +#include + +namespace xe { +namespace gpu { +namespace metal { + +// Metal-local cache of generated DXBC geometry shaders (the shared generator in +// xenia/gpu/dxbc_geometry_shader.cc produces the bytes; Metal converts them via +// the Metal Shader Converter at consumption time). +static std::unordered_map, + GeometryShaderKey::Hasher> + geometry_shaders_; + +const std::vector& GetGeometryShader(GeometryShaderKey key) { + auto it = geometry_shaders_.find(key); + if (it != geometry_shaders_.end()) { + return it->second; + } + std::vector shader; + CreateDxbcGeometryShader(key, shader); + return geometry_shaders_.emplace(key, std::move(shader)).first->second; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_geometry_shader.h b/src/xenia/gpu/metal/metal_geometry_shader.h new file mode 100644 index 000000000..5849442df --- /dev/null +++ b/src/xenia/gpu/metal/metal_geometry_shader.h @@ -0,0 +1,37 @@ +/** + ****************************************************************************** + * 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_GEOMETRY_SHADER_H_ +#define XENIA_GPU_METAL_METAL_GEOMETRY_SHADER_H_ + +#include + +#include "xenia/gpu/dxbc_geometry_shader.h" + +namespace xe { +namespace gpu { +namespace metal { + +// The geometry-shader key, key derivation, and DXBC generation are shared +// across the D3D12 and Metal backends (xenia/gpu/dxbc_geometry_shader.h); +// re-exported here so existing Metal call sites keep their unqualified names. +using xe::gpu::CreateDxbcGeometryShader; +using xe::gpu::GeometryShaderKey; +using xe::gpu::GetGeometryShaderKey; +using xe::gpu::PipelineGeometryShader; + +// Returns the cached DXBC geometry shader for the key. The Metal backend then +// converts the returned DXBC through the Metal Shader Converter. +const std::vector& GetGeometryShader(GeometryShaderKey key); + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_GEOMETRY_SHADER_H_ diff --git a/src/xenia/gpu/metal/metal_heap_pool.cc b/src/xenia/gpu/metal/metal_heap_pool.cc index 0adcbb98a..26e4f45b4 100644 --- a/src/xenia/gpu/metal/metal_heap_pool.cc +++ b/src/xenia/gpu/metal/metal_heap_pool.cc @@ -10,6 +10,7 @@ #include "xenia/gpu/metal/metal_heap_pool.h" #include +#include #include "xenia/base/logging.h" #include "xenia/base/math.h" @@ -50,6 +51,10 @@ MetalHeapPool::MetalHeapPool(MTL::Device* device, MTL::StorageMode storage_mode, MetalHeapPool::~MetalHeapPool() { Shutdown(); } +void MetalHeapPool::SetHeapCreatedCallback(HeapCreatedCallback callback) { + heap_created_callback_ = std::move(callback); +} + void MetalHeapPool::Shutdown() { for (auto& entry : heaps_) { if (entry.heap) { @@ -116,6 +121,9 @@ MTL::Heap* MetalHeapPool::GetHeapForSize(size_t size, size_t alignment) { label_prefix_ + "_heap_" + std::to_string(heaps_.size()); heap->setLabel(NS::String::string(label.c_str(), NS::UTF8StringEncoding)); } + if (heap_created_callback_) { + heap_created_callback_(heap); + } heaps_.push_back({heap, heap_size}); total_heap_bytes_ += heap_size; diff --git a/src/xenia/gpu/metal/metal_heap_pool.h b/src/xenia/gpu/metal/metal_heap_pool.h index f757bd90c..3045639fa 100644 --- a/src/xenia/gpu/metal/metal_heap_pool.h +++ b/src/xenia/gpu/metal/metal_heap_pool.h @@ -11,6 +11,7 @@ #define XENIA_GPU_METAL_METAL_HEAP_POOL_H_ #include +#include #include #include @@ -26,6 +27,9 @@ class MetalHeapPool { size_t min_heap_size, const char* label_prefix); ~MetalHeapPool(); + using HeapCreatedCallback = std::function; + void SetHeapCreatedCallback(HeapCreatedCallback callback); + MTL::Texture* CreateTexture(MTL::TextureDescriptor* descriptor); void Shutdown(); @@ -44,6 +48,7 @@ class MetalHeapPool { size_t total_heap_bytes_ = 0; std::string label_prefix_; std::vector heaps_; + HeapCreatedCallback heap_created_callback_; }; } // namespace metal diff --git a/src/xenia/gpu/metal/metal_pipeline_cache.cc b/src/xenia/gpu/metal/metal_pipeline_cache.cc new file mode 100644 index 000000000..98deffc2f --- /dev/null +++ b/src/xenia/gpu/metal/metal_pipeline_cache.cc @@ -0,0 +1,2955 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/gpu/metal/metal_pipeline_cache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "third_party/metal-cpp/Foundation/NSProcessInfo.hpp" +#include "third_party/metal-cpp/Foundation/NSURL.hpp" + +#include "third_party/fmt/include/fmt/format.h" +#include "xenia/base/assert.h" +#include "xenia/base/cvar.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/base/threading.h" +#include "xenia/base/xxhash.h" +#include "xenia/gpu/draw_util.h" +#include "xenia/gpu/gpu_flags.h" +#include "xenia/gpu/metal/metal_shader_cache.h" +#include "xenia/gpu/registers.h" +#include "xenia/gpu/xenos.h" + +#include "xenia/gpu/metal/metal_shader_converter.h" +using BYTE = uint8_t; +#include "xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_quad_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/adaptive_triangle_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_1cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/continuous_quad_4cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_1cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/continuous_triangle_3cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_1cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/discrete_quad_4cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_1cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/discrete_triangle_3cp_hs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_adaptive_vs.h" +#include "xenia/gpu/metal/d3d12_5_1_bytecode/tessellation_indexed_vs.h" +// Metal IR Converter Runtime - defines IRDescriptorTableEntry and bind points. +#ifndef IR_RUNTIME_METALCPP +#define IR_RUNTIME_METALCPP +#endif +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" + +#ifndef DISPATCH_DATA_DESTRUCTOR_NONE +#define DISPATCH_DATA_DESTRUCTOR_NONE DISPATCH_DATA_DESTRUCTOR_DEFAULT +#endif + +DECLARE_bool(async_shader_compilation); +DECLARE_bool(depth_float24_convert_in_pixel_shader); +DECLARE_bool(depth_float24_round); + +DEFINE_int32(metal_pipeline_creation_threads, -1, + "Number of threads for background pipeline compilation. " + "-1 = auto (75% of cores), 0 = disabled (synchronous).", + "Metal"); + +namespace xe { +namespace gpu { +namespace metal { + +namespace { + +void AtomicMax(std::atomic& target, uint64_t value) { + uint64_t current = target.load(std::memory_order_relaxed); + while (current < value && !target.compare_exchange_weak( + current, value, std::memory_order_relaxed, + std::memory_order_relaxed)) { + } +} + +uint64_t ElapsedMs(std::chrono::steady_clock::time_point start, + std::chrono::steady_clock::time_point end) { + return uint64_t( + std::chrono::duration_cast(end - start) + .count()); +} + +void LogMetalErrorDetails(const char* label, NS::Error* error) { + if (!error) { + return; + } + const char* desc = error->localizedDescription() + ? error->localizedDescription()->utf8String() + : nullptr; + const char* failure = error->localizedFailureReason() + ? error->localizedFailureReason()->utf8String() + : nullptr; + const char* recovery = + error->localizedRecoverySuggestion() + ? error->localizedRecoverySuggestion()->utf8String() + : nullptr; + const char* domain = + error->domain() ? error->domain()->utf8String() : nullptr; + int64_t code = error->code(); + XELOGE("{}: domain={} code={} desc='{}' failure='{}' recovery='{}'", label, + domain ? domain : "", code, desc ? desc : "", + failure ? failure : "", recovery ? recovery : ""); + NS::Dictionary* user_info = error->userInfo(); + if (user_info) { + auto* info_desc = user_info->description(); + XELOGE("{}: userInfo={}", label, + info_desc ? info_desc->utf8String() : ""); + } +} + +void EnsureDepthFormatForDepthWritingFragment(const char* pipeline_name, + bool fragment_writes_depth, + MTL::PixelFormat* depth_format) { + if (!fragment_writes_depth || !depth_format || + *depth_format != MTL::PixelFormatInvalid) { + return; + } + *depth_format = MTL::PixelFormatDepth32Float; + static bool logged = false; + if (!logged) { + logged = true; + XELOGW( + "{}: fragment writes depth without a bound depth attachment; " + "using Depth32Float pipeline fallback", + pipeline_name); + } +} + +bool ShaderUsesVertexFetch(const Shader& shader) { + if (!shader.vertex_bindings().empty()) { + return true; + } + const Shader::ConstantRegisterMap& constant_map = + shader.constant_register_map(); + for (uint32_t i = 0; i < xe::countof(constant_map.vertex_fetch_bitmap); ++i) { + if (constant_map.vertex_fetch_bitmap[i] != 0) { + return true; + } + } + return false; +} + +MTL::CompareFunction ToMetalCompareFunction(xenos::CompareFunction compare) { + static const MTL::CompareFunction kCompareMap[8] = { + MTL::CompareFunctionNever, // 0 + MTL::CompareFunctionLess, // 1 + MTL::CompareFunctionEqual, // 2 + MTL::CompareFunctionLessEqual, // 3 + MTL::CompareFunctionGreater, // 4 + MTL::CompareFunctionNotEqual, // 5 + MTL::CompareFunctionGreaterEqual, // 6 + MTL::CompareFunctionAlways, // 7 + }; + return kCompareMap[uint32_t(compare) & 0x7]; +} + +MTL::StencilOperation ToMetalStencilOperation(xenos::StencilOp op) { + static const MTL::StencilOperation kStencilOpMap[8] = { + MTL::StencilOperationKeep, // 0 + MTL::StencilOperationZero, // 1 + MTL::StencilOperationReplace, // 2 + MTL::StencilOperationIncrementClamp, // 3 + MTL::StencilOperationDecrementClamp, // 4 + MTL::StencilOperationInvert, // 5 + MTL::StencilOperationIncrementWrap, // 6 + MTL::StencilOperationDecrementWrap, // 7 + }; + return kStencilOpMap[uint32_t(op) & 0x7]; +} + +MTL::ColorWriteMask ToMetalColorWriteMask(uint32_t write_mask) { + MTL::ColorWriteMask mtl_mask = MTL::ColorWriteMaskNone; + if (write_mask & 0x1) { + mtl_mask |= MTL::ColorWriteMaskRed; + } + if (write_mask & 0x2) { + mtl_mask |= MTL::ColorWriteMaskGreen; + } + if (write_mask & 0x4) { + mtl_mask |= MTL::ColorWriteMaskBlue; + } + if (write_mask & 0x8) { + mtl_mask |= MTL::ColorWriteMaskAlpha; + } + return mtl_mask; +} + +MTL::BlendOperation ToMetalBlendOperation(xenos::BlendOp blend_op) { + static const MTL::BlendOperation kBlendOpMap[8] = { + MTL::BlendOperationAdd, // 0 + MTL::BlendOperationSubtract, // 1 + MTL::BlendOperationMin, // 2 + MTL::BlendOperationMax, // 3 + MTL::BlendOperationReverseSubtract, // 4 + MTL::BlendOperationAdd, // 5 + MTL::BlendOperationAdd, // 6 + MTL::BlendOperationAdd, // 7 + }; + return kBlendOpMap[uint32_t(blend_op) & 0x7]; +} + +MTL::BlendFactor ToMetalBlendFactorRgb(xenos::BlendFactor blend_factor) { + static const MTL::BlendFactor kBlendFactorMap[32] = { + /* 0 */ MTL::BlendFactorZero, + /* 1 */ MTL::BlendFactorOne, + /* 2 */ MTL::BlendFactorZero, // ? + /* 3 */ MTL::BlendFactorZero, // ? + /* 4 */ MTL::BlendFactorSourceColor, + /* 5 */ MTL::BlendFactorOneMinusSourceColor, + /* 6 */ MTL::BlendFactorSourceAlpha, + /* 7 */ MTL::BlendFactorOneMinusSourceAlpha, + /* 8 */ MTL::BlendFactorDestinationColor, + /* 9 */ MTL::BlendFactorOneMinusDestinationColor, + /* 10 */ MTL::BlendFactorDestinationAlpha, + /* 11 */ MTL::BlendFactorOneMinusDestinationAlpha, + /* 12 */ MTL::BlendFactorBlendColor, // CONSTANT_COLOR + /* 13 */ MTL::BlendFactorOneMinusBlendColor, + /* 14 */ MTL::BlendFactorBlendAlpha, // CONSTANT_ALPHA + /* 15 */ MTL::BlendFactorOneMinusBlendAlpha, + /* 16 */ MTL::BlendFactorSourceAlphaSaturated, + }; + return kBlendFactorMap[uint32_t(blend_factor) & 0x1F]; +} + +MTL::BlendFactor ToMetalBlendFactorAlpha(xenos::BlendFactor blend_factor) { + static const MTL::BlendFactor kBlendFactorAlphaMap[32] = { + /* 0 */ MTL::BlendFactorZero, + /* 1 */ MTL::BlendFactorOne, + /* 2 */ MTL::BlendFactorZero, // ? + /* 3 */ MTL::BlendFactorZero, // ? + /* 4 */ MTL::BlendFactorSourceAlpha, + /* 5 */ MTL::BlendFactorOneMinusSourceAlpha, + /* 6 */ MTL::BlendFactorSourceAlpha, + /* 7 */ MTL::BlendFactorOneMinusSourceAlpha, + /* 8 */ MTL::BlendFactorDestinationAlpha, + /* 9 */ MTL::BlendFactorOneMinusDestinationAlpha, + /* 10 */ MTL::BlendFactorDestinationAlpha, + /* 11 */ MTL::BlendFactorOneMinusDestinationAlpha, + /* 12 */ MTL::BlendFactorBlendAlpha, + /* 13 */ MTL::BlendFactorOneMinusBlendAlpha, + /* 14 */ MTL::BlendFactorBlendAlpha, + /* 15 */ MTL::BlendFactorOneMinusBlendAlpha, + /* 16 */ MTL::BlendFactorSourceAlphaSaturated, + }; + return kBlendFactorAlphaMap[uint32_t(blend_factor) & 0x1F]; +} + +void ApplyBlendStateToDescriptor( + MTL::RenderPipelineColorAttachmentDescriptorArray* color_attachments, + uint32_t normalized_color_mask, const uint32_t blendcontrol[4]) { + for (uint32_t i = 0; i < 4; ++i) { + auto* color_attachment = color_attachments->object(i); + if (color_attachment->pixelFormat() == MTL::PixelFormatInvalid) { + color_attachment->setWriteMask(MTL::ColorWriteMaskNone); + color_attachment->setBlendingEnabled(false); + continue; + } + + uint32_t rt_write_mask = (normalized_color_mask >> (i * 4)) & 0xF; + color_attachment->setWriteMask(ToMetalColorWriteMask(rt_write_mask)); + if (!rt_write_mask) { + color_attachment->setBlendingEnabled(false); + continue; + } + + reg::RB_BLENDCONTROL bc = {}; + bc.value = blendcontrol[i]; + MTL::BlendFactor src_rgb = ToMetalBlendFactorRgb(bc.color_srcblend); + MTL::BlendFactor dst_rgb = ToMetalBlendFactorRgb(bc.color_destblend); + MTL::BlendOperation op_rgb = ToMetalBlendOperation(bc.color_comb_fcn); + MTL::BlendFactor src_alpha = ToMetalBlendFactorAlpha(bc.alpha_srcblend); + MTL::BlendFactor dst_alpha = ToMetalBlendFactorAlpha(bc.alpha_destblend); + MTL::BlendOperation op_alpha = ToMetalBlendOperation(bc.alpha_comb_fcn); + + bool blending_enabled = + src_rgb != MTL::BlendFactorOne || dst_rgb != MTL::BlendFactorZero || + op_rgb != MTL::BlendOperationAdd || src_alpha != MTL::BlendFactorOne || + dst_alpha != MTL::BlendFactorZero || op_alpha != MTL::BlendOperationAdd; + color_attachment->setBlendingEnabled(blending_enabled); + if (blending_enabled) { + color_attachment->setSourceRGBBlendFactor(src_rgb); + color_attachment->setDestinationRGBBlendFactor(dst_rgb); + color_attachment->setRgbBlendOperation(op_rgb); + color_attachment->setSourceAlphaBlendFactor(src_alpha); + color_attachment->setDestinationAlphaBlendFactor(dst_alpha); + color_attachment->setAlphaBlendOperation(op_alpha); + } + } +} + +IRFormat MapVertexFormatToIRFormat( + const ParsedVertexFetchInstruction::Attributes& attrs) { + using xenos::VertexFormat; + switch (attrs.data_format) { + case VertexFormat::k_8_8_8_8: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR8G8B8A8Sint : IRFormatR8G8B8A8Uint; + } + return attrs.is_signed ? IRFormatR8G8B8A8Snorm : IRFormatR8G8B8A8Unorm; + case VertexFormat::k_2_10_10_10: + if (attrs.is_integer) { + return IRFormatR10G10B10A2Uint; + } + return IRFormatR10G10B10A2Unorm; + case VertexFormat::k_10_11_11: + case VertexFormat::k_11_11_10: + return IRFormatR11G11B10Float; + case VertexFormat::k_16_16: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR16G16Sint : IRFormatR16G16Uint; + } + return attrs.is_signed ? IRFormatR16G16Snorm : IRFormatR16G16Unorm; + case VertexFormat::k_16_16_16_16: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR16G16B16A16Sint + : IRFormatR16G16B16A16Uint; + } + return attrs.is_signed ? IRFormatR16G16B16A16Snorm + : IRFormatR16G16B16A16Unorm; + case VertexFormat::k_16_16_FLOAT: + return IRFormatR16G16Float; + case VertexFormat::k_16_16_16_16_FLOAT: + return IRFormatR16G16B16A16Float; + case VertexFormat::k_32: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR32Sint : IRFormatR32Uint; + } + return IRFormatR32Float; + case VertexFormat::k_32_32: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR32G32Sint : IRFormatR32G32Uint; + } + return IRFormatR32G32Float; + case VertexFormat::k_32_FLOAT: + return IRFormatR32Float; + case VertexFormat::k_32_32_FLOAT: + return IRFormatR32G32Float; + case VertexFormat::k_32_32_32_FLOAT: + return IRFormatR32G32B32Float; + case VertexFormat::k_32_32_32_32: + if (attrs.is_integer) { + return attrs.is_signed ? IRFormatR32G32B32A32Sint + : IRFormatR32G32B32A32Uint; + } + return IRFormatR32G32B32A32Float; + case VertexFormat::k_32_32_32_32_FLOAT: + return IRFormatR32G32B32A32Float; + default: + return IRFormatUnknown; + } +} + +IRInputTopology GetGeometryStageInputTopology(GeometryShaderKey key) { + switch (key.type) { + case PipelineGeometryShader::kPointList: + return IRInputTopologyPoint; + case PipelineGeometryShader::kRectangleList: + return IRInputTopologyTriangle; + case PipelineGeometryShader::kQuadList: + default: + return IRInputTopologyUndefined; + } +} + +struct StageInInputAttribute { + uint32_t input_slot = 0; + uint32_t offset = 0; + IRFormat format = IRFormatUnknown; +}; + +struct StageInInputLayout { + IRVersionedInputLayoutDescriptor descriptor = {}; + std::vector semantic_names_storage; +}; + +StageInInputLayout BuildStageInInputLayout( + const Shader& vertex_shader, + const MetalShaderReflectionInfo& vertex_reflection, + const char* log_prefix) { + std::vector attribute_map; + attribute_map.reserve(32); + + uint32_t attr_index = 0; + for (const auto& binding : vertex_shader.vertex_bindings()) { + for (const auto& attr : binding.attributes) { + if (attr_index >= 31) { + break; + } + StageInInputAttribute mapped = {}; + mapped.input_slot = static_cast(binding.binding_index); + mapped.offset = + static_cast(attr.fetch_instr.attributes.offset * 4); + mapped.format = MapVertexFormatToIRFormat(attr.fetch_instr.attributes); + attribute_map.push_back(mapped); + ++attr_index; + } + if (attr_index >= 31) { + break; + } + } + + StageInInputLayout input_layout = {}; + input_layout.descriptor.version = IRInputLayoutDescriptorVersion_1; + input_layout.descriptor.desc_1_0.numElements = 0; + if (vertex_reflection.vertex_inputs.empty()) { + return input_layout; + } + + input_layout.semantic_names_storage.reserve( + vertex_reflection.vertex_inputs.size()); + uint32_t element_count = 0; + for (const auto& input : vertex_reflection.vertex_inputs) { + if (element_count >= 31) { + break; + } + if (input.attribute_index >= attribute_map.size()) { + XELOGW("{}: vertex input {} out of range (max {})", log_prefix, + input.attribute_index, attribute_map.size()); + continue; + } + const StageInInputAttribute& mapped = attribute_map[input.attribute_index]; + if (mapped.format == IRFormatUnknown) { + XELOGW("{}: unknown IRFormat for vertex input {}", log_prefix, + input.attribute_index); + continue; + } + + std::string semantic_base = input.name; + uint32_t semantic_index = 0; + if (!semantic_base.empty()) { + size_t digit_pos = semantic_base.size(); + while (digit_pos > 0 && std::isdigit(static_cast( + semantic_base[digit_pos - 1]))) { + --digit_pos; + } + if (digit_pos < semantic_base.size()) { + semantic_index = static_cast( + std::strtoul(semantic_base.c_str() + digit_pos, nullptr, 10)); + semantic_base.resize(digit_pos); + } + } + if (semantic_base.empty()) { + semantic_base = "TEXCOORD"; + } + + input_layout.semantic_names_storage.push_back(std::move(semantic_base)); + input_layout.descriptor.desc_1_0.semanticNames[element_count] = + input_layout.semantic_names_storage.back().c_str(); + IRInputElementDescriptor1& element = + input_layout.descriptor.desc_1_0.inputElementDescs[element_count]; + element.semanticIndex = semantic_index; + element.format = mapped.format; + element.inputSlot = mapped.input_slot; + element.alignedByteOffset = mapped.offset; + element.instanceDataStepRate = 0; + element.inputSlotClass = IRInputClassificationPerVertexData; + ++element_count; + } + input_layout.descriptor.desc_1_0.numElements = element_count; + return input_layout; +} + +constexpr uint32_t kMetalPipelineStorageMagic = 0x4C544D58; // 'XMTL' +constexpr uint32_t kGeneratedStageGeometryVertex = 0x100; +constexpr uint32_t kGeneratedStageGeometryShader = 0x101; +constexpr uint32_t kGeneratedStageTessellationVertex = 0x200; +constexpr uint32_t kGeneratedStageTessellationHull = 0x201; +constexpr uint32_t kGeneratedStageTessellationDomain = 0x202; + +} // namespace + +class MetalPipelineCache::GeneratedStageCache { + public: + struct GeometryVertexStageState { + MTL::Library* library = nullptr; + MTL::Library* stage_in_library = nullptr; + std::string function_name; + uint32_t vertex_output_size_in_bytes = 0; + }; + + struct GeometryShaderStageState { + MTL::Library* library = nullptr; + std::string function_name; + uint32_t max_input_primitives_per_mesh_threadgroup = 0; + std::vector function_constants; + }; + + struct TessellationVertexStageState { + MTL::Library* library = nullptr; + MTL::Library* stage_in_library = nullptr; + std::string function_name; + uint32_t vertex_output_size_in_bytes = 0; + }; + + struct TessellationHullStageState { + MTL::Library* library = nullptr; + std::string function_name; + MetalShaderReflectionInfo reflection; + }; + + struct TessellationDomainStageState { + MTL::Library* library = nullptr; + std::string function_name; + MetalShaderReflectionInfo reflection; + }; + + GeneratedStageCache(MetalPipelineCache& owner, MTL::Device* device, + DxbcToDxilConverter& dxbc_to_dxil_converter, + MetalShaderConverter& metal_shader_converter) + : owner_(owner), + device_(device), + dxbc_to_dxil_converter_(dxbc_to_dxil_converter), + metal_shader_converter_(metal_shader_converter) {} + + ~GeneratedStageCache() { + for (auto& pair : geometry_vertex_stage_cache_) { + if (pair.second.library) { + pair.second.library->release(); + } + if (pair.second.stage_in_library) { + pair.second.stage_in_library->release(); + } + } + for (auto& pair : geometry_shader_stage_cache_) { + if (pair.second.library) { + pair.second.library->release(); + } + } + for (auto& pair : tessellation_vertex_stage_cache_) { + if (pair.second.library) { + pair.second.library->release(); + } + if (pair.second.stage_in_library) { + pair.second.stage_in_library->release(); + } + } + for (auto& pair : tessellation_hull_stage_cache_) { + if (pair.second.library) { + pair.second.library->release(); + } + } + for (auto& pair : tessellation_domain_stage_cache_) { + if (pair.second.library) { + pair.second.library->release(); + } + } + } + + std::shared_ptr CompileStage( + MetalShaderStage stage, std::shared_ptr> dxil, + bool enable_geometry_emulation, int input_topology, + const IRVersionedInputLayoutDescriptor* input_layout = nullptr, + uint64_t stage_in_layout_key = 0, + std::function + stage_in_layout_builder = nullptr) { + MetalStageCompileRequest request; + request.stage = stage; + request.dxil_data = std::move(dxil); + request.enable_geometry_emulation = enable_geometry_emulation; + request.input_topology = input_topology; + request.input_layout = input_layout; + request.stage_in_layout_key = stage_in_layout_key; + request.stage_in_layout_builder = std::move(stage_in_layout_builder); + request.requested_outputs = kMetalStageCompileOutputMetallib; + if (input_layout || request.stage_in_layout_builder) { + request.requested_outputs |= kMetalStageCompileOutputStageInMetallib; + } + return metal_shader_converter_.CompileStage(request); + } + + GeometryVertexStageState* GetGeometryVertexStage( + MetalShader::MetalTranslation* vertex_translation, + GeometryShaderKey geometry_shader_key) { + auto vertex_it = geometry_vertex_stage_cache_.find(vertex_translation); + if (vertex_it != geometry_vertex_stage_cache_.end()) { + return &vertex_it->second; + } + + if (!owner_.EnsureDxilTranslationReady(vertex_translation, + "geometry vertex")) { + XELOGE("Geometry VS: failed to create a valid DXIL translation"); + return nullptr; + } + std::vector dxil_data = vertex_translation->GetDxilDataCopy(); + + IRInputTopology input_topology = + GetGeometryStageInputTopology(geometry_shader_key); + auto dxil = std::make_shared>(std::move(dxil_data)); + const uint64_t stage_in_key_data[] = { + vertex_translation->shader().ucode_data_hash(), + vertex_translation->modification(), uint64_t(geometry_shader_key.key)}; + uint64_t stage_in_layout_key = + XXH3_64bits(stage_in_key_data, sizeof(stage_in_key_data)); + auto input_layout = std::make_shared(); + std::shared_ptr vertex_result = CompileStage( + MetalShaderStage::kVertex, dxil, true, static_cast(input_topology), + nullptr, stage_in_layout_key, + [input_layout, + vertex_translation](const MetalShaderReflectionInfo& reflection) + -> const IRVersionedInputLayoutDescriptor* { + *input_layout = BuildStageInInputLayout(vertex_translation->shader(), + reflection, "Geometry VS"); + return &input_layout->descriptor; + }); + if (!vertex_result || !vertex_result->success) { + XELOGE("Geometry VS: DXIL to Metal conversion failed: {}", + vertex_result ? vertex_result->error_message : "unknown error"); + return nullptr; + } + const MetalShaderReflectionInfo& vertex_reflection = + vertex_result->reflection; + if (vertex_result->stage_in_metallib.empty()) { + XELOGE( + "Geometry VS: Failed to synthesize stage-in function " + "(vertex_inputs={}, output_size={})", + vertex_reflection.vertex_input_count, + vertex_reflection.vertex_output_size_in_bytes); + return nullptr; + } + + MTL::Library* vertex_library = + owner_.NewLibraryFromBytes(vertex_result->metallib_data, "Geometry VS"); + if (!vertex_library) { + return nullptr; + } + MTL::Library* stage_in_library = owner_.NewLibraryFromBytes( + vertex_result->stage_in_metallib, "Geometry VS stage-in"); + if (!stage_in_library) { + vertex_library->release(); + return nullptr; + } + + GeometryVertexStageState state; + state.library = vertex_library; + state.stage_in_library = stage_in_library; + state.function_name = vertex_result->function_name; + state.vertex_output_size_in_bytes = + vertex_reflection.vertex_output_size_in_bytes; + if (state.vertex_output_size_in_bytes == 0) { + XELOGE( + "Geometry VS: reflection returned zero output size " + "(vertex_inputs={})", + vertex_reflection.vertex_input_count); + } + + auto [inserted_it, inserted] = geometry_vertex_stage_cache_.emplace( + vertex_translation, std::move(state)); + return &inserted_it->second; + } + + GeometryShaderStageState* GetGeometryShaderStage( + GeometryShaderKey geometry_shader_key) { + auto geom_it = geometry_shader_stage_cache_.find(geometry_shader_key); + if (geom_it != geometry_shader_stage_cache_.end()) { + return &geom_it->second; + } + + const std::vector& dxbc_dwords = + GetGeometryShader(geometry_shader_key); + std::vector dxbc_bytes(dxbc_dwords.size() * sizeof(uint32_t)); + std::memcpy(dxbc_bytes.data(), dxbc_dwords.data(), dxbc_bytes.size()); + + std::vector dxil_data; + std::string dxil_error; + if (!owner_.ConvertDxbcToDxil(dxbc_bytes, dxil_data, &dxil_error)) { + XELOGE("Geometry GS: DXBC to DXIL conversion failed: {}", dxil_error); + return nullptr; + } + + IRInputTopology input_topology = + GetGeometryStageInputTopology(geometry_shader_key); + std::shared_ptr geometry_result = + CompileStage( + MetalShaderStage::kGeometry, + std::make_shared>(std::move(dxil_data)), true, + static_cast(input_topology)); + if (!geometry_result || !geometry_result->success) { + XELOGE( + "Geometry GS: DXIL to Metal conversion failed: {}", + geometry_result ? geometry_result->error_message : "unknown error"); + return nullptr; + } + const MetalShaderReflectionInfo& geometry_reflection = + geometry_result->reflection; + if (!geometry_result->has_mesh_stage && + !geometry_result->has_geometry_stage) { + XELOGE( + "Geometry GS: MSC did not emit mesh or geometry stage (mesh={}, " + "geometry={})", + geometry_result->has_mesh_stage, geometry_result->has_geometry_stage); + return nullptr; + } + if (!geometry_result->has_mesh_stage) { + static bool mesh_missing_logged = false; + if (!mesh_missing_logged) { + mesh_missing_logged = true; + XELOGW( + "Geometry GS: MSC did not emit mesh stage; using geometry stage " + "library"); + } + } + + MTL::Library* geometry_library = owner_.NewLibraryFromBytes( + geometry_result->metallib_data, "Geometry GS"); + if (!geometry_library) { + return nullptr; + } + + GeometryShaderStageState state; + state.library = geometry_library; + state.function_name = geometry_result->function_name; + state.max_input_primitives_per_mesh_threadgroup = + geometry_reflection.gs_max_input_primitives_per_mesh_threadgroup; + state.function_constants = geometry_reflection.function_constants; + if (state.max_input_primitives_per_mesh_threadgroup == 0) { + XELOGE("Geometry GS: reflection returned zero max input primitives"); + } + + auto [inserted_it, inserted] = geometry_shader_stage_cache_.emplace( + geometry_shader_key, std::move(state)); + return &inserted_it->second; + } + + TessellationVertexStageState* GetTessellationVertexStage( + MetalShader::MetalTranslation* domain_translation, + xenos::TessellationMode tessellation_mode) { + struct VertexStageKey { + const void* shader; + uint32_t tessellation_mode; + } vertex_key = {domain_translation, uint32_t(tessellation_mode)}; + uint64_t vertex_key_hash = XXH3_64bits(&vertex_key, sizeof(vertex_key)); + auto vertex_it = tessellation_vertex_stage_cache_.find(vertex_key_hash); + if (vertex_it != tessellation_vertex_stage_cache_.end()) { + return &vertex_it->second; + } + + const uint64_t stable_vertex_key[] = { + domain_translation->shader().ucode_data_hash(), + domain_translation->modification(), uint64_t(tessellation_mode)}; + uint64_t stage_in_layout_key = + XXH3_64bits(stable_vertex_key, sizeof(stable_vertex_key)); + + const uint8_t* vs_bytes = nullptr; + size_t vs_size = 0; + if (tessellation_mode == xenos::TessellationMode::kAdaptive) { + vs_bytes = ::tessellation_adaptive_vs; + vs_size = sizeof(::tessellation_adaptive_vs); + } else { + vs_bytes = ::tessellation_indexed_vs; + vs_size = sizeof(::tessellation_indexed_vs); + } + std::vector dxbc_bytes(vs_bytes, vs_bytes + vs_size); + std::vector dxil_data; + std::string dxil_error; + if (!owner_.ConvertDxbcToDxil(dxbc_bytes, dxil_data, &dxil_error)) { + XELOGE("Tessellation VS: DXBC to DXIL conversion failed: {}", dxil_error); + return nullptr; + } + + auto dxil = std::make_shared>(std::move(dxil_data)); + auto input_layout = std::make_shared(); + std::shared_ptr vertex_result = CompileStage( + MetalShaderStage::kVertex, dxil, true, + static_cast(IRInputTopologyUndefined), nullptr, + stage_in_layout_key, + [input_layout, + domain_translation](const MetalShaderReflectionInfo& reflection) + -> const IRVersionedInputLayoutDescriptor* { + *input_layout = BuildStageInInputLayout( + domain_translation->shader(), reflection, "Tessellation VS"); + return &input_layout->descriptor; + }); + if (!vertex_result || !vertex_result->success) { + XELOGE("Tessellation VS: DXIL to Metal conversion failed: {}", + vertex_result ? vertex_result->error_message : "unknown error"); + return nullptr; + } + const MetalShaderReflectionInfo& vertex_reflection = + vertex_result->reflection; + if (vertex_result->stage_in_metallib.empty()) { + XELOGE("Tessellation VS: Failed to synthesize stage-in function"); + return nullptr; + } + + MTL::Library* vertex_library = owner_.NewLibraryFromBytes( + vertex_result->metallib_data, "Tessellation VS"); + if (!vertex_library) { + return nullptr; + } + MTL::Library* stage_in_library = owner_.NewLibraryFromBytes( + vertex_result->stage_in_metallib, "Tessellation VS stage-in"); + if (!stage_in_library) { + vertex_library->release(); + return nullptr; + } + + TessellationVertexStageState state; + state.library = vertex_library; + state.stage_in_library = stage_in_library; + state.function_name = vertex_result->function_name; + state.vertex_output_size_in_bytes = + vertex_reflection.vertex_output_size_in_bytes; + if (state.vertex_output_size_in_bytes == 0) { + XELOGE("Tessellation VS: reflection returned zero output size"); + } + + auto [inserted_it, inserted] = tessellation_vertex_stage_cache_.emplace( + vertex_key_hash, std::move(state)); + return &inserted_it->second; + } + + TessellationHullStageState* GetTessellationHullStage( + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + xenos::TessellationMode tessellation_mode) { + struct HullStageKey { + uint32_t host_vs_type; + uint32_t tessellation_mode; + } hull_key = {uint32_t(primitive_processing_result.host_vertex_shader_type), + uint32_t(tessellation_mode)}; + uint64_t hull_key_hash = XXH3_64bits(&hull_key, sizeof(hull_key)); + auto hull_it = tessellation_hull_stage_cache_.find(hull_key_hash); + if (hull_it != tessellation_hull_stage_cache_.end()) { + return &hull_it->second; + } + + const uint8_t* hs_bytes = nullptr; + size_t hs_size = 0; + switch (tessellation_mode) { + case xenos::TessellationMode::kDiscrete: + switch (primitive_processing_result.host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: + hs_bytes = ::discrete_triangle_3cp_hs; + hs_size = sizeof(::discrete_triangle_3cp_hs); + break; + case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: + hs_bytes = ::discrete_triangle_1cp_hs; + hs_size = sizeof(::discrete_triangle_1cp_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainCPIndexed: + hs_bytes = ::discrete_quad_4cp_hs; + hs_size = sizeof(::discrete_quad_4cp_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: + hs_bytes = ::discrete_quad_1cp_hs; + hs_size = sizeof(::discrete_quad_1cp_hs); + break; + default: + XELOGE( + "Tessellation HS: unsupported host vertex shader type {}", + uint32_t(primitive_processing_result.host_vertex_shader_type)); + return nullptr; + } + break; + case xenos::TessellationMode::kContinuous: + switch (primitive_processing_result.host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainCPIndexed: + hs_bytes = ::continuous_triangle_3cp_hs; + hs_size = sizeof(::continuous_triangle_3cp_hs); + break; + case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: + hs_bytes = ::continuous_triangle_1cp_hs; + hs_size = sizeof(::continuous_triangle_1cp_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainCPIndexed: + hs_bytes = ::continuous_quad_4cp_hs; + hs_size = sizeof(::continuous_quad_4cp_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: + hs_bytes = ::continuous_quad_1cp_hs; + hs_size = sizeof(::continuous_quad_1cp_hs); + break; + default: + XELOGE( + "Tessellation HS: unsupported host vertex shader type {}", + uint32_t(primitive_processing_result.host_vertex_shader_type)); + return nullptr; + } + break; + case xenos::TessellationMode::kAdaptive: + switch (primitive_processing_result.host_vertex_shader_type) { + case Shader::HostVertexShaderType::kTriangleDomainPatchIndexed: + hs_bytes = ::adaptive_triangle_hs; + hs_size = sizeof(::adaptive_triangle_hs); + break; + case Shader::HostVertexShaderType::kQuadDomainPatchIndexed: + hs_bytes = ::adaptive_quad_hs; + hs_size = sizeof(::adaptive_quad_hs); + break; + default: + XELOGE( + "Tessellation HS: unsupported host vertex shader type {}", + uint32_t(primitive_processing_result.host_vertex_shader_type)); + return nullptr; + } + break; + default: + XELOGE("Tessellation HS: unsupported tessellation mode {}", + uint32_t(tessellation_mode)); + return nullptr; + } + + std::vector dxbc_bytes(hs_bytes, hs_bytes + hs_size); + std::vector dxil_data; + std::string dxil_error; + if (!owner_.ConvertDxbcToDxil(dxbc_bytes, dxil_data, &dxil_error)) { + XELOGE("Tessellation HS: DXBC to DXIL conversion failed: {}", dxil_error); + return nullptr; + } + + std::shared_ptr hull_result = CompileStage( + MetalShaderStage::kHull, + std::make_shared>(std::move(dxil_data)), true, + static_cast(IRInputTopologyUndefined)); + if (!hull_result || !hull_result->success) { + XELOGE("Tessellation HS: DXIL to Metal conversion failed: {}", + hull_result ? hull_result->error_message : "unknown error"); + return nullptr; + } + + MTL::Library* hull_library = owner_.NewLibraryFromBytes( + hull_result->metallib_data, "Tessellation HS"); + if (!hull_library) { + return nullptr; + } + + TessellationHullStageState state; + state.library = hull_library; + state.function_name = hull_result->function_name; + state.reflection = hull_result->reflection; + if (!state.reflection.has_hull_info) { + XELOGE("Tessellation HS: reflection missing hull info"); + } + + auto [inserted_it, inserted] = + tessellation_hull_stage_cache_.emplace(hull_key_hash, std::move(state)); + return &inserted_it->second; + } + + TessellationDomainStageState* GetTessellationDomainStage( + MetalShader::MetalTranslation* domain_translation) { + uint64_t domain_key = + XXH3_64bits(&domain_translation, sizeof(domain_translation)); + auto domain_it = tessellation_domain_stage_cache_.find(domain_key); + if (domain_it != tessellation_domain_stage_cache_.end()) { + return &domain_it->second; + } + + if (!owner_.EnsureDxilTranslationReady(domain_translation, + "tessellation domain")) { + XELOGE("Tessellation DS: failed to create a valid DXIL translation"); + return nullptr; + } + std::vector dxil_data = domain_translation->GetDxilDataCopy(); + + std::shared_ptr domain_result = CompileStage( + MetalShaderStage::kDomain, + std::make_shared>(std::move(dxil_data)), true, + static_cast(IRInputTopologyUndefined)); + if (!domain_result || !domain_result->success) { + XELOGE("Tessellation DS: DXIL to Metal conversion failed: {}", + domain_result ? domain_result->error_message : "unknown error"); + return nullptr; + } + + MTL::Library* domain_library = owner_.NewLibraryFromBytes( + domain_result->metallib_data, "Tessellation DS"); + if (!domain_library) { + return nullptr; + } + + TessellationDomainStageState state; + state.library = domain_library; + state.function_name = domain_result->function_name; + state.reflection = domain_result->reflection; + if (!state.reflection.has_domain_info) { + XELOGE("Tessellation DS: reflection missing domain info"); + } + + auto [inserted_it, inserted] = + tessellation_domain_stage_cache_.emplace(domain_key, std::move(state)); + return &inserted_it->second; + } + + private: + MetalPipelineCache& owner_; + MTL::Device* device_ = nullptr; + DxbcToDxilConverter& dxbc_to_dxil_converter_; + MetalShaderConverter& metal_shader_converter_; + + std::unordered_map + geometry_vertex_stage_cache_; + std::unordered_map + geometry_shader_stage_cache_; + std::unordered_map + tessellation_vertex_stage_cache_; + std::unordered_map + tessellation_hull_stage_cache_; + std::unordered_map + tessellation_domain_stage_cache_; +}; + +// --------------------------------------------------------------------------- +// ResolvePipelineRenderingKey (shared fixed-function key derivation) +// --------------------------------------------------------------------------- + +PipelineRenderingKey ResolvePipelineRenderingKey( + const RegisterFile& regs, + const MetalShader::MetalTranslation* pixel_translation, + bool use_fallback_pixel_shader) { + PipelineRenderingKey key = {}; + + uint32_t pixel_shader_writes_color_targets = + (use_fallback_pixel_shader || !pixel_translation) + ? 0 + : pixel_translation->shader().writes_color_targets(); + key.normalized_color_mask = pixel_shader_writes_color_targets + ? draw_util::GetNormalizedColorMask( + regs, pixel_shader_writes_color_targets) + : 0; + // Xenos alpha-to-mask is implemented by the translated pixel shader via + // OMask / sample-mask output. Enabling Metal fixed-function alpha-to-coverage + // would apply a second coverage mask on top of the shader result. + key.alpha_to_mask_enable = 0; + for (uint32_t i = 0; i < 4; ++i) { + key.blendcontrol[i] = regs.Get( + reg::RB_BLENDCONTROL::rt_register_indices[i]) + .value; + } + return key; +} + +// --------------------------------------------------------------------------- +// Construction / destruction +// --------------------------------------------------------------------------- + +MetalPipelineCache::MetalPipelineCache(MTL::Device* device, + const RegisterFile& register_file) + : device_(device), register_file_(register_file) {} + +MetalPipelineCache::~MetalPipelineCache() { + // Shut down async pipeline creation threads. + { + std::lock_guard lock(creation_request_lock_); + creation_threads_shutdown_ = true; + } + creation_request_cond_.notify_all(); + for (auto& thread : creation_threads_) { + if (thread.joinable()) { + thread.join(); + } + } + creation_threads_.clear(); + + // Release MSC pipeline caches. + for (auto& pair : pipeline_cache_) { + if (pair.second) { + auto* ps = pair.second->state.load(std::memory_order_relaxed); + if (ps) { + ps->release(); + } + } + } + pipeline_cache_.clear(); + + bindless_sampler_layout_map_.clear(); + bindless_sampler_layouts_.clear(); + texture_binding_layout_map_.clear(); + texture_binding_layouts_.clear(); + + for (auto& pair : geometry_pipeline_cache_) { + if (pair.second.pipeline) { + pair.second.pipeline->release(); + } + } + geometry_pipeline_cache_.clear(); + + for (auto& pair : tessellation_pipeline_cache_) { + if (pair.second.pipeline) { + pair.second.pipeline->release(); + } + } + tessellation_pipeline_cache_.clear(); + + generated_stages_.reset(); + + if (depth_only_pixel_library_) { + depth_only_pixel_library_->release(); + depth_only_pixel_library_ = nullptr; + } + depth_only_pixel_function_name_.clear(); + + ShutdownShaderStorage(); + + shader_cache_.clear(); + shader_translator_.reset(); + dxbc_to_dxil_converter_.reset(); + metal_shader_converter_.reset(); +} + +// --------------------------------------------------------------------------- +// Shader translation initialization +// --------------------------------------------------------------------------- + +bool MetalPipelineCache::InitializeShaderTranslation( + bool gamma_render_target_as_unorm8, bool msaa_2x_supported, + uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y) { + bool edram_rov_used = false; + generated_stages_.reset(); + + XELOGI( + "DxbcShaderTranslator init: gamma_as_unorm8={}, msaa_2x={}, scale={}x{}", + gamma_render_target_as_unorm8, msaa_2x_supported, draw_resolution_scale_x, + draw_resolution_scale_y); + + shader_translator_ = std::make_unique( + ui::GraphicsProvider::GpuVendorID::kApple, true, edram_rov_used, + gamma_render_target_as_unorm8, msaa_2x_supported, draw_resolution_scale_x, + draw_resolution_scale_y, + false); // force_emit_source_map + + dxbc_to_dxil_converter_ = std::make_unique(); + if (!dxbc_to_dxil_converter_->Initialize()) { + XELOGE("Failed to initialize DXBC to DXIL converter"); + return false; + } + + metal_shader_converter_ = std::make_unique(); + if (!metal_shader_converter_->Initialize()) { + XELOGE("Failed to initialize Metal Shader Converter"); + return false; + } + generated_stages_ = std::make_unique( + *this, device_, *dxbc_to_dxil_converter_, *metal_shader_converter_); + + // Configure MSC minimum targets. + if (device_) { + IRGPUFamily min_family = IRGPUFamilyMetal3; + if (device_->supportsFamily(MTL::GPUFamilyApple10)) { + min_family = IRGPUFamilyApple10; + } else if (device_->supportsFamily(MTL::GPUFamilyApple9)) { + min_family = IRGPUFamilyApple9; + } else if (device_->supportsFamily(MTL::GPUFamilyApple8)) { + min_family = IRGPUFamilyApple8; + } else if (device_->supportsFamily(MTL::GPUFamilyApple7)) { + min_family = IRGPUFamilyApple7; + } else if (device_->supportsFamily(MTL::GPUFamilyApple6)) { + min_family = IRGPUFamilyApple6; + } else if (device_->supportsFamily(MTL::GPUFamilyMac2) || + device_->supportsFamily(MTL::GPUFamilyMetal4) || + device_->supportsFamily(MTL::GPUFamilyMetal3)) { + min_family = IRGPUFamilyMetal3; + } + + NS::OperatingSystemVersion os_version = + NS::ProcessInfo::processInfo()->operatingSystemVersion(); + std::ostringstream version_stream; + version_stream << os_version.majorVersion << "." << os_version.minorVersion + << "." << os_version.patchVersion; + metal_shader_converter_->SetMinimumTarget( + min_family, IROperatingSystem_macOS, version_stream.str()); + } + + // Spawn async pipeline creation threads if enabled. + int32_t thread_count_config = cvars::metal_pipeline_creation_threads; + uint32_t logical_cores = std::thread::hardware_concurrency(); + uint32_t thread_count = 0; + if (thread_count_config < 0) { + thread_count = std::max(1u, logical_cores * 3 / 4); + } else { + thread_count = + std::min(static_cast(thread_count_config), logical_cores); + } + if (thread_count > 0 && cvars::async_shader_compilation) { + creation_threads_.reserve(thread_count); + for (uint32_t i = 0; i < thread_count; ++i) { + creation_threads_.emplace_back(&MetalPipelineCache::CreationThread, this, + i); + } + XELOGI("MetalPipelineCache: spawned {} async pipeline creation threads", + thread_count); + } + + return true; +} + +// --------------------------------------------------------------------------- +// Shader storage lifecycle +// --------------------------------------------------------------------------- + +void MetalPipelineCache::InitializeShaderStorage( + const std::filesystem::path& cache_root, uint32_t title_id, bool blocking) { + InitializeShaderStorageInternal(cache_root, title_id, blocking); +} + +bool MetalPipelineCache::InitializeShaderStorageInternal( + const std::filesystem::path& cache_root, uint32_t title_id, bool blocking) { + ShutdownShaderStorage(); + + if (!device_) { + XELOGW("Metal shader storage init skipped (no device)"); + return false; + } + + shader_storage_local_root_ = GetShaderStorageLocalRoot(cache_root) / "metal" / + GetShaderStorageDeviceTag() / + GetShaderStorageAbiTag(); + shader_storage_title_root_ = + shader_storage_local_root_ / fmt::format("{:08X}", title_id); + + std::error_code ec; + std::filesystem::create_directories(shader_storage_title_root_, ec); + if (ec) { + XELOGW("Metal shader storage: Failed to create {}: {}", + shader_storage_title_root_.string(), ec.message()); + return false; + } + + artifact_store_path_ = shader_storage_title_root_ / + fmt::format("{:08X}.metal.artifacts", title_id); + if (::cvars::metal_shader_disk_cache && g_metal_artifact_store) { + g_metal_artifact_store->Initialize(artifact_store_path_); + } + + ShaderStorageWriter::PipelineStorageConfig + pipeline_config; + pipeline_config.file_suffix = ".metal.xpso"; + pipeline_config.api_magic = kMetalPipelineStorageMagic; + pipeline_config.version = + std::max(MetalPipelineDescription::kVersion, + DxbcShaderTranslator::Modification::kVersion); + + uint32_t storage_index = storage_writer_.storage_index() + 1; + std::vector stored_descriptions; + if (!storage_writer_.InitializeShaderStorage( + cache_root, title_id, pipeline_config, + [&](xenos::ShaderType type, const uint32_t* ucode_dwords, + uint32_t ucode_dword_count, uint64_t ucode_data_hash) { + MetalShader* shader = LoadShader( + type, ucode_dwords, ucode_dword_count, ucode_data_hash); + if (shader->ucode_storage_index() == storage_index) { + return true; + } + shader->set_ucode_storage_index(storage_index); + return true; + }, + [&](const std::set> + & translations_needed) { + XELOGI( + "Metal shader storage: indexed {} cached shader " + "translations", + translations_needed.size()); + }, + stored_descriptions)) { + return false; + } + shader_storage_file_flush_needed_ = false; + pipeline_storage_file_flush_needed_ = false; + shader_storage_title_id_ = title_id; + { + std::lock_guard lock(stored_pipeline_mutex_); + stored_pipeline_hashes_.clear(); + for (const MetalPipelineStoredDescription& desc : stored_descriptions) { + stored_pipeline_hashes_.insert(desc.description_hash); + } + } + + const char* path_suffix = "rtv-bindless"; + std::string storage_suffix = + fmt::format("{}.{}", path_suffix, GetShaderStorageAbiTag()); + pipeline_binary_archive_path_ = + shader_storage_title_root_ / + fmt::format("{:08X}.{}.metal.binarchive", title_id, storage_suffix); + + if (::cvars::metal_pipeline_binary_archive) { + InitializePipelineBinaryArchive(pipeline_binary_archive_path_); + } + + bool prewarm_binary_archive = false; + { + std::lock_guard lock(pipeline_binary_archive_mutex_); + prewarm_binary_archive = pipeline_binary_archive_ != nullptr; + } + if (prewarm_binary_archive && !stored_descriptions.empty()) { + PrewarmStoredPipelines(stored_descriptions, blocking); + } + + return true; +} + +void MetalPipelineCache::ShutdownShaderStorage() { + { + std::lock_guard lock(pipeline_binary_archive_mutex_); + if (pipeline_binary_archive_) { + if (pipeline_binary_archive_dirty_) { + NS::String* path_string = + NS::String::string(pipeline_binary_archive_path_.string().c_str(), + NS::UTF8StringEncoding); + NS::URL* url = NS::URL::fileURLWithPath(path_string); + NS::Error* error = nullptr; + if (!pipeline_binary_archive_->serializeToURL(url, &error)) { + if (error) { + XELOGW("Metal binary archive serialize failed: {}", + error->localizedDescription()->utf8String()); + } + } + pipeline_binary_archive_dirty_ = false; + } + pipeline_binary_archive_->release(); + pipeline_binary_archive_ = nullptr; + } + } + storage_writer_.ShutdownShaderStorage(); + shader_storage_file_flush_needed_ = false; + pipeline_storage_file_flush_needed_ = false; + shader_storage_title_id_ = 0; + { + std::lock_guard lock(stored_pipeline_mutex_); + stored_pipeline_hashes_.clear(); + } + + if (g_metal_artifact_store) { + g_metal_artifact_store->Shutdown(); + } +} + +void MetalPipelineCache::EndSubmission() { + const bool flush_shaders = shader_storage_file_flush_needed_.exchange(false); + const bool flush_pipelines = + pipeline_storage_file_flush_needed_.exchange(false); + if (flush_shaders || flush_pipelines) { + storage_writer_.RequestFlush(flush_shaders, flush_pipelines); + } + if (!creation_threads_.empty()) { + creation_request_cond_.notify_one(); + } +} + +bool MetalPipelineCache::ConvertDxbcToDxil( + const std::vector& dxbc_data, std::vector& dxil_data, + std::string* error_message) { + dxil_convert_requests_.fetch_add(1, std::memory_order_relaxed); + dxil_convert_dxbc_bytes_.fetch_add(dxbc_data.size(), + std::memory_order_relaxed); + MetalDxilBytecodeKey dxil_key = {}; + if (!dxbc_data.empty() && dxbc_to_dxil_converter_) { + XXH128_hash_t dxbc_hash = XXH3_128bits(dxbc_data.data(), dxbc_data.size()); + dxil_key.dxbc_size = dxbc_data.size(); + dxil_key.dxbc_hash_low = dxbc_hash.low64; + dxil_key.dxbc_hash_high = dxbc_hash.high64; + dxil_key.converter_options_hash = dxbc_to_dxil_converter_->GetOptionsHash(); + dxil_key.converter_version = DxbcToDxilConverter::kCacheVersion; + if (::cvars::metal_shader_disk_cache && g_metal_artifact_store && + g_metal_artifact_store->LoadDxilBytecode(dxil_key, &dxil_data)) { + dxil_cache_hits_.fetch_add(1, std::memory_order_relaxed); + dxil_convert_dxil_bytes_.fetch_add(dxil_data.size(), + std::memory_order_relaxed); + return true; + } + } + dxil_cache_misses_.fetch_add(1, std::memory_order_relaxed); + + auto start = std::chrono::steady_clock::now(); + bool ok = dxbc_to_dxil_converter_ && dxbc_to_dxil_converter_->Convert( + dxbc_data, dxil_data, error_message); + auto end = std::chrono::steady_clock::now(); + uint64_t ms = ElapsedMs(start, end); + dxil_convert_ms_total_.fetch_add(ms, std::memory_order_relaxed); + AtomicMax(dxil_convert_ms_max_, ms); + if (ok) { + dxil_convert_dxil_bytes_.fetch_add(dxil_data.size(), + std::memory_order_relaxed); + if (::cvars::metal_shader_disk_cache && g_metal_artifact_store && + !dxbc_data.empty()) { + g_metal_artifact_store->StoreDxilBytecode(dxil_key, dxil_data); + } + } else { + dxil_convert_failures_.fetch_add(1, std::memory_order_relaxed); + } + return ok; +} + +MTL::Library* MetalPipelineCache::NewLibraryFromBytes( + const std::vector& bytes, const char* label) { + if (!device_ || bytes.empty()) { + RecordLibraryCreation(bytes.size(), false, 0); + return nullptr; + } + NS::Error* error = nullptr; + dispatch_data_t data = dispatch_data_create( + bytes.data(), bytes.size(), nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); + auto start = std::chrono::steady_clock::now(); + MTL::Library* library = device_->newLibrary(data, &error); + auto end = std::chrono::steady_clock::now(); + dispatch_release(data); + RecordLibraryCreation(bytes.size(), library != nullptr, + ElapsedMs(start, end)); + if (!library) { + XELOGE( + "{}: Failed to create Metal library: {}", + label ? label : "Metal library", + error ? error->localizedDescription()->utf8String() : "unknown error"); + } + return library; +} + +void MetalPipelineCache::RecordLibraryCreation(size_t byte_count, bool success, + uint64_t ms) { + library_requests_.fetch_add(1, std::memory_order_relaxed); + library_bytes_.fetch_add(byte_count, std::memory_order_relaxed); + if (!success) { + library_failures_.fetch_add(1, std::memory_order_relaxed); + } + library_ms_total_.fetch_add(ms, std::memory_order_relaxed); + AtomicMax(library_ms_max_, ms); +} + +void MetalPipelineCache::RecordRenderPipelineCreation(bool success, + uint64_t ms) { + render_pipeline_requests_.fetch_add(1, std::memory_order_relaxed); + if (!success) { + render_pipeline_failures_.fetch_add(1, std::memory_order_relaxed); + } + render_pipeline_ms_total_.fetch_add(ms, std::memory_order_relaxed); + AtomicMax(render_pipeline_ms_max_, ms); +} + +std::string MetalPipelineCache::GetShaderStorageDeviceTag() const { + std::string tag = "unknown"; + if (device_ && device_->name()) { + tag = device_->name()->utf8String(); + } + + for (char& ch : tag) { + if (!std::isalnum(static_cast(ch))) { + ch = '_'; + } + } + return tag; +} + +std::string MetalPipelineCache::GetShaderStorageAbiTag() const { + return fmt::format("msc{:08X}-ps{:08X}-sh{:08X}", + DxbcShaderTranslator::Modification::kVersion, + MetalPipelineDescription::kVersion, + MetalArtifactStore::kStorageVersion); +} + +// --------------------------------------------------------------------------- +// Pipeline binary archive +// --------------------------------------------------------------------------- + +bool MetalPipelineCache::InitializePipelineBinaryArchive( + const std::filesystem::path& archive_path) { + if (!device_) { + return false; + } + std::lock_guard lock(pipeline_binary_archive_mutex_); + if (pipeline_binary_archive_) { + pipeline_binary_archive_->release(); + pipeline_binary_archive_ = nullptr; + } + + MTL::BinaryArchiveDescriptor* desc = + MTL::BinaryArchiveDescriptor::alloc()->init(); + std::error_code ec; + bool archive_exists = std::filesystem::exists(archive_path, ec); + if (ec) { + XELOGW("Metal binary archive existence check failed for {}: {}", + archive_path.string(), ec.message()); + archive_exists = false; + } + if (archive_exists) { + NS::String* path_string = NS::String::string(archive_path.string().c_str(), + NS::UTF8StringEncoding); + NS::URL* url = NS::URL::fileURLWithPath(path_string); + desc->setUrl(url); + } + + NS::Error* error = nullptr; + pipeline_binary_archive_ = device_->newBinaryArchive(desc, &error); + if (!pipeline_binary_archive_ && archive_exists) { + if (error) { + XELOGW( + "Metal binary archive load failed for existing file {}; retrying " + "with a fresh archive: {}", + archive_path.string(), error->localizedDescription()->utf8String()); + } + desc->setUrl(nullptr); + error = nullptr; + pipeline_binary_archive_ = device_->newBinaryArchive(desc, &error); + } + desc->release(); + if (!pipeline_binary_archive_) { + if (error) { + XELOGW("Metal binary archive init failed: {}", + error->localizedDescription()->utf8String()); + } + return false; + } + pipeline_binary_archive_path_ = archive_path; + pipeline_binary_archive_dirty_ = false; + return true; +} + +void MetalPipelineCache::SerializePipelineBinaryArchive() { + std::lock_guard lock(pipeline_binary_archive_mutex_); + if (!pipeline_binary_archive_ || !pipeline_binary_archive_dirty_) { + return; + } + NS::String* path_string = NS::String::string( + pipeline_binary_archive_path_.string().c_str(), NS::UTF8StringEncoding); + NS::URL* url = NS::URL::fileURLWithPath(path_string); + NS::Error* error = nullptr; + if (!pipeline_binary_archive_->serializeToURL(url, &error)) { + if (error) { + XELOGW("Metal binary archive serialize failed: {}", + error->localizedDescription()->utf8String()); + } + } + pipeline_binary_archive_dirty_ = false; +} + +void MetalPipelineCache::PrewarmStoredPipelines( + const std::vector& descriptions, + bool blocking) { + if (descriptions.empty()) { + return; + } + size_t queued = 0; + size_t skipped = 0; + for (const MetalPipelineStoredDescription& stored : descriptions) { + const MetalPipelineDescription& desc = stored.description; + auto vs_it = shader_cache_.find(desc.vertex_shader_hash); + if (vs_it == shader_cache_.end()) { + ++skipped; + continue; + } + auto* vertex_translation = static_cast( + vs_it->second->GetOrCreateTranslation(desc.vertex_shader_modification)); + if (!vertex_translation || + !EnsureMetalTranslationReady(vertex_translation)) { + ++skipped; + continue; + } + + MetalShader::MetalTranslation* pixel_translation = nullptr; + if (desc.pixel_shader_hash) { + auto ps_it = shader_cache_.find(desc.pixel_shader_hash); + if (ps_it == shader_cache_.end()) { + ++skipped; + continue; + } + pixel_translation = static_cast( + ps_it->second->GetOrCreateTranslation( + desc.pixel_shader_modification)); + if (!pixel_translation || + !EnsureMetalTranslationReady(pixel_translation)) { + ++skipped; + continue; + } + } + + PipelineAttachmentFormats formats = {}; + formats.sample_count = desc.sample_count; + formats.depth_format = static_cast(desc.depth_format); + formats.stencil_format = static_cast(desc.stencil_format); + for (uint32_t i = 0; i < 4; ++i) { + formats.color_formats[i] = + static_cast(desc.color_formats[i]); + } + PipelineRenderingKey rendering = {}; + rendering.normalized_color_mask = desc.normalized_color_mask; + rendering.alpha_to_mask_enable = desc.alpha_to_mask_enable; + std::memcpy(rendering.blendcontrol, desc.blendcontrol, + sizeof(rendering.blendcontrol)); + + if (desc.kind == uint32_t(PipelineKind::kStandard)) { + PipelineHandle* handle = GetOrCreatePipelineState( + vertex_translation, pixel_translation, formats, rendering); + if (handle) { + ++queued; + } + } else if (desc.kind == uint32_t(PipelineKind::kGeometry)) { + GeometryShaderKey geometry_key; + geometry_key.key = uint32_t(desc.auxiliary_hash); + if (GetOrCreateGeometryPipelineState(vertex_translation, + pixel_translation, geometry_key, + formats, rendering)) { + ++queued; + } + } else if (desc.kind == uint32_t(PipelineKind::kTessellation)) { + PrimitiveProcessor::ProcessingResult primitive = {}; + primitive.host_vertex_shader_type = + Shader::HostVertexShaderType(desc.auxiliary0); + primitive.tessellation_mode = xenos::TessellationMode(desc.auxiliary1); + primitive.host_primitive_type = xenos::PrimitiveType(desc.auxiliary2); + if (GetOrCreateTessellationPipelineState(vertex_translation, + pixel_translation, primitive, + formats, rendering)) { + ++queued; + } + } else { + ++skipped; + } + } + if (blocking && !creation_threads_.empty()) { + while (IsCreatingPipelines()) { + creation_request_cond_.notify_one(); + std::this_thread::yield(); + } + } + XELOGI("Metal shader storage: queued {} warm pipelines, skipped {}", queued, + skipped); +} + +// --------------------------------------------------------------------------- +// LoadShader +// --------------------------------------------------------------------------- + +Shader* MetalPipelineCache::LoadShader(xenos::ShaderType shader_type, + const uint32_t* host_address, + uint32_t dword_count) { + uint64_t hash = XXH3_64bits(host_address, dword_count * sizeof(uint32_t)); + return LoadShader(shader_type, host_address, dword_count, hash); +} + +MetalShader* MetalPipelineCache::LoadShader(xenos::ShaderType shader_type, + const uint32_t* host_address, + uint32_t dword_count, + uint64_t hash) { + auto it = shader_cache_.find(hash); + if (it != shader_cache_.end()) { + return it->second.get(); + } + + auto shader = std::make_unique(shader_type, hash, host_address, + dword_count); + + MetalShader* result = shader.get(); + shader_cache_[hash] = std::move(shader); + + XELOGD("Loaded {} shader ({} dwords, hash {:016X})", + shader_type == xenos::ShaderType::kVertex ? "vertex" : "pixel", + dword_count, hash); + + return result; +} + +void MetalPipelineCache::SetupShaderBindingLayoutUserUIDs(MetalShader& shader) { + if (!shader.EnterBindingLayoutUserUIDSetup()) { + return; + } + + const std::vector& texture_bindings = + shader.GetTextureBindingsAfterTranslation(); + const size_t texture_binding_count = texture_bindings.size(); + const std::vector& sampler_bindings = + shader.GetSamplerBindingsAfterTranslation(); + const size_t sampler_binding_count = sampler_bindings.size(); + + const size_t texture_binding_layout_bytes = + texture_binding_count * sizeof(*texture_bindings.data()); + uint64_t texture_binding_layout_hash = 0; + if (texture_binding_count) { + texture_binding_layout_hash = + XXH3_64bits(texture_bindings.data(), texture_binding_layout_bytes); + } + + uint64_t bindless_sampler_layout_hash = 0; + if (sampler_binding_count) { + XXH3_state_t hash_state; + XXH3_64bits_reset(&hash_state); + for (size_t i = 0; i < sampler_binding_count; ++i) { + XXH3_64bits_update(&hash_state, + &sampler_bindings[i].bindless_descriptor_index, + sizeof(sampler_bindings[i].bindless_descriptor_index)); + } + bindless_sampler_layout_hash = XXH3_64bits_digest(&hash_state); + } + + size_t texture_binding_layout_uid = kLayoutUIDEmpty; + size_t sampler_binding_layout_uid = kLayoutUIDEmpty; + if (texture_binding_count || sampler_binding_count) { + std::lock_guard layouts_lock(layouts_mutex_); + if (texture_binding_count) { + auto found_range = + texture_binding_layout_map_.equal_range(texture_binding_layout_hash); + for (auto it = found_range.first; it != found_range.second; ++it) { + if (it->second.vector_span_length == texture_binding_count && + !std::memcmp( + texture_binding_layouts_.data() + it->second.vector_span_offset, + texture_bindings.data(), texture_binding_layout_bytes)) { + texture_binding_layout_uid = it->second.uid; + break; + } + } + if (texture_binding_layout_uid == kLayoutUIDEmpty) { + static_assert( + kLayoutUIDEmpty == 0, + "Layout UID is size + 1 because 0 is the empty layout UID"); + texture_binding_layout_uid = texture_binding_layout_map_.size() + 1; + LayoutUID new_uid; + new_uid.uid = texture_binding_layout_uid; + new_uid.vector_span_offset = texture_binding_layouts_.size(); + new_uid.vector_span_length = texture_binding_count; + texture_binding_layouts_.resize(new_uid.vector_span_offset + + texture_binding_count); + std::memcpy( + texture_binding_layouts_.data() + new_uid.vector_span_offset, + texture_bindings.data(), texture_binding_layout_bytes); + texture_binding_layout_map_.emplace(texture_binding_layout_hash, + new_uid); + } + } + + if (sampler_binding_count) { + auto found_range = bindless_sampler_layout_map_.equal_range( + bindless_sampler_layout_hash); + for (auto it = found_range.first; it != found_range.second; ++it) { + if (it->second.vector_span_length != sampler_binding_count) { + continue; + } + sampler_binding_layout_uid = it->second.uid; + const uint32_t* vector_bindless_sampler_layout = + bindless_sampler_layouts_.data() + it->second.vector_span_offset; + for (size_t i = 0; i < sampler_binding_count; ++i) { + if (vector_bindless_sampler_layout[i] != + sampler_bindings[i].bindless_descriptor_index) { + sampler_binding_layout_uid = kLayoutUIDEmpty; + break; + } + } + if (sampler_binding_layout_uid != kLayoutUIDEmpty) { + break; + } + } + if (sampler_binding_layout_uid == kLayoutUIDEmpty) { + static_assert( + kLayoutUIDEmpty == 0, + "Layout UID is size + 1 because 0 is the empty layout UID"); + LayoutUID new_uid; + new_uid.uid = bindless_sampler_layout_map_.size() + 1; + sampler_binding_layout_uid = new_uid.uid; + new_uid.vector_span_offset = bindless_sampler_layouts_.size(); + new_uid.vector_span_length = sampler_binding_count; + bindless_sampler_layouts_.resize(new_uid.vector_span_offset + + sampler_binding_count); + uint32_t* vector_bindless_sampler_layout = + bindless_sampler_layouts_.data() + new_uid.vector_span_offset; + for (size_t i = 0; i < sampler_binding_count; ++i) { + vector_bindless_sampler_layout[i] = + sampler_bindings[i].bindless_descriptor_index; + } + bindless_sampler_layout_map_.emplace(bindless_sampler_layout_hash, + new_uid); + } + } + } + + shader.SetTextureBindingLayoutUserUID(texture_binding_layout_uid); + shader.SetSamplerBindingLayoutUserUID(sampler_binding_layout_uid); +} + +// --------------------------------------------------------------------------- +// EnsureDepthOnlyPixelShader +// --------------------------------------------------------------------------- + +bool MetalPipelineCache::EnsureDepthOnlyPixelShader() { + if (depth_only_pixel_library_) { + return true; + } + if (!shader_translator_ || !dxbc_to_dxil_converter_ || + !metal_shader_converter_) { + XELOGE("Depth-only PS: shader translation not initialized"); + return false; + } + + std::vector dxbc_data = + shader_translator_->CreateDepthOnlyPixelShader(); + if (dxbc_data.empty()) { + XELOGE("Depth-only PS: failed to create DXBC"); + return false; + } + + std::vector dxil_data; + std::string dxil_error; + if (!ConvertDxbcToDxil(dxbc_data, dxil_data, &dxil_error)) { + XELOGE("Depth-only PS: DXBC to DXIL conversion failed: {}", dxil_error); + return false; + } + + MetalShaderConversionResult result; + if (!metal_shader_converter_->ConvertWithStage(MetalShaderStage::kFragment, + dxil_data, result)) { + XELOGE("Depth-only PS: DXIL to Metal conversion failed: {}", + result.error_message); + return false; + } + + depth_only_pixel_library_ = + NewLibraryFromBytes(result.metallib_data, "Depth-only PS"); + if (!depth_only_pixel_library_) { + return false; + } + depth_only_pixel_function_name_ = result.function_name; + if (depth_only_pixel_function_name_.empty()) { + XELOGE("Depth-only PS: missing function name"); + return false; + } + return true; +} + +// --------------------------------------------------------------------------- +// Shader modification selection +// --------------------------------------------------------------------------- + +DxbcShaderTranslator::Modification +MetalPipelineCache::GetCurrentVertexShaderModification( + const Shader& shader, Shader::HostVertexShaderType host_vertex_shader_type, + uint32_t interpolator_mask) const { + const auto& regs = register_file_; + + DxbcShaderTranslator::Modification modification( + shader_translator_->GetDefaultVertexShaderModification( + shader.GetDynamicAddressableRegisterCount( + regs.Get().vs_num_reg), + host_vertex_shader_type)); + + modification.vertex.interpolator_mask = interpolator_mask; + + auto pa_cl_clip_cntl = regs.Get(); + uint32_t user_clip_planes = + pa_cl_clip_cntl.clip_disable ? 0 : pa_cl_clip_cntl.ucp_ena; + modification.vertex.user_clip_plane_count = xe::bit_count(user_clip_planes); + modification.vertex.user_clip_plane_cull = + uint32_t(user_clip_planes && pa_cl_clip_cntl.ucp_cull_only_ena); + modification.vertex.vertex_kill_and = + uint32_t((shader.writes_point_size_edge_flag_kill_vertex() & 0b100) && + !pa_cl_clip_cntl.vtx_kill_or); + + modification.vertex.output_point_size = + uint32_t((shader.writes_point_size_edge_flag_kill_vertex() & 0b001) && + regs.Get().prim_type == + xenos::PrimitiveType::kPointList); + + return modification; +} + +DxbcShaderTranslator::Modification +MetalPipelineCache::GetCurrentPixelShaderModification( + const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, + reg::RB_DEPTHCONTROL normalized_depth_control) const { + const auto& regs = register_file_; + + DxbcShaderTranslator::Modification modification( + shader_translator_->GetDefaultPixelShaderModification( + shader.GetDynamicAddressableRegisterCount( + regs.Get().ps_num_reg))); + + modification.pixel.interpolator_mask = interpolator_mask; + modification.pixel.interpolators_centroid = + interpolator_mask & + ~xenos::GetInterpolatorSamplingPattern( + regs.Get().msaa_samples, + regs.Get().sc_sample_cntl, + regs.Get().sampling_pattern); + + if (param_gen_pos < xenos::kMaxInterpolators) { + modification.pixel.param_gen_enable = 1; + modification.pixel.param_gen_interpolator = param_gen_pos; + modification.pixel.param_gen_point = + uint32_t(regs.Get().prim_type == + xenos::PrimitiveType::kPointList); + } else { + modification.pixel.param_gen_enable = 0; + modification.pixel.param_gen_interpolator = 0; + modification.pixel.param_gen_point = 0; + } + + using DepthStencilMode = DxbcShaderTranslator::Modification::DepthStencilMode; + if (cvars::depth_float24_convert_in_pixel_shader && + normalized_depth_control.z_enable && + regs.Get().depth_format == + xenos::DepthRenderTargetFormat::kD24FS8) { + modification.pixel.depth_stencil_mode = + cvars::depth_float24_round ? DepthStencilMode::kFloat24Rounding + : DepthStencilMode::kFloat24Truncating; + } else if (shader.implicit_early_z_write_allowed() && + (!shader.writes_color_target(0) || + !draw_util::DoesCoverageDependOnAlpha( + regs.Get()))) { + modification.pixel.depth_stencil_mode = DepthStencilMode::kEarlyHint; + } else { + modification.pixel.depth_stencil_mode = DepthStencilMode::kNoModifiers; + } + + // Check if MIN/MAX blend is used with non-trivial source factors. + // Fixed-function blend ignores factors for MIN/MAX, but Xbox 360 applies + // them. If the destination factor is ONE, we can pre-multiply the shader + // output by the source factor to emulate this. Only RT0 is supported. + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kOne; + modification.pixel.rt0_blend_a_factor_for_premult = xenos::BlendFactor::kOne; + + if (shader.writes_color_target(0)) { + auto blend_control = regs.Get( + reg::RB_BLENDCONTROL::rt_register_indices[0]); + + // Pre-multiply by kSrcAlpha for MIN/MAX blend ops when dstFactor is ONE. + if ((blend_control.color_comb_fcn == xenos::BlendOp::kMin || + blend_control.color_comb_fcn == xenos::BlendOp::kMax) && + blend_control.color_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.color_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_rgb_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + + if ((blend_control.alpha_comb_fcn == xenos::BlendOp::kMin || + blend_control.alpha_comb_fcn == xenos::BlendOp::kMax) && + blend_control.alpha_srcblend == xenos::BlendFactor::kSrcAlpha && + blend_control.alpha_destblend == xenos::BlendFactor::kOne) { + modification.pixel.rt0_blend_a_factor_for_premult = + xenos::BlendFactor::kSrcAlpha; + } + } + + return modification; +} + +// --------------------------------------------------------------------------- +// GetOrCreatePipelineState (standard render pipeline) +// --------------------------------------------------------------------------- + +MetalPipelineCache::MetalPipelineDescription +MetalPipelineCache::BuildPipelineDescription( + PipelineKind kind, MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const { + MetalPipelineDescription desc = {}; + desc.kind = uint32_t(kind); + desc.vertex_shader_hash = vertex_translation->shader().ucode_data_hash(); + desc.vertex_shader_modification = vertex_translation->modification(); + if (pixel_translation) { + desc.pixel_shader_hash = pixel_translation->shader().ucode_data_hash(); + desc.pixel_shader_modification = pixel_translation->modification(); + } + desc.sample_count = attachment_formats.sample_count; + desc.depth_format = uint32_t(attachment_formats.depth_format); + desc.stencil_format = uint32_t(attachment_formats.stencil_format); + for (uint32_t i = 0; i < 4; ++i) { + desc.color_formats[i] = uint32_t(attachment_formats.color_formats[i]); + } + desc.normalized_color_mask = rendering_key.normalized_color_mask; + desc.alpha_to_mask_enable = rendering_key.alpha_to_mask_enable; + std::memcpy(desc.blendcontrol, rendering_key.blendcontrol, + sizeof(desc.blendcontrol)); + return desc; +} + +MetalPipelineCache::MetalPipelineDescription +MetalPipelineCache::BuildStandardPipelineDescription( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const { + return BuildPipelineDescription(PipelineKind::kStandard, vertex_translation, + pixel_translation, attachment_formats, + rendering_key); +} + +MetalPipelineCache::MetalPipelineDescription +MetalPipelineCache::BuildGeometryPipelineDescription( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + GeometryShaderKey geometry_shader_key, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const { + MetalPipelineDescription desc = BuildPipelineDescription( + PipelineKind::kGeometry, vertex_translation, pixel_translation, + attachment_formats, rendering_key); + desc.auxiliary_hash = geometry_shader_key.key; + return desc; +} + +MetalPipelineCache::MetalPipelineDescription +MetalPipelineCache::BuildTessellationPipelineDescription( + MetalShader::MetalTranslation* domain_translation, + MetalShader::MetalTranslation* pixel_translation, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + xenos::TessellationMode tessellation_mode, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const { + MetalPipelineDescription desc = BuildPipelineDescription( + PipelineKind::kTessellation, domain_translation, pixel_translation, + attachment_formats, rendering_key); + desc.auxiliary0 = + uint32_t(primitive_processing_result.host_vertex_shader_type); + desc.auxiliary1 = uint32_t(tessellation_mode); + desc.auxiliary2 = uint32_t(primitive_processing_result.host_primitive_type); + return desc; +} + +void MetalPipelineCache::QueueStoredShader(MetalShader& shader) { + if (!storage_writer_.is_active()) { + return; + } + if (shader.try_set_ucode_storage_index(storage_writer_.storage_index())) { + shader_storage_file_flush_needed_ = true; + storage_writer_.QueueShaderWrite(&shader); + } +} + +void MetalPipelineCache::QueueStoredPipeline( + const MetalPipelineDescription& description, uint64_t description_hash) { + if (!storage_writer_.is_active()) { + return; + } + { + std::lock_guard lock(stored_pipeline_mutex_); + if (!stored_pipeline_hashes_.insert(description_hash).second) { + return; + } + } + MetalPipelineStoredDescription stored = {}; + stored.description_hash = description_hash; + std::memcpy(&stored.description, &description, sizeof(description)); + pipeline_storage_file_flush_needed_ = true; + storage_writer_.QueuePipelineWrite(stored); +} + +bool MetalPipelineCache::EnsureDxbcTranslationReadyLocked( + MetalShader::MetalTranslation* translation, const char* stage_name) { + auto dxbc_translation = + static_cast(translation); + if (translation->is_translated() && dxbc_translation->is_valid() && + !translation->translated_binary().empty()) { + return true; + } + + if (!shader_translator_->TranslateAnalyzedShader(*translation)) { + XELOGE("Failed to translate {} shader {:016X} to DXBC", + stage_name ? stage_name : "unknown", + translation->shader().ucode_data_hash()); + return false; + } + QueueStoredShader(static_cast(translation->shader())); + return true; +} + +bool MetalPipelineCache::EnsureDxilTranslationReady( + MetalShader::MetalTranslation* translation, const char* stage_name) { + if (!translation) { + return true; + } + if (!translation->GetDxilDataCopy().empty()) { + return true; + } + std::lock_guard lock(shader_translation_mutex_); + if (!translation->GetDxilDataCopy().empty()) { + return true; + } + if (!EnsureDxbcTranslationReadyLocked(translation, stage_name)) { + return false; + } + std::vector dxil_data; + std::string dxil_error; + if (!ConvertDxbcToDxil(translation->translated_binary(), dxil_data, + &dxil_error)) { + XELOGE("{} shader {:016X}: DXBC to DXIL conversion failed: {}", + stage_name ? stage_name : "guest", + translation->shader().ucode_data_hash(), dxil_error); + return false; + } + translation->SetDxilData(std::move(dxil_data)); + return true; +} + +bool MetalPipelineCache::EnsureMetalTranslationReady( + MetalShader::MetalTranslation* translation) { + if (!translation) { + return true; + } + if (translation->is_valid()) { + return true; + } + if (!EnsureDxilTranslationReady(translation, "guest")) { + return false; + } + + MetalShaderStage stage; + switch (translation->shader().type()) { + case xenos::ShaderType::kVertex: + stage = MetalShaderStage::kVertex; + break; + case xenos::ShaderType::kPixel: + stage = MetalShaderStage::kFragment; + break; + default: + XELOGE("Unsupported Metal shader type {}", + uint32_t(translation->shader().type())); + return false; + } + + MetalStageCompileRequest request; + request.stage = stage; + request.dxil_data = + std::make_shared>(translation->GetDxilDataCopy()); + request.requested_outputs = kMetalStageCompileOutputMetallib; + std::shared_ptr result = + metal_shader_converter_->CompileStage(request); + if (!result || !result->success) { + XELOGE("Failed to translate shader {:016X} to Metal: {}", + translation->shader().ucode_data_hash(), + result ? result->error_message : "unknown error"); + return false; + } + uint64_t library_ms = 0; + bool library_created = false; + bool installed = translation->InstallMetal(device_, *result, &library_ms, + &library_created); + if (library_created) { + RecordLibraryCreation(result->metallib_data.size(), + translation->metal_library() != nullptr, library_ms); + } + if (!installed) { + return false; + } + + return true; +} + +bool MetalPipelineCache::EnsureDxbcTranslationReady( + MetalShader::MetalTranslation* translation, const char* stage_name) { + if (!translation) { + return true; + } + std::lock_guard lock(shader_translation_mutex_); + return EnsureDxbcTranslationReadyLocked(translation, stage_name); +} + +MetalPipelineCache::PipelineHandle* +MetalPipelineCache::GetOrCreatePipelineState( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) { + if (!vertex_translation) { + XELOGE("No vertex shader translation"); + return nullptr; + } + MetalPipelineDescription description = BuildStandardPipelineDescription( + vertex_translation, pixel_translation, attachment_formats, rendering_key); + uint64_t key = XXH3_64bits(&description, sizeof(description)); + + // Check cache. + auto it = pipeline_cache_.find(key); + if (it != pipeline_cache_.end()) { + return it->second.get(); + } + + // Create a new handle. + auto handle = std::make_unique(); + handle->description_hash = key; + handle->description = description; + handle->pending_vertex_translation = vertex_translation; + handle->pending_pixel_translation = pixel_translation; + handle->pending_formats = attachment_formats; + handle->pending_normalized_color_mask = description.normalized_color_mask; + std::memcpy(handle->pending_blendcontrol, description.blendcontrol, + sizeof(description.blendcontrol)); + handle->pending_alpha_to_mask = (description.alpha_to_mask_enable != 0); + handle->storage_write_pending = true; + + PipelineHandle* raw_handle = handle.get(); + pipeline_cache_.emplace(key, std::move(handle)); + + // Async path: enqueue for background compilation. + if (cvars::async_shader_compilation && !creation_threads_.empty()) { + { + std::lock_guard lock(creation_request_lock_); + creation_queue_.push(PipelineCreationRequest{raw_handle}); + } + creation_request_cond_.notify_one(); + return raw_handle; + } + + // Synchronous path: create immediately. + MTL::RenderPipelineState* pipeline = CreatePipelineFromHandle(raw_handle); + raw_handle->state.store(pipeline, std::memory_order_release); + raw_handle->pending_vertex_translation = nullptr; + raw_handle->pending_pixel_translation = nullptr; + + if (!pipeline) { + return nullptr; + } + + if (raw_handle->storage_write_pending) { + QueueStoredShader(static_cast(vertex_translation->shader())); + if (pixel_translation) { + QueueStoredShader(static_cast(pixel_translation->shader())); + } + QueueStoredPipeline(raw_handle->description, raw_handle->description_hash); + raw_handle->storage_write_pending = false; + } + + return raw_handle; +} + +// --------------------------------------------------------------------------- +// CreatePipelineFromHandle (builds a MTL::RenderPipelineState from a handle) +// --------------------------------------------------------------------------- + +MTL::RenderPipelineState* MetalPipelineCache::CreatePipelineFromHandle( + const PipelineHandle* handle) { + MetalShader::MetalTranslation* vertex_translation = + handle->pending_vertex_translation; + MetalShader::MetalTranslation* pixel_translation = + handle->pending_pixel_translation; + const PipelineAttachmentFormats& formats = handle->pending_formats; + + if (!EnsureMetalTranslationReady(vertex_translation) || + !EnsureMetalTranslationReady(pixel_translation)) { + return nullptr; + } + + // Create pipeline descriptor. + MTL::RenderPipelineDescriptor* desc = + MTL::RenderPipelineDescriptor::alloc()->init(); + + desc->setVertexFunction(vertex_translation->metal_function()); + + if (pixel_translation && pixel_translation->metal_function()) { + desc->setFragmentFunction(pixel_translation->metal_function()); + } + + for (uint32_t i = 0; i < 4; ++i) { + desc->colorAttachments()->object(i)->setPixelFormat( + formats.color_formats[i]); + } + desc->setDepthAttachmentPixelFormat(formats.depth_format); + desc->setStencilAttachmentPixelFormat(formats.stencil_format); + desc->setSampleCount(formats.sample_count); + desc->setAlphaToCoverageEnabled(false); + + ApplyBlendStateToDescriptor(desc->colorAttachments(), + handle->pending_normalized_color_mask, + handle->pending_blendcontrol); + + // Configure vertex fetch layout for MSC stage-in. + const Shader& vertex_shader_ref = vertex_translation->shader(); + const auto& vertex_bindings = vertex_shader_ref.vertex_bindings(); + if (!ShaderUsesVertexFetch(vertex_shader_ref) && !vertex_bindings.empty()) { + auto map_vertex_format = + [](const ParsedVertexFetchInstruction::Attributes& attrs) + -> MTL::VertexFormat { + using xenos::VertexFormat; + switch (attrs.data_format) { + case VertexFormat::k_8_8_8_8: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatChar4 + : MTL::VertexFormatUChar4; + } + return attrs.is_signed ? MTL::VertexFormatChar4Normalized + : MTL::VertexFormatUChar4Normalized; + case VertexFormat::k_2_10_10_10: + return attrs.is_signed ? MTL::VertexFormatInt1010102Normalized + : MTL::VertexFormatUInt1010102Normalized; + case VertexFormat::k_10_11_11: + case VertexFormat::k_11_11_10: + return MTL::VertexFormatFloatRG11B10; + case VertexFormat::k_16_16: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatShort2 + : MTL::VertexFormatUShort2; + } + return attrs.is_signed ? MTL::VertexFormatShort2Normalized + : MTL::VertexFormatUShort2Normalized; + case VertexFormat::k_16_16_16_16: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatShort4 + : MTL::VertexFormatUShort4; + } + return attrs.is_signed ? MTL::VertexFormatShort4Normalized + : MTL::VertexFormatUShort4Normalized; + case VertexFormat::k_16_16_FLOAT: + return MTL::VertexFormatHalf2; + case VertexFormat::k_16_16_16_16_FLOAT: + return MTL::VertexFormatHalf4; + case VertexFormat::k_32: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatInt + : MTL::VertexFormatUInt; + } + return MTL::VertexFormatFloat; + case VertexFormat::k_32_32: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatInt2 + : MTL::VertexFormatUInt2; + } + return MTL::VertexFormatFloat2; + case VertexFormat::k_32_FLOAT: + return MTL::VertexFormatFloat; + case VertexFormat::k_32_32_FLOAT: + return MTL::VertexFormatFloat2; + case VertexFormat::k_32_32_32_FLOAT: + return MTL::VertexFormatFloat3; + case VertexFormat::k_32_32_32_32: + if (attrs.is_integer) { + return attrs.is_signed ? MTL::VertexFormatInt4 + : MTL::VertexFormatUInt4; + } + return MTL::VertexFormatFloat4; + case VertexFormat::k_32_32_32_32_FLOAT: + return MTL::VertexFormatFloat4; + default: + return MTL::VertexFormatInvalid; + } + }; + + MTL::VertexDescriptor* vertex_desc = MTL::VertexDescriptor::alloc()->init(); + + uint32_t attr_index = static_cast(kIRStageInAttributeStartIndex); + for (const auto& binding : vertex_bindings) { + uint64_t buffer_index = + kIRVertexBufferBindPoint + uint64_t(binding.binding_index); + bool used_any_attribute = false; + + for (const auto& attr : binding.attributes) { + MTL::VertexFormat fmt = map_vertex_format(attr.fetch_instr.attributes); + if (fmt == MTL::VertexFormatInvalid) { + ++attr_index; + continue; + } + auto attr_desc = vertex_desc->attributes()->object(attr_index); + attr_desc->setFormat(fmt); + attr_desc->setOffset( + static_cast(attr.fetch_instr.attributes.offset * 4)); + attr_desc->setBufferIndex(static_cast(buffer_index)); + used_any_attribute = true; + ++attr_index; + } + + if (used_any_attribute) { + auto layout = vertex_desc->layouts()->object(buffer_index); + layout->setStride(binding.stride_words * 4); + layout->setStepFunction(MTL::VertexStepFunctionPerVertex); + layout->setStepRate(1); + } + } + + desc->setVertexDescriptor(vertex_desc); + vertex_desc->release(); + } + + { + std::lock_guard lock(pipeline_binary_archive_mutex_); + if (pipeline_binary_archive_) { + NS::Array* archives = NS::Array::array(pipeline_binary_archive_); + desc->setBinaryArchives(archives); + NS::Error* archive_error = nullptr; + if (pipeline_binary_archive_->addRenderPipelineFunctions( + desc, &archive_error)) { + pipeline_binary_archive_dirty_ = true; + } + } + } + + // Create pipeline state. + NS::Error* error = nullptr; + auto pipeline_start = std::chrono::steady_clock::now(); + MTL::RenderPipelineState* pipeline = + device_->newRenderPipelineState(desc, &error); + auto pipeline_end = std::chrono::steady_clock::now(); + RecordRenderPipelineCreation(pipeline != nullptr, + ElapsedMs(pipeline_start, pipeline_end)); + desc->release(); + + if (!pipeline) { + if (error) { + XELOGE("Failed to create pipeline state: {}", + error->localizedDescription()->utf8String()); + } else { + XELOGE("Failed to create pipeline state (unknown error)"); + } + return nullptr; + } + + return pipeline; +} + +// --------------------------------------------------------------------------- +// CreationThread (background pipeline compilation) +// --------------------------------------------------------------------------- + +void MetalPipelineCache::CreationThread(size_t thread_index) { + NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); + + while (true) { + PipelineCreationRequest request; + { + std::unique_lock lock(creation_request_lock_); + while (creation_queue_.empty() && !creation_threads_shutdown_) { + creation_request_cond_.wait(lock); + } + if (creation_threads_shutdown_ && creation_queue_.empty()) { + break; + } + request = creation_queue_.top(); + creation_queue_.pop(); + ++creation_threads_busy_; + } + + // Drain and recreate autorelease pool per pipeline. + pool->drain(); + pool = NS::AutoreleasePool::alloc()->init(); + + // Create the pipeline. + PipelineHandle* handle = request.handle; + MTL::RenderPipelineState* pipeline = CreatePipelineFromHandle(handle); + handle->state.store(pipeline, std::memory_order_release); + if (pipeline && handle->storage_write_pending) { + if (handle->pending_vertex_translation) { + QueueStoredShader(static_cast( + handle->pending_vertex_translation->shader())); + } + if (handle->pending_pixel_translation) { + QueueStoredShader(static_cast( + handle->pending_pixel_translation->shader())); + } + QueueStoredPipeline(handle->description, handle->description_hash); + handle->storage_write_pending = false; + } + + // Clear pending data. + handle->pending_vertex_translation = nullptr; + handle->pending_pixel_translation = nullptr; + + --creation_threads_busy_; + } + + pool->drain(); +} + +// --------------------------------------------------------------------------- +// IsCreatingPipelines +// --------------------------------------------------------------------------- + +bool MetalPipelineCache::IsCreatingPipelines() { + if (creation_threads_.empty()) { + return false; + } + std::lock_guard lock(creation_request_lock_); + return !creation_queue_.empty() || creation_threads_busy_ != 0; +} + +MetalStageCompileCacheStats MetalPipelineCache::GetAndResetStageCompileStats() { + if (!metal_shader_converter_ || + !metal_shader_converter_->stage_compile_cache()) { + return {}; + } + return metal_shader_converter_->stage_compile_cache()->GetAndResetStats(); +} + +MetalPipelineRuntimeStats MetalPipelineCache::GetAndResetRuntimeStats() { + MetalPipelineRuntimeStats stats; + stats.dxil_convert_requests = + dxil_convert_requests_.exchange(0, std::memory_order_relaxed); + stats.dxil_cache_hits = + dxil_cache_hits_.exchange(0, std::memory_order_relaxed); + stats.dxil_cache_misses = + dxil_cache_misses_.exchange(0, std::memory_order_relaxed); + stats.dxil_convert_failures = + dxil_convert_failures_.exchange(0, std::memory_order_relaxed); + stats.dxil_convert_dxbc_bytes = + dxil_convert_dxbc_bytes_.exchange(0, std::memory_order_relaxed); + stats.dxil_convert_dxil_bytes = + dxil_convert_dxil_bytes_.exchange(0, std::memory_order_relaxed); + stats.dxil_convert_ms_total = + dxil_convert_ms_total_.exchange(0, std::memory_order_relaxed); + stats.dxil_convert_ms_max = + dxil_convert_ms_max_.exchange(0, std::memory_order_relaxed); + stats.library_requests = + library_requests_.exchange(0, std::memory_order_relaxed); + stats.library_failures = + library_failures_.exchange(0, std::memory_order_relaxed); + stats.library_bytes = library_bytes_.exchange(0, std::memory_order_relaxed); + stats.library_ms_total = + library_ms_total_.exchange(0, std::memory_order_relaxed); + stats.library_ms_max = library_ms_max_.exchange(0, std::memory_order_relaxed); + stats.render_pipeline_requests = + render_pipeline_requests_.exchange(0, std::memory_order_relaxed); + stats.render_pipeline_failures = + render_pipeline_failures_.exchange(0, std::memory_order_relaxed); + stats.render_pipeline_ms_total = + render_pipeline_ms_total_.exchange(0, std::memory_order_relaxed); + stats.render_pipeline_ms_max = + render_pipeline_ms_max_.exchange(0, std::memory_order_relaxed); + return stats; +} + +// --------------------------------------------------------------------------- +// GetOrCreateGeometryPipelineState +// --------------------------------------------------------------------------- + +MetalPipelineCache::GeometryPipelineState* +MetalPipelineCache::GetOrCreateGeometryPipelineState( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + GeometryShaderKey geometry_shader_key, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) { + if (!vertex_translation) { + XELOGE("No valid vertex shader translation for geometry pipeline"); + return nullptr; + } + bool use_fallback_pixel_shader = (pixel_translation == nullptr); + MTL::Library* pixel_library = + use_fallback_pixel_shader ? nullptr : pixel_translation->metal_library(); + const char* pixel_function = use_fallback_pixel_shader + ? nullptr + : pixel_translation->function_name().c_str(); + if (use_fallback_pixel_shader) { + if (!EnsureDepthOnlyPixelShader()) { + XELOGE("Geometry pipeline: failed to create depth-only PS"); + return nullptr; + } + pixel_library = depth_only_pixel_library_; + pixel_function = depth_only_pixel_function_name_.c_str(); + } else if (!pixel_library) { + XELOGE("No valid pixel shader translation for geometry pipeline"); + return nullptr; + } + + uint32_t sample_count = attachment_formats.sample_count; + MTL::PixelFormat color_formats[4]; + for (uint32_t i = 0; i < 4; ++i) { + color_formats[i] = attachment_formats.color_formats[i]; + } + MTL::PixelFormat depth_format = attachment_formats.depth_format; + MTL::PixelFormat stencil_format = attachment_formats.stencil_format; + + struct GeometryPipelineKey { + const void* vs; + const void* ps; + uint32_t geometry_key; + uint32_t sample_count; + uint32_t depth_format; + uint32_t stencil_format; + uint32_t color_formats[4]; + uint32_t normalized_color_mask; + uint32_t alpha_to_mask_enable; + uint32_t blendcontrol[4]; + } key_data = {}; + + key_data.vs = vertex_translation; + key_data.ps = use_fallback_pixel_shader + ? static_cast(pixel_library) + : static_cast(pixel_translation); + key_data.geometry_key = geometry_shader_key.key; + key_data.sample_count = sample_count; + key_data.depth_format = uint32_t(depth_format); + key_data.stencil_format = uint32_t(stencil_format); + for (uint32_t i = 0; i < 4; ++i) { + key_data.color_formats[i] = uint32_t(color_formats[i]); + } + key_data.normalized_color_mask = rendering_key.normalized_color_mask; + key_data.alpha_to_mask_enable = rendering_key.alpha_to_mask_enable; + std::memcpy(key_data.blendcontrol, rendering_key.blendcontrol, + sizeof(key_data.blendcontrol)); + uint64_t key = XXH3_64bits(&key_data, sizeof(key_data)); + + auto it = geometry_pipeline_cache_.find(key); + if (it != geometry_pipeline_cache_.end()) { + return &it->second; + } + + if (!generated_stages_) { + XELOGE("Geometry pipeline: generated stage cache is not initialized"); + return nullptr; + } + + auto* vertex_stage = generated_stages_->GetGeometryVertexStage( + vertex_translation, geometry_shader_key); + if (!vertex_stage || !vertex_stage->library || + !vertex_stage->stage_in_library) { + return nullptr; + } + auto* geometry_stage = + generated_stages_->GetGeometryShaderStage(geometry_shader_key); + if (!geometry_stage || !geometry_stage->library) { + return nullptr; + } + + MTL::MeshRenderPipelineDescriptor* desc = + MTL::MeshRenderPipelineDescriptor::alloc()->init(); + + for (uint32_t i = 0; i < 4; ++i) { + desc->colorAttachments()->object(i)->setPixelFormat(color_formats[i]); + } + desc->setDepthAttachmentPixelFormat(depth_format); + desc->setStencilAttachmentPixelFormat(stencil_format); + desc->setRasterSampleCount(sample_count); + desc->setAlphaToCoverageEnabled(false); + + ApplyBlendStateToDescriptor(desc->colorAttachments(), + key_data.normalized_color_mask, + key_data.blendcontrol); + if (!vertex_stage->vertex_output_size_in_bytes || + !geometry_stage->max_input_primitives_per_mesh_threadgroup) { + XELOGE( + "Geometry pipeline: invalid reflection (vs_output={}, gs_max_input={})", + vertex_stage->vertex_output_size_in_bytes, + geometry_stage->max_input_primitives_per_mesh_threadgroup); + return nullptr; + } + + IRGeometryEmulationPipelineDescriptor ir_desc = {}; + ir_desc.stageInLibrary = vertex_stage->stage_in_library; + ir_desc.vertexLibrary = vertex_stage->library; + ir_desc.vertexFunctionName = vertex_stage->function_name.c_str(); + ir_desc.geometryLibrary = geometry_stage->library; + ir_desc.geometryFunctionName = geometry_stage->function_name.c_str(); + ir_desc.fragmentLibrary = pixel_library; + ir_desc.fragmentFunctionName = pixel_function; + ir_desc.basePipelineDescriptor = desc; + ir_desc.pipelineConfig.gsVertexSizeInBytes = + vertex_stage->vertex_output_size_in_bytes; + ir_desc.pipelineConfig.gsMaxInputPrimitivesPerMeshThreadgroup = + geometry_stage->max_input_primitives_per_mesh_threadgroup; + + NS::Error* error = nullptr; + auto pipeline_start = std::chrono::steady_clock::now(); + MTL::RenderPipelineState* pipeline = + IRRuntimeNewGeometryEmulationPipeline(device_, &ir_desc, &error); + auto pipeline_end = std::chrono::steady_clock::now(); + RecordRenderPipelineCreation(pipeline != nullptr, + ElapsedMs(pipeline_start, pipeline_end)); + desc->release(); + + if (!pipeline) { + XELOGE( + "Failed to create geometry pipeline state: {}", + error ? error->localizedDescription()->utf8String() : "unknown error"); + XELOGE( + "Geometry pipeline details: vs_fn='{}' gs_fn='{}' ps_fn='{}' " + "depth_format={} stencil_format={} samples={}", + vertex_stage->function_name, geometry_stage->function_name, + pixel_function ? pixel_function : "", uint32_t(depth_format), + uint32_t(stencil_format), sample_count); + LogMetalErrorDetails("Geometry pipeline error", error); + return nullptr; + } + + GeometryPipelineState state; + state.pipeline = pipeline; + state.gs_vertex_size_in_bytes = ir_desc.pipelineConfig.gsVertexSizeInBytes; + state.gs_max_input_primitives_per_mesh_threadgroup = + ir_desc.pipelineConfig.gsMaxInputPrimitivesPerMeshThreadgroup; + + auto [inserted_it, inserted] = + geometry_pipeline_cache_.emplace(key, std::move(state)); + MetalPipelineDescription stored_description = + BuildGeometryPipelineDescription(vertex_translation, pixel_translation, + geometry_shader_key, attachment_formats, + rendering_key); + QueueStoredShader(static_cast(vertex_translation->shader())); + if (pixel_translation) { + QueueStoredShader(static_cast(pixel_translation->shader())); + } + QueueStoredPipeline( + stored_description, + XXH3_64bits(&stored_description, sizeof(stored_description))); + return &inserted_it->second; +} + +// --------------------------------------------------------------------------- +// GetOrCreateTessellationPipelineState +// --------------------------------------------------------------------------- + +MetalPipelineCache::TessellationPipelineState* +MetalPipelineCache::GetOrCreateTessellationPipelineState( + MetalShader::MetalTranslation* domain_translation, + MetalShader::MetalTranslation* pixel_translation, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) { + if (!domain_translation) { + XELOGE("No valid domain shader translation for tessellation pipeline"); + return nullptr; + } + bool use_fallback_pixel_shader = (pixel_translation == nullptr); + MTL::Library* pixel_library = + use_fallback_pixel_shader ? nullptr : pixel_translation->metal_library(); + const char* pixel_function = use_fallback_pixel_shader + ? nullptr + : pixel_translation->function_name().c_str(); + if (use_fallback_pixel_shader) { + if (!EnsureDepthOnlyPixelShader()) { + XELOGE("Tessellation pipeline: failed to create depth-only PS"); + return nullptr; + } + pixel_library = depth_only_pixel_library_; + pixel_function = depth_only_pixel_function_name_.c_str(); + } else if (!pixel_library) { + XELOGE("No valid pixel shader translation for tessellation pipeline"); + return nullptr; + } + + uint32_t sample_count = attachment_formats.sample_count; + MTL::PixelFormat color_formats[4]; + for (uint32_t i = 0; i < 4; ++i) { + color_formats[i] = attachment_formats.color_formats[i]; + } + MTL::PixelFormat depth_format = attachment_formats.depth_format; + MTL::PixelFormat stencil_format = attachment_formats.stencil_format; + + struct TessellationPipelineKey { + const void* ds; + const void* ps; + uint32_t host_vs_type; + uint32_t tessellation_mode; + uint32_t host_prim; + uint32_t sample_count; + uint32_t depth_format; + uint32_t stencil_format; + uint32_t color_formats[4]; + uint32_t normalized_color_mask; + uint32_t alpha_to_mask_enable; + uint32_t blendcontrol[4]; + } key_data = {}; + + key_data.ds = domain_translation; + key_data.ps = use_fallback_pixel_shader + ? static_cast(pixel_library) + : static_cast(pixel_translation); + key_data.host_vs_type = + uint32_t(primitive_processing_result.host_vertex_shader_type); + key_data.tessellation_mode = + uint32_t(primitive_processing_result.tessellation_mode); + key_data.host_prim = + uint32_t(primitive_processing_result.host_primitive_type); + key_data.sample_count = sample_count; + key_data.depth_format = uint32_t(depth_format); + key_data.stencil_format = uint32_t(stencil_format); + for (uint32_t i = 0; i < 4; ++i) { + key_data.color_formats[i] = uint32_t(color_formats[i]); + } + key_data.normalized_color_mask = rendering_key.normalized_color_mask; + key_data.alpha_to_mask_enable = rendering_key.alpha_to_mask_enable; + std::memcpy(key_data.blendcontrol, rendering_key.blendcontrol, + sizeof(key_data.blendcontrol)); + uint64_t key = XXH3_64bits(&key_data, sizeof(key_data)); + + auto it = tessellation_pipeline_cache_.find(key); + if (it != tessellation_pipeline_cache_.end()) { + return &it->second; + } + + xenos::TessellationMode tessellation_mode = + primitive_processing_result.tessellation_mode; + + if (!generated_stages_) { + XELOGE("Tessellation pipeline: generated stage cache is not initialized"); + return nullptr; + } + + auto* vertex_stage = generated_stages_->GetTessellationVertexStage( + domain_translation, tessellation_mode); + if (!vertex_stage || !vertex_stage->library || + !vertex_stage->stage_in_library) { + return nullptr; + } + + auto* hull_stage = generated_stages_->GetTessellationHullStage( + primitive_processing_result, tessellation_mode); + if (!hull_stage || !hull_stage->library) { + return nullptr; + } + + auto* domain_stage = + generated_stages_->GetTessellationDomainStage(domain_translation); + if (!domain_stage || !domain_stage->library) { + return nullptr; + } + + IRRuntimeTessellatorOutputPrimitive output_primitive = + IRRuntimeTessellatorOutputUndefined; + switch (hull_stage->reflection.hs_tessellator_output_primitive) { + case IRRuntimeTessellatorOutputPoint: + output_primitive = IRRuntimeTessellatorOutputPoint; + break; + case IRRuntimeTessellatorOutputLine: + output_primitive = IRRuntimeTessellatorOutputLine; + break; + case IRRuntimeTessellatorOutputTriangleCW: + output_primitive = IRRuntimeTessellatorOutputTriangleCW; + break; + case IRRuntimeTessellatorOutputTriangleCCW: + output_primitive = IRRuntimeTessellatorOutputTriangleCCW; + break; + default: + XELOGE("Tessellation pipeline: unsupported tessellator output {}", + hull_stage->reflection.hs_tessellator_output_primitive); + return nullptr; + } + + IRRuntimePrimitiveType geometry_primitive = IRRuntimePrimitiveTypeTriangle; + const char* geometry_function = kIRTrianglePassthroughGeometryShader; + switch (output_primitive) { + case IRRuntimeTessellatorOutputPoint: + geometry_primitive = IRRuntimePrimitiveTypePoint; + geometry_function = kIRPointPassthroughGeometryShader; + break; + case IRRuntimeTessellatorOutputLine: + geometry_primitive = IRRuntimePrimitiveTypeLine; + geometry_function = kIRLinePassthroughGeometryShader; + break; + case IRRuntimeTessellatorOutputTriangleCW: + case IRRuntimeTessellatorOutputTriangleCCW: + geometry_primitive = IRRuntimePrimitiveTypeTriangle; + geometry_function = kIRTrianglePassthroughGeometryShader; + break; + default: + break; + } + + if (!IRRuntimeValidateTessellationPipeline( + output_primitive, geometry_primitive, + hull_stage->reflection.hs_output_control_point_size, + domain_stage->reflection.ds_input_control_point_size, + hull_stage->reflection.hs_patch_constants_size, + domain_stage->reflection.ds_patch_constants_size, + hull_stage->reflection.hs_output_control_point_count, + domain_stage->reflection.ds_input_control_point_count)) { + XELOGE("Tessellation pipeline: validation failed for HS/DS pairing"); + return nullptr; + } + + MTL::MeshRenderPipelineDescriptor* desc = + MTL::MeshRenderPipelineDescriptor::alloc()->init(); + for (uint32_t i = 0; i < 4; ++i) { + desc->colorAttachments()->object(i)->setPixelFormat(color_formats[i]); + } + desc->setDepthAttachmentPixelFormat(depth_format); + desc->setStencilAttachmentPixelFormat(stencil_format); + desc->setRasterSampleCount(sample_count); + desc->setAlphaToCoverageEnabled(false); + + ApplyBlendStateToDescriptor(desc->colorAttachments(), + key_data.normalized_color_mask, + key_data.blendcontrol); + IRGeometryTessellationEmulationPipelineDescriptor ir_desc = {}; + ir_desc.stageInLibrary = vertex_stage->stage_in_library; + ir_desc.vertexLibrary = vertex_stage->library; + ir_desc.vertexFunctionName = vertex_stage->function_name.c_str(); + ir_desc.hullLibrary = hull_stage->library; + ir_desc.hullFunctionName = hull_stage->function_name.c_str(); + ir_desc.domainLibrary = domain_stage->library; + ir_desc.domainFunctionName = domain_stage->function_name.c_str(); + ir_desc.geometryLibrary = nullptr; + ir_desc.geometryFunctionName = geometry_function; + ir_desc.fragmentLibrary = pixel_library; + ir_desc.fragmentFunctionName = pixel_function; + ir_desc.basePipelineDescriptor = desc; + ir_desc.pipelineConfig.outputPrimitiveType = output_primitive; + ir_desc.pipelineConfig.vsOutputSizeInBytes = + vertex_stage->vertex_output_size_in_bytes; + ir_desc.pipelineConfig.gsMaxInputPrimitivesPerMeshThreadgroup = + domain_stage->reflection.ds_max_input_prims_per_mesh_threadgroup; + ir_desc.pipelineConfig.hsMaxPatchesPerObjectThreadgroup = + hull_stage->reflection.hs_max_patches_per_object_threadgroup; + ir_desc.pipelineConfig.hsInputControlPointCount = + hull_stage->reflection.hs_input_control_point_count; + ir_desc.pipelineConfig.hsMaxObjectThreadsPerThreadgroup = + hull_stage->reflection.hs_max_object_threads_per_patch; + ir_desc.pipelineConfig.hsMaxTessellationFactor = + hull_stage->reflection.hs_max_tessellation_factor; + ir_desc.pipelineConfig.gsInstanceCount = 1; + + if (!ir_desc.pipelineConfig.vsOutputSizeInBytes || + !ir_desc.pipelineConfig.gsMaxInputPrimitivesPerMeshThreadgroup || + !ir_desc.pipelineConfig.hsMaxPatchesPerObjectThreadgroup || + !ir_desc.pipelineConfig.hsInputControlPointCount || + !ir_desc.pipelineConfig.hsMaxObjectThreadsPerThreadgroup) { + XELOGE( + "Tessellation pipeline: invalid reflection values (vs_output={}, " + "gs_max_input={}, hs_patches={}, hs_cp_count={}, hs_threads={})", + ir_desc.pipelineConfig.vsOutputSizeInBytes, + ir_desc.pipelineConfig.gsMaxInputPrimitivesPerMeshThreadgroup, + ir_desc.pipelineConfig.hsMaxPatchesPerObjectThreadgroup, + ir_desc.pipelineConfig.hsInputControlPointCount, + ir_desc.pipelineConfig.hsMaxObjectThreadsPerThreadgroup); + desc->release(); + return nullptr; + } + + NS::Error* error = nullptr; + auto pipeline_start = std::chrono::steady_clock::now(); + MTL::RenderPipelineState* pipeline = + IRRuntimeNewGeometryTessellationEmulationPipeline(device_, &ir_desc, + &error); + auto pipeline_end = std::chrono::steady_clock::now(); + RecordRenderPipelineCreation(pipeline != nullptr, + ElapsedMs(pipeline_start, pipeline_end)); + desc->release(); + if (!pipeline) { + XELOGE( + "Failed to create tessellation pipeline state: {}", + error ? error->localizedDescription()->utf8String() : "unknown error"); + return nullptr; + } + + TessellationPipelineState state; + state.pipeline = pipeline; + state.config = ir_desc.pipelineConfig; + state.primitive = geometry_primitive; + + auto [inserted_it, inserted] = + tessellation_pipeline_cache_.emplace(key, std::move(state)); + MetalPipelineDescription stored_description = + BuildTessellationPipelineDescription( + domain_translation, pixel_translation, primitive_processing_result, + tessellation_mode, attachment_formats, rendering_key); + QueueStoredShader(static_cast(domain_translation->shader())); + if (pixel_translation) { + QueueStoredShader(static_cast(pixel_translation->shader())); + } + QueueStoredPipeline( + stored_description, + XXH3_64bits(&stored_description, sizeof(stored_description))); + return &inserted_it->second; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_pipeline_cache.h b/src/xenia/gpu/metal/metal_pipeline_cache.h new file mode 100644 index 000000000..266667351 --- /dev/null +++ b/src/xenia/gpu/metal/metal_pipeline_cache.h @@ -0,0 +1,398 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_METAL_METAL_PIPELINE_CACHE_H_ +#define XENIA_GPU_METAL_METAL_PIPELINE_CACHE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xenia/base/hash.h" +#include "xenia/base/string_buffer.h" +#include "xenia/gpu/dxbc_shader_translator.h" +#include "xenia/gpu/metal/dxbc_to_dxil_converter.h" +#include "xenia/gpu/metal/metal_geometry_shader.h" +#include "xenia/gpu/metal/metal_shader.h" +#include "xenia/gpu/metal/metal_shader_converter.h" +#include "xenia/gpu/metal/metal_stage_compile_cache.h" +#include "xenia/gpu/primitive_processor.h" +#include "xenia/gpu/register_file.h" +#include "xenia/gpu/registers.h" +#include "xenia/gpu/shader_storage.h" +// clang-format off +// Must come after Metal.hpp (included transitively via metal_shader.h) +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" +// clang-format on +#include "xenia/ui/metal/metal_api.h" + +namespace xe { +namespace gpu { +namespace metal { + +// Attachment format snapshot used to decouple pipeline creation from render +// target cache / render pass descriptor state. +struct PipelineAttachmentFormats { + MTL::PixelFormat color_formats[4]; + MTL::PixelFormat depth_format; + MTL::PixelFormat stencil_format; + uint32_t sample_count; +}; + +// Fixed-function rendering state derived from registers that is common to all +// pipeline paths (standard, geometry emulation, tessellation emulation). +// Computed once per draw and passed into each GetOrCreate* method so the +// duplicated register reads are eliminated. +struct PipelineRenderingKey { + uint32_t normalized_color_mask; + uint32_t alpha_to_mask_enable; + uint32_t blendcontrol[4]; +}; + +struct MetalPipelineRuntimeStats { + uint64_t dxil_convert_requests = 0; + uint64_t dxil_cache_hits = 0; + uint64_t dxil_cache_misses = 0; + uint64_t dxil_convert_failures = 0; + uint64_t dxil_convert_dxbc_bytes = 0; + uint64_t dxil_convert_dxil_bytes = 0; + uint64_t dxil_convert_ms_total = 0; + uint64_t dxil_convert_ms_max = 0; + uint64_t library_requests = 0; + uint64_t library_failures = 0; + uint64_t library_bytes = 0; + uint64_t library_ms_total = 0; + uint64_t library_ms_max = 0; + uint64_t render_pipeline_requests = 0; + uint64_t render_pipeline_failures = 0; + uint64_t render_pipeline_ms_total = 0; + uint64_t render_pipeline_ms_max = 0; +}; + +// Derive the shared PipelineRenderingKey from the current register state and +// the pixel translation (which provides writes_color_targets). +PipelineRenderingKey ResolvePipelineRenderingKey( + const RegisterFile& regs, + const MetalShader::MetalTranslation* pixel_translation, + bool use_fallback_pixel_shader); + +class MetalPipelineCache { + public: + static constexpr size_t kLayoutUIDEmpty = 0; + + MetalPipelineCache(MTL::Device* device, const RegisterFile& register_file); + ~MetalPipelineCache(); + + // Non-copyable. + MetalPipelineCache(const MetalPipelineCache&) = delete; + MetalPipelineCache& operator=(const MetalPipelineCache&) = delete; + + // Initialize shader translation components. Must be called after + // construction; returns false on failure. + bool InitializeShaderTranslation(bool gamma_render_target_as_unorm8, + bool msaa_2x_supported, + uint32_t draw_resolution_scale_x, + uint32_t draw_resolution_scale_y); + + // Shader storage (disk cache) lifecycle. + void InitializeShaderStorage(const std::filesystem::path& cache_root, + uint32_t title_id, bool blocking); + void ShutdownShaderStorage(); + void EndSubmission(); + + // Load or retrieve a cached shader from the ucode hash. + Shader* LoadShader(xenos::ShaderType shader_type, + const uint32_t* host_address, uint32_t dword_count); + void SetupShaderBindingLayoutUserUIDs(MetalShader& shader); + + // Per-draw shader modification selection (mirrors D3D12 PipelineCache). + DxbcShaderTranslator::Modification GetCurrentVertexShaderModification( + const Shader& shader, + Shader::HostVertexShaderType host_vertex_shader_type, + uint32_t interpolator_mask) const; + DxbcShaderTranslator::Modification GetCurrentPixelShaderModification( + const Shader& shader, uint32_t interpolator_mask, uint32_t param_gen_pos, + reg::RB_DEPTHCONTROL normalized_depth_control) const; + + enum class PipelineKind : uint32_t { + kStandard = 0, + kGeometry = 1, + kTessellation = 2, + }; + + XEPACKEDSTRUCT(MetalPipelineDescription, { + uint64_t vertex_shader_hash; + uint64_t vertex_shader_modification; + uint64_t pixel_shader_hash; + uint64_t pixel_shader_modification; + uint64_t auxiliary_hash; + uint32_t auxiliary0; + uint32_t auxiliary1; + uint32_t auxiliary2; + uint32_t auxiliary3; + uint32_t kind; + uint32_t sample_count; + uint32_t depth_format; + uint32_t stencil_format; + uint32_t color_formats[4]; + uint32_t normalized_color_mask; + uint32_t alpha_to_mask_enable; + uint32_t blendcontrol[4]; + + static constexpr uint32_t kVersion = 0x20260530; + }); + + XEPACKEDSTRUCT(MetalPipelineStoredDescription, { + uint64_t description_hash; + MetalPipelineDescription description; + }); + + // Handle for a standard render pipeline. The state pointer is set + // atomically so background compilation threads can publish results that + // the render thread picks up via acquire loads. + struct PipelineHandle { + std::atomic state{nullptr}; + uint64_t description_hash = 0; + MetalPipelineDescription description = {}; + // Data needed by background thread to create the pipeline. + // Cleared after creation. + MetalShader::MetalTranslation* pending_vertex_translation{nullptr}; + MetalShader::MetalTranslation* pending_pixel_translation{nullptr}; + PipelineAttachmentFormats pending_formats{}; + uint32_t pending_normalized_color_mask{0}; + uint32_t pending_blendcontrol[4]{}; + bool pending_alpha_to_mask{false}; + bool storage_write_pending = false; + uint8_t priority{0}; + }; + + // Pipeline state creation -- all accept pre-resolved attachment formats and + // the shared rendering key derived by ResolvePipelineRenderingKey(). + PipelineHandle* GetOrCreatePipelineState( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key); + + // Returns true while background pipeline creation threads are busy. + bool IsCreatingPipelines(); + + MetalStageCompileCacheStats GetAndResetStageCompileStats(); + MetalPipelineRuntimeStats GetAndResetRuntimeStats(); + + struct GeometryPipelineState { + MTL::RenderPipelineState* pipeline = nullptr; + uint32_t gs_vertex_size_in_bytes = 0; + uint32_t gs_max_input_primitives_per_mesh_threadgroup = 0; + }; + + struct TessellationPipelineState { + MTL::RenderPipelineState* pipeline = nullptr; + IRRuntimeTessellationPipelineConfig config = {}; + IRRuntimePrimitiveType primitive = IRRuntimePrimitiveTypeTriangle; + }; + + GeometryPipelineState* GetOrCreateGeometryPipelineState( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + GeometryShaderKey geometry_shader_key, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key); + + TessellationPipelineState* GetOrCreateTessellationPipelineState( + MetalShader::MetalTranslation* domain_translation, + MetalShader::MetalTranslation* pixel_translation, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key); + + // Ensure the shared DXBC translator has produced DXBC bytecode. + bool EnsureDxbcTranslationReady(MetalShader::MetalTranslation* translation, + const char* stage_name); + bool EnsureDxilTranslationReady(MetalShader::MetalTranslation* translation, + const char* stage_name); + bool EnsureMetalTranslationReady(MetalShader::MetalTranslation* translation); + + // Ensure the depth-only pixel shader is compiled and ready. + bool EnsureDepthOnlyPixelShader(); + + // Ucode disassembly scratch buffer (shared with command processor). + StringBuffer& ucode_disasm_buffer() { return ucode_disasm_buffer_; } + + // Serialize pipeline binary archive to disk. + void SerializePipelineBinaryArchive(); + + private: + bool InitializeShaderStorageInternal(const std::filesystem::path& cache_root, + uint32_t title_id, bool blocking); + std::string GetShaderStorageDeviceTag() const; + std::string GetShaderStorageAbiTag() const; + bool InitializePipelineBinaryArchive( + const std::filesystem::path& archive_path); + void PrewarmStoredPipelines( + const std::vector& descriptions, + bool blocking); + MetalShader* LoadShader(xenos::ShaderType shader_type, + const uint32_t* host_address, uint32_t dword_count, + uint64_t data_hash); + MetalPipelineDescription BuildStandardPipelineDescription( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const; + MetalPipelineDescription BuildPipelineDescription( + PipelineKind kind, MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const; + MetalPipelineDescription BuildGeometryPipelineDescription( + MetalShader::MetalTranslation* vertex_translation, + MetalShader::MetalTranslation* pixel_translation, + GeometryShaderKey geometry_shader_key, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const; + MetalPipelineDescription BuildTessellationPipelineDescription( + MetalShader::MetalTranslation* domain_translation, + MetalShader::MetalTranslation* pixel_translation, + const PrimitiveProcessor::ProcessingResult& primitive_processing_result, + xenos::TessellationMode tessellation_mode, + const PipelineAttachmentFormats& attachment_formats, + const PipelineRenderingKey& rendering_key) const; + void QueueStoredShader(MetalShader& shader); + void QueueStoredPipeline(const MetalPipelineDescription& description, + uint64_t description_hash); + bool EnsureDxbcTranslationReadyLocked( + MetalShader::MetalTranslation* translation, const char* stage_name); + bool ConvertDxbcToDxil(const std::vector& dxbc_data, + std::vector& dxil_data, + std::string* error_message); + MTL::Library* NewLibraryFromBytes(const std::vector& bytes, + const char* label); + void RecordLibraryCreation(size_t byte_count, bool success, uint64_t ms); + void RecordRenderPipelineCreation(bool success, uint64_t ms); + + MTL::Device* device_; + const RegisterFile& register_file_; + + // MSC shader translation components. + std::unique_ptr shader_translator_; + std::unique_ptr dxbc_to_dxil_converter_; + std::unique_ptr metal_shader_converter_; + std::mutex shader_translation_mutex_; + + std::atomic dxil_convert_requests_{0}; + std::atomic dxil_cache_hits_{0}; + std::atomic dxil_cache_misses_{0}; + std::atomic dxil_convert_failures_{0}; + std::atomic dxil_convert_dxbc_bytes_{0}; + std::atomic dxil_convert_dxil_bytes_{0}; + std::atomic dxil_convert_ms_total_{0}; + std::atomic dxil_convert_ms_max_{0}; + std::atomic library_requests_{0}; + std::atomic library_failures_{0}; + std::atomic library_bytes_{0}; + std::atomic library_ms_total_{0}; + std::atomic library_ms_max_{0}; + std::atomic render_pipeline_requests_{0}; + std::atomic render_pipeline_failures_{0}; + std::atomic render_pipeline_ms_total_{0}; + std::atomic render_pipeline_ms_max_{0}; + + StringBuffer ucode_disasm_buffer_; + + // MSC shader cache (keyed by ucode hash). + std::unordered_map> shader_cache_; + + struct LayoutUID { + size_t uid; + size_t vector_span_offset; + size_t vector_span_length; + }; + std::mutex layouts_mutex_; + // Texture binding layouts of different shaders, for obtaining layout UIDs. + std::vector texture_binding_layouts_; + // Map of texture binding layouts used by shaders, for obtaining UIDs. Keys + // are XXH3 hashes of layouts, values need manual collision resolution using + // layout_vector_offset:layout_length of texture_binding_layouts_. + std::unordered_multimap> + texture_binding_layout_map_; + // Bindless sampler indices of different shaders, for obtaining layout UIDs. + std::vector bindless_sampler_layouts_; + // Keys are XXH3 hashes of used bindless sampler indices. + std::unordered_multimap> + bindless_sampler_layout_map_; + + class GeneratedStageCache; + + // MSC pipeline caches (keyed by shader combination). + std::unordered_map> pipeline_cache_; + std::unordered_map geometry_pipeline_cache_; + std::unordered_map + tessellation_pipeline_cache_; + std::unique_ptr generated_stages_; + + MTL::Library* depth_only_pixel_library_ = nullptr; + std::string depth_only_pixel_function_name_; + + // Shader storage paths and writers. + ShaderStorageWriter storage_writer_; + std::atomic shader_storage_file_flush_needed_{false}; + std::atomic pipeline_storage_file_flush_needed_{false}; + uint32_t shader_storage_title_id_ = 0; + std::mutex stored_pipeline_mutex_; + std::unordered_set stored_pipeline_hashes_; + std::filesystem::path shader_storage_local_root_; + std::filesystem::path shader_storage_title_root_; + std::filesystem::path artifact_store_path_; + std::filesystem::path pipeline_binary_archive_path_; + MTL::BinaryArchive* pipeline_binary_archive_ = nullptr; + bool pipeline_binary_archive_dirty_ = false; + std::mutex pipeline_binary_archive_mutex_; + + // Async pipeline compilation thread pool. + void CreationThread(size_t thread_index); + MTL::RenderPipelineState* CreatePipelineFromHandle( + const PipelineHandle* handle); + + std::vector creation_threads_; + struct PipelineCreationRequest { + PipelineHandle* handle{nullptr}; + }; + struct PipelineCreationPriorityCompare { + bool operator()(const PipelineCreationRequest& a, + const PipelineCreationRequest& b) const { + return a.handle->priority < b.handle->priority; + } + }; + std::priority_queue, + PipelineCreationPriorityCompare> + creation_queue_; + std::mutex creation_request_lock_; + std::condition_variable creation_request_cond_; + std::atomic creation_threads_busy_{0}; + bool creation_threads_shutdown_{false}; +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_PIPELINE_CACHE_H_ diff --git a/src/xenia/gpu/metal/metal_primitive_processor.cc b/src/xenia/gpu/metal/metal_primitive_processor.cc index 2459dc73b..91694deaa 100644 --- a/src/xenia/gpu/metal/metal_primitive_processor.cc +++ b/src/xenia/gpu/metal/metal_primitive_processor.cc @@ -16,8 +16,8 @@ #include "xenia/base/assert.h" #include "xenia/base/logging.h" -#include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/metal/metal_command_processor.h" +#include "xenia/gpu/metal/metal_upload_buffer_pool.h" namespace xe { namespace gpu { @@ -32,114 +32,48 @@ MetalPrimitiveProcessor::MetalPrimitiveProcessor( MetalPrimitiveProcessor::~MetalPrimitiveProcessor() { Shutdown(true); } bool MetalPrimitiveProcessor::Initialize() { - // When using SPIRV-Cross (no mesh shaders / MSC), point sprites and - // rectangle lists must be expanded in the vertex shader because there - // are no geometry shaders available. The SpirvShaderTranslator has - // built-in support for kPointListAsTriangleStrip and - // kRectangleListAsTriangleStrip host vertex shader types. + // MSC path uses mesh shaders / geometry shaders for point sprites and + // rectangle lists, so no vertex-shader expansion is needed. if (!InitializeCommon(true, // full_32bit_vertex_indices_supported false, // triangle_fans_supported (will convert) false, // line_loops_supported (will convert) false, // quad_lists_supported (will convert) - false, // point_sprites_without_expansion - false)) { + true, // point_sprites_without_expansion + true)) // rect_lists_without_expansion + { Shutdown(); return false; } + frame_index_buffer_pool_ = std::make_unique( + command_processor_.GetMetalDevice(), + std::max(size_t(kMinRequiredConvertedIndexBufferSize), + ui::GraphicsUploadBufferPool::kDefaultPageSize)); - XELOGI("MetalPrimitiveProcessor initialized"); - - { - // The generic primitive processor emits restart-separated triangle strips - // for VS expansion. Keep a no-restart triangle-list fallback for Metal - // SPIRV-Cross draws in case strip restart semantics diverge. - constexpr uint32_t kMaxExpandedPrimitiveCount = UINT16_MAX; - constexpr uint32_t kIndicesPerExpandedPrimitive = 6; - size_t index_count = - size_t(kMaxExpandedPrimitiveCount) * kIndicesPerExpandedPrimitive; - size_t buffer_size_bytes = index_count * sizeof(uint32_t); - MTL::Device* device = command_processor_.GetMetalDevice(); - expansion_triangle_list_index_buffer_ = - device->newBuffer(buffer_size_bytes, MTL::ResourceStorageModeShared); - if (!expansion_triangle_list_index_buffer_) { - XELOGE( - "Failed to create Metal expansion triangle-list fallback index " - "buffer"); - Shutdown(); - return false; - } - expansion_triangle_list_index_buffer_->setLabel(NS::String::string( - "Xenia Expansion Triangle List Index Buffer", NS::UTF8StringEncoding)); - uint32_t* indices = reinterpret_cast( - expansion_triangle_list_index_buffer_->contents()); - for (uint32_t i = 0; i < kMaxExpandedPrimitiveCount; ++i) { - uint32_t base = i << 2; - size_t write_index = size_t(i) * kIndicesPerExpandedPrimitive; - indices[write_index + 0] = base + 0; - indices[write_index + 1] = base + 1; - indices[write_index + 2] = base + 2; - indices[write_index + 3] = base + 2; - indices[write_index + 4] = base + 1; - indices[write_index + 5] = base + 3; - } - } + XELOGI("MetalPrimitiveProcessor initialized (MSC path)"); return true; } void MetalPrimitiveProcessor::Shutdown(bool from_destructor) { - // Release all frame index buffers - for (auto& frame_buffer : frame_index_buffers_) { - if (frame_buffer.buffer) { - frame_buffer.buffer->release(); - } - } - frame_index_buffers_.clear(); + converted_index_buffers_.clear(); + frame_index_buffer_pool_.reset(); // Release built-in index buffer if (builtin_index_buffer_) { builtin_index_buffer_->release(); builtin_index_buffer_ = nullptr; - builtin_index_buffer_gpu_address_ = 0; builtin_index_buffer_size_ = 0; } - if (expansion_triangle_list_index_buffer_) { - expansion_triangle_list_index_buffer_->release(); - expansion_triangle_list_index_buffer_ = nullptr; - } - if (!from_destructor) { ShutdownCommon(); } } -void MetalPrimitiveProcessor::CompletedSubmissionUpdated() { - // Nothing to do for Metal -} - -void MetalPrimitiveProcessor::BeginSubmission() { - // Nothing to do for Metal -} - void MetalPrimitiveProcessor::BeginFrame() { converted_index_buffers_.clear(); - - // Clean up old frame index buffers - ++current_frame_; - uint64_t current_frame = current_frame_; - - frame_index_buffers_.erase( - std::remove_if(frame_index_buffers_.begin(), frame_index_buffers_.end(), - [current_frame](const FrameIndexBuffer& buffer) { - // Keep buffers used in the last 2 frames - if (current_frame - buffer.last_frame_used > 2) { - if (buffer.buffer) { - buffer.buffer->release(); - } - return true; - } - return false; - }), - frame_index_buffers_.end()); + if (frame_index_buffer_pool_) { + frame_index_buffer_pool_->Reclaim( + command_processor_.GetCompletedSubmission()); + } } void MetalPrimitiveProcessor::EndFrame() { @@ -147,6 +81,18 @@ void MetalPrimitiveProcessor::EndFrame() { converted_index_buffers_.clear(); } +bool MetalPrimitiveProcessor::RequestGuestIndexSharedMemoryRange( + uint32_t guest_index_base, uint32_t guest_index_buffer_needed_bytes, + ProcessedIndexBufferType index_buffer_type) { + // Guest index data read by the GPU is collected by IssueDraw and + // materialized with the prepared draw queue. Requesting it here forces a + // separate immediate shared-memory upload before the queue can coalesce it. + (void)guest_index_base; + (void)guest_index_buffer_needed_bytes; + (void)index_buffer_type; + return true; +} + MTL::Buffer* MetalPrimitiveProcessor::GetConvertedIndexBuffer( size_t handle, uint64_t& offset_bytes_out) const { if (handle >= converted_index_buffers_.size()) { @@ -184,9 +130,6 @@ bool MetalPrimitiveProcessor::InitializeBuiltinIndexBuffer( void* buffer_data = builtin_index_buffer_->contents(); fill_callback(buffer_data); - // Get GPU address for binding - builtin_index_buffer_gpu_address_ = builtin_index_buffer_->gpuAddress(); - XELOGI("Created Metal built-in index buffer ({} bytes)", size_bytes); return true; } @@ -194,78 +137,40 @@ bool MetalPrimitiveProcessor::InitializeBuiltinIndexBuffer( void* MetalPrimitiveProcessor::RequestHostConvertedIndexBufferForCurrentFrame( xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd, uint32_t coalignment_original_address, size_t& backend_handle_out) { - // Calculate required size - size_t element_size = format == xenos::IndexFormat::kInt16 ? sizeof(uint16_t) - : sizeof(uint32_t); - size_t required_size = index_count * element_size; + if (!frame_index_buffer_pool_) { + backend_handle_out = 0; + return nullptr; + } + + size_t index_size = format == xenos::IndexFormat::kInt16 ? sizeof(uint16_t) + : sizeof(uint32_t); + size_t request_size = + index_size * index_count + + (coalign_for_simd ? XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE : 0); + MTL::Buffer* buffer = nullptr; + size_t offset = 0; + uint64_t gpu_address = 0; + uint8_t* mapping = frame_index_buffer_pool_->Request( + command_processor_.GetCurrentSubmission(), request_size, index_size, + &buffer, offset, gpu_address); + if (!mapping || !buffer) { + XELOGE("Failed to allocate Metal index buffer for primitive conversion"); + backend_handle_out = 0; + return nullptr; + } - // Add padding for SIMD alignment if requested if (coalign_for_simd) { - required_size += XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE; - } - - // Find or create a buffer large enough - FrameIndexBuffer* chosen_buffer = nullptr; - uint64_t current_frame = current_frame_; - - // First try to find an existing buffer that's large enough - for (auto& frame_buffer : frame_index_buffers_) { - if (frame_buffer.size >= required_size && - frame_buffer.last_frame_used != current_frame) { - chosen_buffer = &frame_buffer; - break; - } - } - - // If no suitable buffer found, create a new one - if (!chosen_buffer) { - MTL::Device* device = command_processor_.GetMetalDevice(); - - // Round up to next power of 2 for better reuse - size_t allocation_size = required_size; - allocation_size = std::max(allocation_size, size_t(4096)); - allocation_size = (allocation_size + 4095) & ~4095; // Round to 4KB - - MTL::Buffer* new_buffer = - device->newBuffer(allocation_size, MTL::ResourceStorageModeShared); - - if (!new_buffer) { - XELOGE("Failed to create Metal index buffer for primitive conversion"); - backend_handle_out = 0; - return nullptr; - } - - char label[256]; - snprintf(label, sizeof(label), "Xenia Converted Index Buffer (%zu bytes)", - allocation_size); - new_buffer->setLabel(NS::String::string(label, NS::UTF8StringEncoding)); - - frame_index_buffers_.push_back({new_buffer, allocation_size, 0}); - chosen_buffer = &frame_index_buffers_.back(); - - XELOGI("Created new Metal index buffer for primitive conversion ({} bytes)", - allocation_size); - } - - // Mark buffer as used this frame - chosen_buffer->last_frame_used = current_frame; - - // Return the buffer handle and CPU mapping. - uint64_t gpu_offset = 0; - void* cpu_buffer = chosen_buffer->buffer->contents(); - - // Apply SIMD co-alignment if requested - if (coalign_for_simd) { - ptrdiff_t offset = - GetSimdCoalignmentOffset(cpu_buffer, coalignment_original_address); - cpu_buffer = static_cast(cpu_buffer) + offset; - gpu_offset += uint64_t(offset); + ptrdiff_t coalignment_offset = + GetSimdCoalignmentOffset(mapping, coalignment_original_address); + mapping += coalignment_offset; + gpu_address += uint64_t(coalignment_offset); } backend_handle_out = converted_index_buffers_.size(); - converted_index_buffers_.push_back({chosen_buffer->buffer, gpu_offset}); + converted_index_buffers_.push_back( + {buffer, gpu_address - buffer->gpuAddress()}); - return cpu_buffer; + return mapping; } } // namespace metal diff --git a/src/xenia/gpu/metal/metal_primitive_processor.h b/src/xenia/gpu/metal/metal_primitive_processor.h index b87ebb2dd..59fc74d8a 100644 --- a/src/xenia/gpu/metal/metal_primitive_processor.h +++ b/src/xenia/gpu/metal/metal_primitive_processor.h @@ -11,7 +11,7 @@ #define XENIA_GPU_METAL_METAL_PRIMITIVE_PROCESSOR_H_ #include -#include +#include #include "third_party/metal-cpp/Metal/Metal.hpp" #include "xenia/gpu/primitive_processor.h" @@ -21,6 +21,7 @@ namespace gpu { namespace metal { class MetalCommandProcessor; +class MetalUploadBufferPool; class MetalPrimitiveProcessor : public PrimitiveProcessor { public: @@ -33,15 +34,10 @@ class MetalPrimitiveProcessor : public PrimitiveProcessor { bool Initialize(); void Shutdown(bool from_destructor = false); - void CompletedSubmissionUpdated(); - void BeginSubmission(); void BeginFrame(); void EndFrame(); MTL::Buffer* GetBuiltinIndexBuffer() const { return builtin_index_buffer_; } - MTL::Buffer* GetExpansionTriangleListIndexBuffer() const { - return expansion_triangle_list_index_buffer_; - } MTL::Buffer* GetConvertedIndexBuffer(size_t handle, uint64_t& offset_bytes_out) const; @@ -53,6 +49,9 @@ class MetalPrimitiveProcessor : public PrimitiveProcessor { xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd, uint32_t coalignment_original_address, size_t& backend_handle_out) override; + bool RequestGuestIndexSharedMemoryRange( + uint32_t guest_index_base, uint32_t guest_index_buffer_needed_bytes, + ProcessedIndexBufferType index_buffer_type) override; private: MetalCommandProcessor& command_processor_; @@ -63,23 +62,11 @@ class MetalPrimitiveProcessor : public PrimitiveProcessor { }; std::vector converted_index_buffers_; - uint64_t current_frame_ = 0; // Built-in index buffer for primitive type conversion MTL::Buffer* builtin_index_buffer_ = nullptr; - uint64_t builtin_index_buffer_gpu_address_ = 0; size_t builtin_index_buffer_size_ = 0; - // Fallback index buffer for SPIRV-Cross point/rectangle VS expansion. - // Uses triangle-list topology to avoid relying on primitive restart. - MTL::Buffer* expansion_triangle_list_index_buffer_ = nullptr; - - // Per-frame index buffer for primitive conversion - struct FrameIndexBuffer { - MTL::Buffer* buffer = nullptr; - size_t size = 0; - uint64_t last_frame_used = 0; - }; - std::vector frame_index_buffers_; + std::unique_ptr frame_index_buffer_pool_; }; } // namespace metal diff --git a/src/xenia/gpu/metal/metal_render_target_cache.cc b/src/xenia/gpu/metal/metal_render_target_cache.cc index c8f594469..f327d03eb 100644 --- a/src/xenia/gpu/metal/metal_render_target_cache.cc +++ b/src/xenia/gpu/metal/metal_render_target_cache.cc @@ -8,19 +8,15 @@ */ #include "xenia/gpu/metal/metal_render_target_cache.h" -#include "xenia/gpu/gpu_flags.h" #include #include -#include #include -#include #include -#include #include #include -#include "third_party/stb/stb_image_write.h" +#include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/byte_order.h" #include "xenia/base/logging.h" @@ -51,6 +47,7 @@ #include "xenia/gpu/shaders/bytecode/metal/resolve_full_8bpp_cs.h" #include "xenia/gpu/shaders/bytecode/metal/resolve_full_8bpp_scaled_cs.h" +#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" #include "xenia/gpu/metal/metal_command_processor.h" #include "xenia/gpu/texture_info.h" #include "xenia/gpu/texture_util.h" @@ -62,16 +59,20 @@ DEFINE_bool( "Metal"); DEFINE_bool(metal_transfer_fast_divmod, true, "Use fast exact div/mod in Metal transfer shaders", "Metal"); -DEFINE_bool(metal_transfer_tile_instancing, false, - "Use per-tile instanced draws for Metal transfer shaders", "Metal"); +DEFINE_bool(metal_transfer_native_stencil_output, true, + "Use Metal fragment stencil output for transfer stencil writes", + "Metal"); DEFINE_bool( metal_transfer_msaa_sample_id, true, "Use sample_id in Metal transfer shaders for MSAA (sample-rate shading)", "Metal"); -DEFINE_int32(metal_memory_log_rate, 0, - "Log Metal render target/pipeline/instance buffer sizes every N " - "frames (0 to disable)", - "Metal"); +DEFINE_bool(metal_transfer_in_draw_pass, true, + "Encode eligible color ownership transfers in the guest draw pass", + "Metal"); +DEFINE_bool(metal_direct_host_resolve, true, + "Resolve eligible fast color/depth copies directly from Metal host " + "render targets to shared/scaled resolve memory", + "Metal"); DEFINE_bool(metal_use_heaps, true, "Use MTLHeap-backed texture allocations in Metal to reduce " "allocation overhead and fragmentation.", @@ -101,35 +102,9 @@ class ScopedAutoreleasePool { NS::AutoreleasePool* pool_; }; -#if XE_PLATFORM_IOS -constexpr size_t kTransferTileInstanceBufferMaxBytes = - 64ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceSoftBaseBytes = 4ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceSoftLowCoverageBytes = - 8ull * 1024ull * 1024ull; -constexpr uint64_t kTransferTileInstanceLowCoverageRatioDivisor = 8ull; -constexpr uint64_t kTransferTileInstanceMediumCoverageRatioDivisor = 3ull; -constexpr size_t kTransferTileInstanceSmallRectPenaltyCount = 4; -constexpr size_t kTransferTileInstanceSmallRectPenaltyNumerator = 1; -constexpr size_t kTransferTileInstanceSmallRectPenaltyDenominator = 2; -constexpr size_t kTransferTileInstanceNearCapReserveBytes = - 8ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceNearCapUsagePercent = 84; -#else -constexpr size_t kTransferTileInstanceBufferMaxBytes = - 256ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceSoftBaseBytes = 32ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceSoftLowCoverageBytes = - 64ull * 1024ull * 1024ull; -constexpr uint64_t kTransferTileInstanceLowCoverageRatioDivisor = 3ull; -constexpr uint64_t kTransferTileInstanceMediumCoverageRatioDivisor = 2ull; -constexpr size_t kTransferTileInstanceSmallRectPenaltyCount = 2; -constexpr size_t kTransferTileInstanceSmallRectPenaltyNumerator = 3; -constexpr size_t kTransferTileInstanceSmallRectPenaltyDenominator = 4; -constexpr size_t kTransferTileInstanceNearCapReserveBytes = - 32ull * 1024ull * 1024ull; -constexpr size_t kTransferTileInstanceNearCapUsagePercent = 90; -#endif +uint32_t EstimateRenderTargetBytesPerPixel(bool is_64bpp) { + return is_64bpp ? 8u : 4u; +} MTL::ComputePipelineState* CreateComputePipelineFromEmbeddedLibrary( MTL::Device* device, const void* metallib_data, size_t metallib_size, @@ -159,8 +134,21 @@ MTL::ComputePipelineState* CreateComputePipelineFromEmbeddedLibrary( return nullptr; } - MTL::ComputePipelineState* pipeline = - device->newComputePipelineState(fn, &error); + // Label the pipeline so each resolve variant is distinguishable in the + // Xcode GPU trace; the entrypoint is always "entry_xe", so without a label + // every variant looks identical there. + MTL::ComputePipelineDescriptor* pipeline_desc = + MTL::ComputePipelineDescriptor::alloc()->init(); + pipeline_desc->setComputeFunction(fn); + if (debug_name) { + pipeline_desc->setLabel( + NS::String::string(debug_name, NS::UTF8StringEncoding)); + } + MTL::ComputePipelineState* pipeline = device->newComputePipelineState( + pipeline_desc, MTL::PipelineOptionNone, + static_cast(nullptr), + &error); + pipeline_desc->release(); fn->release(); lib->release(); @@ -173,6 +161,195 @@ MTL::ComputePipelineState* CreateComputePipelineFromEmbeddedLibrary( return pipeline; } +bool IsResolveDirectHostRTFastCandidate( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: + return true; + default: + return false; + } +} + +size_t DirectHostResolveFullDestIndex( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return 0; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + return 1; + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 3; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 4; + default: + return 5; + } +} + +size_t ResolveFastBppIndex(draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: + return 0; + case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: + return 1; + default: + break; + } + assert_unhandled_case(shader); + return 0; +} + +size_t ResolveFastMsaaIndex(draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: + return 0; + case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: + return 1; + default: + break; + } + assert_unhandled_case(shader); + return 0; +} + +bool IsResolveDirectHostRTFullColorCandidate( + draw_util::ResolveCopyShaderIndex shader) { + return DirectHostResolveFullDestIndex(shader) < 5; +} + +bool IsResolveDirectHostRTFullColorSourcePackable( + xenos::ColorRenderTargetFormat format) { + switch (format) { + case xenos::ColorRenderTargetFormat::k_8_8_8_8: + case xenos::ColorRenderTargetFormat::k_2_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_32_FLOAT: + case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: + return true; + default: + return false; + } +} + +bool IsResolveDirectHostRTCandidate(draw_util::ResolveCopyShaderIndex shader) { + return IsResolveDirectHostRTFastCandidate(shader) || + IsResolveDirectHostRTFullColorCandidate(shader); +} + +uint32_t DirectHostResolveFullDestBppLog2( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return 0; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + return 1; + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 3; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 4; + default: + break; + } + assert_unhandled_case(shader); + return 2; +} + +uint32_t DirectHostResolvePixelsPerThread( + draw_util::ResolveCopyShaderIndex shader, bool source_is_64bpp, + xenos::MsaaSamples msaa_samples) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return msaa_samples >= xenos::MsaaSamples::k4X ? 4 : 8; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 4; + default: + return source_is_64bpp ? 4 : 8; + } +} + +const char* ResolveCopyEncoderLabel(bool direct_host_rt_candidate) { + return direct_host_rt_candidate ? "XeniaResolveCopyDirectCandidate" + : "XeniaResolveCopyFallback"; +} + +const char* ResolveDumpEncoderLabel(bool direct_host_rt_candidate) { + return direct_host_rt_candidate ? "XeniaEDRAMDumpResolveDirectCandidate" + : "XeniaEDRAMDumpResolveFallback"; +} + +struct AttachmentLoadStoreActions { + MTL::LoadAction load = MTL::LoadActionLoad; + MTL::StoreAction store = MTL::StoreActionStore; +}; + +AttachmentLoadStoreActions GetRealAttachmentLoadStoreActions( + bool needs_initial_clear, bool previous_contents_needed = true) { + if (needs_initial_clear) { + return {MTL::LoadActionClear, MTL::StoreActionStore}; + } + return { + previous_contents_needed ? MTL::LoadActionLoad : MTL::LoadActionDontCare, + MTL::StoreActionStore}; +} + +AttachmentLoadStoreActions GetTransientAttachmentLoadStoreActions() { + return {MTL::LoadActionDontCare, MTL::StoreActionDontCare}; +} + +void SetAttachmentLoadStoreActions( + MTL::RenderPassAttachmentDescriptor* attachment, + AttachmentLoadStoreActions actions) { + attachment->setLoadAction(actions.load); + attachment->setStoreAction(actions.store); +} + +void SetEncoderLabel(MTL::CommandEncoder* encoder, const char* label) { + if (!encoder || !label) { + return; + } + encoder->setLabel(NS::String::string(label, NS::UTF8StringEncoding)); +} + +void EndSharedMemoryUploadBlitEncoderForCommandBuffer( + MetalCommandProcessor& command_processor, + MTL::CommandBuffer* command_buffer) { + if (command_buffer && + command_buffer == command_processor.GetCurrentCommandBuffer()) { + command_processor.EndSharedMemoryUploadBlitEncoder(); + } +} + +void PushEncoderDebugGroup(MTL::CommandEncoder* encoder, + const std::string& label) { + if (!encoder || label.empty()) { + return; + } + encoder->pushDebugGroup( + NS::String::string(label.c_str(), NS::UTF8StringEncoding)); +} + // Packing formats for transferring host RT contents to the EDRAM buffer. // Keep numeric values in sync with Metal dump shaders in // InitializeEdramComputeShaders. @@ -195,317 +372,6 @@ constexpr uint32_t kMetalEdramDumpFlagHasStencil = 1u << 0; constexpr uint32_t kMetalEdramDumpFlagDepthRound = 1u << 1; constexpr uint32_t kMetalEdramDumpFlagGammaAsLinear = 1u << 2; -struct DebugColor { - float r; - float g; - float b; - float a; -}; - -uint32_t FloatToBits(float value) { - uint32_t bits = 0; - std::memcpy(&bits, &value, sizeof(bits)); - return bits; -} - -float BitsToFloat(uint32_t value) { - float out = 0.0f; - std::memcpy(&out, &value, sizeof(out)); - return out; -} - -float HalfToFloat(uint16_t value) { - uint32_t sign = (value >> 15) & 1u; - uint32_t exponent = (value >> 10) & 0x1Fu; - uint32_t mantissa = value & 0x3FFu; - if (exponent == 0u) { - if (mantissa == 0u) { - return sign ? -0.0f : 0.0f; - } - float base = float(mantissa) * (1.0f / 1024.0f); - float result = std::ldexp(base, -14); - return sign ? -result : result; - } - if (exponent == 31u) { - float inf = std::numeric_limits::infinity(); - return sign ? -inf : inf; - } - float base = 1.0f + float(mantissa) * (1.0f / 1024.0f); - float result = std::ldexp(base, int(exponent) - 15); - return sign ? -result : result; -} - -uint16_t FloatToHalf(float value) { - uint32_t bits = FloatToBits(value); - uint32_t sign = (bits >> 16) & 0x8000u; - int exponent = int((bits >> 23) & 0xFFu) - 127 + 15; - uint32_t mantissa = bits & 0x7FFFFFu; - if (exponent <= 0) { - if (exponent < -10) { - return uint16_t(sign); - } - mantissa |= 0x800000u; - uint32_t shift = uint32_t(14 - exponent); - uint32_t half = mantissa >> shift; - if ((mantissa >> (shift - 1u)) & 1u) { - ++half; - } - return uint16_t(sign | half); - } - if (exponent >= 31) { - return uint16_t(sign | 0x7C00u); - } - uint32_t half = (uint32_t(exponent) << 10) | (mantissa >> 13); - if (mantissa & 0x1000u) { - ++half; - } - return uint16_t(sign | half); -} - -uint32_t PackUnorm(float value, float scale) { - float clamped = std::min(std::max(value, 0.0f), 1.0f); - return uint32_t(clamped * scale + 0.5f); -} - -uint32_t PackSnorm16(float value) { - float clamped = std::min(std::max(value, -1.0f), 1.0f); - float bias = clamped >= 0.0f ? 0.5f : -0.5f; - int packed = int(clamped * 32767.0f + bias); - return uint32_t(packed) & 0xFFFFu; -} - -uint32_t XePreClampedFloat32To7e3(float value) { - uint32_t f32 = FloatToBits(value); - uint32_t biased_f32; - if (f32 < 0x3E800000u) { - uint32_t f32_exp = f32 >> 23u; - uint32_t shift = 125u - f32_exp; - shift = std::min(shift, 24u); - uint32_t mantissa = (f32 & 0x7FFFFFu) | 0x800000u; - biased_f32 = mantissa >> shift; - } else { - biased_f32 = f32 + 0xC2000000u; - } - uint32_t round_bit = (biased_f32 >> 16u) & 1u; - uint32_t f10 = biased_f32 + 0x7FFFu + round_bit; - return (f10 >> 16u) & 0x3FFu; -} - -uint32_t XeUnclampedFloat32To7e3(float value) { - if (!std::isfinite(value)) { - value = 0.0f; - } - float clamped = std::min(std::max(value, 0.0f), 31.875f); - return XePreClampedFloat32To7e3(clamped); -} - -float XeFloat7e3To32(uint32_t f10) { - f10 &= 0x3FFu; - if (!f10) { - return 0.0f; - } - uint32_t mantissa = f10 & 0x7Fu; - uint32_t exponent = f10 >> 7u; - if (exponent == 0u) { - uint32_t lzcnt = 0; - if (mantissa != 0u) { - lzcnt = uint32_t(__builtin_clz(mantissa)) - 24u; - } - exponent = uint32_t(int32_t(1) - int32_t(lzcnt)); - mantissa = (mantissa << lzcnt) & 0x7Fu; - } - uint32_t f32 = ((exponent + 124u) << 23u) | (mantissa << 16u); - return BitsToFloat(f32); -} - -uint32_t PackR8G8B8A8Unorm(const DebugColor& color) { - uint32_t r = PackUnorm(color.r, 255.0f); - uint32_t g = PackUnorm(color.g, 255.0f); - uint32_t b = PackUnorm(color.b, 255.0f); - uint32_t a = PackUnorm(color.a, 255.0f); - return r | (g << 8u) | (b << 16u) | (a << 24u); -} - -bool PackColor32bpp(uint32_t format, const DebugColor& color, - uint32_t* packed_out) { - switch (format) { - case uint32_t(MetalEdramDumpFormat::kColorRGBA8): { - *packed_out = PackR8G8B8A8Unorm(color); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRGB10A2Unorm): { - uint32_t r = PackUnorm(color.r, 1023.0f); - uint32_t g = PackUnorm(color.g, 1023.0f); - uint32_t b = PackUnorm(color.b, 1023.0f); - uint32_t a = PackUnorm(color.a, 3.0f); - *packed_out = r | (g << 10u) | (b << 20u) | (a << 30u); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRGB10A2Float): { - uint32_t r = XeUnclampedFloat32To7e3(color.r); - uint32_t g = XeUnclampedFloat32To7e3(color.g); - uint32_t b = XeUnclampedFloat32To7e3(color.b); - uint32_t a = PackUnorm(color.a, 3.0f); - *packed_out = (r & 0x3FFu) | ((g & 0x3FFu) << 10u) | - ((b & 0x3FFu) << 20u) | ((a & 0x3u) << 30u); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRG16Snorm): { - uint32_t r = PackSnorm16(color.r); - uint32_t g = PackSnorm16(color.g); - *packed_out = r | (g << 16u); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRG16Float): { - uint16_t r = FloatToHalf(color.r); - uint16_t g = FloatToHalf(color.g); - *packed_out = uint32_t(r) | (uint32_t(g) << 16u); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorR32Float): { - *packed_out = FloatToBits(color.r); - return true; - } - default: - break; - } - return false; -} - -bool UnpackColor32bpp(uint32_t format, uint32_t packed, DebugColor* color_out) { - if (!color_out) { - return false; - } - switch (format) { - case uint32_t(MetalEdramDumpFormat::kColorRGBA8): { - color_out->r = float(packed & 0xFFu) * (1.0f / 255.0f); - color_out->g = float((packed >> 8u) & 0xFFu) * (1.0f / 255.0f); - color_out->b = float((packed >> 16u) & 0xFFu) * (1.0f / 255.0f); - color_out->a = float(packed >> 24u) * (1.0f / 255.0f); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRGB10A2Unorm): { - color_out->r = float(packed & 0x3FFu) * (1.0f / 1023.0f); - color_out->g = float((packed >> 10u) & 0x3FFu) * (1.0f / 1023.0f); - color_out->b = float((packed >> 20u) & 0x3FFu) * (1.0f / 1023.0f); - color_out->a = float((packed >> 30u) & 0x3u) * (1.0f / 3.0f); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRGB10A2Float): { - color_out->r = XeFloat7e3To32(packed & 0x3FFu); - color_out->g = XeFloat7e3To32((packed >> 10u) & 0x3FFu); - color_out->b = XeFloat7e3To32((packed >> 20u) & 0x3FFu); - color_out->a = float((packed >> 30u) & 0x3u) * (1.0f / 3.0f); - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRG16Snorm): { - int16_t r = int16_t(packed & 0xFFFFu); - int16_t g = int16_t(packed >> 16u); - color_out->r = std::max(float(r) * (1.0f / 32767.0f), -1.0f); - color_out->g = std::max(float(g) * (1.0f / 32767.0f), -1.0f); - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorRG16Float): { - uint16_t r = uint16_t(packed & 0xFFFFu); - uint16_t g = uint16_t(packed >> 16u); - color_out->r = HalfToFloat(r); - color_out->g = HalfToFloat(g); - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - case uint32_t(MetalEdramDumpFormat::kColorR32Float): { - color_out->r = BitsToFloat(packed); - color_out->g = 0.0f; - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - default: - break; - } - return false; -} - -bool DecodeColorTexel(MTL::PixelFormat format, const uint8_t* bytes, - DebugColor* color_out) { - if (!color_out) { - return false; - } - switch (format) { - case MTL::PixelFormatRGBA16Float: { - uint16_t components[4]; - std::memcpy(components, bytes, sizeof(components)); - color_out->r = HalfToFloat(components[0]); - color_out->g = HalfToFloat(components[1]); - color_out->b = HalfToFloat(components[2]); - color_out->a = HalfToFloat(components[3]); - return true; - } - case MTL::PixelFormatRG16Float: { - uint16_t components[2]; - std::memcpy(components, bytes, sizeof(components)); - color_out->r = HalfToFloat(components[0]); - color_out->g = HalfToFloat(components[1]); - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - case MTL::PixelFormatRGBA8Unorm: { - color_out->r = float(bytes[0]) * (1.0f / 255.0f); - color_out->g = float(bytes[1]) * (1.0f / 255.0f); - color_out->b = float(bytes[2]) * (1.0f / 255.0f); - color_out->a = float(bytes[3]) * (1.0f / 255.0f); - return true; - } - case MTL::PixelFormatBGRA8Unorm: { - color_out->b = float(bytes[0]) * (1.0f / 255.0f); - color_out->g = float(bytes[1]) * (1.0f / 255.0f); - color_out->r = float(bytes[2]) * (1.0f / 255.0f); - color_out->a = float(bytes[3]) * (1.0f / 255.0f); - return true; - } - case MTL::PixelFormatRGB10A2Unorm: - case MTL::PixelFormatBGR10A2Unorm: { - uint32_t packed = 0; - std::memcpy(&packed, bytes, sizeof(packed)); - DebugColor unpacked; - unpacked.r = float(packed & 0x3FFu) * (1.0f / 1023.0f); - unpacked.g = float((packed >> 10u) & 0x3FFu) * (1.0f / 1023.0f); - unpacked.b = float((packed >> 20u) & 0x3FFu) * (1.0f / 1023.0f); - unpacked.a = float((packed >> 30u) & 0x3u) * (1.0f / 3.0f); - if (format == MTL::PixelFormatBGR10A2Unorm) { - std::swap(unpacked.r, unpacked.b); - } - *color_out = unpacked; - return true; - } - case MTL::PixelFormatR32Float: { - uint32_t packed = 0; - std::memcpy(&packed, bytes, sizeof(packed)); - color_out->r = BitsToFloat(packed); - color_out->g = 0.0f; - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - case MTL::PixelFormatRG32Float: { - uint32_t packed[2] = {}; - std::memcpy(packed, bytes, sizeof(packed)); - color_out->r = BitsToFloat(packed[0]); - color_out->g = BitsToFloat(packed[1]); - color_out->b = 0.0f; - color_out->a = 1.0f; - return true; - } - default: - break; - } - return false; -} - size_t MsaaSamplesToIndex(xenos::MsaaSamples samples) { switch (samples) { case xenos::MsaaSamples::k1X: @@ -570,17 +436,6 @@ struct TransferShaderConstants { uint32_t stencil_clear; }; -struct TransferTileInstance { - float origin_x; - float origin_y; - uint32_t tile_index; - uint32_t padding; - uint32_t source_base_x; - uint32_t source_base_y; - uint32_t host_base_x; - uint32_t host_base_y; -}; - struct TransferRectInstance { float origin_x; float origin_y; @@ -627,10 +482,9 @@ constexpr TransferModeInfo kTransferModeInfos[] = { } // namespace bool MetalRenderTargetCache::IsKey64bpp(RenderTargetKey key) const { - // For host texture storage and transfers, gamma-as-unorm16 uses RGBA16Unorm - // which is 64bpp. This is needed for correct transfer calculations. - // NOTE: EDRAM dump path needs special handling - the EDRAM buffer is still - // 32bpp even when host storage is 64bpp. See DumpRenderTargets. + // For host texture storage and byte estimates, gamma-as-unorm16 uses + // RGBA16Unorm. Guest/EDRAM addressing is still 32bpp, so transfer rectangle + // and shader tile math must use RenderTargetKey::Is64bpp instead. return key.Is64bpp() || (!key.is_depth && key.GetColorFormat() == @@ -719,10 +573,145 @@ MetalRenderTargetCache::MetalRenderTargetCache( MetalRenderTargetCache::~MetalRenderTargetCache() { Shutdown(true); } +MetalRenderTargetCache::TelemetryStats +MetalRenderTargetCache::GetAndResetTelemetryStats() { + TelemetryStats stats = telemetry_; + telemetry_ = TelemetryStats(); + return stats; +} + RenderTargetCache::Path MetalRenderTargetCache::GetPath() const { return Path::kHostRenderTargets; } +bool MetalRenderTargetCache::InitializeEdramBufferViews() { + ReleaseEdramBufferViews(); + if (!edram_buffer_) { + return false; + } + + struct ViewInit { + uint32_t element_size_bytes_pow2; + MTL::PixelFormat format; + MTL::Texture** texture_out; + const char* label; + }; + const ViewInit kViews[] = { + {2, MTL::PixelFormatR32Uint, &edram_r32_uint_buffer_view_, + "XeniaEDRAMR32UintView"}, + {3, MTL::PixelFormatRG32Uint, &edram_r32g32_uint_buffer_view_, + "XeniaEDRAMR32G32UintView"}, + {4, MTL::PixelFormatRGBA32Uint, &edram_r32g32b32a32_uint_buffer_view_, + "XeniaEDRAMR32G32B32A32UintView"}, + }; + + for (const ViewInit& view_init : kViews) { + const NS::UInteger bytes_per_element = NS::UInteger(1u) + << view_init.element_size_bytes_pow2; + const NS::UInteger width = edram_buffer_->length() / bytes_per_element; + if (!width) { + XELOGE("MetalRenderTargetCache: invalid EDRAM bindless width for {}", + view_init.label); + ReleaseEdramBufferViews(); + return false; + } + + MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); + desc->setTextureType(MTL::TextureTypeTextureBuffer); + desc->setPixelFormat(view_init.format); + desc->setWidth(width); + desc->setHeight(1); + desc->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | + MTL::TextureUsagePixelFormatView); + desc->setResourceOptions(edram_buffer_->resourceOptions()); + desc->setStorageMode(edram_buffer_->storageMode()); + + MTL::Texture* texture = + edram_buffer_->newTexture(desc, 0, edram_buffer_->length()); + desc->release(); + if (!texture) { + XELOGE("MetalRenderTargetCache: failed to create EDRAM bindless view {}", + view_init.label); + ReleaseEdramBufferViews(); + return false; + } + texture->setLabel( + NS::String::string(view_init.label, NS::UTF8StringEncoding)); + *view_init.texture_out = texture; + } + + return true; +} + +void MetalRenderTargetCache::ReleaseEdramBufferViews() { + if (edram_r32_uint_buffer_view_) { + edram_r32_uint_buffer_view_->release(); + edram_r32_uint_buffer_view_ = nullptr; + } + if (edram_r32g32_uint_buffer_view_) { + edram_r32g32_uint_buffer_view_->release(); + edram_r32g32_uint_buffer_view_ = nullptr; + } + if (edram_r32g32b32a32_uint_buffer_view_) { + edram_r32g32b32a32_uint_buffer_view_->release(); + edram_r32g32b32a32_uint_buffer_view_ = nullptr; + } +} + +MTL::Texture* MetalRenderTargetCache::GetEdramUintPow2BufferView( + uint32_t element_size_bytes_pow2) const { + switch (element_size_bytes_pow2) { + case 2: + return edram_r32_uint_buffer_view_; + case 3: + return edram_r32g32_uint_buffer_view_; + case 4: + return edram_r32g32b32a32_uint_buffer_view_; + default: + assert_unhandled_case(element_size_bytes_pow2); + return nullptr; + } +} + +bool MetalRenderTargetCache::WriteEdramUintPow2BindlessDescriptor( + IRDescriptorTableEntry* entry, uint32_t element_size_bytes_pow2) const { + if (!entry || !edram_buffer_) { + return false; + } + MTL::Texture* texture_view = + GetEdramUintPow2BufferView(element_size_bytes_pow2); + if (!texture_view) { + return false; + } + IRBufferView buffer_view = {}; + const uint64_t bytes_per_element = uint64_t(1u) << element_size_bytes_pow2; + buffer_view.buffer = edram_buffer_; + buffer_view.bufferOffset = 0; + buffer_view.bufferSize = edram_buffer_->length(); + buffer_view.textureBufferView = texture_view; + buffer_view.textureViewOffsetInElements = uint32_t( + (uint64_t(edram_buffer_->gpuAddress()) % 16u) / bytes_per_element); + buffer_view.typedBuffer = true; + IRDescriptorTableSetBufferView(entry, &buffer_view); + return true; +} + +void MetalRenderTargetCache::CollectBindlessResources( + std::vector& resources_out) const { + if (edram_buffer_) { + resources_out.push_back(edram_buffer_); + } + if (edram_r32_uint_buffer_view_) { + resources_out.push_back(edram_r32_uint_buffer_view_); + } + if (edram_r32g32_uint_buffer_view_) { + resources_out.push_back(edram_r32g32_uint_buffer_view_); + } + if (edram_r32g32b32a32_uint_buffer_view_) { + resources_out.push_back(edram_r32g32b32a32_uint_buffer_view_); + } +} + bool MetalRenderTargetCache::Initialize() { device_ = command_processor_.GetMetalDevice(); if (!device_) { @@ -747,6 +736,9 @@ bool MetalRenderTargetCache::Initialize() { size_t min_heap_bytes = std::max(0, ::cvars::metal_heap_min_bytes); render_target_heap_pool_ = std::make_unique( device_, MTL::StorageModePrivate, min_heap_bytes, "XeniaRT"); + render_target_heap_pool_->SetHeapCreatedCallback([this](MTL::Heap* heap) { + command_processor_.AddResidencySetHeap(heap); + }); } // Create the EDRAM buffer. @@ -780,22 +772,30 @@ bool MetalRenderTargetCache::Initialize() { } } else { ScopedAutoreleasePool autorelease_pool; - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - if (queue) { - MTL::CommandBuffer* cmd = queue->commandBuffer(); - if (cmd) { - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (blit) { - blit->fillBuffer( - edram_buffer_, - NS::Range::Make(0, static_cast(edram_size_bytes)), - 0); - blit->endEncoding(); - cmd->commit(); - } + MTL::CommandBuffer* cmd = + command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=edram-init"); + if (cmd) { + MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); + if (blit) { + blit->fillBuffer( + edram_buffer_, + NS::Range::Make(0, static_cast(edram_size_bytes)), 0); + blit->endEncoding(); + command_processor_.CommitStandaloneAsync(cmd); + } else { + cmd->release(); } } } + if (!InitializeEdramBufferViews()) { + XELOGE("MetalRenderTargetCache: Failed to create EDRAM bindless views"); + return false; + } + ++bindless_resources_serial_; + if (!bindless_resources_serial_) { + bindless_resources_serial_ = 1; + } // Initialize EDRAM compute shaders if (!InitializeEdramComputeShaders()) { XELOGE( @@ -815,8 +815,9 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { } // Clean up dummy target - dummy_color_targets_.clear(); + dummy_color_target_owner_.reset(); dummy_color_target_ = nullptr; + dummy_color_target_shape_key_ = 0; if (cached_render_pass_descriptor_) { cached_render_pass_descriptor_->release(); cached_render_pass_descriptor_ = nullptr; @@ -828,12 +829,6 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { } } transfer_pipelines_.clear(); - for (auto& it : transfer_tile_pipelines_) { - if (it.second) { - it.second->release(); - } - } - transfer_tile_pipelines_.clear(); for (auto& it : edram_load_pipelines_) { if (it.second) { it.second->release(); @@ -874,6 +869,10 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { transfer_stencil_clear_state_->release(); transfer_stencil_clear_state_ = nullptr; } + if (transfer_stencil_output_state_) { + transfer_stencil_output_state_->release(); + transfer_stencil_output_state_ = nullptr; + } for (auto& state : transfer_stencil_bit_states_) { if (state) { state->release(); @@ -884,22 +883,6 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { transfer_dummy_buffer_->release(); transfer_dummy_buffer_ = nullptr; } - for (auto& buffer : transfer_tile_instance_buffers_) { - if (buffer) { - buffer->release(); - buffer = nullptr; - } - } - for (auto& retired_list : transfer_tile_instance_retired_buffers_) { - for (auto* buffer : retired_list) { - if (buffer) { - buffer->release(); - } - } - retired_list.clear(); - } - transfer_tile_instance_buffer_sizes_.fill(0); - transfer_tile_instance_buffer_offset_ = 0; for (size_t i = 0; i < xe::countof(transfer_dummy_color_float_); ++i) { if (transfer_dummy_color_float_[i]) { transfer_dummy_color_float_[i]->release(); @@ -921,6 +904,7 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { // Clean up EDRAM compute shaders ShutdownEdramComputeShaders(); + ReleaseEdramBufferViews(); if (edram_buffer_) { edram_buffer_->release(); @@ -945,148 +929,122 @@ void MetalRenderTargetCache::Shutdown(bool from_destructor) { bool MetalRenderTargetCache::InitializeEdramComputeShaders() { // Initialize the resolve / EDRAM compute pipelines used by the Metal backend. const bool draw_resolution_scaled = IsDrawResolutionScaled(); - edram_load_pipeline_ = nullptr; - edram_store_pipeline_ = nullptr; - edram_dump_color_32bpp_1xmsaa_pipeline_ = nullptr; - edram_dump_color_32bpp_2xmsaa_pipeline_ = nullptr; - edram_dump_color_32bpp_4xmsaa_pipeline_ = nullptr; - edram_dump_color_64bpp_1xmsaa_pipeline_ = nullptr; - edram_dump_color_64bpp_2xmsaa_pipeline_ = nullptr; - edram_dump_color_64bpp_4xmsaa_pipeline_ = nullptr; - edram_dump_depth_32bpp_1xmsaa_pipeline_ = nullptr; - edram_dump_depth_32bpp_2xmsaa_pipeline_ = nullptr; - edram_dump_depth_32bpp_4xmsaa_pipeline_ = nullptr; - resolve_full_8bpp_pipeline_ = nullptr; - resolve_full_16bpp_pipeline_ = nullptr; - resolve_full_32bpp_pipeline_ = nullptr; - resolve_full_64bpp_pipeline_ = nullptr; - resolve_full_128bpp_pipeline_ = nullptr; - resolve_fast_32bpp_1x2xmsaa_pipeline_ = nullptr; - resolve_fast_32bpp_4xmsaa_pipeline_ = nullptr; - resolve_fast_64bpp_1x2xmsaa_pipeline_ = nullptr; - resolve_fast_64bpp_4xmsaa_pipeline_ = nullptr; - resolve_full_8bpp_scaled_pipeline_ = nullptr; - resolve_full_16bpp_scaled_pipeline_ = nullptr; - resolve_full_32bpp_scaled_pipeline_ = nullptr; - resolve_full_64bpp_scaled_pipeline_ = nullptr; - resolve_full_128bpp_scaled_pipeline_ = nullptr; - resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_ = nullptr; - resolve_fast_32bpp_4xmsaa_scaled_pipeline_ = nullptr; - resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_ = nullptr; - resolve_fast_64bpp_4xmsaa_scaled_pipeline_ = nullptr; + for (auto& by_bpp : edram_dump_color_pipelines_) { + for (auto& by_source : by_bpp) { + for (auto*& pipeline : by_source) { + pipeline = nullptr; + } + } + } + for (auto*& pipeline : edram_dump_depth_pipelines_) { + pipeline = nullptr; + } + for (auto& by_scaled : resolve_full_pipelines_) { + for (auto*& pipeline : by_scaled) { + pipeline = nullptr; + } + } + for (auto& by_scaled : resolve_fast_pipelines_) { + for (auto& by_bpp : by_scaled) { + for (auto*& pipeline : by_bpp) { + pipeline = nullptr; + } + } + } + ResetDirectHostResolvePipelines(false); for (size_t i = 0; i < xe::countof(host_depth_store_pipelines_); ++i) { host_depth_store_pipelines_[i] = nullptr; } NS::Error* error = nullptr; - // Resolve compute pipelines. - resolve_full_8bpp_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_8bpp_cs_metallib, - sizeof(resolve_full_8bpp_cs_metallib), "resolve_full_8bpp"); - resolve_full_16bpp_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_16bpp_cs_metallib, - sizeof(resolve_full_16bpp_cs_metallib), "resolve_full_16bpp"); - resolve_full_32bpp_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_32bpp_cs_metallib, - sizeof(resolve_full_32bpp_cs_metallib), "resolve_full_32bpp"); - resolve_full_64bpp_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_64bpp_cs_metallib, - sizeof(resolve_full_64bpp_cs_metallib), "resolve_full_64bpp"); - resolve_full_128bpp_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_128bpp_cs_metallib, - sizeof(resolve_full_128bpp_cs_metallib), "resolve_full_128bpp"); - resolve_fast_32bpp_1x2xmsaa_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_32bpp_1x2xmsaa_cs_metallib, - sizeof(resolve_fast_32bpp_1x2xmsaa_cs_metallib), - "resolve_fast_32bpp_1x2xmsaa"); - resolve_fast_32bpp_4xmsaa_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_32bpp_4xmsaa_cs_metallib, - sizeof(resolve_fast_32bpp_4xmsaa_cs_metallib), - "resolve_fast_32bpp_4xmsaa"); - resolve_fast_64bpp_1x2xmsaa_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_64bpp_1x2xmsaa_cs_metallib, - sizeof(resolve_fast_64bpp_1x2xmsaa_cs_metallib), - "resolve_fast_64bpp_1x2xmsaa"); - resolve_fast_64bpp_4xmsaa_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_64bpp_4xmsaa_cs_metallib, - sizeof(resolve_fast_64bpp_4xmsaa_cs_metallib), - "resolve_fast_64bpp_4xmsaa"); - - if (!resolve_full_8bpp_pipeline_ || !resolve_full_16bpp_pipeline_ || - !resolve_full_32bpp_pipeline_ || !resolve_full_64bpp_pipeline_ || - !resolve_full_128bpp_pipeline_ || - !resolve_fast_32bpp_1x2xmsaa_pipeline_ || - !resolve_fast_32bpp_4xmsaa_pipeline_ || - !resolve_fast_64bpp_1x2xmsaa_pipeline_ || - !resolve_fast_64bpp_4xmsaa_pipeline_) { - XELOGE("Metal: failed to initialize resolve compute pipelines"); - return false; + struct ResolveFullPipelineConfig { + const void* metallib_data; + size_t metallib_size; + bool scaled; + draw_util::ResolveCopyShaderIndex copy_shader; + const char* debug_name; + }; + struct ResolveFastPipelineConfig { + const void* metallib_data; + size_t metallib_size; + bool scaled; + draw_util::ResolveCopyShaderIndex copy_shader; + const char* debug_name; + }; +#define XE_RESOLVE_FULL_CONFIG(id, scaled, copy_shader) \ + {id##_metallib, sizeof(id##_metallib), scaled, \ + draw_util::ResolveCopyShaderIndex::copy_shader, #id} +#define XE_RESOLVE_FAST_CONFIG(id, scaled, copy_shader) \ + { \ + id##_metallib, sizeof(id##_metallib), scaled, \ + draw_util::ResolveCopyShaderIndex::copy_shader, #id \ } + static constexpr ResolveFullPipelineConfig kResolveFullPipelineConfigs[] = { + XE_RESOLVE_FULL_CONFIG(resolve_full_8bpp_cs, false, kFull8bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_16bpp_cs, false, kFull16bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_32bpp_cs, false, kFull32bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_64bpp_cs, false, kFull64bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_128bpp_cs, false, kFull128bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_8bpp_scaled_cs, true, kFull8bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_16bpp_scaled_cs, true, kFull16bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_32bpp_scaled_cs, true, kFull32bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_64bpp_scaled_cs, true, kFull64bpp), + XE_RESOLVE_FULL_CONFIG(resolve_full_128bpp_scaled_cs, true, kFull128bpp), + }; + static constexpr ResolveFastPipelineConfig kResolveFastPipelineConfigs[] = { + XE_RESOLVE_FAST_CONFIG(resolve_fast_32bpp_1x2xmsaa_cs, false, + kFast32bpp1x2xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_32bpp_4xmsaa_cs, false, + kFast32bpp4xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_64bpp_1x2xmsaa_cs, false, + kFast64bpp1x2xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_64bpp_4xmsaa_cs, false, + kFast64bpp4xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_32bpp_1x2xmsaa_scaled_cs, true, + kFast32bpp1x2xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_32bpp_4xmsaa_scaled_cs, true, + kFast32bpp4xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_64bpp_1x2xmsaa_scaled_cs, true, + kFast64bpp1x2xMSAA), + XE_RESOLVE_FAST_CONFIG(resolve_fast_64bpp_4xmsaa_scaled_cs, true, + kFast64bpp4xMSAA), + }; +#undef XE_RESOLVE_FAST_CONFIG +#undef XE_RESOLVE_FULL_CONFIG - if (draw_resolution_scaled) { - resolve_full_8bpp_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_8bpp_scaled_cs_metallib, - sizeof(resolve_full_8bpp_scaled_cs_metallib), - "resolve_full_8bpp_scaled"); - resolve_full_16bpp_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_16bpp_scaled_cs_metallib, - sizeof(resolve_full_16bpp_scaled_cs_metallib), - "resolve_full_16bpp_scaled"); - resolve_full_32bpp_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_32bpp_scaled_cs_metallib, - sizeof(resolve_full_32bpp_scaled_cs_metallib), - "resolve_full_32bpp_scaled"); - resolve_full_64bpp_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_64bpp_scaled_cs_metallib, - sizeof(resolve_full_64bpp_scaled_cs_metallib), - "resolve_full_64bpp_scaled"); - resolve_full_128bpp_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_full_128bpp_scaled_cs_metallib, - sizeof(resolve_full_128bpp_scaled_cs_metallib), - "resolve_full_128bpp_scaled"); - resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_32bpp_1x2xmsaa_scaled_cs_metallib, - sizeof(resolve_fast_32bpp_1x2xmsaa_scaled_cs_metallib), - "resolve_fast_32bpp_1x2xmsaa_scaled"); - resolve_fast_32bpp_4xmsaa_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_32bpp_4xmsaa_scaled_cs_metallib, - sizeof(resolve_fast_32bpp_4xmsaa_scaled_cs_metallib), - "resolve_fast_32bpp_4xmsaa_scaled"); - resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_64bpp_1x2xmsaa_scaled_cs_metallib, - sizeof(resolve_fast_64bpp_1x2xmsaa_scaled_cs_metallib), - "resolve_fast_64bpp_1x2xmsaa_scaled"); - resolve_fast_64bpp_4xmsaa_scaled_pipeline_ = - CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_fast_64bpp_4xmsaa_scaled_cs_metallib, - sizeof(resolve_fast_64bpp_4xmsaa_scaled_cs_metallib), - "resolve_fast_64bpp_4xmsaa_scaled"); - if (!resolve_full_8bpp_scaled_pipeline_ || - !resolve_full_16bpp_scaled_pipeline_ || - !resolve_full_32bpp_scaled_pipeline_ || - !resolve_full_64bpp_scaled_pipeline_ || - !resolve_full_128bpp_scaled_pipeline_ || - !resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_ || - !resolve_fast_32bpp_4xmsaa_scaled_pipeline_ || - !resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_ || - !resolve_fast_64bpp_4xmsaa_scaled_pipeline_) { - XELOGE("Metal: failed to initialize scaled resolve compute pipelines"); + for (const ResolveFullPipelineConfig& cfg : kResolveFullPipelineConfigs) { + if (cfg.scaled && !draw_resolution_scaled) { + continue; + } + size_t scaled_index = cfg.scaled ? 1u : 0u; + MTL::ComputePipelineState*& pipeline = + resolve_full_pipelines_[scaled_index][DirectHostResolveFullDestIndex( + cfg.copy_shader)]; + pipeline = CreateComputePipelineFromEmbeddedLibrary( + device_, cfg.metallib_data, cfg.metallib_size, cfg.debug_name); + if (!pipeline) { + XELOGE("Metal: failed to initialize resolve compute pipelines"); return false; } } + for (const ResolveFastPipelineConfig& cfg : kResolveFastPipelineConfigs) { + if (cfg.scaled && !draw_resolution_scaled) { + continue; + } + size_t scaled_index = cfg.scaled ? 1u : 0u; + MTL::ComputePipelineState*& pipeline = + resolve_fast_pipelines_[scaled_index][ResolveFastBppIndex( + cfg.copy_shader)][ResolveFastMsaaIndex(cfg.copy_shader)]; + pipeline = CreateComputePipelineFromEmbeddedLibrary( + device_, cfg.metallib_data, cfg.metallib_size, cfg.debug_name); + if (!pipeline) { + XELOGE("Metal: failed to initialize resolve compute pipelines"); + return false; + } + } + + InitializeDirectHostResolvePipelines(draw_resolution_scaled); host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)] = CreateComputePipelineFromEmbeddedLibrary( @@ -1111,9 +1069,14 @@ bool MetalRenderTargetCache::InitializeEdramComputeShaders() { } } - // EDRAM dump compute shader for 32-bpp color, 1x MSAA. - { - static const char kEdramDumpColor32bpp1xMsaaShader[] = R"METAL( + // EDRAM dump compute shaders -- parameterized MSL template compiled for + // float, uint ownership-transfer, and depth sources. + // with different #defines. Each variant differs in MSAA sample count, + // bits-per-pixel, and whether it dumps color or depth data. + // + // Shared preamble: constants, utilities, and ALL pack functions so every + // variant can reference them through #if guards in the kernel template. + static const char kEdramDumpPreamble[] = R"METAL( #include using namespace metal; @@ -1163,9 +1126,12 @@ constant uint kDumpFormatColorRGBA16Unorm = 8; constant uint kDumpFormatColorRG32Float = 9; constant uint kDumpFormatDepthD24S8 = 16; constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; // bit 0 -constant uint kDumpFlagDepthRound = 2; // bit 1 -constant uint kDumpFlagGammaAsLinear = 4; // bit 2: source is linear, needs PWL gamma encode +constant uint kDumpFlagHasStencil = 1; // bit 0 +constant uint kDumpFlagDepthRound = 2; // bit 1 +constant uint kDumpFlagGammaAsLinear = 4; // bit 2 + +// --- Color 32bpp helpers (guarded so depth-only compiles skip them) --- +#if DUMP_IS_DEPTH == 0 && DUMP_BPP == 32 // PWL gamma encode: linear -> gamma (for gamma RTs stored as linear RGBA16Unorm) inline float XeLinearToPWLGamma(float value) { @@ -1260,161 +1226,22 @@ uint XePackColor32bpp(uint format, float4 color) { } } -kernel void edram_dump_color_32bpp_1xmsaa( - texture2d source [[texture(0)]], - device uint* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_coord = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - float4 color = source.read(source_coord); - - // If source is a linear RGBA16Unorm gamma RT, convert to PWL gamma encoding - if (constants.flags & kDumpFlagGammaAsLinear) { - color.rgb = XeLinearToPWLGamma3(color.rgb); - } - - uint packed = XePackColor32bpp(constants.format, color); - - edram[edram_index] = packed; -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor32bpp1xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_32bpp_1xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_32bpp_1xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_32bpp_1xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_32bpp_1xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_32bpp_1xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_32bpp_1xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } - - // EDRAM dump compute shader for 32-bpp color, 2x MSAA. - { - static const char kEdramDumpColor32bpp2xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; +uint XePackColor32bppUint(uint format, uint4 color) { + switch (format) { + case kDumpFormatColorRG16Snorm: + case kDumpFormatColorRG16Float: + return (color.r & 0xFFFFu) | ((color.g & 0xFFFFu) << 16u); + case kDumpFormatColorR32Float: + return color.r; + default: + return color.r; } } -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; // bit 0 -constant uint kDumpFlagDepthRound = 2; // bit 1 -constant uint kDumpFlagGammaAsLinear = 4; // bit 2: source is linear, needs PWL gamma encode +#endif // DUMP_IS_DEPTH == 0 && DUMP_BPP == 32 -// PWL gamma encode: linear -> gamma (for gamma RTs stored as linear RGBA16Unorm) -inline float XeLinearToPWLGamma(float value) { - float clamped = clamp(value, 0.0f, 1.0f); - float scale, offset; - if (clamped >= (128.0f / 1023.0f)) { - if (clamped >= (512.0f / 1023.0f)) { scale = 1023.0f / 8.0f; offset = 128.0f / 255.0f; } - else { scale = 1023.0f / 4.0f; offset = 64.0f / 255.0f; } - } else { - if (clamped >= (64.0f / 1023.0f)) { scale = 1023.0f / 2.0f; offset = 32.0f / 255.0f; } - else { scale = 1023.0f; offset = 0.0f; } - } - return trunc(clamped * scale) * (1.0f / 255.0f) + offset; -} -inline float3 XeLinearToPWLGamma3(float3 v) { - return float3(XeLinearToPWLGamma(v.r), XeLinearToPWLGamma(v.g), XeLinearToPWLGamma(v.b)); -} - -inline uint XePackUnorm(float value, float scale) { - return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); -} +// --- Color 64bpp helpers --- +#if DUMP_IS_DEPTH == 0 && DUMP_BPP == 64 inline uint XePackSnorm16(float value) { float clamped = clamp(value, -1.0f, 1.0f); @@ -1423,440 +1250,67 @@ inline uint XePackSnorm16(float value) { return uint(packed) & 0xFFFFu; } -uint XePreClampedFloat32To7e3(float value) { - uint f32 = as_type(value); - uint biased_f32; - if (f32 < 0x3E800000u) { - uint f32_exp = f32 >> 23u; - uint shift = 125u - f32_exp; - shift = min(shift, 24u); - uint mantissa = (f32 & 0x7FFFFFu) | 0x800000u; - biased_f32 = mantissa >> shift; - } else { - biased_f32 = f32 + 0xC2000000u; - } - uint round_bit = (biased_f32 >> 16u) & 1u; - uint f10 = biased_f32 + 0x7FFFu + round_bit; - return (f10 >> 16u) & 0x3FFu; -} - -uint XeUnclampedFloat32To7e3(float value) { - float clamped = min(max(value, 0.0f), 31.875f); - return XePreClampedFloat32To7e3(clamped); -} - -uint XePackColor32bpp(uint format, float4 color) { - switch (format) { - case kDumpFormatColorRGBA8: { - uint r = XePackUnorm(color.r, 255.0f); - uint g = XePackUnorm(color.g, 255.0f); - uint b = XePackUnorm(color.b, 255.0f); - uint a = XePackUnorm(color.a, 255.0f); - return r | (g << 8u) | (b << 16u) | (a << 24u); - } - case kDumpFormatColorRGB10A2Unorm: { - uint r = XePackUnorm(color.r, 1023.0f); - uint g = XePackUnorm(color.g, 1023.0f); - uint b = XePackUnorm(color.b, 1023.0f); - uint a = XePackUnorm(color.a, 3.0f); - return r | (g << 10u) | (b << 20u) | (a << 30u); - } - case kDumpFormatColorRGB10A2Float: { - uint r = XeUnclampedFloat32To7e3(color.r); - uint g = XeUnclampedFloat32To7e3(color.g); - uint b = XeUnclampedFloat32To7e3(color.b); - uint a = XePackUnorm(color.a, 3.0f); - return (r & 0x3FFu) | ((g & 0x3FFu) << 10u) | - ((b & 0x3FFu) << 20u) | ((a & 0x3u) << 30u); - } - case kDumpFormatColorRG16Snorm: { - uint r = XePackSnorm16(color.r); - uint g = XePackSnorm16(color.g); - return r | (g << 16u); - } - case kDumpFormatColorRG16Float: - return as_type(half2(color.rg)); - case kDumpFormatColorR32Float: - return as_type(color.r); - default: { - uint r = XePackUnorm(color.r, 255.0f); - uint g = XePackUnorm(color.g, 255.0f); - uint b = XePackUnorm(color.b, 255.0f); - uint a = XePackUnorm(color.a, 255.0f); - return r | (g << 8u) | (b << 16u) | (a << 24u); - } - } -} - -kernel void edram_dump_color_32bpp_2xmsaa( - texture2d_ms source [[texture(0)]], - device uint* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_id = source_sample.y & 1u; - uint2 pixel_coord = uint2(source_sample.x, source_sample.y >> 1); - - float4 color = source.read(pixel_coord, sample_id); - - // If source is a linear RGBA16Unorm gamma RT, convert to PWL gamma encoding - if (constants.flags & kDumpFlagGammaAsLinear) { - color.rgb = XeLinearToPWLGamma3(color.rgb); - } - - uint packed = XePackColor32bpp(constants.format, color); - - edram[edram_index] = packed; -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor32bpp2xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_32bpp_2xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_32bpp_2xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_32bpp_2xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_32bpp_2xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_32bpp_2xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_32bpp_2xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } - - // EDRAM dump compute shader for 32-bpp color, 4x MSAA. - { - static const char kEdramDumpColor32bpp4xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; // bit 0 -constant uint kDumpFlagDepthRound = 2; // bit 1 -constant uint kDumpFlagGammaAsLinear = 4; // bit 2: source is linear, needs PWL gamma encode - -// PWL gamma encode: linear -> gamma (for gamma RTs stored as linear RGBA16Unorm) -inline float XeLinearToPWLGamma(float value) { - float clamped = clamp(value, 0.0f, 1.0f); - float scale, offset; - if (clamped >= (128.0f / 1023.0f)) { - if (clamped >= (512.0f / 1023.0f)) { scale = 1023.0f / 8.0f; offset = 128.0f / 255.0f; } - else { scale = 1023.0f / 4.0f; offset = 64.0f / 255.0f; } - } else { - if (clamped >= (64.0f / 1023.0f)) { scale = 1023.0f / 2.0f; offset = 32.0f / 255.0f; } - else { scale = 1023.0f; offset = 0.0f; } - } - return trunc(clamped * scale) * (1.0f / 255.0f) + offset; -} -inline float3 XeLinearToPWLGamma3(float3 v) { - return float3(XeLinearToPWLGamma(v.r), XeLinearToPWLGamma(v.g), XeLinearToPWLGamma(v.b)); -} - inline uint XePackUnorm(float value, float scale) { return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); } -inline uint XePackSnorm16(float value) { - float clamped = clamp(value, -1.0f, 1.0f); - float bias = clamped >= 0.0f ? 0.5f : -0.5f; - int packed = int(clamped * 32767.0f + bias); - return uint(packed) & 0xFFFFu; -} - -uint XePreClampedFloat32To7e3(float value) { - uint f32 = as_type(value); - uint biased_f32; - if (f32 < 0x3E800000u) { - uint f32_exp = f32 >> 23u; - uint shift = 125u - f32_exp; - shift = min(shift, 24u); - uint mantissa = (f32 & 0x7FFFFFu) | 0x800000u; - biased_f32 = mantissa >> shift; - } else { - biased_f32 = f32 + 0xC2000000u; - } - uint round_bit = (biased_f32 >> 16u) & 1u; - uint f10 = biased_f32 + 0x7FFFu + round_bit; - return (f10 >> 16u) & 0x3FFu; -} - -uint XeUnclampedFloat32To7e3(float value) { - float clamped = min(max(value, 0.0f), 31.875f); - return XePreClampedFloat32To7e3(clamped); -} - -uint XePackColor32bpp(uint format, float4 color) { +uint2 XePackColor64bpp(uint format, float4 color) { switch (format) { - case kDumpFormatColorRGBA8: { - uint r = XePackUnorm(color.r, 255.0f); - uint g = XePackUnorm(color.g, 255.0f); - uint b = XePackUnorm(color.b, 255.0f); - uint a = XePackUnorm(color.a, 255.0f); - return r | (g << 8u) | (b << 16u) | (a << 24u); - } - case kDumpFormatColorRGB10A2Unorm: { - uint r = XePackUnorm(color.r, 1023.0f); - uint g = XePackUnorm(color.g, 1023.0f); - uint b = XePackUnorm(color.b, 1023.0f); - uint a = XePackUnorm(color.a, 3.0f); - return r | (g << 10u) | (b << 20u) | (a << 30u); - } - case kDumpFormatColorRGB10A2Float: { - uint r = XeUnclampedFloat32To7e3(color.r); - uint g = XeUnclampedFloat32To7e3(color.g); - uint b = XeUnclampedFloat32To7e3(color.b); - uint a = XePackUnorm(color.a, 3.0f); - return (r & 0x3FFu) | ((g & 0x3FFu) << 10u) | - ((b & 0x3FFu) << 20u) | ((a & 0x3u) << 30u); - } - case kDumpFormatColorRG16Snorm: { + case kDumpFormatColorRGBA16Snorm: { uint r = XePackSnorm16(color.r); uint g = XePackSnorm16(color.g); - return r | (g << 16u); + uint b = XePackSnorm16(color.b); + uint a = XePackSnorm16(color.a); + uint rg = r | (g << 16u); + uint ba = b | (a << 16u); + return uint2(rg, ba); + } + case kDumpFormatColorRGBA16Float: { + uint rg = as_type(half2(color.rg)); + uint ba = as_type(half2(color.ba)); + return uint2(rg, ba); + } + case kDumpFormatColorRGBA16Unorm: { + uint r = XePackUnorm(color.r, 65535.0f); + uint g = XePackUnorm(color.g, 65535.0f); + uint b = XePackUnorm(color.b, 65535.0f); + uint a = XePackUnorm(color.a, 65535.0f); + uint rg = r | (g << 16u); + uint ba = b | (a << 16u); + return uint2(rg, ba); + } + case kDumpFormatColorRG32Float: { + uint r = as_type(color.r); + uint g = as_type(color.g); + return uint2(r, g); } - case kDumpFormatColorRG16Float: - return as_type(half2(color.rg)); - case kDumpFormatColorR32Float: - return as_type(color.r); default: { - uint r = XePackUnorm(color.r, 255.0f); - uint g = XePackUnorm(color.g, 255.0f); - uint b = XePackUnorm(color.b, 255.0f); - uint a = XePackUnorm(color.a, 255.0f); - return r | (g << 8u) | (b << 16u) | (a << 24u); + uint rg = as_type(half2(color.rg)); + uint ba = as_type(half2(color.ba)); + return uint2(rg, ba); } } } -kernel void edram_dump_color_32bpp_4xmsaa( - texture2d_ms source [[texture(0)]], - device uint* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_x = source_sample.x & 1u; - uint sample_y = source_sample.y & 1u; - uint sample_id = sample_x | (sample_y << 1u); - uint2 pixel_coord = uint2(source_sample.x >> 1, source_sample.y >> 1); - - float4 color = source.read(pixel_coord, sample_id); - - // If source is a linear RGBA16Unorm gamma RT, convert to PWL gamma encoding - if (constants.flags & kDumpFlagGammaAsLinear) { - color.rgb = XeLinearToPWLGamma3(color.rgb); - } - - uint packed = XePackColor32bpp(constants.format, color); - - edram[edram_index] = packed; -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor32bpp4xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_32bpp_4xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_32bpp_4xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_32bpp_4xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_32bpp_4xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_32bpp_4xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_32bpp_4xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } +uint2 XePackColor64bppUint(uint format, uint4 color) { + switch (format) { + case kDumpFormatColorRGBA16Snorm: + case kDumpFormatColorRGBA16Float: { + uint rg = (color.r & 0xFFFFu) | ((color.g & 0xFFFFu) << 16u); + uint ba = (color.b & 0xFFFFu) | ((color.a & 0xFFFFu) << 16u); + return uint2(rg, ba); } - } - - // EDRAM dump compute shader for 32-bpp depth, 4x MSAA. - { - static const char kEdramDumpDepth32bpp4xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; + case kDumpFormatColorRG32Float: + return uint2(color.r, color.g); + default: + return uint2(color.r, color.g); } } -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; +#endif // DUMP_IS_DEPTH == 0 && DUMP_BPP == 64 + +// --- Depth 32bpp helpers --- +#if DUMP_IS_DEPTH == 1 inline uint XeRoundToNearestEven(float value) { float floor_value = floor(value); @@ -1880,364 +1334,44 @@ uint XeFloat32To20e4(float value, bool round_to_nearest_even) { return (f24 >> 3u) & 0xFFFFFFu; } -kernel void edram_dump_depth_32bpp_4xmsaa( - texture2d_ms source [[texture(0)]], - texture2d_ms stencil [[texture(1)]], - device uint* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - uint2 edram_sample_in_tile = sample_in_tile; - uint tile_width_half = tile_size.x >> 1u; - edram_sample_in_tile.x = - (edram_sample_in_tile.x < tile_width_half) - ? (edram_sample_in_tile.x + tile_width_half) - : (edram_sample_in_tile.x - tile_width_half); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = - edram_sample_in_tile.y * tile_size.x + edram_sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_x = source_sample.x & 1u; - uint sample_y = source_sample.y & 1u; - uint sample_id = sample_x | (sample_y << 1u); - uint2 pixel_coord = uint2(source_sample.x >> 1, source_sample.y >> 1); - - float depth = source.read(pixel_coord, sample_id).r; - - uint depth24; - if (constants.format == kDumpFormatDepthD24FS8) { - bool round_depth = (constants.flags & kDumpFlagDepthRound) != 0u; - depth24 = XeFloat32To20e4(depth * 2.0f, round_depth); - } else { - float depth_f = clamp(depth, 0.0f, 1.0f) * 16777215.0f; - depth24 = XeRoundToNearestEven(depth_f); - } - - uint stencil_value = 0u; - if ((constants.flags & kDumpFlagHasStencil) != 0u) { - stencil_value = stencil.read(pixel_coord, sample_id).x & 0xFFu; - } - - uint packed = (depth24 << 8u) | stencil_value; - - edram[edram_index] = packed; -} +#endif // DUMP_IS_DEPTH == 1 )METAL"; - NS::String* source = NS::String::string(kEdramDumpDepth32bpp4xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_depth_32bpp_4xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_depth_32bpp_4xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_depth_32bpp_4xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_depth_32bpp_4xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_depth_32bpp_4xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_depth_32bpp_4xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } + // Parameterized kernel template. Uses #if on DUMP_MSAA_SAMPLES, DUMP_BPP, + // and DUMP_IS_DEPTH to select texture type, sample access, EDRAM element + // type, pack function, and the depth-specific tile-half swizzle. + static const char kEdramDumpKernelTemplate[] = R"METAL( - // EDRAM dump compute shader for 32-bpp depth, 2x MSAA. - { - static const char kEdramDumpDepth32bpp2xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; - -inline uint XeRoundToNearestEven(float value) { - float floor_value = floor(value); - float frac = value - floor_value; - uint result = uint(floor_value); - if (frac > 0.5f || (frac == 0.5f && (result & 1u))) { - result += 1u; - } - return result; -} - -uint XeFloat32To20e4(float value, bool round_to_nearest_even) { - uint f32 = as_type(value); - f32 = min((f32 <= 0x7FFFFFFFu) ? f32 : 0u, 0x3FFFFFF8u); - uint denormalized = - ((f32 & 0x7FFFFFu) | 0x800000u) >> min(113u - (f32 >> 23u), 24u); - uint f24 = (f32 < 0x38800000u) ? denormalized : (f32 + 0xC8000000u); - if (round_to_nearest_even) { - f24 += 3u + ((f24 >> 3u) & 1u); - } - return (f24 >> 3u) & 0xFFFFFFu; -} - -kernel void edram_dump_depth_32bpp_2xmsaa( - texture2d_ms source [[texture(0)]], - texture2d_ms stencil [[texture(1)]], - device uint* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - uint2 edram_sample_in_tile = sample_in_tile; - uint tile_width_half = tile_size.x >> 1u; - edram_sample_in_tile.x = - (edram_sample_in_tile.x < tile_width_half) - ? (edram_sample_in_tile.x + tile_width_half) - : (edram_sample_in_tile.x - tile_width_half); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = - edram_sample_in_tile.y * tile_size.x + edram_sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_id = source_sample.y & 1u; - uint2 pixel_coord = uint2(source_sample.x, source_sample.y >> 1); - - float depth = source.read(pixel_coord, sample_id).r; - - uint depth24; - if (constants.format == kDumpFormatDepthD24FS8) { - bool round_depth = (constants.flags & kDumpFlagDepthRound) != 0u; - depth24 = XeFloat32To20e4(depth * 2.0f, round_depth); - } else { - float depth_f = clamp(depth, 0.0f, 1.0f) * 16777215.0f; - depth24 = XeRoundToNearestEven(depth_f); - } - - uint stencil_value = 0u; - if ((constants.flags & kDumpFlagHasStencil) != 0u) { - stencil_value = stencil.read(pixel_coord, sample_id).x & 0xFFu; - } - - uint packed = (depth24 << 8u) | stencil_value; - - edram[edram_index] = packed; -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpDepth32bpp2xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_depth_32bpp_2xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_depth_32bpp_2xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_depth_32bpp_2xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_depth_32bpp_2xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_depth_32bpp_2xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_depth_32bpp_2xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } - - // EDRAM dump compute shader for 32-bpp depth, 1x MSAA. - { - static const char kEdramDumpDepth32bpp1xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; - -inline uint XeRoundToNearestEven(float value) { - float floor_value = floor(value); - float frac = value - floor_value; - uint result = uint(floor_value); - if (frac > 0.5f || (frac == 0.5f && (result & 1u))) { - result += 1u; - } - return result; -} - -uint XeFloat32To20e4(float value, bool round_to_nearest_even) { - uint f32 = as_type(value); - f32 = min((f32 <= 0x7FFFFFFFu) ? f32 : 0u, 0x3FFFFFF8u); - uint denormalized = - ((f32 & 0x7FFFFFu) | 0x800000u) >> min(113u - (f32 >> 23u), 24u); - uint f24 = (f32 < 0x38800000u) ? denormalized : (f32 + 0xC8000000u); - if (round_to_nearest_even) { - f24 += 3u + ((f24 >> 3u) & 1u); - } - return (f24 >> 3u) & 0xFFFFFFu; -} - -kernel void edram_dump_depth_32bpp_1xmsaa( +kernel void DUMP_KERNEL_NAME( +#if DUMP_IS_DEPTH == 1 + #if DUMP_MSAA_SAMPLES == 1 texture2d source [[texture(0)]], texture2d stencil [[texture(1)]], + #else + texture2d_ms source [[texture(0)]], + texture2d_ms stencil [[texture(1)]], + #endif device uint* edram [[buffer(0)]], +#else // color + #if DUMP_SOURCE_IS_UINT == 1 + #if DUMP_MSAA_SAMPLES == 1 + texture2d source [[texture(0)]], + #else + texture2d_ms source [[texture(0)]], + #endif + #else + #if DUMP_MSAA_SAMPLES == 1 + texture2d source [[texture(0)]], + #else + texture2d_ms source [[texture(0)]], + #endif + #endif + #if DUMP_BPP == 64 + device uint2* edram [[buffer(0)]], + #else + device uint* edram [[buffer(0)]], + #endif +#endif constant EdramDumpConstants& constants [[buffer(1)]], uint3 tid [[thread_position_in_grid]]) { const uint kEdramTileCount = 2048u; @@ -2254,12 +1388,16 @@ kernel void edram_dump_depth_32bpp_1xmsaa( sample_in_tile_y); uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); + +#if DUMP_IS_DEPTH == 1 + // Depth tiles use a half-width swizzle for EDRAM sample indexing. uint2 edram_sample_in_tile = sample_in_tile; uint tile_width_half = tile_size.x >> 1u; edram_sample_in_tile.x = (edram_sample_in_tile.x < tile_width_half) ? (edram_sample_in_tile.x + tile_width_half) : (edram_sample_in_tile.x - tile_width_half); +#endif uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; @@ -2267,8 +1405,12 @@ kernel void edram_dump_depth_32bpp_1xmsaa( uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); uint tile_samples = tile_size.x * tile_size.y; +#if DUMP_IS_DEPTH == 1 uint sample_index = edram_sample_in_tile.y * tile_size.x + edram_sample_in_tile.x; +#else + uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; +#endif uint edram_index = wrapped_tile * tile_samples + sample_index; uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; @@ -2276,10 +1418,32 @@ kernel void edram_dump_depth_32bpp_1xmsaa( uint source_tile_x = 0u; XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); + +#if DUMP_MSAA_SAMPLES == 1 uint2 source_coord = uint2(source_tile_x * tile_size.x + sample_in_tile.x, source_tile_y * tile_size.y + sample_in_tile.y); +#else + uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, + source_tile_y * tile_size.y + sample_in_tile.y); + #if DUMP_MSAA_SAMPLES == 2 + uint sample_id = (source_sample.y & 1u) ? DUMP_2X_SAMPLE_MAP_1 + : DUMP_2X_SAMPLE_MAP_0; + uint2 pixel_coord = uint2(source_sample.x, source_sample.y >> 1); + #else // 4x + uint sample_x = source_sample.x & 1u; + uint sample_y = source_sample.y & 1u; + uint sample_id = sample_x | (sample_y << 1u); + uint2 pixel_coord = uint2(source_sample.x >> 1, source_sample.y >> 1); + #endif +#endif + // --- Read and pack --- +#if DUMP_IS_DEPTH == 1 + #if DUMP_MSAA_SAMPLES == 1 float depth = source.read(source_coord).r; + #else + float depth = source.read(pixel_coord, sample_id).r; + #endif uint depth24; if (constants.format == kDumpFormatDepthD24FS8) { @@ -2292,581 +1456,165 @@ kernel void edram_dump_depth_32bpp_1xmsaa( uint stencil_value = 0u; if ((constants.flags & kDumpFlagHasStencil) != 0u) { + #if DUMP_MSAA_SAMPLES == 1 stencil_value = stencil.read(source_coord).x & 0xFFu; + #else + stencil_value = stencil.read(pixel_coord, sample_id).x & 0xFFu; + #endif } - uint packed = (depth24 << 8u) | stencil_value; + edram[edram_index] = (depth24 << 8u) | stencil_value; - edram[edram_index] = packed; +#elif DUMP_BPP == 32 + #if DUMP_SOURCE_IS_UINT == 1 + #if DUMP_MSAA_SAMPLES == 1 + uint4 color = source.read(source_coord); + #else + uint4 color = source.read(pixel_coord, sample_id); + #endif + + edram[edram_index] = XePackColor32bppUint(constants.format, color); + #else + #if DUMP_MSAA_SAMPLES == 1 + float4 color = source.read(source_coord); + #else + float4 color = source.read(pixel_coord, sample_id); + #endif + + // If source is a linear RGBA16Unorm gamma RT, convert to PWL gamma encoding + if (constants.flags & kDumpFlagGammaAsLinear) { + color.rgb = XeLinearToPWLGamma3(color.rgb); + } + + edram[edram_index] = XePackColor32bpp(constants.format, color); + #endif + +#else // color 64bpp + #if DUMP_SOURCE_IS_UINT == 1 + #if DUMP_MSAA_SAMPLES == 1 + uint4 color = source.read(source_coord); + #else + uint4 color = source.read(pixel_coord, sample_id); + #endif + + edram[edram_index] = XePackColor64bppUint(constants.format, color); + #else + #if DUMP_MSAA_SAMPLES == 1 + float4 color = source.read(source_coord); + #else + float4 color = source.read(pixel_coord, sample_id); + #endif + + edram[edram_index] = XePackColor64bpp(constants.format, color); + #endif +#endif } )METAL"; - NS::String* source = NS::String::string(kEdramDumpDepth32bpp1xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); + // Configuration table for the EDRAM dump shader variants. + struct EdramDumpConfig { + const char* kernel_name; + uint32_t msaa_samples; + uint32_t bpp; + uint32_t is_depth; + uint32_t source_is_uint; + MTL::ComputePipelineState** pipeline; + }; + const EdramDumpConfig kEdramDumpConfigs[] = { + {"edram_dump_color_32bpp_1xmsaa", 1, 32, 0, 0, + &edram_dump_color_pipelines_[0][0][0]}, + {"edram_dump_color_32bpp_2xmsaa", 2, 32, 0, 0, + &edram_dump_color_pipelines_[0][0][1]}, + {"edram_dump_color_32bpp_4xmsaa", 4, 32, 0, 0, + &edram_dump_color_pipelines_[0][0][2]}, + {"edram_dump_color_uint_32bpp_1xmsaa", 1, 32, 0, 1, + &edram_dump_color_pipelines_[0][1][0]}, + {"edram_dump_color_uint_32bpp_2xmsaa", 2, 32, 0, 1, + &edram_dump_color_pipelines_[0][1][1]}, + {"edram_dump_color_uint_32bpp_4xmsaa", 4, 32, 0, 1, + &edram_dump_color_pipelines_[0][1][2]}, + {"edram_dump_depth_32bpp_1xmsaa", 1, 32, 1, 0, + &edram_dump_depth_pipelines_[0]}, + {"edram_dump_depth_32bpp_2xmsaa", 2, 32, 1, 0, + &edram_dump_depth_pipelines_[1]}, + {"edram_dump_depth_32bpp_4xmsaa", 4, 32, 1, 0, + &edram_dump_depth_pipelines_[2]}, + {"edram_dump_color_64bpp_1xmsaa", 1, 64, 0, 0, + &edram_dump_color_pipelines_[1][0][0]}, + {"edram_dump_color_64bpp_2xmsaa", 2, 64, 0, 0, + &edram_dump_color_pipelines_[1][0][1]}, + {"edram_dump_color_64bpp_4xmsaa", 4, 64, 0, 0, + &edram_dump_color_pipelines_[1][0][2]}, + {"edram_dump_color_uint_64bpp_1xmsaa", 1, 64, 0, 1, + &edram_dump_color_pipelines_[1][1][0]}, + {"edram_dump_color_uint_64bpp_2xmsaa", 2, 64, 0, 1, + &edram_dump_color_pipelines_[1][1][1]}, + {"edram_dump_color_uint_64bpp_4xmsaa", 4, 64, 0, 1, + &edram_dump_color_pipelines_[1][1][2]}, + }; + + auto append_dump_define = [](std::string& src, const char* name, + uint32_t value) { + src.append("#define "); + src.append(name); + src.push_back(' '); + src.append(std::to_string(value)); + src.push_back('\n'); + }; + auto append_dump_define_str = [](std::string& src, const char* name, + const char* value) { + src.append("#define "); + src.append(name); + src.push_back(' '); + src.append(value); + src.push_back('\n'); + }; + + for (const auto& cfg : kEdramDumpConfigs) { + std::string dump_source; + dump_source.reserve(8192); + + // Prepend per-variant #defines before the shared preamble. + append_dump_define(dump_source, "DUMP_MSAA_SAMPLES", cfg.msaa_samples); + append_dump_define(dump_source, "DUMP_BPP", cfg.bpp); + append_dump_define(dump_source, "DUMP_IS_DEPTH", cfg.is_depth); + append_dump_define(dump_source, "DUMP_SOURCE_IS_UINT", cfg.source_is_uint); + append_dump_define_str(dump_source, "DUMP_KERNEL_NAME", cfg.kernel_name); + if (cfg.msaa_samples == 2) { + // 2x MSAA sample remapping: guest sample 0/1 -> host sample indices. + uint32_t map_0 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA(0, msaa_2x_supported_); + uint32_t map_1 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA(1, msaa_2x_supported_); + append_dump_define(dump_source, "DUMP_2X_SAMPLE_MAP_0", map_0); + append_dump_define(dump_source, "DUMP_2X_SAMPLE_MAP_1", map_1); + } + + dump_source.append(kEdramDumpPreamble); + dump_source.append(kEdramDumpKernelTemplate); + + NS::String* ns_source = + NS::String::string(dump_source.c_str(), NS::UTF8StringEncoding); + MTL::Library* lib = device_->newLibrary(ns_source, nullptr, &error); if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_depth_32bpp_1xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_depth_32bpp_1xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_depth_32bpp_1xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_depth_32bpp_1xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_depth_32bpp_1xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_depth_32bpp_1xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } + XELOGW("Metal: failed to compile {} shader: {}", cfg.kernel_name, + error ? error->localizedDescription()->utf8String() : "unknown"); + continue; } - } - - // EDRAM dump compute shader for 64-bpp color, 1x MSAA. - // 64bpp tiles are half the horizontal width (40 samples per tile, not 80). - { - static const char kEdramDumpColor64bpp1xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; - -inline uint XePackSnorm16(float value) { - float clamped = clamp(value, -1.0f, 1.0f); - float bias = clamped >= 0.0f ? 0.5f : -0.5f; - int packed = int(clamped * 32767.0f + bias); - return uint(packed) & 0xFFFFu; -} - -inline uint XePackUnorm(float value, float scale) { - return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); -} - -uint2 XePackColor64bpp(uint format, float4 color) { - switch (format) { - case kDumpFormatColorRGBA16Snorm: { - uint r = XePackSnorm16(color.r); - uint g = XePackSnorm16(color.g); - uint b = XePackSnorm16(color.b); - uint a = XePackSnorm16(color.a); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); + NS::String* fn_name = + NS::String::string(cfg.kernel_name, NS::UTF8StringEncoding); + MTL::Function* fn = lib->newFunction(fn_name); + if (!fn) { + XELOGW("Metal: {} missing entrypoint", cfg.kernel_name); + lib->release(); + continue; } - case kDumpFormatColorRGBA16Float: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - case kDumpFormatColorRGBA16Unorm: { - uint r = XePackUnorm(color.r, 65535.0f); - uint g = XePackUnorm(color.g, 65535.0f); - uint b = XePackUnorm(color.b, 65535.0f); - uint a = XePackUnorm(color.a, 65535.0f); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); - } - case kDumpFormatColorRG32Float: { - uint r = as_type(color.r); - uint g = as_type(color.g); - return uint2(r, g); - } - default: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - } -} - -kernel void edram_dump_color_64bpp_1xmsaa( - texture2d source [[texture(0)]], - device uint2* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - // 64bpp: 40 samples wide per tile instead of 80. - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_coord = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - float4 color = source.read(source_coord); - - edram[edram_index] = XePackColor64bpp(constants.format, color); -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor64bpp1xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_64bpp_1xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_64bpp_1xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_64bpp_1xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_64bpp_1xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_64bpp_1xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_64bpp_1xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } - - // EDRAM dump compute shader for 64-bpp color, 2x MSAA. - { - static const char kEdramDumpColor64bpp2xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; - -inline uint XePackSnorm16(float value) { - float clamped = clamp(value, -1.0f, 1.0f); - float bias = clamped >= 0.0f ? 0.5f : -0.5f; - int packed = int(clamped * 32767.0f + bias); - return uint(packed) & 0xFFFFu; -} - -inline uint XePackUnorm(float value, float scale) { - return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); -} - -uint2 XePackColor64bpp(uint format, float4 color) { - switch (format) { - case kDumpFormatColorRGBA16Snorm: { - uint r = XePackSnorm16(color.r); - uint g = XePackSnorm16(color.g); - uint b = XePackSnorm16(color.b); - uint a = XePackSnorm16(color.a); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); - } - case kDumpFormatColorRGBA16Float: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - case kDumpFormatColorRGBA16Unorm: { - uint r = XePackUnorm(color.r, 65535.0f); - uint g = XePackUnorm(color.g, 65535.0f); - uint b = XePackUnorm(color.b, 65535.0f); - uint a = XePackUnorm(color.a, 65535.0f); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); - } - case kDumpFormatColorRG32Float: { - uint r = as_type(color.r); - uint g = as_type(color.g); - return uint2(r, g); - } - default: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - } -} - -kernel void edram_dump_color_64bpp_2xmsaa( - texture2d_ms source [[texture(0)]], - device uint2* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - // 64bpp: 40 samples wide per tile instead of 80. - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_id = source_sample.y & 1u; - uint2 pixel_coord = uint2(source_sample.x, source_sample.y >> 1); - - float4 color = source.read(pixel_coord, sample_id); - - edram[edram_index] = XePackColor64bpp(constants.format, color); -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor64bpp2xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_64bpp_2xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_64bpp_2xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_64bpp_2xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_64bpp_2xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_64bpp_2xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_64bpp_2xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } - } - } - - // EDRAM dump compute shader for 64-bpp color, 4x MSAA. - { - static const char kEdramDumpColor64bpp4xMsaaShader[] = R"METAL( -#include -using namespace metal; - -struct EdramDumpConstants { - uint dispatch_first_tile; - uint source_base_tiles; - uint dest_pitch_tiles; - uint source_pitch_tiles; - uint2 resolution_scale; - uint tile_size_x; - uint tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint format; - uint flags; - uint padding; -}; - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - if (w == 0u) { - q = 0u; - r = 0u; - return; - } - q = uint(float(x) * inv_w); - r = x - q * w; - if (r >= w) { - r -= w; - q += 1u; - } else if (r > x) { - r += w; - q -= 1u; - } -} - -constant uint kDumpFormatColorRGBA8 = 0; -constant uint kDumpFormatColorRGB10A2Unorm = 1; -constant uint kDumpFormatColorRGB10A2Float = 2; -constant uint kDumpFormatColorRG16Snorm = 3; -constant uint kDumpFormatColorRG16Float = 4; -constant uint kDumpFormatColorR32Float = 5; -constant uint kDumpFormatColorRGBA16Snorm = 6; -constant uint kDumpFormatColorRGBA16Float = 7; -constant uint kDumpFormatColorRGBA16Unorm = 8; -constant uint kDumpFormatColorRG32Float = 9; -constant uint kDumpFormatDepthD24S8 = 16; -constant uint kDumpFormatDepthD24FS8 = 17; -constant uint kDumpFlagHasStencil = 1; -constant uint kDumpFlagDepthRound = 2; - -inline uint XePackSnorm16(float value) { - float clamped = clamp(value, -1.0f, 1.0f); - float bias = clamped >= 0.0f ? 0.5f : -0.5f; - int packed = int(clamped * 32767.0f + bias); - return uint(packed) & 0xFFFFu; -} - -inline uint XePackUnorm(float value, float scale) { - return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); -} - -uint2 XePackColor64bpp(uint format, float4 color) { - switch (format) { - case kDumpFormatColorRGBA16Snorm: { - uint r = XePackSnorm16(color.r); - uint g = XePackSnorm16(color.g); - uint b = XePackSnorm16(color.b); - uint a = XePackSnorm16(color.a); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); - } - case kDumpFormatColorRGBA16Float: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - case kDumpFormatColorRGBA16Unorm: { - uint r = XePackUnorm(color.r, 65535.0f); - uint g = XePackUnorm(color.g, 65535.0f); - uint b = XePackUnorm(color.b, 65535.0f); - uint a = XePackUnorm(color.a, 65535.0f); - uint rg = r | (g << 16u); - uint ba = b | (a << 16u); - return uint2(rg, ba); - } - case kDumpFormatColorRG32Float: { - uint r = as_type(color.r); - uint g = as_type(color.g); - return uint2(r, g); - } - default: { - uint rg = as_type(half2(color.rg)); - uint ba = as_type(half2(color.ba)); - return uint2(rg, ba); - } - } -} - -kernel void edram_dump_color_64bpp_4xmsaa( - texture2d_ms source [[texture(0)]], - device uint2* edram [[buffer(0)]], - constant EdramDumpConstants& constants [[buffer(1)]], - uint3 tid [[thread_position_in_grid]]) { - const uint kEdramTileCount = 2048u; - - // 64bpp: 40 samples wide per tile instead of 80. - uint2 tile_size = uint2(constants.tile_size_x, constants.tile_size_y); - - uint tile_coord_x = 0u; - uint tile_coord_y = 0u; - uint sample_in_tile_x = 0u; - uint sample_in_tile_y = 0u; - XeFastDivMod(tid.x, tile_size.x, constants.tile_size_inv_x, tile_coord_x, - sample_in_tile_x); - XeFastDivMod(tid.y, tile_size.y, constants.tile_size_inv_y, tile_coord_y, - sample_in_tile_y); - uint2 tile_coord = uint2(tile_coord_x, tile_coord_y); - uint2 sample_in_tile = uint2(sample_in_tile_x, sample_in_tile_y); - - uint rect_tile_index = tile_coord.y * constants.dest_pitch_tiles + tile_coord.x; - - uint nonwrapped_tile = constants.dispatch_first_tile + rect_tile_index; - uint wrapped_tile = nonwrapped_tile & (kEdramTileCount - 1u); - - uint tile_samples = tile_size.x * tile_size.y; - uint sample_index = sample_in_tile.y * tile_size.x + sample_in_tile.x; - uint edram_index = wrapped_tile * tile_samples + sample_index; - - uint source_linear_tile = nonwrapped_tile - constants.source_base_tiles; - uint source_tile_y = 0u; - uint source_tile_x = 0u; - XeFastDivMod(source_linear_tile, constants.source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_y, source_tile_x); - uint2 source_sample = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - uint sample_x = source_sample.x & 1u; - uint sample_y = source_sample.y & 1u; - uint sample_id = sample_x | (sample_y << 1u); - uint2 pixel_coord = uint2(source_sample.x >> 1, source_sample.y >> 1); - - float4 color = source.read(pixel_coord, sample_id); - - edram[edram_index] = XePackColor64bpp(constants.format, color); -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpColor64bpp4xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(source, nullptr, &error); - if (!lib) { - XELOGW( - "Metal: failed to compile edram_dump_color_64bpp_4xmsaa shader: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } else { - NS::String* fn_name = NS::String::string("edram_dump_color_64bpp_4xmsaa", - NS::UTF8StringEncoding); - MTL::Function* fn = lib->newFunction(fn_name); - if (!fn) { - XELOGW("Metal: edram_dump_color_64bpp_4xmsaa missing entrypoint"); - lib->release(); - } else { - edram_dump_color_64bpp_4xmsaa_pipeline_ = - device_->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!edram_dump_color_64bpp_4xmsaa_pipeline_) { - XELOGW( - "Metal: failed to create edram_dump_color_64bpp_4xmsaa pipeline: " - "{}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - } + *cfg.pipeline = device_->newComputePipelineState(fn, &error); + fn->release(); + lib->release(); + if (!*cfg.pipeline) { + XELOGW("Metal: failed to create {} pipeline: {}", cfg.kernel_name, + error ? error->localizedDescription()->utf8String() : "unknown"); } } @@ -2874,126 +1622,41 @@ kernel void edram_dump_color_64bpp_4xmsaa( } void MetalRenderTargetCache::ShutdownEdramComputeShaders() { - if (edram_load_pipeline_) { - edram_load_pipeline_->release(); - edram_load_pipeline_ = nullptr; + for (auto& by_bpp : edram_dump_color_pipelines_) { + for (auto& by_source : by_bpp) { + for (auto*& pipeline : by_source) { + if (pipeline) { + pipeline->release(); + pipeline = nullptr; + } + } + } } - if (edram_store_pipeline_) { - edram_store_pipeline_->release(); - edram_store_pipeline_ = nullptr; + for (auto*& pipeline : edram_dump_depth_pipelines_) { + if (pipeline) { + pipeline->release(); + pipeline = nullptr; + } } - // Release 32bpp color dump pipelines - if (edram_dump_color_32bpp_1xmsaa_pipeline_) { - edram_dump_color_32bpp_1xmsaa_pipeline_->release(); - edram_dump_color_32bpp_1xmsaa_pipeline_ = nullptr; + for (auto& by_scaled : resolve_full_pipelines_) { + for (auto*& pipeline : by_scaled) { + if (pipeline) { + pipeline->release(); + pipeline = nullptr; + } + } } - if (edram_dump_color_32bpp_2xmsaa_pipeline_) { - edram_dump_color_32bpp_2xmsaa_pipeline_->release(); - edram_dump_color_32bpp_2xmsaa_pipeline_ = nullptr; - } - if (edram_dump_color_32bpp_4xmsaa_pipeline_) { - edram_dump_color_32bpp_4xmsaa_pipeline_->release(); - edram_dump_color_32bpp_4xmsaa_pipeline_ = nullptr; - } - // Release 64bpp color dump pipelines - if (edram_dump_color_64bpp_1xmsaa_pipeline_) { - edram_dump_color_64bpp_1xmsaa_pipeline_->release(); - edram_dump_color_64bpp_1xmsaa_pipeline_ = nullptr; - } - if (edram_dump_color_64bpp_2xmsaa_pipeline_) { - edram_dump_color_64bpp_2xmsaa_pipeline_->release(); - edram_dump_color_64bpp_2xmsaa_pipeline_ = nullptr; - } - if (edram_dump_color_64bpp_4xmsaa_pipeline_) { - edram_dump_color_64bpp_4xmsaa_pipeline_->release(); - edram_dump_color_64bpp_4xmsaa_pipeline_ = nullptr; - } - // Release 32bpp depth dump pipelines - if (edram_dump_depth_32bpp_1xmsaa_pipeline_) { - edram_dump_depth_32bpp_1xmsaa_pipeline_->release(); - edram_dump_depth_32bpp_1xmsaa_pipeline_ = nullptr; - } - if (edram_dump_depth_32bpp_2xmsaa_pipeline_) { - edram_dump_depth_32bpp_2xmsaa_pipeline_->release(); - edram_dump_depth_32bpp_2xmsaa_pipeline_ = nullptr; - } - if (edram_dump_depth_32bpp_4xmsaa_pipeline_) { - edram_dump_depth_32bpp_4xmsaa_pipeline_->release(); - edram_dump_depth_32bpp_4xmsaa_pipeline_ = nullptr; - } - // Release resolve pipelines - if (resolve_full_8bpp_pipeline_) { - resolve_full_8bpp_pipeline_->release(); - resolve_full_8bpp_pipeline_ = nullptr; - } - if (resolve_full_16bpp_pipeline_) { - resolve_full_16bpp_pipeline_->release(); - resolve_full_16bpp_pipeline_ = nullptr; - } - if (resolve_full_32bpp_pipeline_) { - resolve_full_32bpp_pipeline_->release(); - resolve_full_32bpp_pipeline_ = nullptr; - } - if (resolve_full_64bpp_pipeline_) { - resolve_full_64bpp_pipeline_->release(); - resolve_full_64bpp_pipeline_ = nullptr; - } - if (resolve_full_128bpp_pipeline_) { - resolve_full_128bpp_pipeline_->release(); - resolve_full_128bpp_pipeline_ = nullptr; - } - if (resolve_fast_32bpp_1x2xmsaa_pipeline_) { - resolve_fast_32bpp_1x2xmsaa_pipeline_->release(); - resolve_fast_32bpp_1x2xmsaa_pipeline_ = nullptr; - } - if (resolve_fast_32bpp_4xmsaa_pipeline_) { - resolve_fast_32bpp_4xmsaa_pipeline_->release(); - resolve_fast_32bpp_4xmsaa_pipeline_ = nullptr; - } - if (resolve_fast_64bpp_1x2xmsaa_pipeline_) { - resolve_fast_64bpp_1x2xmsaa_pipeline_->release(); - resolve_fast_64bpp_1x2xmsaa_pipeline_ = nullptr; - } - if (resolve_fast_64bpp_4xmsaa_pipeline_) { - resolve_fast_64bpp_4xmsaa_pipeline_->release(); - resolve_fast_64bpp_4xmsaa_pipeline_ = nullptr; - } - if (resolve_full_8bpp_scaled_pipeline_) { - resolve_full_8bpp_scaled_pipeline_->release(); - resolve_full_8bpp_scaled_pipeline_ = nullptr; - } - if (resolve_full_16bpp_scaled_pipeline_) { - resolve_full_16bpp_scaled_pipeline_->release(); - resolve_full_16bpp_scaled_pipeline_ = nullptr; - } - if (resolve_full_32bpp_scaled_pipeline_) { - resolve_full_32bpp_scaled_pipeline_->release(); - resolve_full_32bpp_scaled_pipeline_ = nullptr; - } - if (resolve_full_64bpp_scaled_pipeline_) { - resolve_full_64bpp_scaled_pipeline_->release(); - resolve_full_64bpp_scaled_pipeline_ = nullptr; - } - if (resolve_full_128bpp_scaled_pipeline_) { - resolve_full_128bpp_scaled_pipeline_->release(); - resolve_full_128bpp_scaled_pipeline_ = nullptr; - } - if (resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_) { - resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_->release(); - resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_ = nullptr; - } - if (resolve_fast_32bpp_4xmsaa_scaled_pipeline_) { - resolve_fast_32bpp_4xmsaa_scaled_pipeline_->release(); - resolve_fast_32bpp_4xmsaa_scaled_pipeline_ = nullptr; - } - if (resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_) { - resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_->release(); - resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_ = nullptr; - } - if (resolve_fast_64bpp_4xmsaa_scaled_pipeline_) { - resolve_fast_64bpp_4xmsaa_scaled_pipeline_->release(); - resolve_fast_64bpp_4xmsaa_scaled_pipeline_ = nullptr; + for (auto& by_scaled : resolve_fast_pipelines_) { + for (auto& by_bpp : by_scaled) { + for (auto*& pipeline : by_bpp) { + if (pipeline) { + pipeline->release(); + pipeline = nullptr; + } + } + } } + ResetDirectHostResolvePipelines(true); for (size_t i = 0; i < xe::countof(host_depth_store_pipelines_); ++i) { if (host_depth_store_pipelines_[i]) { host_depth_store_pipelines_[i]->release(); @@ -3002,18 +1665,23 @@ void MetalRenderTargetCache::ShutdownEdramComputeShaders() { } } +void MetalRenderTargetCache::MarkRenderPassDescriptorDirty() { + render_pass_descriptor_dirty_ = true; +} + void MetalRenderTargetCache::ClearCache() { + ClearPendingDrawPassTransfers(); + // Clear current bindings for (uint32_t i = 0; i < 4; ++i) { current_color_targets_[i] = nullptr; } current_depth_target_ = nullptr; - render_pass_descriptor_dirty_ = true; + MarkRenderPassDescriptorDirty(); - // Clear the tracking of which render targets have been cleared - cleared_render_targets_this_frame_.clear(); - dummy_color_targets_.clear(); + dummy_color_target_owner_.reset(); dummy_color_target_ = nullptr; + dummy_color_target_shape_key_ = 0; render_target_map_.clear(); // Call base implementation @@ -3021,31 +1689,36 @@ void MetalRenderTargetCache::ClearCache() { } void MetalRenderTargetCache::BeginFrame() { - ++frame_id_; - - // Clear the tracking of which render targets have been cleared this frame - cleared_render_targets_this_frame_.clear(); + (void)FlushPendingDrawPassTransfers(); // Call base implementation RenderTargetCache::BeginFrame(); +} - if (::cvars::metal_memory_log_rate > 0 && - (frame_id_ % uint64_t(::cvars::metal_memory_log_rate)) == 0) { - XELOGI( - "Metal mem: frame={} rt={} map={} dummy={} pipelines={} " - "tile_pipelines={} inst_buf_sizes=[{}, {}, {}]", - frame_id_, render_target_map_.size(), render_target_map_.size(), - dummy_color_targets_.size(), transfer_pipelines_.size(), - transfer_tile_pipelines_.size(), - transfer_tile_instance_buffer_sizes_[0], - transfer_tile_instance_buffer_sizes_[1], - transfer_tile_instance_buffer_sizes_[2]); +MTL::ComputePipelineState* MetalRenderTargetCache::GetResolvePipeline( + draw_util::ResolveCopyShaderIndex copy_shader, bool scaled) const { + size_t scaled_index = scaled ? 1u : 0u; + if (IsResolveDirectHostRTFastCandidate(copy_shader)) { + return resolve_fast_pipelines_[scaled_index][ResolveFastBppIndex( + copy_shader)][ResolveFastMsaaIndex(copy_shader)]; } + size_t full_dest_index = DirectHostResolveFullDestIndex(copy_shader); + if (full_dest_index < kResolveFullDestCount) { + return resolve_full_pipelines_[scaled_index][full_dest_index]; + } + return nullptr; } bool MetalRenderTargetCache::Update( bool is_rasterization_done, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask, const Shader& vertex_shader) { + // Pending draw-pass transfers are ownership-visible already. If control + // reaches another RT update before the command processor encoded them, + // preserve correctness by falling back to the standalone transfer path first. + if (!FlushPendingDrawPassTransfers()) { + return false; + } + // Use the base class logic to update the current render target setup. if (!RenderTargetCache::Update(is_rasterization_done, normalized_depth_control, @@ -3054,32 +1727,11 @@ bool MetalRenderTargetCache::Update( return false; } - if (::cvars::metal_memory_log_rate > 0) { - static uint64_t memory_log_counter = 0; - if ((++memory_log_counter % uint64_t(::cvars::metal_memory_log_rate)) == - 0) { - XELOGI( - "Metal mem: frame={} rt={} map={} dummy={} pipelines={} " - "tile_pipelines={} inst_buf_sizes=[{}, {}, {}]", - frame_id_, render_target_map_.size(), render_target_map_.size(), - dummy_color_targets_.size(), transfer_pipelines_.size(), - transfer_tile_pipelines_.size(), - transfer_tile_instance_buffer_sizes_[0], - transfer_tile_instance_buffer_sizes_[1], - transfer_tile_instance_buffer_sizes_[2]); - } - } - - // After base class update, retrieve the actual render targets that were - // selected This is the KEY to connecting base class management with - // Metal-specific rendering RenderTarget* const* accumulated_targets = last_update_accumulated_render_targets(); - // Check if render targets actually changed bool targets_changed = false; - // Check depth target MetalRenderTarget* new_depth_target = accumulated_targets[0] ? static_cast(accumulated_targets[0]) @@ -3087,14 +1739,8 @@ bool MetalRenderTargetCache::Update( if (new_depth_target != current_depth_target_) { targets_changed = true; current_depth_target_ = new_depth_target; - if (current_depth_target_) { - XELOGD( - "MetalRenderTargetCache::Update - Depth target changed: key={:08X}", - current_depth_target_->key().key); - } } - // Check color targets for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { MetalRenderTarget* new_color_target = accumulated_targets[i + 1] @@ -3103,31 +1749,698 @@ bool MetalRenderTargetCache::Update( if (new_color_target != current_color_targets_[i]) { targets_changed = true; current_color_targets_[i] = new_color_target; - if (current_color_targets_[i]) { - XELOGD( - "MetalRenderTargetCache::Update - Color target {} changed: " - "key={:08X}", - i, current_color_targets_[i]->key().key); + } + } + + // Queue or perform the ownership transfers collected by the base update. + const std::vector* update_transfers = last_update_transfers(); + if (::cvars::metal_transfer_in_draw_pass) { + std::array, 1 + xenos::kMaxColorRenderTargets> + fallback_transfers; + bool fallback_transfer_work = false; + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + const std::vector& transfers = update_transfers[i]; + if (transfers.empty()) { + continue; + } + if (CanQueueDrawPassTransfers(i, accumulated_targets, transfers)) { + pending_draw_pass_render_targets_[i] = accumulated_targets[i]; + pending_draw_pass_transfers_[i] = transfers; + pending_draw_pass_transfer_mask_ |= uint32_t(1) << i; + pending_draw_pass_load_dontcare_mask_ = 0; + if (PendingDrawPassTransfersFullyOverwriteTarget( + i, accumulated_targets[i], transfers)) { + pending_draw_pass_full_overwrite_mask_ |= uint32_t(1) << i; + MarkRenderPassDescriptorDirty(); + } + auto* dest_metal_rt = + static_cast(accumulated_targets[i]); + if (dest_metal_rt->needs_initial_clear()) { + dest_metal_rt->SetNeedsInitialClear(false); + MarkRenderPassDescriptorDirty(); + } + } else { + fallback_transfers[i] = transfers; + fallback_transfer_work = true; + } + } + if (fallback_transfer_work) { + PerformTransfersAndResolveClears( + 1 + xenos::kMaxColorRenderTargets, accumulated_targets, + fallback_transfers.data(), nullptr, nullptr, nullptr); + } + if (HasPendingDrawPassTransfers()) { + TransferAttachmentFormats attachment_formats; + if (!GetCurrentTransferAttachmentFormats(attachment_formats) || + !PreflightPendingDrawPassTransfers(attachment_formats)) { + if (!FlushPendingDrawPassTransfers()) { + return false; + } + } + } + } else { + PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, + accumulated_targets, update_transfers, + nullptr, nullptr, nullptr); + } + + // Only mark render pass descriptor as dirty if targets actually changed + if (targets_changed) { + MarkRenderPassDescriptorDirty(); + } + + return true; +} + +void MetalRenderTargetCache::ClearPendingDrawPassTransfers() { + bool load_dontcare_descriptor_used = + pending_draw_pass_load_dontcare_mask_ != 0; + for (auto& transfers : pending_draw_pass_transfers_) { + transfers.clear(); + } + pending_draw_pass_render_targets_.fill(nullptr); + pending_draw_pass_transfer_mask_ = 0; + pending_draw_pass_full_overwrite_mask_ = 0; + pending_draw_pass_load_dontcare_mask_ = 0; + if (load_dontcare_descriptor_used) { + MarkRenderPassDescriptorDirty(); + } +} + +bool MetalRenderTargetCache::CanQueueDrawPassTransfers( + uint32_t render_target_index, RenderTarget* const* render_targets, + const std::vector& transfers) const { + if (!render_targets || transfers.empty() || + render_target_index > xenos::kMaxColorRenderTargets) { + return false; + } + auto* dest_metal_rt = + static_cast(render_targets[render_target_index]); + if (!dest_metal_rt) { + return false; + } + RenderTargetKey dest_key = dest_metal_rt->key(); + if (dest_key.is_depth != (render_target_index == 0)) { + return false; + } + + bool dest_is_uint = false; + MTL::Texture* dest_draw_texture = dest_metal_rt->draw_texture(); + if (dest_key.is_depth) { + MTL::PixelFormat depth_format = + GetDepthPixelFormat(dest_key.GetDepthFormat()); + if (!dest_draw_texture || + dest_draw_texture->pixelFormat() != depth_format) { + return false; + } + } else { + MTL::PixelFormat transfer_format = GetColorOwnershipTransferPixelFormat( + dest_key.GetColorFormat(), &dest_is_uint); + MTL::PixelFormat draw_format = + GetColorDrawPixelFormat(dest_key.GetColorFormat()); + if (dest_is_uint || draw_format != transfer_format || !dest_draw_texture || + dest_draw_texture != dest_metal_rt->transfer_texture() || + dest_draw_texture->pixelFormat() != transfer_format) { + return false; + } + } + + auto is_active_draw_pass_texture = [&](const MetalRenderTarget* rt, + MTL::Texture* texture) -> bool { + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + auto* active_rt = static_cast(render_targets[i]); + if (!active_rt) { + continue; + } + MTL::Texture* active_draw_texture = active_rt->draw_texture(); + if (rt == active_rt || texture == active_draw_texture || + (rt && rt->draw_texture() == active_draw_texture)) { + return true; + } + } + return false; + }; + + for (const Transfer& transfer : transfers) { + if (!transfer.source) { + return false; + } + auto* source_rt = static_cast(transfer.source); + RenderTargetKey source_key = source_rt->key(); + if (transfer.host_depth_source) { + if (!dest_key.is_depth) { + return false; + } + auto* host_depth_rt = + static_cast(transfer.host_depth_source); + if (!host_depth_rt || host_depth_rt == dest_metal_rt) { + return false; + } + RenderTargetKey host_depth_key = host_depth_rt->key(); + if (!host_depth_key.is_depth) { + return false; + } + MTL::Texture* host_depth_texture = host_depth_rt->texture(); + if (!host_depth_texture || host_depth_texture == dest_draw_texture || + host_depth_rt->draw_texture() == dest_draw_texture) { + return false; + } + MTL::PixelFormat host_depth_format = + GetDepthPixelFormat(host_depth_key.GetDepthFormat()); + if (host_depth_texture->pixelFormat() != host_depth_format) { + return false; + } + if (is_active_draw_pass_texture(host_depth_rt, host_depth_texture)) { + return false; + } + } + if (source_rt == dest_metal_rt) { + return false; + } + + MTL::Texture* source_texture = source_key.is_depth + ? source_rt->texture() + : source_rt->transfer_texture(); + if (!source_texture || source_texture == dest_draw_texture || + source_rt->draw_texture() == dest_draw_texture) { + return false; + } + if (source_key.is_depth) { + MTL::PixelFormat source_depth_format = + GetDepthPixelFormat(source_key.GetDepthFormat()); + if (source_texture->pixelFormat() != source_depth_format) { + return false; + } + } else { + MTL::PixelFormat source_transfer_format = + GetColorOwnershipTransferPixelFormat(source_key.GetColorFormat(), + nullptr); + if (source_texture->pixelFormat() != source_transfer_format) { + return false; + } + } + + if (is_active_draw_pass_texture(source_rt, source_texture)) { + return false; + } + } + + std::vector transfer_rectangles; + if (!BuildTransferRectanglePlans(dest_key, transfers, nullptr, true, + transfer_rectangles)) { + return false; + } + + return true; +} + +bool MetalRenderTargetCache::BuildTransferRectanglePlans( + RenderTargetKey dest_key, const std::vector& transfers, + const Transfer::Rectangle* cutout, bool require_all_rectangles, + std::vector& transfer_rectangles_out) const { + transfer_rectangles_out.clear(); + transfer_rectangles_out.reserve(transfers.size()); + for (uint32_t transfer_index = 0; transfer_index < transfers.size(); + ++transfer_index) { + const Transfer& transfer = transfers[transfer_index]; + TransferRectanglePlan plan; + plan.transfer_index = transfer_index; + plan.rectangle_count = transfer.GetRectangles( + dest_key.base_tiles, dest_key.GetPitchTiles(), dest_key.msaa_samples, + dest_key.Is64bpp(), plan.rectangles.data(), cutout); + if (!plan.rectangle_count) { + if (require_all_rectangles) { + transfer_rectangles_out.clear(); + return false; + } + continue; + } + transfer_rectangles_out.push_back(plan); + } + return true; +} + +bool MetalRenderTargetCache::PendingDrawPassTransfersFullyOverwriteTarget( + uint32_t render_target_index, RenderTarget* render_target, + const std::vector& transfers) const { + if (!render_target || transfers.empty() || + render_target_index > xenos::kMaxColorRenderTargets) { + return false; + } + + auto* dest_metal_rt = static_cast(render_target); + RenderTargetKey dest_key = dest_metal_rt->key(); + if (dest_key.is_depth != (render_target_index == 0)) { + return false; + } + + MTL::Texture* dest_texture = dest_metal_rt->draw_texture(); + if (!dest_texture) { + return false; + } + uint32_t dest_width = uint32_t(dest_texture->width()); + uint32_t dest_height = uint32_t(dest_texture->height()); + if (!dest_width || !dest_height) { + return false; + } + + auto is_full_target_rectangle = [&](const Transfer::Rectangle& rect) -> bool { + uint32_t scaled_x = rect.x_pixels * draw_resolution_scale_x(); + uint32_t scaled_y = rect.y_pixels * draw_resolution_scale_y(); + uint32_t scaled_width = rect.width_pixels * draw_resolution_scale_x(); + uint32_t scaled_height = rect.height_pixels * draw_resolution_scale_y(); + return !scaled_x && !scaled_y && scaled_width == dest_width && + scaled_height == dest_height; + }; + + std::vector transfer_rectangles; + if (!BuildTransferRectanglePlans(dest_key, transfers, nullptr, true, + transfer_rectangles) || + transfer_rectangles.size() != transfers.size()) { + return false; + } + for (const TransferRectanglePlan& transfer_plan : transfer_rectangles) { + if (transfer_plan.rectangle_count != 1 || + !is_full_target_rectangle(transfer_plan.rectangles[0])) { + return false; + } + } + return true; +} + +bool MetalRenderTargetCache::GetActiveTransferAttachmentFormats( + MTL::RenderPassDescriptor* pass_descriptor, + TransferAttachmentFormats& attachment_formats_out) const { + attachment_formats_out.color_attachment_formats.fill(MTL::PixelFormatInvalid); + attachment_formats_out.depth_attachment_format = MTL::PixelFormatInvalid; + attachment_formats_out.stencil_attachment_format = MTL::PixelFormatInvalid; + if (!pass_descriptor) { + return false; + } + + auto* color_attachments = pass_descriptor->colorAttachments(); + if (color_attachments) { + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + auto* color_attachment = color_attachments->object(i); + MTL::Texture* texture = + color_attachment ? color_attachment->texture() : nullptr; + if (texture) { + attachment_formats_out.color_attachment_formats[i] = + texture->pixelFormat(); } } } - // Perform ownership transfers - this is critical for correct rendering when - // EDRAM regions are aliased between different RT configurations. - // The base class Update() populates last_update_transfers() with the needed - // transfers based on EDRAM tile overlaps. - PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, - accumulated_targets, last_update_transfers(), - nullptr, nullptr, nullptr); + if (auto* depth_attachment = pass_descriptor->depthAttachment()) { + if (MTL::Texture* texture = depth_attachment->texture()) { + attachment_formats_out.depth_attachment_format = texture->pixelFormat(); + } + } + if (auto* stencil_attachment = pass_descriptor->stencilAttachment()) { + if (MTL::Texture* texture = stencil_attachment->texture()) { + attachment_formats_out.stencil_attachment_format = texture->pixelFormat(); + } + } + return true; +} - // Only mark render pass descriptor as dirty if targets actually changed - if (targets_changed) { - render_pass_descriptor_dirty_ = true; +bool MetalRenderTargetCache::GetCurrentTransferAttachmentFormats( + TransferAttachmentFormats& attachment_formats_out) const { + attachment_formats_out.color_attachment_formats.fill(MTL::PixelFormatInvalid); + attachment_formats_out.depth_attachment_format = MTL::PixelFormatInvalid; + attachment_formats_out.stencil_attachment_format = MTL::PixelFormatInvalid; + + bool has_color_attachment = false; + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + MTL::Texture* texture = current_color_targets_[i] + ? current_color_targets_[i]->draw_texture() + : nullptr; + if (!texture) { + continue; + } + attachment_formats_out.color_attachment_formats[i] = texture->pixelFormat(); + has_color_attachment = true; + } + if (!has_color_attachment) { + attachment_formats_out.color_attachment_formats[0] = + GetColorDrawPixelFormat(xenos::ColorRenderTargetFormat::k_8_8_8_8); + } + + MTL::Texture* depth_texture = + current_depth_target_ ? current_depth_target_->draw_texture() : nullptr; + if (depth_texture) { + MTL::PixelFormat depth_pixel_format = depth_texture->pixelFormat(); + attachment_formats_out.depth_attachment_format = depth_pixel_format; + if (depth_pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || + depth_pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8 || + depth_pixel_format == MTL::PixelFormatX32_Stencil8) { + attachment_formats_out.stencil_attachment_format = depth_pixel_format; + } + } + return true; +} + +bool MetalRenderTargetCache::PreflightPendingDrawPassTransfers( + const TransferAttachmentFormats& attachment_formats) { + if (!HasPendingDrawPassTransfers()) { + return true; + } + + // Shared RenderTargetCache state uses slot 0 for depth and slots 1..4 for + // color, so preflight depth transfers against the active depth attachment. + for (uint32_t i = 0; i <= xenos::kMaxColorRenderTargets; ++i) { + if (!(pending_draw_pass_transfer_mask_ & (uint32_t(1) << i))) { + continue; + } + + auto* dest_metal_rt = + static_cast(pending_draw_pass_render_targets_[i]); + if (!dest_metal_rt || pending_draw_pass_transfers_[i].empty()) { + return false; + } + + RenderTargetKey dest_key = dest_metal_rt->key(); + std::vector pending_rectangles; + if (!BuildTransferRectanglePlans(dest_key, pending_draw_pass_transfers_[i], + nullptr, true, pending_rectangles)) { + return false; + } + bool dest_is_uint = false; + uint32_t color_attachment_index = 0; + MTL::PixelFormat dest_format = MTL::PixelFormatInvalid; + if (dest_key.is_depth) { + if (i != 0) { + return false; + } + dest_format = GetDepthPixelFormat(dest_key.GetDepthFormat()); + MTL::Texture* depth_texture = dest_metal_rt->draw_texture(); + if (!depth_texture || depth_texture->pixelFormat() != dest_format || + attachment_formats.depth_attachment_format != dest_format || + !GetTransferDepthStencilState(true)) { + return false; + } + if (dest_format == MTL::PixelFormatDepth32Float_Stencil8 || + dest_format == MTL::PixelFormatDepth24Unorm_Stencil8) { + if (attachment_formats.stencil_attachment_format != dest_format || + !GetTransferStencilClearState()) { + return false; + } + bool native_stencil_output_ready = + ::cvars::metal_transfer_native_stencil_output && + GetTransferStencilOutputState(); + if (!native_stencil_output_ready) { + for (uint32_t bit = 0; bit < 8; ++bit) { + if (!GetTransferStencilBitState(bit)) { + return false; + } + } + } + } + uint32_t sample_count = MsaaSamplesToCount(dest_key.msaa_samples); + if (!GetOrCreateTransferClearPipeline( + dest_format, false, true, sample_count, 0, + &attachment_formats.color_attachment_formats, + attachment_formats.depth_attachment_format, + attachment_formats.stencil_attachment_format)) { + return false; + } + } else { + if (i == 0) { + return false; + } + color_attachment_index = i - 1; + MTL::Texture* attachment_texture = dest_metal_rt->draw_texture(); + if (!attachment_texture) { + return false; + } + dest_format = GetColorOwnershipTransferPixelFormat( + dest_key.GetColorFormat(), &dest_is_uint); + if (dest_is_uint || + attachment_formats.color_attachment_formats[color_attachment_index] != + dest_format || + dest_metal_rt->transfer_texture() != attachment_texture || + !GetTransferNoDepthStencilState()) { + return false; + } + } + + auto is_active_draw_pass_texture = [&](const MetalRenderTarget* rt, + MTL::Texture* texture) -> bool { + for (uint32_t active_index = 0; + active_index <= xenos::kMaxColorRenderTargets; ++active_index) { + auto* active_rt = static_cast( + pending_draw_pass_render_targets_[active_index]); + if (!active_rt) { + continue; + } + MTL::Texture* active_draw_texture = active_rt->draw_texture(); + if (rt == active_rt || texture == active_draw_texture || + (rt && rt->draw_texture() == active_draw_texture)) { + return true; + } + } + return false; + }; + + for (const Transfer& transfer : pending_draw_pass_transfers_[i]) { + auto* source_rt = static_cast(transfer.source); + if (!source_rt) { + return false; + } + RenderTargetKey source_key = source_rt->key(); + MetalRenderTarget* host_depth_rt = nullptr; + RenderTargetKey host_depth_key; + if (transfer.host_depth_source) { + host_depth_rt = + static_cast(transfer.host_depth_source); + if (!dest_key.is_depth || !host_depth_rt || + host_depth_rt == dest_metal_rt) { + return false; + } + host_depth_key = host_depth_rt->key(); + if (!host_depth_key.is_depth) { + return false; + } + MTL::Texture* host_depth_texture = host_depth_rt->texture(); + if (!host_depth_texture || + host_depth_texture != host_depth_rt->draw_texture() || + host_depth_texture == dest_metal_rt->draw_texture() || + host_depth_texture->pixelFormat() != + GetDepthPixelFormat(host_depth_key.GetDepthFormat()) || + is_active_draw_pass_texture(host_depth_rt, host_depth_texture)) { + return false; + } + } + if (source_key.is_depth && !GetStencilTextureView(source_rt)) { + return false; + } + bool dest_sample_id_from_sample_default = + dest_key.msaa_samples != xenos::MsaaSamples::k1X && + ::cvars::metal_transfer_msaa_sample_id; + TransferShaderKey shader_key = GetTransferShaderKey( + source_key, dest_key, host_depth_rt ? &host_depth_key : nullptr, + false, false, dest_sample_id_from_sample_default); + if (!GetOrCreateTransferPipelines( + shader_key, dest_format, false, false, color_attachment_index, + &attachment_formats.color_attachment_formats, + attachment_formats.depth_attachment_format, + attachment_formats.stencil_attachment_format)) { + return false; + } + if (dest_key.is_depth) { + TransferShaderKey stencil_shader_key = + GetTransferShaderKey(source_key, dest_key, nullptr, false, true, + dest_sample_id_from_sample_default); + bool native_stencil_output_ready = + ::cvars::metal_transfer_native_stencil_output && + GetTransferStencilOutputState() && + GetOrCreateTransferPipelines( + stencil_shader_key, dest_format, false, true, + color_attachment_index, + &attachment_formats.color_attachment_formats, + attachment_formats.depth_attachment_format, + attachment_formats.stencil_attachment_format); + if (!native_stencil_output_ready && + !GetOrCreateTransferPipelines( + stencil_shader_key, dest_format, false, false, + color_attachment_index, + &attachment_formats.color_attachment_formats, + attachment_formats.depth_attachment_format, + attachment_formats.stencil_attachment_format)) { + return false; + } + } + } } return true; } +bool MetalRenderTargetCache::PreflightPendingDrawPassTransfers( + MTL::RenderPassDescriptor* pass_descriptor) { + TransferAttachmentFormats attachment_formats; + if (!GetActiveTransferAttachmentFormats(pass_descriptor, + attachment_formats)) { + return false; + } + return PreflightPendingDrawPassTransfers(attachment_formats); +} + +bool MetalRenderTargetCache::BuildCurrentAttachmentPlan( + uint32_t expected_sample_count, AttachmentPlan& plan_out) { + plan_out = AttachmentPlan(); + plan_out.coverage_samples = std::max(1u, expected_sample_count); + + pending_draw_pass_load_dontcare_mask_ = 0; + if (HasPendingDrawPassTransfers()) { + TransferAttachmentFormats attachment_formats; + if (GetCurrentTransferAttachmentFormats(attachment_formats) && + PreflightPendingDrawPassTransfers(attachment_formats)) { + for (uint32_t i = 0; i <= xenos::kMaxColorRenderTargets; ++i) { + if ((pending_draw_pass_transfer_mask_ & (uint32_t(1) << i)) && + (pending_draw_pass_full_overwrite_mask_ & (uint32_t(1) << i))) { + pending_draw_pass_load_dontcare_mask_ |= uint32_t(1) << i; + } + } + } + } + auto update_coverage = [&](MTL::Texture* texture) { + if (!texture || plan_out.coverage_width) { + return; + } + plan_out.coverage_width = static_cast(texture->width()); + plan_out.coverage_height = static_cast(texture->height()); + if (texture->sampleCount() > 0) { + plan_out.coverage_samples = + std::max(plan_out.coverage_samples, + static_cast(texture->sampleCount())); + } + }; + auto fill_attachment = [&](AttachmentPlanAttachment& attachment, + MetalRenderTarget* render_target, + uint32_t pending_index) { + if (!render_target || !render_target->texture()) { + return; + } + attachment.render_target = render_target; + attachment.texture = render_target->draw_texture(); + attachment.bound = attachment.texture != nullptr; + attachment.needs_initial_clear = render_target->needs_initial_clear(); + const uint32_t pending_bit = uint32_t(1) << pending_index; + attachment.load_action_safe = + (pending_draw_pass_load_dontcare_mask_ & pending_bit) != 0; + if (attachment.bound) { + update_coverage(attachment.texture); + } + }; + + fill_attachment(plan_out.depth, current_depth_target_, 0); + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + fill_attachment(plan_out.colors[i], current_color_targets_[i], i + 1); + if (plan_out.colors[i].bound) { + plan_out.has_any_color_target = true; + } + } + return true; +} + +bool MetalRenderTargetCache::EncodePendingDrawPassTransfers( + MTL::RenderCommandEncoder* encoder, + MTL::RenderPassDescriptor* pass_descriptor, + DrawPassTransferEncoderMutationMask* mutations_out) { + if (mutations_out) { + *mutations_out = kDrawPassTransferEncoderMutationNone; + } + if (!HasPendingDrawPassTransfers()) { + return true; + } + if (!encoder) { + return false; + } + if (!PreflightPendingDrawPassTransfers(pass_descriptor)) { + return false; + } + + bool success = PerformTransfersAndResolveClears( + 1 + xenos::kMaxColorRenderTargets, + pending_draw_pass_render_targets_.data(), + pending_draw_pass_transfers_.data(), nullptr, nullptr, nullptr, encoder, + pass_descriptor, mutations_out); + if (success) { + ClearPendingDrawPassTransfers(); + } + return success; +} + +bool MetalRenderTargetCache::FlushPendingDrawPassTransfers() { + if (!HasPendingDrawPassTransfers()) { + return true; + } + bool success = PerformTransfersAndResolveClears( + 1 + xenos::kMaxColorRenderTargets, + pending_draw_pass_render_targets_.data(), + pending_draw_pass_transfers_.data(), nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr); + if (!success) { + return false; + } + ClearPendingDrawPassTransfers(); + return true; +} + +MetalRenderTargetCache::TransferShaderKey +MetalRenderTargetCache::GetTransferShaderKey( + RenderTargetKey source_key, RenderTargetKey dest_key, + const RenderTargetKey* host_depth_source_key, + bool host_depth_source_is_copy, bool stencil_bit, + bool dest_sample_id_from_sample_default) const { + TransferShaderKey shader_key = {}; + shader_key.source_msaa_samples = source_key.msaa_samples; + shader_key.dest_msaa_samples = dest_key.msaa_samples; + shader_key.source_resource_format = source_key.resource_format; + shader_key.dest_resource_format = dest_key.resource_format; + shader_key.host_depth_source_msaa_samples = xenos::MsaaSamples::k1X; + shader_key.host_depth_source_is_copy = 0; + + if (stencil_bit) { + shader_key.mode = source_key.is_depth ? TransferMode::kDepthToStencilBit + : TransferMode::kColorToStencilBit; + } else if (dest_key.is_depth) { + if (host_depth_source_key) { + shader_key.mode = source_key.is_depth + ? TransferMode::kDepthAndHostDepthToDepth + : TransferMode::kColorAndHostDepthToDepth; + shader_key.host_depth_source_is_copy = host_depth_source_is_copy ? 1 : 0; + shader_key.host_depth_source_msaa_samples = + host_depth_source_is_copy ? xenos::MsaaSamples::k1X + : host_depth_source_key->msaa_samples; + } else { + shader_key.mode = source_key.is_depth ? TransferMode::kDepthToDepth + : TransferMode::kColorToDepth; + } + } else { + shader_key.mode = source_key.is_depth ? TransferMode::kDepthToColor + : TransferMode::kColorToColor; + } + + const TransferModeInfo& mode_info = + kTransferModeInfos[size_t(shader_key.mode)]; + bool transfer_use_sample_id = dest_sample_id_from_sample_default; + if (transfer_use_sample_id) { + bool source_is_multisample = + source_key.msaa_samples != xenos::MsaaSamples::k1X; + bool host_depth_is_multisample = + mode_info.uses_host_depth && + shader_key.host_depth_source_msaa_samples != xenos::MsaaSamples::k1X && + !shader_key.host_depth_source_is_copy; + if (!source_is_multisample && !host_depth_is_multisample) { + transfer_use_sample_id = false; + } + } + shader_key.dest_sample_id_from_sample = transfer_use_sample_id ? 1u : 0u; + return shader_key; +} + uint32_t MetalRenderTargetCache::GetMaxRenderTargetWidth() const { // Metal maximum texture dimension return 16384; @@ -3142,6 +2455,11 @@ bool MetalRenderTargetCache::IsGammaFormatHostStorageSeparate() const { return gamma_render_target_as_unorm16_; } +void MetalRenderTargetCache::RequestPixelShaderInterlockBarrier() { + command_processor_.EndRenderEncoder(); + PixelShaderInterlockFullEdramBarrierPlaced(); +} + RenderTargetCache::RenderTarget* MetalRenderTargetCache::CreateRenderTarget( RenderTargetKey key) { // Calculate dimensions @@ -3181,25 +2499,33 @@ RenderTargetCache::RenderTarget* MetalRenderTargetCache::CreateRenderTarget( GetColorOwnershipTransferPixelFormat(key.GetColorFormat(), nullptr); if (draw_format != resource_format) { MTL::Texture* draw_view = texture->newTextureView(draw_format); - RecordRenderTargetViewCreated(); + if (!draw_view) { + XELOGE("Failed to create texture view for render target"); + } render_target->SetDrawTexture(draw_view); } if (transfer_format != resource_format) { MTL::Texture* transfer_view = texture->newTextureView(transfer_format); - RecordRenderTargetViewCreated(); + if (!transfer_view) { + XELOGE("Failed to create texture view for render target"); + } render_target->SetTransferTexture(transfer_view); } if (render_target->msaa_texture()) { if (draw_format != render_target->msaa_texture()->pixelFormat()) { MTL::Texture* msaa_draw_view = render_target->msaa_texture()->newTextureView(draw_format); - RecordRenderTargetViewCreated(); + if (!msaa_draw_view) { + XELOGE("Failed to create texture view for render target"); + } render_target->SetMsaaDrawTexture(msaa_draw_view); } if (transfer_format != render_target->msaa_texture()->pixelFormat()) { MTL::Texture* msaa_transfer_view = render_target->msaa_texture()->newTextureView(transfer_format); - RecordRenderTargetViewCreated(); + if (!msaa_transfer_view) { + XELOGE("Failed to create texture view for render target"); + } render_target->SetMsaaTransferTexture(msaa_transfer_view); } } @@ -3239,6 +2565,53 @@ void MetalRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) { return; } + if (GetPath() == Path::kPixelShaderInterlock) { + if (!edram_buffer_) { + return; + } + + constexpr size_t kSnapshotSize = xenos::kEdramSizeBytes; + if (void* edram_contents = edram_buffer_->contents()) { + std::memcpy(edram_contents, snapshot, kSnapshotSize); + return; + } + + MTL::ResourceOptions staging_options = + MTL::ResourceStorageModeShared | MTL::ResourceCPUCacheModeWriteCombined; + MTL::Buffer* staging = device_->newBuffer(kSnapshotSize, staging_options); + if (!staging) { + return; + } + void* staging_contents = staging->contents(); + if (!staging_contents) { + staging->release(); + return; + } + std::memcpy(staging_contents, snapshot, kSnapshotSize); + + ScopedAutoreleasePool autorelease_pool; + MTL::CommandBuffer* cmd = + command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=edram-snapshot-restore"); + if (!cmd) { + staging->release(); + return; + } + + MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); + if (!blit) { + cmd->release(); + staging->release(); + return; + } + + blit->copyFromBuffer(staging, 0, edram_buffer_, 0, kSnapshotSize); + blit->endEncoding(); + command_processor_.CommitStandaloneAndWait(cmd); + staging->release(); + return; + } + RenderTarget* full_edram_rt = PrepareFullEdram1280xRenderTargetForSnapshotRestoration( xenos::ColorRenderTargetFormat::k_32_FLOAT); @@ -3294,13 +2667,9 @@ void MetalRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) { } ScopedAutoreleasePool autorelease_pool; - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - if (!queue) { - staging->release(); - return; - } - - MTL::CommandBuffer* cmd = queue->commandBuffer(); + MTL::CommandBuffer* cmd = + command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=rt-texture-upload"); if (!cmd) { staging->release(); return; @@ -3308,7 +2677,7 @@ void MetalRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) { MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); if (!blit) { - // cmd is autoreleased from commandBuffer() - do not release + cmd->release(); staging->release(); return; } @@ -3317,19 +2686,18 @@ void MetalRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) { MTL::Size::Make(kWidth, kHeight, 1), texture, 0, 0, MTL::Origin::Make(0, 0, 0)); blit->endEncoding(); - cmd->commit(); - cmd->waitUntilCompleted(); - // cmd is autoreleased from commandBuffer() - do not release + command_processor_.CommitStandaloneAndWait(cmd); staging->release(); if (metal_rt->needs_initial_clear()) { metal_rt->SetNeedsInitialClear(false); - render_pass_descriptor_dirty_ = true; + MarkRenderPassDescriptorDirty(); } // Seed edram_buffer_ with the restored full-EDRAM render target contents // so subsequent DumpRenderTargets and resolve passes see the same initial // EDRAM state as D3D12/Vulkan. - DumpRenderTargets(0, kPitchTilesAt32bpp, kTileRows, kPitchTilesAt32bpp); + DumpRenderTargets(0, kPitchTilesAt32bpp, kTileRows, kPitchTilesAt32bpp, + nullptr, "XeniaEDRAMDumpRestoreUpload"); } MTL::Texture* MetalRenderTargetCache::CreateColorTexture( @@ -3360,13 +2728,12 @@ MTL::Texture* MetalRenderTargetCache::CreateColorTexture( desc->setUsage(usage); MTL::Texture* texture = nullptr; - bool can_use_memoryless = false; -#if XE_PLATFORM_IOS - can_use_memoryless = transient_render_target_only && !needs_pixel_format_view; -#endif + bool can_use_memoryless = transient_render_target_only && + !needs_pixel_format_view && + device_->supportsFamily(MTL::GPUFamilyApple1); if (can_use_memoryless) { // Dummy fallback color targets are transient (load/store don't care) and - // never sampled - memoryless is optimal on iOS TBDR. + // never sampled - memoryless is optimal on Apple TBDR GPUs. desc->setStorageMode(MTL::StorageModeMemoryless); texture = device_->newTexture(desc); } @@ -3418,6 +2785,41 @@ MTL::Texture* MetalRenderTargetCache::CreateDepthTexture( return texture; } +MTL::Texture* MetalRenderTargetCache::CreateTransientDepthTexture( + uint32_t width, uint32_t height, uint32_t samples) { + if (!device_) { + return nullptr; + } + + MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); + desc->setWidth(std::max(1u, width)); + desc->setHeight(std::max(1u, height)); + desc->setPixelFormat(MTL::PixelFormatDepth32Float); + desc->setTextureType(samples > 1 ? MTL::TextureType2DMultisample + : MTL::TextureType2D); + desc->setSampleCount(std::max(1u, samples)); + desc->setUsage(MTL::TextureUsageRenderTarget); + + MTL::Texture* texture = nullptr; + bool can_use_memoryless = device_->supportsFamily(MTL::GPUFamilyApple1); + if (can_use_memoryless) { + desc->setStorageMode(MTL::StorageModeMemoryless); + texture = device_->newTexture(desc); + } + if (!texture) { + desc->setStorageMode(MTL::StorageModePrivate); + if (render_target_heap_pool_ && !can_use_memoryless) { + texture = render_target_heap_pool_->CreateTexture(desc); + } + if (!texture) { + texture = device_->newTexture(desc); + } + } + + desc->release(); + return texture; +} + MTL::PixelFormat MetalRenderTargetCache::GetColorResourcePixelFormat( xenos::ColorRenderTargetFormat format) const { switch (format) { @@ -3538,13 +2940,17 @@ MTL::Texture* MetalRenderTargetCache::GetStencilTextureView( } MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( - uint32_t expected_sample_count) { + uint32_t expected_sample_count, bool fallback_depth_attachment_required) { if (!render_pass_descriptor_dirty_ && cached_render_pass_descriptor_ && - cached_render_pass_descriptor_sample_count_ == expected_sample_count) { + cached_render_pass_descriptor_sample_count_ == expected_sample_count && + cached_render_pass_descriptor_fallback_depth_required_ == + fallback_depth_attachment_required) { return cached_render_pass_descriptor_; } - if (cached_render_pass_descriptor_sample_count_ != expected_sample_count) { - render_pass_descriptor_dirty_ = true; + if (cached_render_pass_descriptor_sample_count_ != expected_sample_count || + cached_render_pass_descriptor_fallback_depth_required_ != + fallback_depth_attachment_required) { + MarkRenderPassDescriptorDirty(); } // Release old descriptor @@ -3562,111 +2968,90 @@ MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( } cached_render_pass_descriptor_->retain(); cached_render_pass_descriptor_sample_count_ = expected_sample_count; + cached_render_pass_descriptor_fallback_depth_required_ = + fallback_depth_attachment_required; + render_pass_descriptor_dirty_ = false; - bool has_any_render_target = false; - bool has_any_color_target = false; - bool needs_descriptor_refresh = false; - uint32_t coverage_width = 0; - uint32_t coverage_height = 0; - uint32_t coverage_samples = std::max(1u, expected_sample_count); + AttachmentPlan attachment_plan; + if (!BuildCurrentAttachmentPlan(expected_sample_count, attachment_plan)) { + return nullptr; + } + bool has_any_color_target = attachment_plan.has_any_color_target; + uint32_t coverage_width = attachment_plan.coverage_width; + uint32_t coverage_height = attachment_plan.coverage_height; + uint32_t coverage_samples = attachment_plan.coverage_samples; // Bind the actual render targets retrieved from base class in Update() // Bind depth target if present - if (current_depth_target_ && current_depth_target_->texture()) { + const AttachmentPlanAttachment& depth_plan = attachment_plan.depth; + if (depth_plan.bound && depth_plan.render_target && depth_plan.texture) { auto* depth_attachment = cached_render_pass_descriptor_->depthAttachment(); - depth_attachment->setTexture(current_depth_target_->draw_texture()); + depth_attachment->setTexture(depth_plan.texture); // Clear on first bind to avoid synchronous clears at creation. - uint32_t depth_key = current_depth_target_->key().key; - bool depth_needs_clear = current_depth_target_->needs_initial_clear(); + bool depth_needs_clear = depth_plan.needs_initial_clear; + bool depth_load_dontcare = depth_plan.load_action_safe; + AttachmentLoadStoreActions depth_load_store = + GetRealAttachmentLoadStoreActions(depth_needs_clear, + !depth_load_dontcare); + SetAttachmentLoadStoreActions(depth_attachment, depth_load_store); if (depth_needs_clear) { - depth_attachment->setLoadAction(MTL::LoadActionClear); - depth_attachment->setClearDepth(1.0); - current_depth_target_->SetNeedsInitialClear(false); - needs_descriptor_refresh = true; - } else { - depth_attachment->setLoadAction(MTL::LoadActionLoad); + depth_attachment->setClearDepth(GetDepthTargetClearDepth()); + depth_plan.render_target->SetNeedsInitialClear(false); + MarkRenderPassDescriptorDirty(); } - depth_attachment->setStoreAction(MTL::StoreActionStore); // If the depth texture includes stencil, bind the same texture to the // stencil attachment too (Metal requires explicit stencil attachment // binding to match pipeline state). - MTL::PixelFormat depth_pixel_format = - current_depth_target_->draw_texture()->pixelFormat(); + MTL::PixelFormat depth_pixel_format = depth_plan.texture->pixelFormat(); if (depth_pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || depth_pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8 || depth_pixel_format == MTL::PixelFormatX32_Stencil8) { auto* stencil_attachment = cached_render_pass_descriptor_->stencilAttachment(); - stencil_attachment->setTexture(current_depth_target_->draw_texture()); - if (depth_needs_clear) { - stencil_attachment->setLoadAction(MTL::LoadActionClear); + stencil_attachment->setTexture(depth_plan.texture); + AttachmentLoadStoreActions stencil_load_store = depth_load_store; + if (!depth_needs_clear && depth_load_dontcare) { + stencil_load_store = {MTL::LoadActionClear, MTL::StoreActionStore}; + } + SetAttachmentLoadStoreActions(stencil_attachment, stencil_load_store); + if (depth_needs_clear || (!depth_needs_clear && depth_load_dontcare)) { stencil_attachment->setClearStencil(0); - } else { - stencil_attachment->setLoadAction(MTL::LoadActionLoad); } - stencil_attachment->setStoreAction(MTL::StoreActionStore); } - has_any_render_target = true; - // Track this as a real render target for capture - last_real_depth_target_ = current_depth_target_; - - if (!coverage_width && current_depth_target_->draw_texture()) { - coverage_width = - static_cast(current_depth_target_->draw_texture()->width()); - coverage_height = static_cast( - current_depth_target_->draw_texture()->height()); - if (current_depth_target_->draw_texture()->sampleCount() > 0) { - coverage_samples = std::max( - coverage_samples, - static_cast( - current_depth_target_->draw_texture()->sampleCount())); - } - } + last_real_depth_target_ = depth_plan.render_target; } // Bind color targets for (uint32_t i = 0; i < 4; ++i) { - if (current_color_targets_[i] && current_color_targets_[i]->texture()) { + const AttachmentPlanAttachment& color_plan = attachment_plan.colors[i]; + if (color_plan.bound && color_plan.render_target && color_plan.texture) { auto* color_attachment = cached_render_pass_descriptor_->colorAttachments()->object(i); - color_attachment->setTexture(current_color_targets_[i]->draw_texture()); + color_attachment->setTexture(color_plan.texture); // Clear on first bind to avoid synchronous clears at creation. - bool color_needs_clear = current_color_targets_[i]->needs_initial_clear(); + bool color_needs_clear = color_plan.needs_initial_clear; + bool color_load_dontcare = color_plan.load_action_safe; + AttachmentLoadStoreActions color_load_store = + GetRealAttachmentLoadStoreActions(color_needs_clear, + !color_load_dontcare); + SetAttachmentLoadStoreActions(color_attachment, color_load_store); if (color_needs_clear) { - color_attachment->setLoadAction(MTL::LoadActionClear); color_attachment->setClearColor( MTL::ClearColor::Make(0.0, 0.0, 0.0, 0.0)); - current_color_targets_[i]->SetNeedsInitialClear(false); - needs_descriptor_refresh = true; - } else { - color_attachment->setLoadAction(MTL::LoadActionLoad); + color_plan.render_target->SetNeedsInitialClear(false); + MarkRenderPassDescriptorDirty(); } - color_attachment->setStoreAction(MTL::StoreActionStore); - has_any_render_target = true; has_any_color_target = true; // Track this as a real render target for capture - last_real_color_targets_[i] = current_color_targets_[i]; - - if (!coverage_width) { - coverage_width = static_cast( - current_color_targets_[i]->draw_texture()->width()); - coverage_height = static_cast( - current_color_targets_[i]->draw_texture()->height()); - if (current_color_targets_[i]->draw_texture()->sampleCount() > 0) { - coverage_samples = std::max( - coverage_samples, - static_cast( - current_color_targets_[i]->draw_texture()->sampleCount())); - } - } + last_real_color_targets_[i] = color_plan.render_target; } } @@ -3710,34 +3095,19 @@ MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( uint32_t dummy_sample_count = samples >= 4u ? 4u : (samples == 2u ? 2u : 1u); - // Cache dummy color targets by shape/format only so depth-only passes with - // changing EDRAM bases can reuse the same transient attachment. + // No color render targets are bound: attach a single transient dummy color + // target so the render pass/pipeline has a matching color output. Cache it + // by shape/format and recreate it only when the required shape changes. uint64_t dummy_key = uint64_t(width & 0xFFFFu) | (uint64_t(height & 0xFFFFu) << 16) | (uint64_t(dummy_sample_count & 0xFFu) << 32) | (uint64_t(uint32_t(fmt) & 0xFFFFu) << 40); - auto evict_oldest_dummy_target = [&](uint64_t keep_key) -> bool { - uint64_t oldest_key = 0; - uint64_t oldest_frame = frame_id_; - bool found = false; - for (const auto& it : dummy_color_targets_) { - if (it.first == keep_key) { - continue; - } - if (!found || it.second.last_used_frame < oldest_frame) { - oldest_frame = it.second.last_used_frame; - oldest_key = it.first; - found = true; - } - } - if (found) { - dummy_color_targets_.erase(oldest_key); - } - return found; - }; - - auto& entry = dummy_color_targets_[dummy_key]; - if (!entry.target || !entry.target->texture()) { + if (!dummy_color_target_owner_ || !dummy_color_target_owner_->texture() || + dummy_color_target_shape_key_ != dummy_key) { + // Release the previous dummy first so its heap space is freed before the + // new allocation. + dummy_color_target_owner_.reset(); + dummy_color_target_ = nullptr; RenderTargetKey dummy_rt_key; dummy_rt_key.key = 0; dummy_rt_key.is_depth = 0; @@ -3746,75 +3116,56 @@ MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( dummy_sample_count >= 4u ? xenos::MsaaSamples::k4X : dummy_sample_count == 2u ? xenos::MsaaSamples::k2X : xenos::MsaaSamples::k1X; - entry.target = std::make_unique(dummy_rt_key); - entry.last_cleared_frame = frame_id_ - 1; - // Prefer memoryless transient attachments on iOS (inside - // CreateColorTexture), otherwise keep dummy allocations in heap budget. + auto dummy = std::make_unique(dummy_rt_key); MTL::Texture* tex = CreateColorTexture(width, height, fmt, dummy_sample_count, /*transient_render_target_only=*/true, /*allow_unpooled_fallback=*/false); - while (!tex && render_target_heap_pool_ && - dummy_color_targets_.size() > 1 && - evict_oldest_dummy_target(dummy_key)) { - tex = CreateColorTexture(width, height, fmt, dummy_sample_count, - /*transient_render_target_only=*/true, - /*allow_unpooled_fallback=*/false); - } if (!tex) { - static uint64_t last_unpooled_fallback_log_frame = 0; - if (render_target_heap_pool_ && - last_unpooled_fallback_log_frame != frame_id_) { + static bool logged_unpooled_fallback = false; + if (render_target_heap_pool_ && !logged_unpooled_fallback) { XELOGW( "Metal RT dummy target: heap allocation failed for {}x{} {}x; " "falling back to unpooled texture", width, height, dummy_sample_count); - last_unpooled_fallback_log_frame = frame_id_; + logged_unpooled_fallback = true; } tex = CreateColorTexture(width, height, fmt, dummy_sample_count, /*transient_render_target_only=*/true, /*allow_unpooled_fallback=*/true); } - entry.target->SetTexture(tex); + dummy->SetTexture(tex); if (tex) { MTL::PixelFormat resource_format = GetColorResourcePixelFormat(fmt); MTL::PixelFormat draw_format = GetColorDrawPixelFormat(fmt); MTL::PixelFormat transfer_format = GetColorOwnershipTransferPixelFormat(fmt, nullptr); if (draw_format != resource_format) { - entry.target->SetDrawTexture(tex->newTextureView(draw_format)); - RecordRenderTargetViewCreated(); + MTL::Texture* draw_view = tex->newTextureView(draw_format); + if (!draw_view) { + XELOGE("Failed to create texture view for render target"); + } + dummy->SetDrawTexture(draw_view); } if (transfer_format != resource_format) { - entry.target->SetTransferTexture( - tex->newTextureView(transfer_format)); - RecordRenderTargetViewCreated(); + MTL::Texture* transfer_view = tex->newTextureView(transfer_format); + if (!transfer_view) { + XELOGE("Failed to create texture view for render target"); + } + dummy->SetTransferTexture(transfer_view); } } + dummy_color_target_owner_ = std::move(dummy); + dummy_color_target_shape_key_ = dummy_key; } - - entry.last_used_frame = frame_id_; - dummy_color_target_ = entry.target.get(); - - // Keep this cache small - dummy targets are transient fallback attachments. -#if XE_PLATFORM_IOS - constexpr size_t kMaxDummyColorTargets = 4; -#else - constexpr size_t kMaxDummyColorTargets = 8; -#endif - while (dummy_color_targets_.size() > kMaxDummyColorTargets) { - if (!evict_oldest_dummy_target(dummy_key)) { - break; - } - } + dummy_color_target_ = dummy_color_target_owner_.get(); auto* color_attachment = cached_render_pass_descriptor_->colorAttachments()->object(0); color_attachment->setTexture(dummy_color_target_->draw_texture()); - color_attachment->setLoadAction(MTL::LoadActionDontCare); - color_attachment->setStoreAction(MTL::StoreActionDontCare); + SetAttachmentLoadStoreActions(color_attachment, + GetTransientAttachmentLoadStoreActions()); - has_any_render_target = true; if (!coverage_width && dummy_color_target_->draw_texture()) { coverage_width = static_cast(dummy_color_target_->draw_texture()->width()); @@ -3829,10 +3180,134 @@ MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( } } - render_pass_descriptor_dirty_ = needs_descriptor_refresh; + if (fallback_depth_attachment_required && !current_depth_target_) { + uint32_t width = coverage_width ? coverage_width : 1280; + uint32_t height = coverage_height ? coverage_height : 720; + uint32_t samples = std::max(1u, coverage_samples); + MTL::Texture* fallback_depth_texture = + CreateTransientDepthTexture(width, height, samples); + if (!fallback_depth_texture) { + XELOGE( + "MetalRenderTargetCache: Failed to create transient depth " + "attachment"); + cached_render_pass_descriptor_->release(); + cached_render_pass_descriptor_ = nullptr; + MarkRenderPassDescriptorDirty(); + return nullptr; + } + + auto* depth_attachment = cached_render_pass_descriptor_->depthAttachment(); + depth_attachment->setTexture(fallback_depth_texture); + SetAttachmentLoadStoreActions(depth_attachment, + GetTransientAttachmentLoadStoreActions()); + fallback_depth_texture->release(); + } + return cached_render_pass_descriptor_; } +bool MetalRenderTargetCache::IsRenderPassDescriptorCompatible( + MTL::RenderPassDescriptor* pass_descriptor, uint32_t expected_sample_count, + bool fallback_depth_attachment_required) const { + if (pass_descriptor && pass_descriptor == cached_render_pass_descriptor_ && + !render_pass_descriptor_dirty_ && + cached_render_pass_descriptor_sample_count_ == expected_sample_count && + cached_render_pass_descriptor_fallback_depth_required_ == + fallback_depth_attachment_required) { + return true; + } + return IsRenderPassDescriptorCompatibleSlow( + pass_descriptor, expected_sample_count, + fallback_depth_attachment_required); +} + +bool MetalRenderTargetCache::IsRenderPassDescriptorCompatibleSlow( + MTL::RenderPassDescriptor* pass_descriptor, uint32_t expected_sample_count, + bool fallback_depth_attachment_required) const { + (void)expected_sample_count; + if (!pass_descriptor) { + return false; + } + + MTL::Texture* expected_depth = + current_depth_target_ ? current_depth_target_->draw_texture() : nullptr; + auto* depth_attachment = pass_descriptor->depthAttachment(); + auto* stencil_attachment = pass_descriptor->stencilAttachment(); + if (expected_depth) { + if (!depth_attachment || depth_attachment->texture() != expected_depth) { + return false; + } + MTL::PixelFormat depth_pixel_format = expected_depth->pixelFormat(); + bool expects_stencil = + depth_pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || + depth_pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8 || + depth_pixel_format == MTL::PixelFormatX32_Stencil8; + if (expects_stencil) { + if (!stencil_attachment || + stencil_attachment->texture() != expected_depth) { + return false; + } + } else if (stencil_attachment && stencil_attachment->texture()) { + return false; + } + } else { + MTL::Texture* depth_texture = + depth_attachment ? depth_attachment->texture() : nullptr; + MTL::Texture* stencil_texture = + stencil_attachment ? stencil_attachment->texture() : nullptr; + if (fallback_depth_attachment_required) { + if (!depth_texture || + depth_texture->pixelFormat() != MTL::PixelFormatDepth32Float || + stencil_texture) { + return false; + } + } else if (depth_texture || stencil_texture) { + return false; + } + } + + auto* color_attachments = pass_descriptor->colorAttachments(); + bool has_current_color_target = false; + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + if (current_color_targets_[i] && + current_color_targets_[i]->draw_texture()) { + has_current_color_target = true; + break; + } + } + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + auto* color_attachment = color_attachments->object(i); + MTL::Texture* expected_color = + current_color_targets_[i] ? current_color_targets_[i]->draw_texture() + : nullptr; + if (expected_color) { + if (!color_attachment || color_attachment->texture() != expected_color) { + return false; + } + continue; + } + if (color_attachment && color_attachment->texture()) { + if (!has_current_color_target && i == 0) { + continue; + } + return false; + } + } + + if (has_current_color_target) { + return true; + } + + MTL::Texture* expected_dummy = + dummy_color_target_ ? dummy_color_target_->draw_texture() : nullptr; + auto* color_attachment_0 = color_attachments->object(0); + if (expected_dummy && color_attachment_0 && + color_attachment_0->texture() == expected_dummy) { + return true; + } + return false; +} + MTL::Texture* MetalRenderTargetCache::GetColorTarget(uint32_t index) const { if (index >= 4 || !current_color_targets_[index]) { return nullptr; @@ -3854,18 +3329,6 @@ MTL::Texture* MetalRenderTargetCache::GetDummyColorTarget() const { return nullptr; } -void MetalRenderTargetCache::RecordRenderTargetViewCreated() { - render_target_views_created_.fetch_add(1, std::memory_order_relaxed); -} - -MetalRenderTargetCache::MetalRenderTarget* -MetalRenderTargetCache::GetColorRenderTarget(uint32_t index) const { - if (index >= 4) { - return nullptr; - } - return current_color_targets_[index]; -} - MTL::Texture* MetalRenderTargetCache::GetColorTargetForDraw( uint32_t index) const { if (index >= 4 || !current_color_targets_[index]) { @@ -3888,173 +3351,22 @@ MTL::Texture* MetalRenderTargetCache::GetDummyColorTargetForDraw() const { return nullptr; } -MTL::Texture* MetalRenderTargetCache::GetLastRealColorTarget( - uint32_t index) const { - if (index >= 4 || !last_real_color_targets_[index]) { - return nullptr; +double MetalRenderTargetCache::GetDepthTargetClearDepth() const { + if (!current_depth_target_) { + return 1.0; } - return last_real_color_targets_[index]->texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetLastRealDepthTarget() const { - if (!last_real_depth_target_) { - return nullptr; - } - return last_real_depth_target_->texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetRenderTargetTexture( - RenderTargetKey key) const { - auto it = render_target_map_.find(key.key); - if (it == render_target_map_.end()) { - return nullptr; - } - MetalRenderTarget* target = it->second; - return target ? target->texture() : nullptr; -} - -MTL::Texture* MetalRenderTargetCache::GetColorRenderTargetTexture( - uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, - xenos::ColorRenderTargetFormat format) const { - if (!pitch) { - return nullptr; - } - RenderTargetKey key; - key.base_tiles = base; - uint32_t msaa_samples_x_log2 = uint32_t(samples >= xenos::MsaaSamples::k4X); - key.pitch_tiles_at_32bpp = - ((pitch << msaa_samples_x_log2) + (xenos::kEdramTileWidthSamples - 1)) / - xenos::kEdramTileWidthSamples; - key.msaa_samples = samples; - key.is_depth = 0; - xenos::ColorRenderTargetFormat resource_format = - (format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && - !gamma_render_target_as_unorm16_) - ? xenos::ColorRenderTargetFormat::k_8_8_8_8 - : xenos::GetStorageColorFormat(format); - key.resource_format = uint32_t(resource_format); - return GetRenderTargetTexture(key); -} - -void MetalRenderTargetCache::StoreTiledData(MTL::CommandBuffer* command_buffer, - MTL::Texture* texture, - uint32_t edram_base, - uint32_t pitch_tiles, - uint32_t height_tiles, - bool is_depth) { - MTL::Texture* source_texture = texture; - MTL::Texture* temp_texture = nullptr; - - // Check if this is a depth/stencil texture - bool is_depth_stencil_format = - texture->pixelFormat() == MTL::PixelFormatDepth32Float_Stencil8 || - texture->pixelFormat() == MTL::PixelFormatDepth32Float || - texture->pixelFormat() == MTL::PixelFormatDepth16Unorm || - texture->pixelFormat() == MTL::PixelFormatDepth24Unorm_Stencil8 || - texture->pixelFormat() == MTL::PixelFormatX32_Stencil8; - - if (is_depth_stencil_format) { - // Depth/stencil textures can't be sampled directly from a compute shader, - // so storing them back to EDRAM would need a depth-read intermediate path. - // Depth buffers are typically write-only during rendering, so skip. - return; - } - - // If texture is multisample, create a temporary non-multisample texture and - // resolve to it first - if (texture->textureType() == MTL::TextureType2DMultisample) { - MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); - desc->setWidth(texture->width()); - desc->setHeight(texture->height()); - desc->setPixelFormat(texture->pixelFormat()); - desc->setTextureType(MTL::TextureType2D); // Regular 2D texture - desc->setSampleCount(1); // Non-multisample - desc->setUsage(MTL::TextureUsageRenderTarget | MTL::TextureUsageShaderRead); - desc->setStorageMode(MTL::StorageModePrivate); - - if (render_target_heap_pool_) { - temp_texture = render_target_heap_pool_->CreateTexture(desc); - } - if (!temp_texture) { - temp_texture = device_->newTexture(desc); - } - desc->release(); - if (!temp_texture) { - XELOGE( - "MetalRenderTargetCache::StoreTiledData - Failed to create " - "temporary " - "texture"); - return; - } - - // Resolve multisample texture to temporary texture - MTL::RenderPassDescriptor* resolve_desc = - MTL::RenderPassDescriptor::renderPassDescriptor(); - if (resolve_desc) { - auto* color_attachment = resolve_desc->colorAttachments()->object(0); - color_attachment->setTexture(texture); // Multisample source - color_attachment->setResolveTexture(temp_texture); // Resolved output - color_attachment->setLoadAction(MTL::LoadActionLoad); - color_attachment->setStoreAction(MTL::StoreActionMultisampleResolve); - - MTL::RenderCommandEncoder* render_encoder = - command_buffer->renderCommandEncoder(resolve_desc); - if (render_encoder) { - render_encoder->endEncoding(); - // render_encoder is autoreleased - do not release - } - } - - source_texture = temp_texture; - } - - // Create compute encoder - MTL::ComputeCommandEncoder* encoder = command_buffer->computeCommandEncoder(); - if (!encoder) { - if (temp_texture) { - temp_texture->release(); - } - return; - } - - // Set compute pipeline - encoder->setComputePipelineState(edram_store_pipeline_); - - // Bind input texture (either original or resolved) - encoder->setTexture(source_texture, 0); - - // Bind EDRAM buffer - encoder->setBuffer(edram_buffer_, 0, 0); - encoder->useResource(source_texture, MTL::ResourceUsageRead); - encoder->useResource(edram_buffer_, MTL::ResourceUsageWrite); - - // Create parameter buffers - uint32_t params[2] = {edram_base, pitch_tiles}; - MTL::Buffer* param_buffer = device_->newBuffer( - ¶ms, sizeof(params), MTL::ResourceStorageModeShared); - encoder->setBuffer(param_buffer, 0, 1); - encoder->setBuffer(param_buffer, sizeof(uint32_t), 2); - - // Calculate thread group sizes - MTL::Size threads_per_threadgroup = MTL::Size::Make(8, 8, 1); - MTL::Size threadgroups = MTL::Size::Make( - (source_texture->width() + 7) / 8, (source_texture->height() + 7) / 8, 1); - - // Dispatch compute - encoder->dispatchThreadgroups(threadgroups, threads_per_threadgroup); - encoder->endEncoding(); - // encoder is autoreleased - do not release - - if (temp_texture) { - temp_texture->release(); - } - - param_buffer->release(); + return current_depth_target_->key().GetDepthFormat() == + xenos::DepthRenderTargetFormat::kD24FS8 + ? 0.0 + : 1.0; } void MetalRenderTargetCache::DumpRenderTargets( uint32_t dump_base, uint32_t dump_row_length_used, uint32_t dump_rows, - uint32_t dump_pitch, MTL::CommandBuffer* command_buffer) { + uint32_t dump_pitch, MTL::CommandBuffer* command_buffer, + const char* encoder_label) { + assert_true(GetPath() == Path::kHostRenderTargets); + XELOGGPU( "MetalRenderTargetCache::DumpRenderTargets: base={} row_length_used={} " "rows={} pitch={}", @@ -4098,30 +3410,35 @@ void MetalRenderTargetCache::DumpRenderTargets( uint32_t padding; }; - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - if (!queue) { - XELOGE("MetalRenderTargetCache::DumpRenderTargets: no command queue"); - return; - } - ScopedAutoreleasePool autorelease_pool; - bool owns_command_buffer = false; + bool standalone = false; MTL::CommandBuffer* cmd = command_buffer; if (!cmd) { - cmd = queue->commandBuffer(); + cmd = command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=rt-dump"); if (!cmd) { XELOGE("MetalRenderTargetCache::DumpRenderTargets: no command buffer"); return; } - owns_command_buffer = true; + standalone = true; } + EndSharedMemoryUploadBlitEncoderForCommandBuffer(command_processor_, cmd); MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); if (!encoder) { XELOGE("MetalRenderTargetCache::DumpRenderTargets: no compute encoder"); - // cmd is autoreleased from commandBuffer() - do not release + if (standalone) { + cmd->release(); + } return; } + SetEncoderLabel(encoder, + encoder_label ? encoder_label : "XeniaEDRAMDumpEncoder"); + PushEncoderDebugGroup( + encoder, + fmt::format("{} base={} rows={} pitch={}", + encoder_label ? encoder_label : "XeniaEDRAMDumpEncoder", + dump_base, dump_rows, dump_pitch)); encoder->setBuffer(edram_buffer_, 0, 0); encoder->useResource(edram_buffer_, MTL::ResourceUsageWrite); @@ -4136,20 +3453,42 @@ void MetalRenderTargetCache::DumpRenderTargets( } RenderTargetKey key = rt->key(); - MTL::Texture* tex = rt->texture(); - if (!tex) { - continue; - } + MTL::Texture* tex = nullptr; + bool dump_source_is_uint = false; if (key.is_depth) { + tex = rt->texture(); + if (!tex) { + continue; + } MTL::PixelFormat expected_format = GetDepthPixelFormat(key.GetDepthFormat()); assert_true(tex->pixelFormat() == expected_format, "Dump depth must bind resource pixel format"); } else { + bool ownership_transfer_is_uint = false; + MTL::PixelFormat ownership_transfer_format = + GetColorOwnershipTransferPixelFormat(key.GetColorFormat(), + &ownership_transfer_is_uint); + dump_source_is_uint = ownership_transfer_is_uint; + if (dump_source_is_uint) { + tex = + (key.msaa_samples != xenos::MsaaSamples::k1X && rt->msaa_texture()) + ? rt->msaa_transfer_texture() + : rt->transfer_texture(); + } else { + tex = rt->texture(); + } + if (!tex) { + continue; + } MTL::PixelFormat expected_format = - GetColorResourcePixelFormat(key.GetColorFormat()); + dump_source_is_uint + ? ownership_transfer_format + : GetColorResourcePixelFormat(key.GetColorFormat()); assert_true(tex->pixelFormat() == expected_format, - "Dump color must bind resource pixel format"); + dump_source_is_uint + ? "Dump color uint must bind ownership-transfer format" + : "Dump color must bind resource pixel format"); } uint32_t dump_format = GetMetalEdramDumpFormat(key); @@ -4185,53 +3524,15 @@ void MetalRenderTargetCache::DumpRenderTargets( dump_flags |= kMetalEdramDumpFlagGammaAsLinear; } - if (!key.is_depth) { - // Color render target - if (is_64bpp) { - // 64bpp color - switch (key.msaa_samples) { - case xenos::MsaaSamples::k1X: - dump_pipeline = edram_dump_color_64bpp_1xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k2X: - dump_pipeline = edram_dump_color_64bpp_2xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k4X: - dump_pipeline = edram_dump_color_64bpp_4xmsaa_pipeline_; - break; - default: - break; - } + size_t msaa_index = MsaaSamplesToIndex(key.msaa_samples); + if (msaa_index != SIZE_MAX) { + if (!key.is_depth) { + dump_pipeline = + edram_dump_color_pipelines_[is_64bpp ? 1u : 0u] + [dump_source_is_uint ? 1u : 0u] + [msaa_index]; } else { - // 32bpp color - switch (key.msaa_samples) { - case xenos::MsaaSamples::k1X: - dump_pipeline = edram_dump_color_32bpp_1xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k2X: - dump_pipeline = edram_dump_color_32bpp_2xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k4X: - dump_pipeline = edram_dump_color_32bpp_4xmsaa_pipeline_; - break; - default: - break; - } - } - } else { - // Depth render target (always 32bpp for D24S8/D24FS8) - switch (key.msaa_samples) { - case xenos::MsaaSamples::k1X: - dump_pipeline = edram_dump_depth_32bpp_1xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k2X: - dump_pipeline = edram_dump_depth_32bpp_2xmsaa_pipeline_; - break; - case xenos::MsaaSamples::k4X: - dump_pipeline = edram_dump_depth_32bpp_4xmsaa_pipeline_; - break; - default: - break; + dump_pipeline = edram_dump_depth_pipelines_[msaa_index]; } } @@ -4246,10 +3547,11 @@ void MetalRenderTargetCache::DumpRenderTargets( XELOGGPU( "MetalRenderTargetCache::DumpRenderTargets: dump RT key=0x{:08X} " - "(is_depth={}, is_64bpp={}, msaa={}) tex={}x{} pipeline={:p}", + "(is_depth={}, is_64bpp={}, source_uint={}, msaa={}) tex={}x{} " + "pipeline={:p}", key.key, key.is_depth ? 1 : 0, is_64bpp ? 1 : 0, - static_cast(key.msaa_samples), tex->width(), tex->height(), - static_cast(dump_pipeline)); + dump_source_is_uint ? 1 : 0, static_cast(key.msaa_samples), + tex->width(), tex->height(), static_cast(dump_pipeline)); ResolveCopyDumpRectangle::Dispatch dispatches[ResolveCopyDumpRectangle::kMaxDispatches]; @@ -4312,12 +3614,11 @@ void MetalRenderTargetCache::DumpRenderTargets( } } + encoder->popDebugGroup(); encoder->endEncoding(); - if (owns_command_buffer) { - cmd->commit(); - cmd->waitUntilCompleted(); + if (standalone) { + command_processor_.CommitStandaloneAndWait(cmd); } - // cmd is autoreleased from commandBuffer() - do not release } MTL::Library* MetalRenderTargetCache::GetOrCreateEdramLoadLibrary(bool msaa) { @@ -4600,13 +3901,10 @@ MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateEdramLoadPipeline( return pipeline; } -bool MetalRenderTargetCache::Resolve(Memory& memory, uint32_t& written_address, - uint32_t& written_length, - MTL::CommandBuffer* command_buffer) { - written_address = 0; - written_length = 0; +bool MetalRenderTargetCache::PrepareResolvePlan(Memory& memory, + ResolvePlan& plan_out) { + plan_out = ResolvePlan(); const RegisterFile& regs = register_file(); - draw_util::ResolveInfo resolve_info; // Fixed16 formats may be truncated to -1..1 when backed by SNORM. bool fixed_rg16_trunc = IsFixedRG16TruncatedToMinus1To1(); @@ -4620,305 +3918,272 @@ bool MetalRenderTargetCache::Resolve(Memory& memory, uint32_t& written_address, if (!draw_util::GetResolveInfo(regs, memory, *trace_writer_, draw_resolution_scale_x(), draw_resolution_scale_y(), fixed_rg16_trunc, - fixed_rgba16_trunc, resolve_info)) { + fixed_rgba16_trunc, plan_out.resolve_info)) { XELOGE("MetalRenderTargetCache::Resolve: GetResolveInfo failed"); return false; } + plan_out.valid = true; + + const draw_util::ResolveInfo& resolve_info = plan_out.resolve_info; + plan_out.noop = + !resolve_info.coordinate_info.width_div_8 || !resolve_info.height_div_8; + if (plan_out.noop) { + return true; + } + plan_out.needs_copy_export = resolve_info.copy_dest_extent_length != 0; + plan_out.needs_resolve_clear = + resolve_info.IsClearingDepth() || resolve_info.IsClearingColor(); + // TODO (xenios-jp): Add a queued resolve-clear system for clear-only resolves + // that can be materialized before the next observer. Full clears could become + // descriptor-time loadActionClear in the next render pass, avoiding an + // immediate render encoder end on Apple TBDR GPUs. This needs a real + // ownership transaction because PrepareHostRenderTargetsResolveClear mutates + // tile ownership, and every later draw, transfer, export, readback, or + // ownership query must observe the cleared contents. + plan_out.needs_render_encoder_end = + plan_out.needs_copy_export || plan_out.needs_resolve_clear; + if (plan_out.needs_copy_export) { + plan_out.written_address = resolve_info.copy_dest_extent_start; + plan_out.written_length = resolve_info.copy_dest_extent_length; + } + return true; +} + +bool MetalRenderTargetCache::Resolve(Memory& memory, uint32_t& written_address, + uint32_t& written_length, + MTL::CommandBuffer* command_buffer, + const ResolvePlan* prepared_resolve_plan) { + written_address = 0; + written_length = 0; + ResolvePlan resolve_plan_storage; + if (!prepared_resolve_plan) { + if (!PrepareResolvePlan(memory, resolve_plan_storage)) { + return false; + } + prepared_resolve_plan = &resolve_plan_storage; + } + const ResolvePlan& resolve_plan = *prepared_resolve_plan; + const draw_util::ResolveInfo& resolve_info = resolve_plan.resolve_info; // Nothing to do. - if (!resolve_info.coordinate_info.width_div_8 || !resolve_info.height_div_8) { + if (resolve_plan.noop) { return true; } bool is_depth = resolve_info.IsCopyingDepth(); - if (!resolve_info.copy_dest_extent_length) { - return true; - } - bool draw_resolution_scaled = IsDrawResolutionScaled(); const auto& coord = resolve_info.coordinate_info; uint32_t resolve_width = coord.width_div_8 * 8; uint32_t resolve_height = resolve_info.height_div_8 * 8; - // Compute the EDRAM tile span for this resolve. - uint32_t dump_base, dump_row_length_used, dump_rows, dump_pitch; - resolve_info.GetCopyEdramTileSpan(dump_base, dump_row_length_used, dump_rows, - dump_pitch); - // Match D3D12/Vulkan: dump host RT ownership into EDRAM, then resolve - // from EDRAM to shared memory. Resolve-time blend fallback is not correct - // because blending state is per-draw, not per-resolve. - DumpRenderTargets(dump_base, dump_row_length_used, dump_rows, dump_pitch, - command_buffer); - - uint32_t dest_base = resolve_info.copy_dest_base; - uint32_t dest_local_start = resolve_info.copy_dest_extent_start - dest_base; - uint32_t dest_local_end = - dest_local_start + resolve_info.copy_dest_extent_length; - - command_processor_.SetSwapDestSwap( - dest_base, resolve_info.copy_dest_info.copy_dest_swap); - - // Color resolves are 8888; depth resolves may use different destination - // formats, so only apply the 4-byte-per-pixel assumption to color. - uint32_t bytes_per_pixel = 4; - // Try GPU compute resolve first (RT -> EDRAM -> shared memory), matching // D3D12/Vulkan behavior for the supported cases. if (edram_buffer_) { - draw_util::ResolveCopyShaderConstants copy_constants; - uint32_t group_count_x = 0, group_count_y = 0; - draw_util::ResolveCopyShaderIndex copy_shader = resolve_info.GetCopyShader( - draw_resolution_scale_x(), draw_resolution_scale_y(), copy_constants, - group_count_x, group_count_y); + // Copy dispatch -- only when there is an actual copy extent. + bool copy_succeeded = !resolve_plan.needs_copy_export; + if (resolve_plan.needs_copy_export) { + draw_util::ResolveCopyShaderConstants copy_constants; + uint32_t group_count_x = 0, group_count_y = 0; + draw_util::ResolveCopyShaderIndex copy_shader = + resolve_info.GetCopyShader(draw_resolution_scale_x(), + draw_resolution_scale_y(), copy_constants, + group_count_x, group_count_y); + bool direct_host_resolve_enabled = ::cvars::metal_direct_host_resolve; + bool direct_host_rt_candidate = + direct_host_resolve_enabled && + IsResolveDirectHostRTCandidate(copy_shader); + const draw_util::ResolveCopyShaderInfo& copy_shader_info = + draw_util::resolve_copy_shader_info[size_t(copy_shader)]; - // Select the appropriate Metal pipeline for this shader. - MTL::ComputePipelineState* pipeline = nullptr; - if (draw_resolution_scaled) { - switch (copy_shader) { - case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: - pipeline = resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: - pipeline = resolve_fast_32bpp_4xmsaa_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: - pipeline = resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: - pipeline = resolve_fast_64bpp_4xmsaa_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull8bpp: - pipeline = resolve_full_8bpp_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull16bpp: - pipeline = resolve_full_16bpp_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull32bpp: - pipeline = resolve_full_32bpp_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull64bpp: - pipeline = resolve_full_64bpp_scaled_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull128bpp: - pipeline = resolve_full_128bpp_scaled_pipeline_; - break; - default: - pipeline = nullptr; - break; - } - } else { - switch (copy_shader) { - case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: - pipeline = resolve_fast_32bpp_1x2xmsaa_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: - pipeline = resolve_fast_32bpp_4xmsaa_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: - pipeline = resolve_fast_64bpp_1x2xmsaa_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: - pipeline = resolve_fast_64bpp_4xmsaa_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull8bpp: - pipeline = resolve_full_8bpp_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull16bpp: - pipeline = resolve_full_16bpp_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull32bpp: - pipeline = resolve_full_32bpp_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull64bpp: - pipeline = resolve_full_64bpp_pipeline_; - break; - case draw_util::ResolveCopyShaderIndex::kFull128bpp: - pipeline = resolve_full_128bpp_pipeline_; - break; - default: - pipeline = nullptr; - break; - } - } - if (draw_resolution_scaled && !pipeline) { - static uint32_t missing_scaled_pipeline_log_count = 0; - if (missing_scaled_pipeline_log_count < 8) { - ++missing_scaled_pipeline_log_count; - XELOGW("MetalResolve: scaled resolve pipeline missing for shader {}", - int(copy_shader)); - } - } + // Match D3D12/Vulkan: dump host RT ownership into EDRAM, then resolve + // from EDRAM to shared memory. Resolve-time blend fallback is not correct + // because blending state is per-draw, not per-resolve. + uint32_t dump_base, dump_row_length_used, dump_rows, dump_pitch; + resolve_info.GetCopyEdramTileSpan(dump_base, dump_row_length_used, + dump_rows, dump_pitch); + if (direct_host_resolve_enabled && + TryDirectHostResolveCopy(resolve_info, copy_constants, copy_shader, + dump_base, dump_row_length_used, dump_rows, + dump_pitch, command_buffer, written_address, + written_length)) { + copy_succeeded = true; + } else { + DumpRenderTargets(dump_base, dump_row_length_used, dump_rows, + dump_pitch, command_buffer, + ResolveDumpEncoderLabel(direct_host_rt_candidate)); - if (pipeline && group_count_x && group_count_y) { - uint32_t dest_pitch_pixels = - copy_constants.dest_relative.dest_coordinate_info.pitch_aligned_div_32 - << 5; - if (dest_pitch_pixels < resolve_width) { - uint32_t new_pitch_pixels = (resolve_width + 31) & ~31u; - XELOGW( - "MetalResolve: overriding dest pitch {} -> {} " - "(resolve_width={})", - dest_pitch_pixels, new_pitch_pixels, resolve_width); - copy_constants.dest_relative.dest_coordinate_info.pitch_aligned_div_32 = - new_pitch_pixels >> 5; - } - auto* shared = command_processor_.shared_memory(); - auto* texture_cache = command_processor_.texture_cache(); - MTL::Buffer* dest_buffer = nullptr; - size_t dest_buffer_offset = 0; - size_t dest_buffer_length = 0; - const uint8_t* shared_bytes = nullptr; - uint32_t scaled_range_length = 0; - if (draw_resolution_scaled) { - auto* metal_texture_cache = - texture_cache ? static_cast(texture_cache) - : nullptr; - if (!metal_texture_cache) { - XELOGE("MetalResolve: missing MetalTextureCache for scaled resolve"); - return false; + uint32_t dest_base = resolve_info.copy_dest_base; + uint32_t dest_local_start = + resolve_info.copy_dest_extent_start - dest_base; + uint32_t dest_local_end = + dest_local_start + resolve_info.copy_dest_extent_length; + + command_processor_.SetSwapDestSwap( + dest_base, resolve_info.copy_dest_info.copy_dest_swap); + + // For now, only apply the 8888 restriction to color resolves; depth + // resolves may use different destination formats. + uint32_t bytes_per_pixel = 4; + + // Select the appropriate Metal pipeline for this shader. + MTL::ComputePipelineState* pipeline = + GetResolvePipeline(copy_shader, draw_resolution_scaled); + if (draw_resolution_scaled && !pipeline) { + static uint32_t missing_scaled_pipeline_log_count = 0; + if (missing_scaled_pipeline_log_count < 8) { + ++missing_scaled_pipeline_log_count; + XELOGW( + "MetalResolve: scaled resolve pipeline missing for shader {}", + int(copy_shader)); + } } - uint32_t range_length = resolve_info.copy_dest_extent_start - - resolve_info.copy_dest_base + - resolve_info.copy_dest_extent_length; - scaled_range_length = range_length; - if (!metal_texture_cache->EnsureScaledResolveMemoryCommitted( + + if (pipeline && group_count_x && group_count_y) { + ResolveDestinationBuffer destination = {}; + if (!PrepareResolveDestinationBuffer( + resolve_info, draw_resolution_scaled, destination)) { + XELOGE( + "MetalRenderTargetCache::Resolve: failed to prepare resolve " + "destination for 0x{:08X} len {}", resolve_info.copy_dest_extent_start, - resolve_info.copy_dest_extent_length) || - !metal_texture_cache->MakeScaledResolveRangeCurrent( - resolve_info.copy_dest_base, range_length) || - !metal_texture_cache->GetCurrentScaledResolveBuffer( - dest_buffer, dest_buffer_offset, dest_buffer_length)) { - XELOGE("MetalResolve: failed to select scaled resolve buffer"); - return false; - } - (void)dest_buffer_length; - } else { - dest_buffer = shared ? shared->GetBuffer() : nullptr; - if (!dest_buffer) { - XELOGE("MetalResolve: missing shared memory buffer"); - return false; - } - // Request the destination shared memory range before the GPU write, - // mirroring D3D12/Vulkan behavior. This ensures pages are committed and - // any CPU data is uploaded before the GPU overwrites it. - if (!shared->RequestRange(resolve_info.copy_dest_extent_start, - resolve_info.copy_dest_extent_length)) { - XELOGE( - "MetalRenderTargetCache::Resolve: RequestRange failed for " - "0x{:08X} len {}", - resolve_info.copy_dest_extent_start, - resolve_info.copy_dest_extent_length); - return false; - } + resolve_info.copy_dest_extent_length); + return false; + } - shared_bytes = static_cast(dest_buffer->contents()); - } - if (draw_resolution_scaled) { + { + ScopedAutoreleasePool autorelease_pool; + bool standalone = false; + MTL::CommandBuffer* cmd = command_buffer; + if (!cmd) { + cmd = command_processor_.CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=resolve-compute"); + if (!cmd) { + XELOGE( + "MetalRenderTargetCache::Resolve: failed to get command " + "buffer for GPU path"); + } + standalone = (cmd != nullptr); + } + if (cmd) { + EndSharedMemoryUploadBlitEncoderForCommandBuffer( + command_processor_, cmd); + MTL::ComputeCommandEncoder* encoder = + cmd->computeCommandEncoder(); + if (!encoder) { + XELOGE( + "MetalRenderTargetCache::Resolve: failed to get compute " + "encoder for GPU path"); + if (standalone) { + cmd->release(); + } + } else { + SetEncoderLabel( + encoder, ResolveCopyEncoderLabel(direct_host_rt_candidate)); + PushEncoderDebugGroup( + encoder, + fmt::format( + "{} shader=\"{}\" direct_host_rt_candidate={} " + "source=edram_buffer dest={}", + ResolveCopyEncoderLabel(direct_host_rt_candidate), + copy_shader_info.debug_name, + direct_host_rt_candidate ? 1 : 0, + draw_resolution_scaled ? "scaled_resolve_memory" + : "shared_memory")); + encoder->setComputePipelineState(pipeline); + + // Buffer 0: push constants + if (draw_resolution_scaled) { + encoder->setBytes(©_constants.dest_relative, + sizeof(copy_constants.dest_relative), 0); + } else { + encoder->setBytes(©_constants, sizeof(copy_constants), 0); + } + + // Buffer 1: destination memory (shared or scaled resolve). + encoder->setBuffer(destination.buffer, destination.offset, 1); + + // Buffer 2: EDRAM source buffer. + encoder->setBuffer(edram_buffer_, 0, 2); + encoder->useResource(destination.buffer, + MTL::ResourceUsageWrite); + encoder->useResource(edram_buffer_, MTL::ResourceUsageRead); + + encoder->dispatchThreadgroups( + MTL::Size::Make(group_count_x, group_count_y, 1), + MTL::Size::Make(8, 8, 1)); + + if (!draw_resolution_scaled) { + command_processor_.MarkSharedMemoryComputeWritePending( + resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length, encoder); + if (auto* shared_memory = + command_processor_.shared_memory()) { + shared_memory->MarkGpuAccess( + resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length, + command_processor_.GetCurrentSubmission()); + } + } + + encoder->popDebugGroup(); + encoder->endEncoding(); + if (standalone) { + command_processor_.CommitStandaloneAndWait(cmd); + } + + written_address = resolve_plan.written_address; + written_length = resolve_plan.written_length; + + // Mark the range as resolved in the texture cache so that any + // textures overlapping this range will be reloaded from the + // updated shared memory. This matches D3D12/Vulkan behavior. + if (auto* tex_cache = command_processor_.texture_cache()) { + tex_cache->MarkRangeAsResolved(written_address, + written_length); + } + + copy_succeeded = true; + } + } + } + } } - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - - if (!queue) { + if (!copy_succeeded) { XELOGE( - "MetalRenderTargetCache::Resolve: no command queue for GPU path"); - } else { - ScopedAutoreleasePool autorelease_pool; - bool owns_command_buffer = false; - MTL::CommandBuffer* cmd = command_buffer; - if (!cmd) { - cmd = queue->commandBuffer(); - if (!cmd) { - XELOGE( - "MetalRenderTargetCache::Resolve: failed to get command " - "buffer for GPU path"); - cmd = nullptr; - } - owns_command_buffer = true; - } - if (cmd) { - MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); - if (!encoder) { - XELOGE( - "MetalRenderTargetCache::Resolve: failed to get compute " - "encoder for GPU path"); - // cmd is autoreleased from commandBuffer() - do not release - } else { - encoder->setComputePipelineState(pipeline); + "MetalRenderTargetCache::Resolve: no valid GPU resolve shader / " + "pipeline for this configuration"); + } + } // if (needs_copy_export) - // Buffer 0: push constants - if (draw_resolution_scaled) { - encoder->setBytes(©_constants.dest_relative, - sizeof(copy_constants.dest_relative), 0); - } else { - encoder->setBytes(©_constants, sizeof(copy_constants), 0); - } - - // Buffer 1: destination memory (shared or scaled resolve). - encoder->setBuffer(dest_buffer, dest_buffer_offset, 1); - - // Buffer 2: EDRAM source buffer. - encoder->setBuffer(edram_buffer_, 0, 2); - encoder->useResource(dest_buffer, MTL::ResourceUsageWrite); - encoder->useResource(edram_buffer_, MTL::ResourceUsageRead); - - encoder->dispatchThreadgroups( - MTL::Size::Make(group_count_x, group_count_y, 1), - MTL::Size::Make(8, 8, 1)); - - encoder->endEncoding(); - if (owns_command_buffer) { - cmd->commit(); - cmd->waitUntilCompleted(); - } - // cmd is autoreleased from commandBuffer() - do not release - - written_address = resolve_info.copy_dest_extent_start; - written_length = resolve_info.copy_dest_extent_length; - - // Mark the shared memory range as GPU-written resolve data so - // texture caches and trace dumping can see it without an extra - // CPU copy. This mirrors D3D12/Vulkan behavior. - if (!draw_resolution_scaled) { - if (auto* shared_after = command_processor_.shared_memory()) { - shared_after->RangeWrittenByGpu(written_address, - written_length); - } - } - - // Mark the range as resolved in the texture cache so that any - // textures overlapping this range will be reloaded from the - // updated shared memory. This matches D3D12/Vulkan behavior. - if (auto* tex_cache = command_processor_.texture_cache()) { - tex_cache->MarkRangeAsResolved(written_address, written_length); - } - - bool clear_depth = resolve_info.IsClearingDepth(); - bool clear_color = resolve_info.IsClearingColor(); - if (clear_depth || clear_color) { - Transfer::Rectangle clear_rectangle; - RenderTarget* clear_targets[2] = {}; - std::vector clear_transfers[2]; - if (PrepareHostRenderTargetsResolveClear( - resolve_info, clear_rectangle, clear_targets[0], - clear_transfers[0], clear_targets[1], - clear_transfers[1])) { - uint64_t clear_values[2]; - clear_values[0] = resolve_info.rb_depth_clear; - clear_values[1] = - resolve_info.rb_color_clear | - (uint64_t(resolve_info.rb_color_clear_lo) << 32); - PerformTransfersAndResolveClears( - 2, clear_targets, clear_transfers, clear_values, - &clear_rectangle, command_buffer); - } - } - return true; - } - } + // Clearing -- runs independently of whether the copy succeeded, matching + // D3D12/Vulkan behavior. + bool clear_depth = + resolve_plan.needs_resolve_clear && resolve_info.IsClearingDepth(); + bool clear_color = + resolve_plan.needs_resolve_clear && resolve_info.IsClearingColor(); + bool clear_succeeded = !(clear_depth || clear_color); + if (clear_depth || clear_color) { + clear_succeeded = true; + Transfer::Rectangle clear_rectangle; + RenderTarget* clear_targets[2] = {}; + std::vector clear_transfers[2]; + if (PrepareHostRenderTargetsResolveClear( + resolve_info, clear_rectangle, clear_targets[0], + clear_transfers[0], clear_targets[1], clear_transfers[1])) { + uint64_t clear_values[2]; + clear_values[0] = resolve_info.rb_depth_clear; + clear_values[1] = resolve_info.rb_color_clear | + (uint64_t(resolve_info.rb_color_clear_lo) << 32); + PerformTransfersAndResolveClears(2, clear_targets, clear_transfers, + clear_values, &clear_rectangle, + command_buffer); } } + + return copy_succeeded && clear_succeeded; } XELOGE( @@ -4927,20 +4192,52 @@ bool MetalRenderTargetCache::Resolve(Memory& memory, uint32_t& written_address, return false; } -void MetalRenderTargetCache::PerformTransfersAndResolveClears( +bool MetalRenderTargetCache::PerformTransfersAndResolveClears( uint32_t render_target_count, RenderTarget* const* render_targets, const std::vector* render_target_transfers, const uint64_t* render_target_resolve_clear_values, const Transfer::Rectangle* resolve_clear_rectangle, - MTL::CommandBuffer* command_buffer) { + MTL::CommandBuffer* command_buffer, + MTL::RenderCommandEncoder* active_render_encoder, + MTL::RenderPassDescriptor* active_render_pass_descriptor, + DrawPassTransferEncoderMutationMask* mutations_out) { + if (mutations_out) { + *mutations_out = kDrawPassTransferEncoderMutationNone; + } if (!render_targets || !render_target_transfers) { - return; + return false; } bool resolve_clear_needed = render_target_resolve_clear_values && resolve_clear_rectangle; + bool use_active_render_encoder = active_render_encoder != nullptr; + if (use_active_render_encoder && + (resolve_clear_needed || !active_render_pass_descriptor)) { + return false; + } + auto mark_active_encoder_mutation = + [&](DrawPassTransferEncoderMutationMask mutations) { + if (use_active_render_encoder && mutations_out) { + *mutations_out |= mutations; + } + }; + TransferAttachmentFormats active_attachment_formats; + if (use_active_render_encoder && + !GetActiveTransferAttachmentFormats(active_render_pass_descriptor, + active_attachment_formats)) { + return false; + } bool any_work = false; bool host_depth_store_needed = false; + std::array, + 1 + xenos::kMaxColorRenderTargets> + local_transfer_rectangle_plans; + std::array*, + 1 + xenos::kMaxColorRenderTargets> + transfer_rectangle_plans = {}; + if (render_target_count > transfer_rectangle_plans.size()) { + return false; + } for (uint32_t i = 0; i < render_target_count; ++i) { RenderTarget* dest_rt = render_targets[i]; if (!dest_rt) { @@ -4953,34 +4250,55 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( if (transfers.empty()) { continue; } - any_work = true; - if (!dest_rt->key().is_depth) { + RenderTargetKey dest_key = dest_rt->key(); + std::vector& local_transfer_rectangles = + local_transfer_rectangle_plans[i]; + if (!BuildTransferRectanglePlans(dest_key, transfers, + resolve_clear_rectangle, false, + local_transfer_rectangles)) { continue; } - for (const Transfer& transfer : transfers) { - if (transfer.host_depth_source == dest_rt) { + transfer_rectangle_plans[i] = &local_transfer_rectangles; + if (local_transfer_rectangles.empty()) { + continue; + } + for (const TransferRectanglePlan& transfer_plan : + local_transfer_rectangles) { + const Transfer& transfer = transfers[transfer_plan.transfer_index]; + if (dest_key.is_depth && transfer.host_depth_source == dest_rt) { host_depth_store_needed = true; - break; } } + any_work = true; } if (!any_work) { - return; + return true; + } + if (use_active_render_encoder && host_depth_store_needed) { + return false; } MTL::CommandBuffer* cmd = command_buffer; - if (!cmd) { - cmd = command_processor_.EnsureCommandBuffer(); + if (use_active_render_encoder) { + cmd = command_processor_.GetCurrentCommandBuffer(); + } else if (!cmd) { + // RequestTransferCommandBuffer ends any active render encoder and ensures + // transfer work has a command buffer. It does not require a standalone + // command-buffer submission if the current one can be reused. + cmd = command_processor_.RequestTransferCommandBuffer( + MetalCommandProcessor::TransferRequestSource::kRenderTargetTransfer); + } else { + // An externally-provided command buffer still requires the render + // encoder to be ended before transfer work can proceed. + command_processor_.EndRenderEncoder(); } if (!cmd) { XELOGE( "MetalRenderTargetCache::PerformTransfersAndResolveClears: no command " "buffer"); - return; + return false; } - command_processor_.EndRenderEncoder(); - uint32_t scale_x = draw_resolution_scale_x(); uint32_t scale_y = draw_resolution_scale_y(); uint32_t tile_width_samples = @@ -4993,8 +4311,9 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( : 0u; // Host depth store pass (dest depth where host depth source == dest). - bool host_depth_store_dispatched = false; + // Use a single compute encoder for all depth store dispatches. if (host_depth_store_needed) { + MTL::ComputeCommandEncoder* depth_store_encoder = nullptr; for (uint32_t i = 0; i < render_target_count; ++i) { RenderTarget* dest_rt = render_targets[i]; if (!dest_rt) { @@ -5005,7 +4324,15 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( continue; } const std::vector& depth_transfers = render_target_transfers[i]; - for (const Transfer& transfer : depth_transfers) { + const std::vector* depth_transfer_rectangles = + transfer_rectangle_plans[i]; + if (!depth_transfer_rectangles) { + continue; + } + for (const TransferRectanglePlan& transfer_plan : + *depth_transfer_rectangles) { + const Transfer& transfer = + depth_transfers[transfer_plan.transfer_index]; if (transfer.host_depth_source != dest_rt) { continue; } @@ -5023,57 +4350,60 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( uint32_t(dest_key.msaa_samples)); continue; } - Transfer::Rectangle rectangles[Transfer::kMaxRectanglesWithCutout]; - uint32_t rectangle_count = transfer.GetRectangles( - dest_key.base_tiles, dest_key.pitch_tiles_at_32bpp, - dest_key.msaa_samples, false, rectangles, resolve_clear_rectangle); - if (!rectangle_count) { + if (!transfer_plan.rectangle_count) { continue; } HostDepthStoreRenderTargetConstant render_target_constant = GetHostDepthStoreRenderTargetConstant(dest_key.pitch_tiles_at_32bpp, msaa_2x_supported_); - MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); - if (!encoder) { - XELOGE( - "MetalRenderTargetCache::PerformTransfersAndResolveClears: " - "failed to create host depth store encoder"); - continue; + if (!depth_store_encoder) { + EndSharedMemoryUploadBlitEncoderForCommandBuffer(command_processor_, + cmd); + depth_store_encoder = cmd->computeCommandEncoder(); + if (!depth_store_encoder) { + XELOGE( + "MetalRenderTargetCache::PerformTransfersAndResolveClears: " + "failed to create host depth store encoder"); + break; + } + depth_store_encoder->setLabel(NS::String::string( + "XeniaHostDepthStoreEncoder", NS::UTF8StringEncoding)); + depth_store_encoder->setComputePipelineState( + host_depth_store_pipelines_[pipeline_index]); + depth_store_encoder->setBuffer(edram_buffer_, 0, 1); + depth_store_encoder->setTexture(depth_texture, 0); + depth_store_encoder->useResource(edram_buffer_, + MTL::ResourceUsageWrite); + depth_store_encoder->useResource(depth_texture, + MTL::ResourceUsageRead); } - encoder->setComputePipelineState( - host_depth_store_pipelines_[pipeline_index]); - encoder->setBuffer(edram_buffer_, 0, 1); - encoder->setTexture(depth_texture, 0); - encoder->useResource(edram_buffer_, MTL::ResourceUsageWrite); - encoder->useResource(depth_texture, MTL::ResourceUsageRead); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { + for (uint32_t rect_index = 0; + rect_index < transfer_plan.rectangle_count; ++rect_index) { uint32_t group_count_x = 0; uint32_t group_count_y = 0; HostDepthStoreRectangleConstant rectangle_constant; GetHostDepthStoreRectangleInfo( - rectangles[rect_index], dest_key.msaa_samples, rectangle_constant, - group_count_x, group_count_y); + transfer_plan.rectangles[rect_index], dest_key.msaa_samples, + rectangle_constant, group_count_x, group_count_y); if (!group_count_x || !group_count_y) { continue; } HostDepthStoreConstants constants = {}; constants.rectangle = rectangle_constant; constants.render_target = render_target_constant; - encoder->setBytes(&constants, sizeof(constants), 0); - encoder->dispatchThreadgroups( + depth_store_encoder->setBytes(&constants, sizeof(constants), 0); + depth_store_encoder->dispatchThreadgroups( MTL::Size::Make(group_count_x, group_count_y, 1), MTL::Size::Make(8, 8, 1)); - host_depth_store_dispatched = true; } - encoder->endEncoding(); } break; } + if (depth_store_encoder) { + depth_store_encoder->endEncoding(); + } } - bool any_transfers_done = false; - for (uint32_t i = 0; i < render_target_count; ++i) { RenderTarget* dest_rt = render_targets[i]; if (!dest_rt) { @@ -5081,17 +4411,21 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } const std::vector& transfers = render_target_transfers[i]; - if (transfers.empty() && !resolve_clear_needed) { + const std::vector* target_transfer_rectangles = + transfer_rectangle_plans[i]; + if ((!target_transfer_rectangles || target_transfer_rectangles->empty()) && + !resolve_clear_needed) { continue; } auto* dest_metal_rt = static_cast(dest_rt); if (dest_metal_rt->needs_initial_clear()) { dest_metal_rt->SetNeedsInitialClear(false); - render_pass_descriptor_dirty_ = true; + MarkRenderPassDescriptorDirty(); } RenderTargetKey dest_key = dest_metal_rt->key(); bool dest_is_depth = dest_key.is_depth; + uint32_t active_color_attachment_index = 0; bool dest_is_uint = false; MTL::PixelFormat dest_pixel_format = @@ -5116,6 +4450,43 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( assert_true(dest_texture->pixelFormat() == dest_pixel_format, "Transfer color must use ownership pixel format"); } + if (use_active_render_encoder) { + if (dest_is_depth) { + if (i != 0 || active_attachment_formats.depth_attachment_format != + dest_pixel_format) { + return false; + } + auto* depth_attachment = + active_render_pass_descriptor->depthAttachment(); + MTL::Texture* depth_texture = + depth_attachment ? depth_attachment->texture() : nullptr; + if (depth_texture != dest_metal_rt->draw_texture()) { + return false; + } + if (dest_pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || + dest_pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8) { + auto* stencil_attachment = + active_render_pass_descriptor->stencilAttachment(); + MTL::Texture* stencil_texture = + stencil_attachment ? stencil_attachment->texture() : nullptr; + if (stencil_texture != depth_texture || + active_attachment_formats.stencil_attachment_format != + dest_pixel_format) { + return false; + } + } + } else { + if (i == 0 || i > xenos::kMaxColorRenderTargets) { + return false; + } + active_color_attachment_index = i - 1; + if (active_attachment_formats + .color_attachment_formats[active_color_attachment_index] != + dest_pixel_format) { + return false; + } + } + } uint32_t dest_sample_count = MsaaSamplesToCount(dest_key.msaa_samples); bool transfer_use_sample_id_default = @@ -5164,414 +4535,50 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( vp.znear = 0.0; vp.zfar = 1.0; encoder->setViewport(vp); + mark_active_encoder_mutation(kDrawPassTransferEncoderMutationViewport); MTL::ScissorRect scissor; scissor.x = scaled_x; scissor.y = scaled_y; scissor.width = scaled_width; scissor.height = scaled_height; encoder->setScissorRect(scissor); + mark_active_encoder_mutation(kDrawPassTransferEncoderMutationScissor); return true; }; - struct TransferTileBatch { - MTL::Buffer* buffer = nullptr; - size_t buffer_offset = 0; - uint32_t instance_count = 0; - MTL::ScissorRect scissor = {}; - }; - - struct TransferTileBatchBuildInfo { - TransferTileBatch batch; - uint32_t tile_x_start = 0; - uint32_t tile_x_end = 0; - uint32_t tile_y_start = 0; - uint32_t tile_y_end = 0; - }; - bool transfer_tile_instance_budget_hit = false; - bool transfer_tile_instance_adaptive_cutoff_hit = false; - size_t transfer_tile_instance_adaptive_candidate_bytes = 0; - size_t transfer_tile_instance_adaptive_limit_bytes = 0; - uint32_t transfer_tile_instance_adaptive_rect_count = 0; - bool transfer_tile_instance_predictive_cutoff_hit = false; - size_t transfer_tile_instance_predictive_used_bytes = 0; - size_t transfer_tile_instance_predictive_candidate_bytes = 0; - size_t transfer_tile_instance_predictive_threshold_bytes = 0; - - auto allocate_instance_buffer = [&](size_t size, MTL::Buffer*& buffer, - size_t& offset) -> bool { - if (!device_) { - return false; + std::vector all_transfer_plan_indices; + if (target_transfer_rectangles) { + all_transfer_plan_indices.reserve(target_transfer_rectangles->size()); + for (uint32_t transfer_plan_index = 0; + transfer_plan_index < target_transfer_rectangles->size(); + ++transfer_plan_index) { + all_transfer_plan_indices.push_back(transfer_plan_index); } - uint32_t buffer_index = - uint32_t(frame_id_ % kTransferInstanceBufferCount); - if (transfer_tile_instance_buffer_frame_id_ != frame_id_) { - transfer_tile_instance_buffer_frame_id_ = frame_id_; - transfer_tile_instance_buffer_offset_ = 0; - auto& retired_buffers = - transfer_tile_instance_retired_buffers_[buffer_index]; - for (auto* retired_buffer : retired_buffers) { - if (retired_buffer) { - retired_buffer->release(); - } - } - retired_buffers.clear(); - } - constexpr size_t kAlignment = 256; - size_t aligned_offset = - xe::align(transfer_tile_instance_buffer_offset_, size_t(kAlignment)); - if (size > kTransferTileInstanceBufferMaxBytes || - aligned_offset > kTransferTileInstanceBufferMaxBytes || - aligned_offset > (kTransferTileInstanceBufferMaxBytes - size)) { - transfer_tile_instance_budget_hit = true; - return false; - } - size_t required = aligned_offset + size; - if (transfer_tile_instance_buffers_[buffer_index] && - transfer_tile_instance_buffer_sizes_[buffer_index] < required && - aligned_offset != 0) { - transfer_tile_instance_budget_hit = true; - return false; - } - if (!transfer_tile_instance_buffers_[buffer_index] || - transfer_tile_instance_buffer_sizes_[buffer_index] < required) { - size_t new_size = xe::round_up(required, 65536); - if (new_size > kTransferTileInstanceBufferMaxBytes) { - new_size = kTransferTileInstanceBufferMaxBytes; - } - if (new_size < required) { - transfer_tile_instance_budget_hit = true; - return false; - } - if (transfer_tile_instance_buffers_[buffer_index]) { - transfer_tile_instance_retired_buffers_[buffer_index].push_back( - transfer_tile_instance_buffers_[buffer_index]); - transfer_tile_instance_buffers_[buffer_index] = nullptr; - } - MTL::ResourceOptions options = MTL::ResourceStorageModeShared | - MTL::ResourceCPUCacheModeWriteCombined; - MTL::Buffer* new_buffer = device_->newBuffer(new_size, options); - if (!new_buffer) { - transfer_tile_instance_buffer_sizes_[buffer_index] = 0; - return false; - } - transfer_tile_instance_buffers_[buffer_index] = new_buffer; - transfer_tile_instance_buffer_sizes_[buffer_index] = new_size; - } - buffer = transfer_tile_instance_buffers_[buffer_index]; - if (!buffer) { - return false; - } - offset = aligned_offset; - transfer_tile_instance_buffer_offset_ = aligned_offset + size; - return true; - }; - - auto build_tile_batches = - [&](const Transfer::Rectangle* rectangles, uint32_t rectangle_count, - const TransferShaderConstants& constants, bool uses_host_depth, - bool host_depth_is_copy, - std::vector& out_batches) -> bool { - out_batches.clear(); - if (!constants.dest_tile_width_pixels || - !constants.dest_tile_height_pixels) { - return false; - } - if (!cmd) { - return false; - } - uint32_t max_tile_x = - (dest_width + constants.dest_tile_width_pixels - 1) / - constants.dest_tile_width_pixels; - uint32_t max_tile_y = - (dest_height + constants.dest_tile_height_pixels - 1) / - constants.dest_tile_height_pixels; - if (!max_tile_x || !max_tile_y) { - return false; - } - --max_tile_x; - --max_tile_y; - std::vector build_infos; - size_t total_instance_bytes = 0; - uint64_t total_covered_pixels = 0; - constexpr size_t kAlignment = 256; - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - uint32_t scaled_x = 0; - uint32_t scaled_y = 0; - uint32_t scaled_width = 0; - uint32_t scaled_height = 0; - if (!get_scaled_rect(rectangles[rect_index], scaled_x, scaled_y, - scaled_width, scaled_height)) { - continue; - } - uint32_t tile_x_start = scaled_x / constants.dest_tile_width_pixels; - uint32_t tile_y_start = scaled_y / constants.dest_tile_height_pixels; - uint32_t tile_x_end = - (scaled_x + scaled_width - 1) / constants.dest_tile_width_pixels; - uint32_t tile_y_end = - (scaled_y + scaled_height - 1) / constants.dest_tile_height_pixels; - tile_x_end = std::min(tile_x_end, max_tile_x); - tile_y_end = std::min(tile_y_end, max_tile_y); - if (tile_x_start > tile_x_end || tile_y_start > tile_y_end) { - continue; - } - uint32_t tiles_x = tile_x_end - tile_x_start + 1; - uint32_t tiles_y = tile_y_end - tile_y_start + 1; - uint32_t tile_count = tiles_x * tiles_y; - if (!tile_count) { - continue; - } - TransferTileBatchBuildInfo info; - info.tile_x_start = tile_x_start; - info.tile_x_end = tile_x_end; - info.tile_y_start = tile_y_start; - info.tile_y_end = tile_y_end; - info.batch.instance_count = tile_count; - info.batch.scissor.x = scaled_x; - info.batch.scissor.y = scaled_y; - info.batch.scissor.width = scaled_width; - info.batch.scissor.height = scaled_height; - total_covered_pixels += - uint64_t(scaled_width) * uint64_t(scaled_height); - total_instance_bytes = xe::align(total_instance_bytes, kAlignment); - info.batch.buffer_offset = total_instance_bytes; - total_instance_bytes += - size_t(tile_count) * sizeof(TransferTileInstance); - build_infos.push_back(info); - } - - if (build_infos.empty() || !total_instance_bytes) { - return false; - } - - size_t adaptive_soft_limit_bytes = kTransferTileInstanceSoftBaseBytes; - if (dest_width && dest_height) { - uint64_t dest_pixels = uint64_t(dest_width) * uint64_t(dest_height); - if (total_covered_pixels * - kTransferTileInstanceLowCoverageRatioDivisor <= - dest_pixels) { - adaptive_soft_limit_bytes = kTransferTileInstanceSoftLowCoverageBytes; - } else if (total_covered_pixels * - kTransferTileInstanceMediumCoverageRatioDivisor <= - dest_pixels) { - adaptive_soft_limit_bytes = - (kTransferTileInstanceSoftBaseBytes + - kTransferTileInstanceSoftLowCoverageBytes) / - 2; - } - } - if (build_infos.size() <= kTransferTileInstanceSmallRectPenaltyCount && - adaptive_soft_limit_bytes > (kAlignment * 64)) { - size_t penalized_soft_limit_bytes = - adaptive_soft_limit_bytes * - kTransferTileInstanceSmallRectPenaltyNumerator / - kTransferTileInstanceSmallRectPenaltyDenominator; - adaptive_soft_limit_bytes = - std::max(penalized_soft_limit_bytes, kAlignment * 64); - } - adaptive_soft_limit_bytes = std::min(adaptive_soft_limit_bytes, - kTransferTileInstanceBufferMaxBytes); - if (total_instance_bytes > adaptive_soft_limit_bytes) { - transfer_tile_instance_adaptive_cutoff_hit = true; - transfer_tile_instance_adaptive_candidate_bytes = total_instance_bytes; - transfer_tile_instance_adaptive_limit_bytes = adaptive_soft_limit_bytes; - transfer_tile_instance_adaptive_rect_count = - uint32_t(build_infos.size()); - return false; - } - - size_t current_frame_instance_offset = 0; - if (transfer_tile_instance_buffer_frame_id_ == frame_id_) { - current_frame_instance_offset = - xe::align(transfer_tile_instance_buffer_offset_, kAlignment); - } - size_t near_cap_threshold_by_percent = - kTransferTileInstanceBufferMaxBytes * - kTransferTileInstanceNearCapUsagePercent / 100; - size_t near_cap_threshold_by_reserve = 0; - if (kTransferTileInstanceBufferMaxBytes > - kTransferTileInstanceNearCapReserveBytes) { - near_cap_threshold_by_reserve = - kTransferTileInstanceBufferMaxBytes - - kTransferTileInstanceNearCapReserveBytes; - } - size_t near_cap_threshold_bytes = std::min(near_cap_threshold_by_percent, - near_cap_threshold_by_reserve); - size_t projected_instance_bytes = current_frame_instance_offset; - if (projected_instance_bytes > - kTransferTileInstanceBufferMaxBytes - total_instance_bytes || - projected_instance_bytes + total_instance_bytes > - near_cap_threshold_bytes) { - transfer_tile_instance_predictive_cutoff_hit = true; - transfer_tile_instance_predictive_used_bytes = - current_frame_instance_offset; - transfer_tile_instance_predictive_candidate_bytes = - total_instance_bytes; - transfer_tile_instance_predictive_threshold_bytes = - near_cap_threshold_bytes; - return false; - } - - MTL::Buffer* buffer = nullptr; - size_t buffer_base_offset = 0; - if (!allocate_instance_buffer(total_instance_bytes, buffer, - buffer_base_offset)) { - return false; - } - - uint8_t* base_ptr = - reinterpret_cast(buffer->contents()) + buffer_base_offset; - uint32_t source_pitch_tiles = constants.address.source_pitch; - uint32_t source_tile_width_pixels = - tile_width_samples >> - ((constants.source_is_64bpp != 0u) + - (constants.source_msaa_samples >= 4u ? 1u : 0u)); - uint32_t source_tile_height_pixels = - tile_height_samples >> - (constants.source_msaa_samples >= 2u ? 1u : 0u); - uint32_t host_tile_width_pixels = - tile_width_samples >> - (constants.host_depth_source_msaa_samples >= 4u ? 1u : 0u); - uint32_t host_tile_height_pixels = - tile_height_samples >> - (constants.host_depth_source_msaa_samples >= 2u ? 1u : 0u); - - for (const auto& info : build_infos) { - uint8_t* batch_ptr = base_ptr + info.batch.buffer_offset; - auto* instances = reinterpret_cast(batch_ptr); - uint32_t instance_index = 0; - for (uint32_t tile_y = info.tile_y_start; tile_y <= info.tile_y_end; - ++tile_y) { - uint32_t row_base = tile_y * constants.address.dest_pitch; - float origin_y = float(tile_y * constants.dest_tile_height_pixels); - for (uint32_t tile_x = info.tile_x_start; tile_x <= info.tile_x_end; - ++tile_x) { - TransferTileInstance& instance = instances[instance_index++]; - instance.origin_x = - float(tile_x * constants.dest_tile_width_pixels); - instance.origin_y = origin_y; - instance.tile_index = row_base + tile_x; - uint32_t dest_tile_index = instance.tile_index; - uint32_t source_tile_index = - uint32_t(int32_t(dest_tile_index) + - constants.address.source_to_dest) & - (xenos::kEdramTileCount - 1u); - uint32_t source_tile_index_y = 0u; - uint32_t source_tile_index_x = 0u; - if (source_pitch_tiles) { - source_tile_index_y = source_tile_index / source_pitch_tiles; - source_tile_index_x = - source_tile_index - source_tile_index_y * source_pitch_tiles; - } - instance.source_base_x = - source_tile_index_x * source_tile_width_pixels; - instance.source_base_y = - source_tile_index_y * source_tile_height_pixels; - instance.host_base_x = 0; - instance.host_base_y = 0; - if (uses_host_depth && !host_depth_is_copy) { - uint32_t host_pitch_tiles = - constants.host_depth_address.source_pitch; - uint32_t host_tile_index = - uint32_t(int32_t(dest_tile_index) + - constants.host_depth_address.source_to_dest) & - (xenos::kEdramTileCount - 1u); - uint32_t host_tile_index_y = 0u; - uint32_t host_tile_index_x = 0u; - if (host_pitch_tiles) { - host_tile_index_y = host_tile_index / host_pitch_tiles; - host_tile_index_x = - host_tile_index - host_tile_index_y * host_pitch_tiles; - } - instance.host_base_x = host_tile_index_x * host_tile_width_pixels; - instance.host_base_y = - host_tile_index_y * host_tile_height_pixels; - } - } - } - TransferTileBatch batch = info.batch; - batch.buffer = buffer; - batch.buffer_offset = buffer_base_offset + info.batch.buffer_offset; - out_batches.push_back(batch); - } - - if (out_batches.empty()) { - return false; - } - return true; - }; - - auto build_rect_instance_stream = - [&](const Transfer::Rectangle* rectangles, uint32_t rectangle_count, - MTL::Buffer*& out_buffer, size_t& out_buffer_offset, - uint32_t& out_instance_count) -> bool { - out_buffer = nullptr; - out_buffer_offset = 0; - out_instance_count = 0; - if (!rectangles || !rectangle_count) { - return false; - } - size_t buffer_size = - size_t(rectangle_count) * sizeof(TransferRectInstance); - if (!buffer_size) { - return false; - } - MTL::Buffer* buffer = nullptr; - size_t buffer_offset = 0; - if (!allocate_instance_buffer(buffer_size, buffer, buffer_offset)) { - return false; - } - if (!buffer) { - return false; - } - auto* instances = reinterpret_cast( - reinterpret_cast(buffer->contents()) + buffer_offset); - if (!instances) { - return false; - } - uint32_t instance_count = 0; - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - uint32_t scaled_x = 0; - uint32_t scaled_y = 0; - uint32_t scaled_width = 0; - uint32_t scaled_height = 0; - if (!get_scaled_rect(rectangles[rect_index], scaled_x, scaled_y, - scaled_width, scaled_height)) { - continue; - } - if (!scaled_width || !scaled_height) { - continue; - } - TransferRectInstance& instance = instances[instance_count++]; - instance.origin_x = float(scaled_x); - instance.origin_y = float(scaled_y); - instance.size_x = float(scaled_width); - instance.size_y = float(scaled_height); - } - if (!instance_count) { - return false; - } - out_buffer = buffer; - out_buffer_offset = buffer_offset; - out_instance_count = instance_count; - return true; - }; - - std::vector filtered_transfers; + } + std::vector filtered_transfer_plan_indices; bool used_blit = false; MTL::BlitCommandEncoder* blit_encoder = nullptr; auto ensure_blit_encoder = [&]() -> MTL::BlitCommandEncoder* { if (!blit_encoder) { + EndSharedMemoryUploadBlitEncoderForCommandBuffer(command_processor_, + cmd); blit_encoder = cmd->blitCommandEncoder(); + if (blit_encoder) { + blit_encoder->setLabel(NS::String::string( + "XeniaRTTransferBlitEncoder", NS::UTF8StringEncoding)); + } } return blit_encoder; }; // Fast path: when source/dest share compatible EDRAM layout and format, // use a blit instead of shader-based transfers. - if (!transfers.empty()) { - auto try_blit_transfer = [&](const Transfer& transfer) -> bool { + if (!use_active_render_encoder && target_transfer_rectangles && + !target_transfer_rectangles->empty()) { + auto try_blit_transfer = [&](uint32_t transfer_plan_index) -> bool { + const TransferRectanglePlan& transfer_plan = + (*target_transfer_rectangles)[transfer_plan_index]; + const Transfer& transfer = transfers[transfer_plan.transfer_index]; auto* source_rt = static_cast(transfer.source); if (!source_rt || transfer.host_depth_source) { return false; @@ -5588,7 +4595,9 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } bool base_tiles_match = source_key.base_tiles == dest_key.base_tiles; - if (dest_is_depth && !base_tiles_match) { + // Different-base color transfers need shader transfer address + // semantics. + if (!base_tiles_match) { return false; } @@ -5607,18 +4616,12 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } if (source_texture->pixelFormat() != dest_texture->pixelFormat() || source_texture->sampleCount() != dest_texture->sampleCount() || - source_texture->sampleCount() != 1 || source_texture->width() != dest_width || source_texture->height() != dest_height) { return false; } - Transfer::Rectangle rectangles[Transfer::kMaxRectanglesWithCutout]; - uint32_t rectangle_count = transfer.GetRectangles( - dest_key.base_tiles, dest_key.pitch_tiles_at_32bpp, - dest_key.msaa_samples, IsKey64bpp(dest_key), rectangles, - resolve_clear_rectangle); - if (!rectangle_count) { + if (!transfer_plan.rectangle_count) { return false; } @@ -5627,161 +4630,29 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( return false; } - if (base_tiles_match || dest_is_depth) { - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - uint32_t scaled_x = 0; - uint32_t scaled_y = 0; - uint32_t scaled_width = 0; - uint32_t scaled_height = 0; - if (!get_scaled_rect(rectangles[rect_index], scaled_x, scaled_y, - scaled_width, scaled_height)) { - continue; - } - MTL::Origin origin = MTL::Origin::Make(scaled_x, scaled_y, 0); - MTL::Size size = MTL::Size::Make(scaled_width, scaled_height, 1); - blit->copyFromTexture(source_texture, 0, 0, origin, size, - dest_texture, 0, 0, origin); - } - } else { - // Base-tile offset blit (color only, non-MSAA, tile-aligned). - uint32_t pitch_tiles = dest_key.pitch_tiles_at_32bpp; - if (!pitch_tiles) { - return false; - } - uint32_t tile_width_pixels = - tile_width_samples >> - ((IsKey64bpp(dest_key) ? 1u : 0u) + - uint32_t(dest_key.msaa_samples >= xenos::MsaaSamples::k4X)); - uint32_t tile_height_pixels = - tile_height_samples >> - uint32_t(dest_key.msaa_samples >= xenos::MsaaSamples::k2X); - if (!tile_width_pixels || !tile_height_pixels) { - return false; - } - uint32_t delta_tiles = (dest_key.base_tiles - source_key.base_tiles) & - (xenos::kEdramTileCount - 1u); - uint32_t delta_rows = delta_tiles / pitch_tiles; - uint32_t delta_x = delta_tiles % pitch_tiles; - uint32_t total_rows = - (xenos::kEdramTileCount + pitch_tiles - 1u) / pitch_tiles; - - struct ScaledRect { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; - }; - std::vector scaled_rects; - scaled_rects.reserve(rectangle_count); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - uint32_t scaled_x = 0; - uint32_t scaled_y = 0; - uint32_t scaled_width = 0; - uint32_t scaled_height = 0; - if (!get_scaled_rect(rectangles[rect_index], scaled_x, scaled_y, - scaled_width, scaled_height)) { - continue; - } - if ((scaled_x % tile_width_pixels) || - (scaled_y % tile_height_pixels) || - (scaled_width % tile_width_pixels) || - (scaled_height % tile_height_pixels)) { - return false; - } - if (!scaled_width || !scaled_height) { - continue; - } - scaled_rects.push_back( - {scaled_x, scaled_y, scaled_width, scaled_height}); - } - if (scaled_rects.empty()) { - return false; - } - - for (const auto& rect : scaled_rects) { - uint32_t tile_x = rect.x / tile_width_pixels; - uint32_t tile_y = rect.y / tile_height_pixels; - uint32_t tiles_w = rect.width / tile_width_pixels; - uint32_t tiles_h = rect.height / tile_height_pixels; - if (!tiles_w || !tiles_h) { - continue; - } - - uint32_t source_tile_x_base = tile_x + delta_x; - uint32_t source_tile_x = source_tile_x_base % pitch_tiles; - uint32_t source_tile_y = - tile_y + delta_rows + (source_tile_x_base / pitch_tiles); - if (source_tile_y >= total_rows) { - source_tile_y %= total_rows; - } - - uint32_t rows_before_wrap = - std::min(tiles_h, total_rows - source_tile_y); - uint32_t rows_after_wrap = tiles_h - rows_before_wrap; - - uint32_t tiles_before_wrap_x = - (source_tile_x + tiles_w <= pitch_tiles) - ? tiles_w - : (pitch_tiles - source_tile_x); - uint32_t tiles_after_wrap_x = tiles_w - tiles_before_wrap_x; - - for (uint32_t wrap_y = 0; wrap_y <= (rows_after_wrap ? 1u : 0u); - ++wrap_y) { - uint32_t y_offset_tiles = wrap_y ? rows_before_wrap : 0u; - uint32_t rows = wrap_y ? rows_after_wrap : rows_before_wrap; - if (!rows) { - continue; - } - uint32_t dest_y_pixels = - rect.y + y_offset_tiles * tile_height_pixels; - uint32_t source_y_tiles = wrap_y ? 0u : source_tile_y; - uint32_t source_y_pixels = source_y_tiles * tile_height_pixels; - uint32_t height_pixels = rows * tile_height_pixels; - - // X segment 0. - if (tiles_before_wrap_x) { - uint32_t dest_x_pixels = rect.x; - uint32_t source_x_pixels = source_tile_x * tile_width_pixels; - uint32_t width_pixels = tiles_before_wrap_x * tile_width_pixels; - MTL::Origin src_origin = - MTL::Origin::Make(source_x_pixels, source_y_pixels, 0); - MTL::Origin dst_origin = - MTL::Origin::Make(dest_x_pixels, dest_y_pixels, 0); - MTL::Size size = - MTL::Size::Make(width_pixels, height_pixels, 1); - blit->copyFromTexture(source_texture, 0, 0, src_origin, size, - dest_texture, 0, 0, dst_origin); - } - - // X segment 1 (wrap). - if (tiles_after_wrap_x) { - uint32_t dest_x_pixels = - rect.x + tiles_before_wrap_x * tile_width_pixels; - uint32_t source_x_pixels = 0; - uint32_t width_pixels = tiles_after_wrap_x * tile_width_pixels; - MTL::Origin src_origin = - MTL::Origin::Make(source_x_pixels, source_y_pixels, 0); - MTL::Origin dst_origin = - MTL::Origin::Make(dest_x_pixels, dest_y_pixels, 0); - MTL::Size size = - MTL::Size::Make(width_pixels, height_pixels, 1); - blit->copyFromTexture(source_texture, 0, 0, src_origin, size, - dest_texture, 0, 0, dst_origin); - } - } + for (uint32_t rect_index = 0; + rect_index < transfer_plan.rectangle_count; ++rect_index) { + uint32_t scaled_x = 0; + uint32_t scaled_y = 0; + uint32_t scaled_width = 0; + uint32_t scaled_height = 0; + if (!get_scaled_rect(transfer_plan.rectangles[rect_index], scaled_x, + scaled_y, scaled_width, scaled_height)) { + continue; } + MTL::Origin origin = MTL::Origin::Make(scaled_x, scaled_y, 0); + MTL::Size size = MTL::Size::Make(scaled_width, scaled_height, 1); + blit->copyFromTexture(source_texture, 0, 0, origin, size, + dest_texture, 0, 0, origin); } used_blit = true; - any_transfers_done = true; return true; }; - for (const Transfer& transfer : transfers) { - if (!try_blit_transfer(transfer)) { - filtered_transfers.push_back(transfer); + for (uint32_t transfer_plan_index : all_transfer_plan_indices) { + if (!try_blit_transfer(transfer_plan_index)) { + filtered_transfer_plan_indices.push_back(transfer_plan_index); } } } @@ -5790,9 +4661,8 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( blit_encoder->endEncoding(); } - const bool disable_transfer_shaders = false; - const std::vector& transfers_for_shaders = - used_blit ? filtered_transfers : transfers; + const std::vector& transfer_plan_indices_for_shaders = + used_blit ? filtered_transfer_plan_indices : all_transfer_plan_indices; auto is_full_target_rectangle = [&](const Transfer::Rectangle& rect) -> bool { @@ -5809,16 +4679,15 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( }; auto transfers_fully_overwrite_target = [&]() -> bool { - if (transfers_for_shaders.empty()) { + if (transfer_plan_indices_for_shaders.empty() || + !target_transfer_rectangles) { return false; } - for (const Transfer& transfer : transfers_for_shaders) { - Transfer::Rectangle rectangles[Transfer::kMaxRectanglesWithCutout]; - uint32_t rectangle_count = transfer.GetRectangles( - dest_key.base_tiles, dest_key.GetPitchTiles(), - dest_key.msaa_samples, IsKey64bpp(dest_key), rectangles, - resolve_clear_rectangle); - if (rectangle_count != 1 || !is_full_target_rectangle(rectangles[0])) { + for (uint32_t transfer_plan_index : transfer_plan_indices_for_shaders) { + const TransferRectanglePlan& transfer_plan = + (*target_transfer_rectangles)[transfer_plan_index]; + if (transfer_plan.rectangle_count != 1 || + !is_full_target_rectangle(transfer_plan.rectangles[0])) { return false; } } @@ -5935,6 +4804,21 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } } } + if (resolve_clear_via_load_action && + transfer_plan_indices_for_shaders.empty()) { + resolve_clear_via_load_action = false; + } + + // Depth transfers that fully overwrite the destination still need a clean + // stencil surface before the per-bit stencil draws run. A load-action + // clear is cheaper than a separate clear draw in that case. + bool transfer_stencil_clear_via_load_action = + dest_is_depth && !transfer_plan_indices_for_shaders.empty() && + transfers_fully_overwrite_target() && !resolve_clear_via_load_action; + if (transfer_stencil_clear_via_load_action) { + resolve_clear_depth = 0.0; + resolve_clear_stencil = 0; + } // Prefer DontCare on transfer-pass loads only when destination contents are // provably fully overwritten by this pass. @@ -5943,11 +4827,13 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( !resolve_clear_via_load_action) { transfer_pass_load_dontcare = true; } - if (!transfer_pass_load_dontcare && !resolve_clear_needed) { + if (!transfer_pass_load_dontcare && !resolve_clear_needed && + !transfer_stencil_clear_via_load_action) { transfer_pass_load_dontcare = transfers_fully_overwrite_target(); } MTL::LoadAction transfer_load_action = MTL::LoadActionLoad; - if (resolve_clear_via_load_action) { + if (resolve_clear_via_load_action || + transfer_stencil_clear_via_load_action) { transfer_load_action = MTL::LoadActionClear; } else if (transfer_pass_load_dontcare) { transfer_load_action = MTL::LoadActionDontCare; @@ -5958,6 +4844,10 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( if (transfer_encoder) { return transfer_encoder; } + if (use_active_render_encoder) { + transfer_encoder = active_render_encoder; + return transfer_encoder; + } MTL::RenderPassDescriptor* rp = MTL::RenderPassDescriptor::renderPassDescriptor(); if (dest_is_depth) { @@ -5987,28 +4877,29 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( ca->setClearColor(resolve_clear_color); } } + EndSharedMemoryUploadBlitEncoderForCommandBuffer(command_processor_, cmd); transfer_encoder = cmd->renderCommandEncoder(rp); + if (transfer_encoder) { + transfer_encoder->setLabel( + NS::String::string("XeniaTransferEncoder", NS::UTF8StringEncoding)); + } return transfer_encoder; }; - if (!transfers_for_shaders.empty() && disable_transfer_shaders) { - static uint32_t transfer_shader_skip_log_count = 0; - if (transfer_shader_skip_log_count < 8) { - ++transfer_shader_skip_log_count; - XELOGW( - "MetalRenderTargetCache::PerformTransfersAndResolveClears: " - "transfer shaders disabled; skipping {} transfers for RT {}", - transfers_for_shaders.size(), i); - } - } else if (!transfers_for_shaders.empty()) { + if (!transfer_plan_indices_for_shaders.empty() && + target_transfer_rectangles) { bool need_stencil_bit_draws = dest_is_depth; - bool stencil_clear_needed = need_stencil_bit_draws; + bool stencil_clear_needed = + need_stencil_bit_draws && !transfer_stencil_clear_via_load_action; transfer_invocations_.clear(); - transfer_invocations_.reserve(transfers_for_shaders.size() * + transfer_invocations_.reserve(transfer_plan_indices_for_shaders.size() * (need_stencil_bit_draws ? 2 : 1)); - for (const Transfer& transfer : transfers_for_shaders) { + for (uint32_t transfer_plan_index : transfer_plan_indices_for_shaders) { + const TransferRectanglePlan& transfer_plan = + (*target_transfer_rectangles)[transfer_plan_index]; + const Transfer& transfer = transfers[transfer_plan.transfer_index]; if (transfer.source) { auto* source = static_cast(transfer.source); source->SetTemporarySortIndex(UINT32_MAX); @@ -6029,7 +4920,10 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( for (uint32_t pass = 0; pass <= uint32_t(need_stencil_bit_draws); ++pass) { - for (const Transfer& transfer : transfers_for_shaders) { + for (uint32_t transfer_plan_index : transfer_plan_indices_for_shaders) { + const TransferRectanglePlan& transfer_plan = + (*target_transfer_rectangles)[transfer_plan_index]; + const Transfer& transfer = transfers[transfer_plan.transfer_index]; if (!transfer.source) { continue; } @@ -6042,67 +4936,17 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( ensure_sort_index(host_depth_rt); RenderTargetKey source_key = source_rt->key(); - TransferShaderKey shader_key = {}; - shader_key.source_msaa_samples = source_key.msaa_samples; - shader_key.dest_msaa_samples = dest_key.msaa_samples; - shader_key.source_resource_format = source_key.resource_format; - shader_key.dest_resource_format = dest_key.resource_format; - - if (pass) { - shader_key.mode = source_key.is_depth - ? TransferMode::kDepthToStencilBit - : TransferMode::kColorToStencilBit; - shader_key.host_depth_source_msaa_samples = xenos::MsaaSamples::k1X; - shader_key.host_depth_source_is_copy = 0; - } else { - if (dest_is_depth) { - if (host_depth_rt) { - bool host_depth_is_copy = host_depth_rt == dest_metal_rt; - shader_key.mode = source_key.is_depth - ? TransferMode::kDepthAndHostDepthToDepth - : TransferMode::kColorAndHostDepthToDepth; - shader_key.host_depth_source_is_copy = - host_depth_is_copy ? 1 : 0; - shader_key.host_depth_source_msaa_samples = - host_depth_is_copy ? xenos::MsaaSamples::k1X - : host_depth_rt->key().msaa_samples; - } else { - shader_key.mode = source_key.is_depth - ? TransferMode::kDepthToDepth - : TransferMode::kColorToDepth; - shader_key.host_depth_source_msaa_samples = - xenos::MsaaSamples::k1X; - shader_key.host_depth_source_is_copy = 0; - } - } else { - shader_key.mode = source_key.is_depth - ? TransferMode::kDepthToColor - : TransferMode::kColorToColor; - shader_key.host_depth_source_msaa_samples = - xenos::MsaaSamples::k1X; - shader_key.host_depth_source_is_copy = 0; - } + bool host_depth_is_copy = host_depth_rt == dest_metal_rt; + RenderTargetKey host_depth_key; + if (host_depth_rt) { + host_depth_key = host_depth_rt->key(); } + TransferShaderKey shader_key = GetTransferShaderKey( + source_key, dest_key, host_depth_rt ? &host_depth_key : nullptr, + host_depth_is_copy, pass != 0, transfer_use_sample_id_default); - const TransferModeInfo& mode_info = - kTransferModeInfos[size_t(shader_key.mode)]; - bool transfer_use_sample_id = transfer_use_sample_id_default; - if (transfer_use_sample_id) { - bool source_is_multisample = - source_key.msaa_samples != xenos::MsaaSamples::k1X; - bool host_depth_is_multisample = - mode_info.uses_host_depth && - shader_key.host_depth_source_msaa_samples != - xenos::MsaaSamples::k1X && - !shader_key.host_depth_source_is_copy; - if (!source_is_multisample && !host_depth_is_multisample) { - transfer_use_sample_id = false; - } - } - shader_key.dest_sample_id_from_sample = - transfer_use_sample_id ? 1u : 0u; - - transfer_invocations_.emplace_back(transfer, shader_key); + transfer_invocations_.emplace_back(transfer, shader_key, + &transfer_plan); if (pass) { transfer_invocations_.back().transfer.host_depth_source = nullptr; } @@ -6113,8 +4957,17 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( if (stencil_clear_needed) { MTL::RenderPipelineState* clear_pipeline = - GetOrCreateTransferClearPipeline(dest_pixel_format, false, true, - dest_sample_count); + GetOrCreateTransferClearPipeline( + dest_pixel_format, false, true, dest_sample_count, 0, + use_active_render_encoder + ? &active_attachment_formats.color_attachment_formats + : nullptr, + use_active_render_encoder + ? active_attachment_formats.depth_attachment_format + : MTL::PixelFormatInvalid, + use_active_render_encoder + ? active_attachment_formats.stencil_attachment_format + : MTL::PixelFormatInvalid); MTL::DepthStencilState* stencil_clear_state = GetTransferStencilClearState(); if (clear_pipeline && stencil_clear_state) { @@ -6123,19 +4976,25 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( TransferClearDepthConstants constants = {}; constants.depth = 0.0f; encoder->setRenderPipelineState(clear_pipeline); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationPipeline); encoder->setDepthStencilState(stencil_clear_state); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationDepthStencil); encoder->setStencilReferenceValue(0); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationStencilReference); encoder->setFragmentBytes(&constants, sizeof(constants), 0); - for (const Transfer& transfer : transfers_for_shaders) { - Transfer::Rectangle - rectangles[Transfer::kMaxRectanglesWithCutout]; - uint32_t rectangle_count = transfer.GetRectangles( - dest_key.base_tiles, dest_key.GetPitchTiles(), - dest_key.msaa_samples, IsKey64bpp(dest_key), rectangles, - resolve_clear_rectangle); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - if (!set_rect_viewport(encoder, rectangles[rect_index])) { + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationFragmentSlot0); + for (uint32_t transfer_plan_index : + transfer_plan_indices_for_shaders) { + const TransferRectanglePlan& transfer_plan = + (*target_transfer_rectangles)[transfer_plan_index]; + for (uint32_t rect_index = 0; + rect_index < transfer_plan.rectangle_count; ++rect_index) { + if (!set_rect_viewport(encoder, + transfer_plan.rectangles[rect_index])) { continue; } encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, @@ -6159,22 +5018,21 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( uint32_t last_transfer_stencil_reference = 0; bool transfer_constants_valid = false; TransferShaderConstants last_transfer_constants = {}; - enum class TransferVertexSlot1Binding { kNone, kBuffer, kBytes }; - TransferVertexSlot1Binding last_transfer_vertex_slot_1_binding = - TransferVertexSlot1Binding::kNone; - MTL::Buffer* last_transfer_vertex_buffer_1 = nullptr; - size_t last_transfer_vertex_buffer_1_offset = 0; bool last_transfer_vertex_bytes_1_valid = false; TransferRectInstance last_transfer_vertex_bytes_1 = {}; auto bind_transfer_pipeline = [&](MTL::RenderPipelineState* pipeline) { if (last_transfer_pipeline != pipeline) { encoder->setRenderPipelineState(pipeline); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationPipeline); last_transfer_pipeline = pipeline; } }; auto bind_transfer_depth_state = [&](MTL::DepthStencilState* state) { if (last_transfer_depth_state != state) { encoder->setDepthStencilState(state); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationDepthStencil); last_transfer_depth_state = state; } }; @@ -6185,12 +5043,16 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } if (last_transfer_fragment_textures[index] != texture) { encoder->setFragmentTexture(texture, index); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationFragmentTextures); last_transfer_fragment_textures[index] = texture; } }; auto bind_transfer_fragment_buffer_1 = [&](MTL::Buffer* buffer) { if (last_transfer_fragment_buffer_1 != buffer) { encoder->setFragmentBuffer(buffer, 0, 1); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationFragmentSlot1); last_transfer_fragment_buffer_1 = buffer; } }; @@ -6198,6 +5060,8 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( if (!last_transfer_stencil_reference_valid || last_transfer_stencil_reference != reference) { encoder->setStencilReferenceValue(reference); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationStencilReference); last_transfer_stencil_reference = reference; last_transfer_stencil_reference_valid = true; } @@ -6209,6 +5073,9 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( sizeof(constants)) != 0) { encoder->setVertexBytes(&constants, sizeof(constants), 0); encoder->setFragmentBytes(&constants, sizeof(constants), 0); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationVertexSlot0 | + kDrawPassTransferEncoderMutationFragmentSlot0); last_transfer_constants = constants; transfer_constants_valid = true; } @@ -6220,37 +5087,21 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( last_transfer_scissor.width != scissor.width || last_transfer_scissor.height != scissor.height) { encoder->setScissorRect(scissor); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationScissor); last_transfer_scissor = scissor; last_transfer_scissor_valid = true; } }; - auto bind_transfer_vertex_buffer_1 = [&](MTL::Buffer* buffer, - size_t offset) { - if (last_transfer_vertex_slot_1_binding != - TransferVertexSlot1Binding::kBuffer || - last_transfer_vertex_buffer_1 != buffer || - last_transfer_vertex_buffer_1_offset != offset) { - encoder->setVertexBuffer(buffer, offset, 1); - last_transfer_vertex_slot_1_binding = - TransferVertexSlot1Binding::kBuffer; - last_transfer_vertex_buffer_1 = buffer; - last_transfer_vertex_buffer_1_offset = offset; - last_transfer_vertex_bytes_1_valid = false; - } - }; auto bind_transfer_vertex_bytes_1 = [&](const TransferRectInstance& rect_instance) { - if (last_transfer_vertex_slot_1_binding != - TransferVertexSlot1Binding::kBytes || - !last_transfer_vertex_bytes_1_valid || + if (!last_transfer_vertex_bytes_1_valid || std::memcmp(&last_transfer_vertex_bytes_1, &rect_instance, sizeof(rect_instance)) != 0) { encoder->setVertexBytes(&rect_instance, sizeof(rect_instance), 1); - last_transfer_vertex_slot_1_binding = - TransferVertexSlot1Binding::kBytes; - last_transfer_vertex_buffer_1 = nullptr; - last_transfer_vertex_buffer_1_offset = 0; + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationVertexSlot1); last_transfer_vertex_bytes_1 = rect_instance; last_transfer_vertex_bytes_1_valid = true; } @@ -6265,10 +5116,8 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( rect_instances, size_t(rect_instance_count) * sizeof(TransferRectInstance), 1); - last_transfer_vertex_slot_1_binding = - TransferVertexSlot1Binding::kBytes; - last_transfer_vertex_buffer_1 = nullptr; - last_transfer_vertex_buffer_1_offset = 0; + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationVertexSlot1); last_transfer_vertex_bytes_1_valid = false; }; auto set_full_transfer_viewport_scissor = [&]() { @@ -6281,6 +5130,8 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( vp.znear = 0.0; vp.zfar = 1.0; encoder->setViewport(vp); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationViewport); transfer_viewport_full_set = true; } MTL::ScissorRect scissor; @@ -6308,27 +5159,42 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( Transfer::kMaxRectanglesWithCutout); for (size_t merged_index = invocation_index; merged_index < merged_invocation_end; ++merged_index) { - Transfer::Rectangle rectangles[Transfer::kMaxRectanglesWithCutout]; - uint32_t rectangle_count = - transfer_invocations_[merged_index].transfer.GetRectangles( - dest_key.base_tiles, dest_key.GetPitchTiles(), - dest_key.msaa_samples, IsKey64bpp(dest_key), rectangles, - resolve_clear_rectangle); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - merged_transfer_rectangles.push_back(rectangles[rect_index]); + const TransferRectanglePlan* rectangle_plan = + transfer_invocations_[merged_index].rectangle_plan; + if (!rectangle_plan) { + continue; + } + for (uint32_t rect_index = 0; + rect_index < rectangle_plan->rectangle_count; ++rect_index) { + merged_transfer_rectangles.push_back( + rectangle_plan->rectangles[rect_index]); } } invocation_index = merged_invocation_end; if (merged_transfer_rectangles.empty()) { continue; } + uint64_t merged_transfer_pixels = 0; + for (const Transfer::Rectangle& rect : merged_transfer_rectangles) { + uint32_t scaled_x = 0; + uint32_t scaled_y = 0; + uint32_t scaled_width = 0; + uint32_t scaled_height = 0; + if (get_scaled_rect(rect, scaled_x, scaled_y, scaled_width, + scaled_height)) { + merged_transfer_pixels += + uint64_t(scaled_width) * uint64_t(scaled_height); + } + } const Transfer& transfer = invocation.transfer; const TransferShaderKey& shader_key = invocation.shader_key; const TransferModeInfo& mode_info = kTransferModeInfos[size_t(shader_key.mode)]; bool is_stencil_bit = mode_info.output == TransferOutput::kStencilBit; + bool needs_source_depth = + !mode_info.source_is_color && + mode_info.output != TransferOutput::kStencilBit; bool needs_source_stencil = !mode_info.source_is_color && (mode_info.output == TransferOutput::kColor || @@ -6370,11 +5236,13 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( "Transfer source must use ownership pixel format"); bind_transfer_fragment_texture(0, source_texture); } else { - MTL::Texture* depth_texture = source_rt->texture(); - if (!depth_texture) { - continue; + if (needs_source_depth) { + MTL::Texture* depth_texture = source_rt->texture(); + if (!depth_texture) { + continue; + } + bind_transfer_fragment_texture(0, depth_texture); } - bind_transfer_fragment_texture(0, depth_texture); if (needs_source_stencil) { MTL::Texture* stencil_texture = GetStencilTextureView(source_rt); if (!stencil_texture) { @@ -6447,11 +5315,11 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( constants.dest_is_depth = dest_key.is_depth ? 1 : 0; constants.source_is_uint = source_is_uint ? 1 : 0; constants.dest_is_uint = dest_is_uint ? 1 : 0; - // Don't treat gamma-as-unorm16 as 64bpp for source coordinate math. + // Don't treat gamma-as-unorm16 as 64bpp for guest coordinate math. // It's 64bpp in storage, but represents a single pixel, not two // packed 32bpp halves. constants.source_is_64bpp = source_key.Is64bpp() ? 1 : 0; - constants.dest_is_64bpp = IsKey64bpp(dest_key) ? 1 : 0; + constants.dest_is_64bpp = dest_key.Is64bpp() ? 1 : 0; constants.source_msaa_samples = MsaaSamplesToCount(source_key.msaa_samples); constants.dest_msaa_samples = @@ -6502,90 +5370,62 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( const uint32_t rectangle_count = uint32_t(merged_transfer_rectangles.size()); - std::vector tile_batches; - MTL::Buffer* rect_instance_buffer = nullptr; - size_t rect_instance_buffer_offset = 0; - uint32_t rect_instance_count = 0; std::vector rect_instance_fallback; - bool use_tile_instancing = false; - if (::cvars::metal_transfer_tile_instancing) { - use_tile_instancing = build_tile_batches( - merged_transfer_rectangles.data(), rectangle_count, constants, - mode_info.uses_host_depth, - shader_key.host_depth_source_is_copy != 0, tile_batches); - if (!use_tile_instancing && - transfer_tile_instance_predictive_cutoff_hit) { - static uint64_t last_tile_predictive_log_frame = 0; - if (last_tile_predictive_log_frame != frame_id_) { - XELOGI( - "Metal transfer tile instancing fallback: predicted " - "frame usage {} KiB + {} KiB near cap {} KiB", - transfer_tile_instance_predictive_used_bytes >> 10, - transfer_tile_instance_predictive_candidate_bytes >> 10, - transfer_tile_instance_predictive_threshold_bytes >> 10); - last_tile_predictive_log_frame = frame_id_; - } - transfer_tile_instance_predictive_cutoff_hit = false; - } - if (!use_tile_instancing && - transfer_tile_instance_adaptive_cutoff_hit) { - static uint64_t last_tile_adaptive_log_frame = 0; - if (last_tile_adaptive_log_frame != frame_id_) { - XELOGI( - "Metal transfer tile instancing fallback: adaptive cutoff " - "{} KiB > {} KiB (rects={})", - transfer_tile_instance_adaptive_candidate_bytes >> 10, - transfer_tile_instance_adaptive_limit_bytes >> 10, - transfer_tile_instance_adaptive_rect_count); - last_tile_adaptive_log_frame = frame_id_; - } - transfer_tile_instance_adaptive_cutoff_hit = false; - } - if (!use_tile_instancing && transfer_tile_instance_budget_hit) { - static uint64_t last_tile_budget_log_frame = 0; - if (last_tile_budget_log_frame != frame_id_) { - XELOGW( - "Metal transfer tile instancing fallback: exceeded {} MiB " - "instance-buffer budget this frame", - kTransferTileInstanceBufferMaxBytes >> 20); - last_tile_budget_log_frame = frame_id_; - } - transfer_tile_instance_budget_hit = false; - } - } - if (!use_tile_instancing) { - if (rectangle_count > 1) { - build_rect_instance_stream(merged_transfer_rectangles.data(), - rectangle_count, rect_instance_buffer, - rect_instance_buffer_offset, - rect_instance_count); - } - if (!rect_instance_buffer || !rect_instance_count) { - rect_instance_fallback.reserve(rectangle_count); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - uint32_t scaled_x = 0; - uint32_t scaled_y = 0; - uint32_t scaled_width = 0; - uint32_t scaled_height = 0; - if (!get_scaled_rect(merged_transfer_rectangles[rect_index], - scaled_x, scaled_y, scaled_width, - scaled_height) || - !scaled_width || !scaled_height) { - continue; - } - TransferRectInstance rect_instance = {}; - rect_instance.origin_x = float(scaled_x); - rect_instance.origin_y = float(scaled_y); - rect_instance.size_x = float(scaled_width); - rect_instance.size_y = float(scaled_height); - rect_instance_fallback.push_back(rect_instance); - } + rect_instance_fallback.reserve(rectangle_count); + for (uint32_t rect_index = 0; rect_index < rectangle_count; + ++rect_index) { + uint32_t scaled_x = 0; + uint32_t scaled_y = 0; + uint32_t scaled_width = 0; + uint32_t scaled_height = 0; + if (!get_scaled_rect(merged_transfer_rectangles[rect_index], + scaled_x, scaled_y, scaled_width, + scaled_height) || + !scaled_width || !scaled_height) { + continue; } + TransferRectInstance rect_instance = {}; + rect_instance.origin_x = float(scaled_x); + rect_instance.origin_y = float(scaled_y); + rect_instance.size_x = float(scaled_width); + rect_instance.size_y = float(scaled_height); + rect_instance_fallback.push_back(rect_instance); } + MTL::DepthStencilState* native_stencil_output_state = + is_stencil_bit && ::cvars::metal_transfer_native_stencil_output + ? GetTransferStencilOutputState() + : nullptr; + bool use_native_stencil_output = + native_stencil_output_state != nullptr; MTL::RenderPipelineState* pipeline = GetOrCreateTransferPipelines( - shader_key, dest_pixel_format, dest_is_uint, use_tile_instancing); + shader_key, dest_pixel_format, dest_is_uint, + use_native_stencil_output, active_color_attachment_index, + use_active_render_encoder + ? &active_attachment_formats.color_attachment_formats + : nullptr, + use_active_render_encoder + ? active_attachment_formats.depth_attachment_format + : MTL::PixelFormatInvalid, + use_active_render_encoder + ? active_attachment_formats.stencil_attachment_format + : MTL::PixelFormatInvalid); + if (!pipeline && use_native_stencil_output) { + use_native_stencil_output = false; + native_stencil_output_state = nullptr; + pipeline = GetOrCreateTransferPipelines( + shader_key, dest_pixel_format, dest_is_uint, false, + active_color_attachment_index, + use_active_render_encoder + ? &active_attachment_formats.color_attachment_formats + : nullptr, + use_active_render_encoder + ? active_attachment_formats.depth_attachment_format + : MTL::PixelFormatInvalid, + use_active_render_encoder + ? active_attachment_formats.stencil_attachment_format + : MTL::PixelFormatInvalid); + } if (!pipeline) { continue; } @@ -6607,69 +5447,62 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( auto draw_transfer = [&](uint32_t sample_id) { constants.dest_sample_id = sample_id; bind_transfer_constants(constants); - if (use_tile_instancing) { - set_full_transfer_viewport_scissor(); - for (const auto& batch : tile_batches) { - bind_transfer_scissor(batch.scissor); - bind_transfer_vertex_buffer_1(batch.buffer, - batch.buffer_offset); - encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, - NS::UInteger(0), NS::UInteger(4), - NS::UInteger(batch.instance_count)); - } - } else { - if (rect_instance_buffer && rect_instance_count) { - set_full_transfer_viewport_scissor(); - bind_transfer_vertex_buffer_1(rect_instance_buffer, - rect_instance_buffer_offset); - encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, - NS::UInteger(0), NS::UInteger(4), - NS::UInteger(rect_instance_count)); - } else if (!rect_instance_fallback.empty()) { - set_full_transfer_viewport_scissor(); - constexpr uint32_t kTransferRectInlineBatchMax = 240; - const TransferRectInstance* rect_instances = - rect_instance_fallback.data(); - uint32_t rect_instances_remaining = - uint32_t(rect_instance_fallback.size()); - while (rect_instances_remaining) { - uint32_t batch_count = std::min(rect_instances_remaining, - kTransferRectInlineBatchMax); - if (batch_count == 1) { - bind_transfer_vertex_bytes_1(*rect_instances); - } else { - bind_transfer_vertex_bytes_1_span(rect_instances, - batch_count); - } - encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, - NS::UInteger(0), NS::UInteger(4), - NS::UInteger(batch_count)); - rect_instances += batch_count; - rect_instances_remaining -= batch_count; - } + if (rect_instance_fallback.empty()) { + return; + } + set_full_transfer_viewport_scissor(); + constexpr uint32_t kTransferRectInlineBatchMax = 240; + const TransferRectInstance* rect_instances = + rect_instance_fallback.data(); + uint32_t rect_instances_remaining = + uint32_t(rect_instance_fallback.size()); + while (rect_instances_remaining) { + uint32_t batch_count = std::min(rect_instances_remaining, + kTransferRectInlineBatchMax); + if (batch_count == 1) { + bind_transfer_vertex_bytes_1(*rect_instances); + } else { + bind_transfer_vertex_bytes_1_span(rect_instances, batch_count); } + encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, + NS::UInteger(0), NS::UInteger(4), + NS::UInteger(batch_count)); + rect_instances += batch_count; + rect_instances_remaining -= batch_count; } }; if (is_stencil_bit) { - for (uint32_t bit = 0; bit < 8; ++bit) { - MTL::DepthStencilState* stencil_state = - GetTransferStencilBitState(bit); - if (!stencil_state) { - continue; + if (use_native_stencil_output) { + if (!native_stencil_output_state) { + use_native_stencil_output = false; + } else { + constants.stencil_mask = 0xFFu; + constants.stencil_clear = 0; + bind_transfer_depth_state(native_stencil_output_state); + bind_transfer_stencil_reference(0); + draw_transfer_samples(draw_transfer); + } + } + if (!use_native_stencil_output) { + for (uint32_t bit = 0; bit < 8; ++bit) { + MTL::DepthStencilState* stencil_state = + GetTransferStencilBitState(bit); + if (!stencil_state) { + continue; + } + constants.stencil_mask = uint32_t(1) << bit; + constants.stencil_clear = 0; + bind_transfer_depth_state(stencil_state); + bind_transfer_stencil_reference(uint32_t(1) << bit); + draw_transfer_samples(draw_transfer); } - constants.stencil_mask = uint32_t(1) << bit; - constants.stencil_clear = 0; - bind_transfer_depth_state(stencil_state); - bind_transfer_stencil_reference(uint32_t(1) << bit); - draw_transfer_samples(draw_transfer); } } else { constants.stencil_mask = 0; constants.stencil_clear = 0; draw_transfer_samples(draw_transfer); } - any_transfers_done = true; } } } @@ -6700,12 +5533,19 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( TransferClearDepthConstants constants = {}; constants.depth = depth_host_clear_value; clear_encoder->setRenderPipelineState(clear_pipeline); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationPipeline); clear_encoder->setDepthStencilState(clear_state); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationDepthStencil); clear_encoder->setStencilReferenceValue(uint32_t(clear_value) & 0xFF); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationStencilReference); clear_encoder->setFragmentBytes(&constants, sizeof(constants), 0); - Transfer::Rectangle clear_rect = *resolve_clear_rectangle; - if (set_rect_viewport(clear_encoder, clear_rect)) { + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationFragmentSlot0); + if (set_rect_viewport(clear_encoder, *resolve_clear_rectangle)) { clear_encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3)); } @@ -6845,7 +5685,11 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( continue; } clear_encoder->setRenderPipelineState(clear_pipeline); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationPipeline); clear_encoder->setDepthStencilState(no_depth_state); + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationDepthStencil); if (clear_use_uint) { clear_encoder->setFragmentBytes(&uint_constants, sizeof(uint_constants), 0); @@ -6853,8 +5697,9 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( clear_encoder->setFragmentBytes(&float_constants, sizeof(float_constants), 0); } - Transfer::Rectangle clear_rect = *resolve_clear_rectangle; - if (set_rect_viewport(clear_encoder, clear_rect)) { + mark_active_encoder_mutation( + kDrawPassTransferEncoderMutationFragmentSlot0); + if (set_rect_viewport(clear_encoder, *resolve_clear_rectangle)) { clear_encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3)); } @@ -6864,28 +5709,26 @@ void MetalRenderTargetCache::PerformTransfersAndResolveClears( } } - if (transfer_encoder) { + if (transfer_encoder && !use_active_render_encoder) { transfer_encoder->endEncoding(); } } + return true; } MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( const TransferShaderKey& key, MTL::PixelFormat dest_format, - bool dest_is_uint, bool tile_instanced) { - TransferShaderKey pipeline_key = key; - pipeline_key.host_depth_source_is_copy = 0; - auto& pipeline_map = - tile_instanced ? transfer_tile_pipelines_ : transfer_pipelines_; - auto it = pipeline_map.find(pipeline_key); - if (it != pipeline_map.end()) { - return it->second; - } - + bool dest_is_uint, bool native_stencil_output, + uint32_t color_attachment_index, + const TransferColorAttachmentFormats* color_attachment_formats, + MTL::PixelFormat depth_attachment_format, + MTL::PixelFormat stencil_attachment_format) { const TransferModeInfo& mode_info = kTransferModeInfos[size_t(key.mode)]; TransferOutput output = mode_info.output; bool source_is_color = mode_info.source_is_color; bool has_host_depth = mode_info.uses_host_depth; + native_stencil_output = + native_stencil_output && output == TransferOutput::kStencilBit; xenos::ColorRenderTargetFormat source_color_format = xenos::ColorRenderTargetFormat(key.source_resource_format); @@ -6906,13 +5749,71 @@ MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( xenos::IsColorRenderTargetFormat64bpp(source_color_format); } bool dest_is_depth = output != TransferOutput::kColor; - bool dest_is_64bpp = false; - if (!dest_is_depth) { - dest_is_64bpp = - xenos::IsColorRenderTargetFormat64bpp(dest_color_format) || - (dest_color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && - gamma_render_target_as_unorm16_); + TransferPipelineKey pipeline_key = {}; + pipeline_key.shader_key = key; + pipeline_key.shader_key.host_depth_source_is_copy = 0; + pipeline_key.native_stencil_output = native_stencil_output ? 1u : 0u; + if (output == TransferOutput::kColor) { + if (color_attachment_index >= xenos::kMaxColorRenderTargets) { + return nullptr; + } + pipeline_key.color_attachment_index = color_attachment_index; + if (color_attachment_formats) { + pipeline_key.color_attachment_formats = *color_attachment_formats; + } else { + pipeline_key.color_attachment_formats.fill(MTL::PixelFormatInvalid); + pipeline_key.color_attachment_formats[color_attachment_index] = + dest_format; + } + if (pipeline_key.color_attachment_formats[color_attachment_index] != + dest_format) { + return nullptr; + } + pipeline_key.depth_attachment_format = depth_attachment_format; + pipeline_key.stencil_attachment_format = stencil_attachment_format; + } else { + pipeline_key.color_attachment_index = 0; + if (color_attachment_formats) { + pipeline_key.color_attachment_formats = *color_attachment_formats; + } else { + pipeline_key.color_attachment_formats.fill(MTL::PixelFormatInvalid); + } + pipeline_key.depth_attachment_format = + depth_attachment_format != MTL::PixelFormatInvalid + ? depth_attachment_format + : dest_format; + if (pipeline_key.depth_attachment_format != dest_format) { + return nullptr; + } + if (dest_format == MTL::PixelFormatDepth32Float_Stencil8 || + dest_format == MTL::PixelFormatDepth24Unorm_Stencil8) { + pipeline_key.stencil_attachment_format = + stencil_attachment_format != MTL::PixelFormatInvalid + ? stencil_attachment_format + : dest_format; + if (pipeline_key.stencil_attachment_format != dest_format) { + return nullptr; + } + } else { + pipeline_key.stencil_attachment_format = stencil_attachment_format; + } } + + auto it = transfer_pipelines_.find(pipeline_key); + if (it != transfer_pipelines_.end()) { + return it->second; + } + + bool dest_is_64bpp = false; + bool dest_is_gamma_unorm16 = false; + if (!dest_is_depth) { + dest_is_64bpp = xenos::IsColorRenderTargetFormat64bpp(dest_color_format); + dest_is_gamma_unorm16 = + dest_color_format == xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && + gamma_render_target_as_unorm16_ && !dest_is_uint; + } + bool source_needs_depth = + !source_is_color && output != TransferOutput::kStencilBit; bool source_needs_stencil = !source_is_color && (output == TransferOutput::kColor || output == TransferOutput::kStencilBit); @@ -6943,6 +5844,8 @@ MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( source.reserve(16384); append_define(source, "XE_TRANSFER_SOURCE_IS_COLOR", source_is_color ? 1 : 0); append_define(source, "XE_TRANSFER_SOURCE_IS_DEPTH", source_is_color ? 0 : 1); + append_define(source, "XE_TRANSFER_SOURCE_NEEDS_DEPTH", + source_needs_depth ? 1 : 0); append_define(source, "XE_TRANSFER_SOURCE_NEEDS_STENCIL", source_needs_stencil ? 1 : 0); append_define(source, "XE_TRANSFER_SOURCE_IS_UINT", source_is_uint ? 1 : 0); @@ -6952,11 +5855,15 @@ MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( append_define(source, "XE_TRANSFER_DEST_IS_UINT", dest_is_uint ? 1 : 0); append_define(source, "XE_TRANSFER_DEST_IS_DEPTH", dest_is_depth ? 1 : 0); append_define(source, "XE_TRANSFER_DEST_IS_64BPP", dest_is_64bpp ? 1 : 0); + append_define(source, "XE_TRANSFER_DEST_IS_GAMMA_UNORM16", + dest_is_gamma_unorm16 ? 1 : 0); append_define(source, "XE_TRANSFER_DEST_COMPONENTS", dest_component_count); append_define(source, "XE_TRANSFER_DEST_IS_MULTISAMPLE", dest_is_multisample ? 1 : 0); append_define(source, "XE_TRANSFER_DEST_SAMPLE_ID_FROM_SAMPLE", key.dest_sample_id_from_sample ? 1 : 0); + append_define(source, "XE_TRANSFER_COLOR_ATTACHMENT_INDEX", + pipeline_key.color_attachment_index); append_define(source, "XE_TRANSFER_HAS_HOST_DEPTH", has_host_depth ? 1 : 0); append_define(source, "XE_TRANSFER_HOST_DEPTH_IS_MULTISAMPLE", host_depth_is_multisample ? 1 : 0); @@ -6979,7 +5886,8 @@ MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( output == TransferOutput::kDepth ? 1 : 0); append_define(source, "XE_TRANSFER_OUTPUT_STENCIL_BIT", output == TransferOutput::kStencilBit ? 1 : 0); - append_define(source, "XE_TRANSFER_TILE_INSTANCED", tile_instanced ? 1 : 0); + append_define(source, "XE_TRANSFER_NATIVE_STENCIL_OUTPUT", + native_stencil_output ? 1 : 0); append_define(source, "XE_TRANSFER_FAST_DIVMOD", ::cvars::metal_transfer_fast_divmod ? 1 : 0); append_define(source, "XE_FMT_8_8_8_8", @@ -7058,14 +5966,6 @@ struct TransferShaderConstants { uint stencil_clear; }; -struct TransferTileInstance { - float2 tile_origin; - uint tile_index; - uint padding; - uint2 source_base; - uint2 host_base; -}; - struct TransferRectInstance { float2 origin; float2 size; @@ -7185,6 +6085,33 @@ inline float3 XeLinearToPWLGamma3(float3 v) { XeLinearToPWLGamma(v.b)); } +inline float XePWLGammaByteToLinearMidpoint(uint byte_value) { + byte_value &= 0xFFu; + float recip = 1.0f / 1023.0f; + float offset = 0.5f / 1023.0f; + if (byte_value >= 64u) { + recip = 1.0f / 511.5f; + offset = -31.5f / 511.5f; + } + if (byte_value >= 96u) { + recip = 1.0f / 255.75f; + offset = -63.5f / 255.75f; + } + if (byte_value >= 192u) { + recip = 1.0f / 127.875f; + offset = -127.5f / 127.875f; + } + return float(byte_value) * recip + offset; +} + +inline float4 XePWLGammaPackedRGBA8ToLinearMidpoint(uint packed) { + return float4( + XePWLGammaByteToLinearMidpoint(packed), + XePWLGammaByteToLinearMidpoint(packed >> 8u), + XePWLGammaByteToLinearMidpoint(packed >> 16u), + float((packed >> 24u) & 0xFFu) * (1.0f / 255.0f)); +} + uint XePreClampedFloat32To7e3(float value) { uint f32 = as_type(value); uint biased_f32; @@ -7283,20 +6210,12 @@ uint XePackColorRGB10A2Float(float4 color) { struct VSOut { float4 position [[position]]; - float2 tile_origin [[flat]]; - uint tile_index [[flat]]; - uint2 source_base [[flat]]; - uint2 host_base [[flat]]; }; vertex VSOut transfer_vs(uint vid [[vertex_id]]) { float2 pt = float2((vid << 1) & 2, vid & 2); VSOut out; out.position = float4(pt * 2.0f - 1.0f, 0.0f, 1.0f); - out.tile_origin = float2(0.0f); - out.tile_index = 0u; - out.source_base = uint2(0u); - out.host_base = uint2(0u); return out; } @@ -7314,33 +6233,6 @@ vertex VSOut transfer_rect_vs(uint vid [[vertex_id]], ndc.y = 1.0f - pos_pixel.y * constants.dest_pixel_to_ndc_y; VSOut out; out.position = float4(ndc, 0.0f, 1.0f); - out.tile_origin = float2(0.0f); - out.tile_index = 0u; - out.source_base = uint2(0u); - out.host_base = uint2(0u); - return out; -} - -vertex VSOut transfer_tile_vs(uint vid [[vertex_id]], - uint iid [[instance_id]], - constant TransferShaderConstants& constants - [[buffer(0)]], - device const TransferTileInstance* instances - [[buffer(1)]]) { - float2 quad = float2(float(vid & 1), float(vid >> 1)); - TransferTileInstance inst = instances[iid]; - float2 tile_size = float2(constants.dest_tile_width_pixels, - constants.dest_tile_height_pixels); - float2 pos_pixel = inst.tile_origin + quad * tile_size; - float2 ndc; - ndc.x = pos_pixel.x * constants.dest_pixel_to_ndc_x - 1.0f; - ndc.y = 1.0f - pos_pixel.y * constants.dest_pixel_to_ndc_y; - VSOut out; - out.position = float4(ndc, 0.0f, 1.0f); - out.tile_origin = inst.tile_origin; - out.tile_index = inst.tile_index; - out.source_base = inst.source_base; - out.host_base = inst.host_base; return out; } @@ -7367,7 +6259,7 @@ vertex VSOut transfer_tile_vs(uint vid [[vertex_id]], #endif #endif #else - #if XE_TRANSFER_SOURCE_NEEDS_STENCIL + #if XE_TRANSFER_SOURCE_NEEDS_DEPTH && XE_TRANSFER_SOURCE_NEEDS_STENCIL #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE #define XE_TRANSFER_SOURCE_PARAMS \ , texture2d_ms xe_transfer_source_depth \ @@ -7381,7 +6273,7 @@ vertex VSOut transfer_tile_vs(uint vid [[vertex_id]], texture2d xe_transfer_source_stencil \ [[texture(XE_TRANSFER_STENCIL_TEXTURE_INDEX)]] #endif - #else + #elif XE_TRANSFER_SOURCE_NEEDS_DEPTH #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE #define XE_TRANSFER_SOURCE_PARAMS \ , texture2d_ms xe_transfer_source_depth \ @@ -7391,6 +6283,18 @@ vertex VSOut transfer_tile_vs(uint vid [[vertex_id]], , texture2d xe_transfer_source_depth \ [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] #endif + #elif XE_TRANSFER_SOURCE_NEEDS_STENCIL + #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE + #define XE_TRANSFER_SOURCE_PARAMS \ + , texture2d_ms xe_transfer_source_stencil \ + [[texture(XE_TRANSFER_STENCIL_TEXTURE_INDEX)]] + #else + #define XE_TRANSFER_SOURCE_PARAMS \ + , texture2d xe_transfer_source_stencil \ + [[texture(XE_TRANSFER_STENCIL_TEXTURE_INDEX)]] + #endif + #else + #define XE_TRANSFER_SOURCE_PARAMS #endif #endif @@ -7441,7 +6345,7 @@ vertex VSOut transfer_tile_vs(uint vid [[vertex_id]], #endif struct TransferColorOut { - XeTransferColorOutType color [[color(0)]]; + XeTransferColorOutType color [[color(XE_TRANSFER_COLOR_ATTACHMENT_INDEX)]]; #if XE_TRANSFER_DEST_IS_MULTISAMPLE uint sample_mask [[sample_mask]]; #endif @@ -7472,12 +6376,6 @@ fragment TransferColorOut transfer_ps( uint dest_tile_pixel_x = 0u; uint dest_tile_pixel_y = 0u; uint dest_tile_index = 0u; -#if XE_TRANSFER_TILE_INSTANCED - uint2 tile_origin = uint2(in.tile_origin); - dest_tile_pixel_x = dest_pixel.x - tile_origin.x; - dest_tile_pixel_y = dest_pixel.y - tile_origin.y; - dest_tile_index = in.tile_index; -#else uint dest_tile_index_x = 0u; uint dest_tile_index_y = 0u; #if XE_TRANSFER_FAST_DIVMOD @@ -7497,7 +6395,6 @@ fragment TransferColorOut transfer_ps( dest_tile_index = dest_tile_index_x + dest_tile_index_y * constants.address.dest_pitch; -#endif uint source_sample_id = dest_sample_id; uint source_tile_pixel_x = dest_tile_pixel_x; @@ -7644,10 +6541,6 @@ fragment TransferColorOut transfer_ps( uint source_pixel_x = 0u; uint source_pixel_y = 0u; -#if XE_TRANSFER_TILE_INSTANCED - source_pixel_x = in.source_base.x + source_tile_pixel_x; - source_pixel_y = in.source_base.y + source_tile_pixel_y; -#else uint source_tile_index = uint(int(dest_tile_index) + constants.address.source_to_dest) & (kEdramTileCount - 1u); @@ -7665,7 +6558,6 @@ fragment TransferColorOut transfer_ps( source_tile_index_y * (tile_height_samples >> (source_msaa >= 2u ? 1u : 0u)) + source_tile_pixel_y; -#endif bool load_two = !source_is_64bpp && dest_is_64bpp; uint source_pixel_x1 = source_pixel_x; @@ -7717,6 +6609,7 @@ fragment TransferColorOut transfer_ps( } #endif #else +#if XE_TRANSFER_SOURCE_NEEDS_DEPTH float source_depth0 = #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y), @@ -7734,6 +6627,7 @@ fragment TransferColorOut transfer_ps( uint2(source_pixel_x1, source_pixel_y)).r; #endif } +#endif #if XE_TRANSFER_SOURCE_NEEDS_STENCIL uint source_stencil0 = #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE @@ -7954,7 +6848,8 @@ fragment TransferColorOut transfer_ps( case XE_FMT_8_8_8_8_GAMMA: { float4 color = source_color0; #if XE_GAMMA_RT_AS_UNORM16 - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_8_8_8_8_GAMMA && + if ((XE_TRANSFER_SOURCE_FORMAT == XE_FMT_8_8_8_8_GAMMA || + XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8_GAMMA) && (XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8 || XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8_GAMMA) && XE_TRANSFER_DEST_FORMAT != XE_TRANSFER_SOURCE_FORMAT) { @@ -8077,6 +6972,9 @@ fragment TransferColorOut transfer_ps( case XE_FMT_8_8_8_8_GAMMA: { #if XE_TRANSFER_DEST_IS_UINT out_color = uint4(packed32, 0u, 0u, 0u); +#else +#if XE_TRANSFER_DEST_IS_GAMMA_UNORM16 + out_color = XePWLGammaPackedRGBA8ToLinearMidpoint(packed32); #else float4 color = float4( float((packed32 >> 0u) & 0xFFu) * (1.0f / 255.0f), @@ -8087,6 +6985,7 @@ fragment TransferColorOut transfer_ps( color.rgb = XePWLGammaToLinear3(color.rgb); #endif out_color = color; +#endif #endif } break; case XE_FMT_2_10_10_10: @@ -8150,7 +7049,11 @@ fragment TransferColorOut transfer_ps( } #elif XE_TRANSFER_OUTPUT_DEPTH || XE_TRANSFER_OUTPUT_STENCIL_BIT struct TransferDepthOut { +#if XE_TRANSFER_OUTPUT_STENCIL_BIT && XE_TRANSFER_NATIVE_STENCIL_OUTPUT + uint stencil [[stencil]]; +#else float depth [[depth(any)]]; +#endif #if XE_TRANSFER_DEST_IS_MULTISAMPLE uint sample_mask [[sample_mask]]; #endif @@ -8181,12 +7084,6 @@ fragment TransferDepthOut transfer_ps( uint dest_tile_pixel_x = 0u; uint dest_tile_pixel_y = 0u; uint dest_tile_index = 0u; -#if XE_TRANSFER_TILE_INSTANCED - uint2 tile_origin = uint2(in.tile_origin); - dest_tile_pixel_x = dest_pixel.x - tile_origin.x; - dest_tile_pixel_y = dest_pixel.y - tile_origin.y; - dest_tile_index = in.tile_index; -#else uint dest_tile_index_x = 0u; uint dest_tile_index_y = 0u; #if XE_TRANSFER_FAST_DIVMOD @@ -8206,7 +7103,6 @@ fragment TransferDepthOut transfer_ps( dest_tile_index = dest_tile_index_x + dest_tile_index_y * constants.address.dest_pitch; -#endif uint source_sample_id = dest_sample_id; uint source_tile_pixel_x = dest_tile_pixel_x; @@ -8347,10 +7243,6 @@ fragment TransferDepthOut transfer_ps( uint source_pixel_x = 0u; uint source_pixel_y = 0u; -#if XE_TRANSFER_TILE_INSTANCED - source_pixel_x = in.source_base.x + source_tile_pixel_x; - source_pixel_y = in.source_base.y + source_tile_pixel_y; -#else uint source_tile_index = uint(int(dest_tile_index) + constants.address.source_to_dest) & (kEdramTileCount - 1u); @@ -8368,7 +7260,6 @@ fragment TransferDepthOut transfer_ps( source_tile_index_y * (tile_height_samples >> (source_msaa >= 2u ? 1u : 0u)) + source_tile_pixel_y; -#endif bool load_two = !source_is_64bpp && dest_is_64bpp; uint source_pixel_x1 = source_pixel_x; @@ -8420,6 +7311,7 @@ fragment TransferDepthOut transfer_ps( } #endif #else +#if XE_TRANSFER_SOURCE_NEEDS_DEPTH float source_depth0 = #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y), @@ -8437,6 +7329,7 @@ fragment TransferDepthOut transfer_ps( uint2(source_pixel_x1, source_pixel_y)).r; #endif } +#endif #if XE_TRANSFER_SOURCE_NEEDS_STENCIL uint source_stencil0 = #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE @@ -8514,6 +7407,9 @@ fragment TransferDepthOut transfer_ps( break; } #endif +#else +#if XE_TRANSFER_OUTPUT_STENCIL_BIT + packed = source_stencil0; #else if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_D24FS8) { bool round_depth = constants.depth_round != 0u; @@ -8530,11 +7426,17 @@ fragment TransferDepthOut transfer_ps( packed = (packed << 8u) | (source_stencil0 & 0xFFu); } #endif -#if XE_TRANSFER_OUTPUT_STENCIL_BIT && !XE_TRANSFER_SOURCE_IS_COLOR - packed = source_stencil0; #endif #if XE_TRANSFER_OUTPUT_STENCIL_BIT +#if XE_TRANSFER_NATIVE_STENCIL_OUTPUT + TransferDepthOut out; + out.stencil = packed & 0xFFu; +#if XE_TRANSFER_DEST_IS_MULTISAMPLE + out.sample_mask = 1u << dest_sample_id; +#endif + return out; +#else if (constants.stencil_clear == 0u) { if ((packed & constants.stencil_mask) == 0u) { discard_fragment(); @@ -8546,6 +7448,7 @@ fragment TransferDepthOut transfer_ps( out.sample_mask = 1u << dest_sample_id; #endif return out; +#endif #else uint guest_depth24 = packed; if (!packed_only_depth) { @@ -8623,10 +7526,6 @@ fragment TransferDepthOut transfer_ps( uint host_pixel_x = 0u; uint host_pixel_y = 0u; -#if XE_TRANSFER_TILE_INSTANCED - host_pixel_x = in.host_base.x + host_tile_pixel_x; - host_pixel_y = in.host_base.y + host_tile_pixel_y; -#else uint host_tile_index = uint(int(dest_tile_index) + constants.host_depth_address.source_to_dest) & @@ -8645,7 +7544,6 @@ fragment TransferDepthOut transfer_ps( host_tile_index_y * (tile_height_samples >> (host_msaa >= 2u ? 1u : 0u)) + host_tile_pixel_y; -#endif #if XE_TRANSFER_HOST_DEPTH_IS_MULTISAMPLE host_depth32 = xe_transfer_host_depth.read( @@ -8692,7 +7590,7 @@ fragment TransferDepthOut transfer_ps( uint host_depth24 = 0u; if (XE_TRANSFER_DEST_FORMAT == XE_FMT_D24FS8) { bool round_depth = constants.depth_round != 0u; - host_depth24 = XeFloat32To20e4(host_depth32, round_depth); + host_depth24 = XeFloat32To20e4(host_depth32 * 2.0f, round_depth); } else { host_depth24 = XeRoundToNearestEven( clamp(host_depth32, 0.0f, 1.0f) * 16777215.0f); @@ -8717,7 +7615,11 @@ fragment TransferDepthOut transfer_ps( NS::Error* error = nullptr; auto src_str = NS::String::string(source.c_str(), NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(src_str, nullptr, &error); + MTL::CompileOptions* compile_options = MTL::CompileOptions::alloc()->init(); + compile_options->setFastMathEnabled(true); + compile_options->setLanguageVersion(MTL::LanguageVersion2_4); + MTL::Library* lib = device_->newLibrary(src_str, compile_options, &error); + compile_options->release(); if (!lib) { XELOGE( "GetOrCreateTransferPipelines: failed to compile transfer MSL " @@ -8730,9 +7632,7 @@ fragment TransferDepthOut transfer_ps( return nullptr; } - const char* vs_entry = - tile_instanced ? "transfer_tile_vs" : "transfer_rect_vs"; - auto vs_name = NS::String::string(vs_entry, NS::UTF8StringEncoding); + auto vs_name = NS::String::string("transfer_rect_vs", NS::UTF8StringEncoding); auto ps_name = NS::String::string("transfer_ps", NS::UTF8StringEncoding); MTL::Function* vs = lib->newFunction(vs_name); MTL::Function* ps = lib->newFunction(ps_name); @@ -8754,15 +7654,27 @@ fragment TransferDepthOut transfer_ps( desc->setFragmentFunction(ps); if (output == TransferOutput::kColor) { - desc->colorAttachments()->object(0)->setPixelFormat(dest_format); - } else { - desc->colorAttachments()->object(0)->setPixelFormat( - MTL::PixelFormatInvalid); - desc->setDepthAttachmentPixelFormat(dest_format); - if (dest_format == MTL::PixelFormatDepth32Float_Stencil8 || - dest_format == MTL::PixelFormatDepth24Unorm_Stencil8) { - desc->setStencilAttachmentPixelFormat(dest_format); + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + auto* color_attachment = desc->colorAttachments()->object(i); + color_attachment->setPixelFormat( + pipeline_key.color_attachment_formats[i]); + color_attachment->setWriteMask(i == pipeline_key.color_attachment_index + ? MTL::ColorWriteMaskAll + : MTL::ColorWriteMaskNone); } + desc->setDepthAttachmentPixelFormat(pipeline_key.depth_attachment_format); + desc->setStencilAttachmentPixelFormat( + pipeline_key.stencil_attachment_format); + } else { + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + auto* color_attachment = desc->colorAttachments()->object(i); + color_attachment->setPixelFormat( + pipeline_key.color_attachment_formats[i]); + color_attachment->setWriteMask(MTL::ColorWriteMaskNone); + } + desc->setDepthAttachmentPixelFormat(pipeline_key.depth_attachment_format); + desc->setStencilAttachmentPixelFormat( + pipeline_key.stencil_attachment_format); } uint32_t sample_count = 1; @@ -8791,7 +7703,7 @@ fragment TransferDepthOut transfer_ps( return nullptr; } - pipeline_map.emplace(pipeline_key, pipeline); + transfer_pipelines_.emplace(pipeline_key, pipeline); return pipeline; } @@ -8868,14 +7780,51 @@ fragment TransferDepthOut transfer_clear_depth_ps( MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferClearPipeline( MTL::PixelFormat dest_format, bool dest_is_uint, bool is_depth, - uint32_t sample_count) { - uint32_t key = uint32_t(dest_format); - key ^= (sample_count & 0x7u) << 24; - if (dest_is_uint) { - key ^= 1u << 30; + uint32_t sample_count, uint32_t color_attachment_index, + const TransferColorAttachmentFormats* color_attachment_formats, + MTL::PixelFormat depth_attachment_format, + MTL::PixelFormat stencil_attachment_format) { + TransferClearPipelineKey key = {}; + key.sample_count = sample_count ? sample_count : 1; + key.dest_is_uint = dest_is_uint ? 1u : 0u; + key.is_depth = is_depth ? 1u : 0u; + key.color_attachment_formats.fill(MTL::PixelFormatInvalid); + if (color_attachment_formats) { + key.color_attachment_formats = *color_attachment_formats; } if (is_depth) { - key ^= 1u << 31; + key.depth_attachment_format = + depth_attachment_format != MTL::PixelFormatInvalid + ? depth_attachment_format + : dest_format; + if (key.depth_attachment_format != dest_format) { + return nullptr; + } + if (dest_format == MTL::PixelFormatDepth32Float_Stencil8 || + dest_format == MTL::PixelFormatDepth24Unorm_Stencil8) { + key.stencil_attachment_format = + stencil_attachment_format != MTL::PixelFormatInvalid + ? stencil_attachment_format + : dest_format; + if (key.stencil_attachment_format != dest_format) { + return nullptr; + } + } else { + key.stencil_attachment_format = stencil_attachment_format; + } + } else { + if (color_attachment_index >= xenos::kMaxColorRenderTargets) { + return nullptr; + } + key.color_attachment_index = color_attachment_index; + if (!color_attachment_formats) { + key.color_attachment_formats[color_attachment_index] = dest_format; + } + if (key.color_attachment_formats[color_attachment_index] != dest_format) { + return nullptr; + } + key.depth_attachment_format = depth_attachment_format; + key.stencil_attachment_format = stencil_attachment_format; } auto it = transfer_clear_pipelines_.find(key); if (it != transfer_clear_pipelines_.end()) { @@ -8916,19 +7865,17 @@ MetalRenderTargetCache::GetOrCreateTransferClearPipeline( MTL::RenderPipelineDescriptor::alloc()->init(); desc->setVertexFunction(vs); desc->setFragmentFunction(ps); - desc->setSampleCount(sample_count ? sample_count : 1); + desc->setSampleCount(key.sample_count); - if (is_depth) { - desc->colorAttachments()->object(0)->setPixelFormat( - MTL::PixelFormatInvalid); - desc->setDepthAttachmentPixelFormat(dest_format); - if (dest_format == MTL::PixelFormatDepth32Float_Stencil8 || - dest_format == MTL::PixelFormatDepth24Unorm_Stencil8) { - desc->setStencilAttachmentPixelFormat(dest_format); - } - } else { - desc->colorAttachments()->object(0)->setPixelFormat(dest_format); + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + auto* color_attachment = desc->colorAttachments()->object(i); + color_attachment->setPixelFormat(key.color_attachment_formats[i]); + color_attachment->setWriteMask(!is_depth && i == key.color_attachment_index + ? MTL::ColorWriteMaskAll + : MTL::ColorWriteMaskNone); } + desc->setDepthAttachmentPixelFormat(key.depth_attachment_format); + desc->setStencilAttachmentPixelFormat(key.stencil_attachment_format); NS::Error* error = nullptr; MTL::RenderPipelineState* pipeline = @@ -9037,107 +7984,89 @@ MTL::Buffer* MetalRenderTargetCache::GetTransferDummyBuffer() { return transfer_dummy_buffer_; } -MTL::DepthStencilState* MetalRenderTargetCache::GetTransferDepthStencilState( - bool depth_write) { - if (transfer_depth_state_) { - return transfer_depth_state_; - } +MTL::DepthStencilState* MetalRenderTargetCache::BuildTransferDepthStencilState( + MTL::CompareFunction depth_compare, bool depth_write, bool stencil_enable, + uint32_t stencil_write_mask) { MTL::DepthStencilDescriptor* desc = MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(::cvars::depth_transfer_not_equal_test - ? MTL::CompareFunctionNotEqual - : MTL::CompareFunctionAlways); + desc->setDepthCompareFunction(depth_compare); desc->setDepthWriteEnabled(depth_write); - transfer_depth_state_ = device_->newDepthStencilState(desc); + if (stencil_enable) { + MTL::StencilDescriptor* stencil = MTL::StencilDescriptor::alloc()->init(); + stencil->setStencilCompareFunction(MTL::CompareFunctionAlways); + stencil->setStencilFailureOperation(MTL::StencilOperationKeep); + stencil->setDepthFailureOperation(MTL::StencilOperationKeep); + stencil->setDepthStencilPassOperation(MTL::StencilOperationReplace); + stencil->setReadMask(0xFF); + stencil->setWriteMask(stencil_write_mask); + desc->setFrontFaceStencil(stencil); + desc->setBackFaceStencil(stencil); + stencil->release(); + } + MTL::DepthStencilState* state = device_->newDepthStencilState(desc); desc->release(); + return state; +} + +MTL::DepthStencilState* MetalRenderTargetCache::GetTransferDepthStencilState( + bool depth_write) { + if (!transfer_depth_state_) { + transfer_depth_state_ = BuildTransferDepthStencilState( + ::cvars::depth_transfer_not_equal_test ? MTL::CompareFunctionNotEqual + : MTL::CompareFunctionAlways, + depth_write, /*stencil_enable=*/false, 0); + } return transfer_depth_state_; } MTL::DepthStencilState* MetalRenderTargetCache::GetTransferNoDepthStencilState() { - if (transfer_depth_state_none_) { - return transfer_depth_state_none_; + if (!transfer_depth_state_none_) { + transfer_depth_state_none_ = BuildTransferDepthStencilState( + MTL::CompareFunctionAlways, /*depth_write=*/false, + /*stencil_enable=*/false, 0); } - MTL::DepthStencilDescriptor* desc = - MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(MTL::CompareFunctionAlways); - desc->setDepthWriteEnabled(false); - transfer_depth_state_none_ = device_->newDepthStencilState(desc); - desc->release(); return transfer_depth_state_none_; } MTL::DepthStencilState* MetalRenderTargetCache::GetTransferDepthClearState() { - if (transfer_depth_clear_state_) { - return transfer_depth_clear_state_; + if (!transfer_depth_clear_state_) { + transfer_depth_clear_state_ = BuildTransferDepthStencilState( + MTL::CompareFunctionAlways, /*depth_write=*/true, + /*stencil_enable=*/true, 0xFF); } - MTL::DepthStencilDescriptor* desc = - MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(MTL::CompareFunctionAlways); - desc->setDepthWriteEnabled(true); - MTL::StencilDescriptor* stencil = MTL::StencilDescriptor::alloc()->init(); - stencil->setStencilCompareFunction(MTL::CompareFunctionAlways); - stencil->setStencilFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthStencilPassOperation(MTL::StencilOperationReplace); - stencil->setReadMask(0xFF); - stencil->setWriteMask(0xFF); - desc->setFrontFaceStencil(stencil); - desc->setBackFaceStencil(stencil); - transfer_depth_clear_state_ = device_->newDepthStencilState(desc); - stencil->release(); - desc->release(); return transfer_depth_clear_state_; } MTL::DepthStencilState* MetalRenderTargetCache::GetTransferStencilClearState() { - if (transfer_stencil_clear_state_) { - return transfer_stencil_clear_state_; + if (!transfer_stencil_clear_state_) { + transfer_stencil_clear_state_ = BuildTransferDepthStencilState( + MTL::CompareFunctionAlways, /*depth_write=*/false, + /*stencil_enable=*/true, 0xFF); } - MTL::DepthStencilDescriptor* desc = - MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(MTL::CompareFunctionAlways); - desc->setDepthWriteEnabled(false); - MTL::StencilDescriptor* stencil = MTL::StencilDescriptor::alloc()->init(); - stencil->setStencilCompareFunction(MTL::CompareFunctionAlways); - stencil->setStencilFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthStencilPassOperation(MTL::StencilOperationReplace); - stencil->setReadMask(0xFF); - stencil->setWriteMask(0xFF); - desc->setFrontFaceStencil(stencil); - desc->setBackFaceStencil(stencil); - transfer_stencil_clear_state_ = device_->newDepthStencilState(desc); - stencil->release(); - desc->release(); return transfer_stencil_clear_state_; } +MTL::DepthStencilState* +MetalRenderTargetCache::GetTransferStencilOutputState() { + if (!transfer_stencil_output_state_) { + transfer_stencil_output_state_ = BuildTransferDepthStencilState( + MTL::CompareFunctionAlways, /*depth_write=*/false, + /*stencil_enable=*/true, 0xFF); + } + return transfer_stencil_output_state_; +} + MTL::DepthStencilState* MetalRenderTargetCache::GetTransferStencilBitState( uint32_t bit) { if (bit >= 8) { return nullptr; } - if (transfer_stencil_bit_states_[bit]) { - return transfer_stencil_bit_states_[bit]; + if (!transfer_stencil_bit_states_[bit]) { + transfer_stencil_bit_states_[bit] = BuildTransferDepthStencilState( + MTL::CompareFunctionAlways, /*depth_write=*/false, + /*stencil_enable=*/true, uint32_t(1) << bit); } - uint32_t mask = uint32_t(1) << bit; - MTL::DepthStencilDescriptor* desc = - MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(MTL::CompareFunctionAlways); - desc->setDepthWriteEnabled(false); - MTL::StencilDescriptor* stencil = MTL::StencilDescriptor::alloc()->init(); - stencil->setStencilCompareFunction(MTL::CompareFunctionAlways); - stencil->setStencilFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthFailureOperation(MTL::StencilOperationKeep); - stencil->setDepthStencilPassOperation(MTL::StencilOperationReplace); - stencil->setReadMask(0xFF); - stencil->setWriteMask(uint32_t(mask)); - desc->setFrontFaceStencil(stencil); - desc->setBackFaceStencil(stencil); - transfer_stencil_bit_states_[bit] = device_->newDepthStencilState(desc); - stencil->release(); - desc->release(); return transfer_stencil_bit_states_[bit]; } diff --git a/src/xenia/gpu/metal/metal_render_target_cache.h b/src/xenia/gpu/metal/metal_render_target_cache.h index 16d51ba34..a844e0f46 100644 --- a/src/xenia/gpu/metal/metal_render_target_cache.h +++ b/src/xenia/gpu/metal/metal_render_target_cache.h @@ -11,12 +11,13 @@ #define XENIA_GPU_METAL_METAL_RENDER_TARGET_CACHE_H_ #include -#include +#include #include #include #include -#include +#include +#include "xenia/gpu/draw_util.h" #include "xenia/gpu/register_file.h" #include "xenia/gpu/render_target_cache.h" #include "xenia/gpu/trace_writer.h" @@ -25,6 +26,8 @@ #include "third_party/metal-cpp/Metal/Metal.hpp" +struct IRDescriptorTableEntry; + namespace xe { namespace gpu { namespace metal { @@ -151,7 +154,49 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { // Metal-specific methods MTL::RenderPassDescriptor* GetRenderPassDescriptor( - uint32_t expected_sample_count = 1); + uint32_t expected_sample_count = 1, + bool fallback_depth_attachment_required = false); + bool IsRenderPassDescriptorCompatible( + MTL::RenderPassDescriptor* pass_descriptor, + uint32_t expected_sample_count = 1, + bool fallback_depth_attachment_required = false) const; + bool HasPendingDrawPassTransfers() const { + return pending_draw_pass_transfer_mask_ != 0; + } + enum DrawPassTransferEncoderMutation : uint32_t { + kDrawPassTransferEncoderMutationNone = 0, + kDrawPassTransferEncoderMutationPipeline = 1u << 0, + kDrawPassTransferEncoderMutationDepthStencil = 1u << 1, + kDrawPassTransferEncoderMutationStencilReference = 1u << 2, + kDrawPassTransferEncoderMutationViewport = 1u << 3, + kDrawPassTransferEncoderMutationScissor = 1u << 4, + kDrawPassTransferEncoderMutationVertexSlot0 = 1u << 5, + kDrawPassTransferEncoderMutationVertexSlot1 = 1u << 6, + kDrawPassTransferEncoderMutationFragmentSlot0 = 1u << 7, + kDrawPassTransferEncoderMutationFragmentSlot1 = 1u << 8, + kDrawPassTransferEncoderMutationFragmentTextures = 1u << 9, + }; + using DrawPassTransferEncoderMutationMask = uint32_t; + bool EncodePendingDrawPassTransfers( + MTL::RenderCommandEncoder* encoder, + MTL::RenderPassDescriptor* pass_descriptor, + DrawPassTransferEncoderMutationMask* mutations_out = nullptr); + bool FlushPendingDrawPassTransfers(); + + struct TelemetryStats { + struct ResolveDirectHostTelemetry { + uint64_t direct_host_attempt = 0; + uint64_t direct_host_success = 0; + uint64_t direct_host_reject_gamma = 0; + uint64_t direct_host_reject_exp_bias = 0; + uint64_t direct_host_reject_format_mismatch = 0; + uint64_t direct_host_reject_sample_select = 0; + uint64_t direct_host_reject_depth_no_fast = 0; + }; + + ResolveDirectHostTelemetry resolve_direct_host = {}; + }; + TelemetryStats GetAndResetTelemetryStats(); bool IsRenderPassDescriptorDirty() const { return render_pass_descriptor_dirty_; @@ -161,34 +206,42 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { MTL::Texture* GetColorTarget(uint32_t index) const; MTL::Texture* GetDepthTarget() const; MTL::Texture* GetDummyColorTarget() const; - MetalRenderTarget* GetColorRenderTarget(uint32_t index) const; // Get current render targets for pipeline attachment formats. MTL::Texture* GetColorTargetForDraw(uint32_t index) const; MTL::Texture* GetDepthTargetForDraw() const; MTL::Texture* GetDummyColorTargetForDraw() const; - - // Get the last REAL (non-dummy) render targets for capture - MTL::Texture* GetLastRealColorTarget(uint32_t index) const; - MTL::Texture* GetLastRealDepthTarget() const; - - // Look up a render target texture by key for debug/trace viewer use. - MTL::Texture* GetRenderTargetTexture(RenderTargetKey key) const; - // Look up a color render target texture by key components for the trace - // viewer without exposing RenderTargetKey. - MTL::Texture* GetColorRenderTargetTexture( - uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, - xenos::ColorRenderTargetFormat format) const; + double GetDepthTargetClearDepth() const; // Restore EDRAM contents from snapshot (for trace playback), matching // D3D12RenderTargetCache::RestoreEdramSnapshot. void RestoreEdramSnapshot(const void* snapshot); MTL::Buffer* GetEdramBuffer() const { return edram_buffer_; } + bool WriteEdramUintPow2BindlessDescriptor( + IRDescriptorTableEntry* entry, uint32_t element_size_bytes_pow2) const; + uint64_t GetBindlessResourcesSerial() const { + return bindless_resources_serial_; + } + void CollectBindlessResources( + std::vector& resources_out) const; + + struct ResolvePlan { + draw_util::ResolveInfo resolve_info = {}; + bool valid = false; + bool noop = false; + bool needs_copy_export = false; + bool needs_resolve_clear = false; + bool needs_render_encoder_end = false; + uint32_t written_address = 0; + uint32_t written_length = 0; + }; // Resolve (copy) render targets to shared memory + bool PrepareResolvePlan(Memory& memory, ResolvePlan& plan_out); bool Resolve(Memory& memory, uint32_t& written_address, uint32_t& written_length, - MTL::CommandBuffer* command_buffer = nullptr); + MTL::CommandBuffer* command_buffer = nullptr, + const ResolvePlan* prepared_resolve_plan = nullptr); protected: // Virtual methods from RenderTargetCache @@ -199,80 +252,80 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { bool IsHostDepthEncodingDifferent( xenos::DepthRenderTargetFormat format) const override; + void RequestPixelShaderInterlockBarrier() override; private: - void RecordRenderTargetViewCreated(); - static uint32_t GetMetalEdramDumpFormat(RenderTargetKey key); MTL::Library* GetOrCreateEdramLoadLibrary(bool msaa); MTL::RenderPipelineState* GetOrCreateEdramLoadPipeline( MTL::PixelFormat dest_format, uint32_t sample_count); + bool InitializeEdramBufferViews(); + void ReleaseEdramBufferViews(); + MTL::Texture* GetEdramUintPow2BufferView( + uint32_t element_size_bytes_pow2) const; MetalCommandProcessor& command_processor_; TraceWriter* trace_writer_; - std::atomic render_target_views_created_{0}; - // Metal device reference MTL::Device* device_ = nullptr; - bool gamma_render_target_as_srgb_ = false; bool gamma_render_target_as_unorm16_ = false; std::unique_ptr render_target_heap_pool_; // EDRAM buffer (10MB embedded DRAM) MTL::Buffer* edram_buffer_ = nullptr; + MTL::Texture* edram_r32_uint_buffer_view_ = nullptr; + MTL::Texture* edram_r32g32_uint_buffer_view_ = nullptr; + MTL::Texture* edram_r32g32b32a32_uint_buffer_view_ = nullptr; - // EDRAM compute shaders for tile operations - MTL::ComputePipelineState* edram_load_pipeline_ = nullptr; // Tiled → Linear - MTL::ComputePipelineState* edram_store_pipeline_ = nullptr; // Linear → Tiled + // EDRAM render pipelines for drawing cached render-target contents. std::unordered_map edram_load_pipelines_; MTL::Library* edram_load_library_ = nullptr; MTL::Library* edram_load_library_msaa_ = nullptr; // EDRAM dump compute shaders for host render target → EDRAM copies. - // Color, 32bpp. - 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; - // Color, 64bpp. - MTL::ComputePipelineState* edram_dump_color_64bpp_1xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* edram_dump_color_64bpp_2xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* edram_dump_color_64bpp_4xmsaa_pipeline_ = nullptr; - // Depth (D24x / D24FS8 encoded as 32bpp in EDRAM snapshot). - MTL::ComputePipelineState* edram_dump_depth_32bpp_1xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* edram_dump_depth_32bpp_2xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* edram_dump_depth_32bpp_4xmsaa_pipeline_ = nullptr; + static constexpr size_t kEdramDumpBppCount = 2; // 32, 64 + static constexpr size_t kEdramDumpSourceCount = 2; // float, uint + static constexpr size_t kEdramDumpMsaaCount = 3; // 1x, 2x, 4x + MTL::ComputePipelineState* + edram_dump_color_pipelines_[kEdramDumpBppCount][kEdramDumpSourceCount] + [kEdramDumpMsaaCount] = {}; + MTL::ComputePipelineState* edram_dump_depth_pipelines_[kEdramDumpMsaaCount] = + {}; - // Resolve compute shaders (Metal XeSL → MSL metallib) - MTL::ComputePipelineState* resolve_full_8bpp_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_16bpp_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_32bpp_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_64bpp_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_128bpp_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_fast_32bpp_1x2xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_fast_32bpp_4xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_fast_64bpp_1x2xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_fast_64bpp_4xmsaa_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_8bpp_scaled_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_16bpp_scaled_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_32bpp_scaled_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_64bpp_scaled_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_full_128bpp_scaled_pipeline_ = nullptr; - MTL::ComputePipelineState* resolve_fast_32bpp_1x2xmsaa_scaled_pipeline_ = - nullptr; - MTL::ComputePipelineState* resolve_fast_32bpp_4xmsaa_scaled_pipeline_ = - nullptr; - MTL::ComputePipelineState* resolve_fast_64bpp_1x2xmsaa_scaled_pipeline_ = - nullptr; - MTL::ComputePipelineState* resolve_fast_64bpp_4xmsaa_scaled_pipeline_ = - nullptr; + // Resolve compute shaders (Metal XeSL -> MSL metallib). + static constexpr size_t kResolveScaledCount = 2; // false, true + static constexpr size_t kResolveFullDestCount = 5; // 8, 16, 32, 64, 128 + static constexpr size_t kResolveFastBppCount = 2; // 32, 64 + static constexpr size_t kResolveFastMsaaCount = 2; // 1/2x, 4x + MTL::ComputePipelineState* + resolve_full_pipelines_[kResolveScaledCount][kResolveFullDestCount] = {}; + MTL::ComputePipelineState* + resolve_fast_pipelines_[kResolveScaledCount][kResolveFastBppCount] + [kResolveFastMsaaCount] = {}; + + // Direct host resolve compute shaders (host RT -> shared/scaled resolve + // memory) for fast and full color copies plus depth copies. + static constexpr size_t kDirectHostResolveBppCount = 2; // 32, 64 + static constexpr size_t kDirectHostResolveMsaaCount = 3; // 1x, 2x, 4x + static constexpr size_t kDirectHostResolveScaledCount = 2; // false, true + static constexpr size_t kDirectHostResolveSourceCount = 2; // float, uint + static constexpr size_t kDirectHostResolveFullDestCount = + 5; // 8, 16, 32, 64, 128 + MTL::ComputePipelineState* direct_host_resolve_pipelines_ + [kDirectHostResolveBppCount][kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount][kDirectHostResolveSourceCount] = {}; + MTL::ComputePipelineState* direct_host_color_full_resolve_pipelines_ + [kDirectHostResolveMsaaCount][kDirectHostResolveScaledCount] + [kDirectHostResolveSourceCount][kDirectHostResolveFullDestCount] = {}; + MTL::ComputePipelineState* + direct_host_depth_resolve_pipelines_[kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount] = {}; // Host depth store compute shaders (1x/2x/4x MSAA). MTL::ComputePipelineState* host_depth_store_pipelines_[3] = {}; - // Transfer shaders (host RT ownership transfers) - modeled after D3D12. - // TransferMode list mirrors D3D12RenderTargetCache::TransferMode so logs and // structure stay in sync, even if many modes are not implemented yet. enum class TransferMode { @@ -296,46 +349,11 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { uint32_t dest_sample_id_from_sample; uint32_t host_depth_source_is_copy; - bool operator==(const TransferShaderKey& other) const { - return mode == other.mode && - source_msaa_samples == other.source_msaa_samples && - dest_msaa_samples == other.dest_msaa_samples && - host_depth_source_msaa_samples == - 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 { - return !(*this == other); - } - bool operator<(const TransferShaderKey& other) const { - if (mode != other.mode) { - return mode < other.mode; - } - if (source_msaa_samples != other.source_msaa_samples) { - return source_msaa_samples < other.source_msaa_samples; - } - if (dest_msaa_samples != other.dest_msaa_samples) { - return dest_msaa_samples < other.dest_msaa_samples; - } - if (host_depth_source_msaa_samples != - other.host_depth_source_msaa_samples) { - return host_depth_source_msaa_samples < - other.host_depth_source_msaa_samples; - } - if (source_resource_format != other.source_resource_format) { - return source_resource_format < other.source_resource_format; - } - 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; - } + // Members above are declared in comparison order, so defaulted member-wise + // == and <=> reproduce the previous hand-written equality and ordering + // exactly. (!=, <, >, <=, >= are synthesized from these.) + bool operator==(const TransferShaderKey& other) const = default; + auto operator<=>(const TransferShaderKey& other) const = default; struct Hasher { size_t operator()(const TransferShaderKey& key) const { @@ -352,12 +370,90 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { }; }; + using TransferColorAttachmentFormats = + std::array; + + struct TransferAttachmentFormats { + TransferColorAttachmentFormats color_attachment_formats = {}; + MTL::PixelFormat depth_attachment_format = MTL::PixelFormatInvalid; + MTL::PixelFormat stencil_attachment_format = MTL::PixelFormatInvalid; + }; + + struct TransferPipelineKey { + TransferShaderKey shader_key; + uint32_t color_attachment_index = 0; + uint32_t native_stencil_output = 0; + TransferColorAttachmentFormats color_attachment_formats = {}; + MTL::PixelFormat depth_attachment_format = MTL::PixelFormatInvalid; + MTL::PixelFormat stencil_attachment_format = MTL::PixelFormatInvalid; + + bool operator==(const TransferPipelineKey& other) const = default; + + struct Hasher { + size_t operator()(const TransferPipelineKey& key) const { + auto combine = [](size_t seed, size_t value) { + return seed ^ (value + 0x9E3779B9 + (seed << 6) + (seed >> 2)); + }; + size_t h = TransferShaderKey::Hasher()(key.shader_key); + h = combine(h, key.color_attachment_index); + h = combine(h, key.native_stencil_output); + h = combine(h, size_t(key.depth_attachment_format)); + h = combine(h, size_t(key.stencil_attachment_format)); + for (MTL::PixelFormat color_format : key.color_attachment_formats) { + h = combine(h, size_t(color_format)); + } + return h; + } + }; + }; + + struct TransferClearPipelineKey { + uint32_t color_attachment_index = 0; + uint32_t sample_count = 1; + uint32_t dest_is_uint = 0; + uint32_t is_depth = 0; + TransferColorAttachmentFormats color_attachment_formats = {}; + MTL::PixelFormat depth_attachment_format = MTL::PixelFormatInvalid; + MTL::PixelFormat stencil_attachment_format = MTL::PixelFormatInvalid; + + bool operator==(const TransferClearPipelineKey& other) const = default; + + struct Hasher { + size_t operator()(const TransferClearPipelineKey& key) const { + auto combine = [](size_t seed, size_t value) { + return seed ^ (value + 0x9E3779B9 + (seed << 6) + (seed >> 2)); + }; + size_t h = key.color_attachment_index; + h = combine(h, key.sample_count); + h = combine(h, key.dest_is_uint); + h = combine(h, key.is_depth); + h = combine(h, size_t(key.depth_attachment_format)); + h = combine(h, size_t(key.stencil_attachment_format)); + for (MTL::PixelFormat color_format : key.color_attachment_formats) { + h = combine(h, size_t(color_format)); + } + return h; + } + }; + }; + + struct TransferRectanglePlan { + uint32_t transfer_index = 0; + std::array + rectangles = {}; + uint32_t rectangle_count = 0; + }; + struct TransferInvocation { Transfer transfer; TransferShaderKey shader_key; + const TransferRectanglePlan* rectangle_plan = nullptr; TransferInvocation(const Transfer& transfer, - const TransferShaderKey& shader_key) - : transfer(transfer), shader_key(shader_key) {} + const TransferShaderKey& shader_key, + const TransferRectanglePlan* rectangle_plan = nullptr) + : transfer(transfer), + shader_key(shader_key), + rectangle_plan(rectangle_plan) {} bool operator<(const TransferInvocation& other) const { if (shader_key != other.shader_key) { return shader_key < other.shader_key; @@ -381,29 +477,44 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { } }; - std::unordered_map + struct AttachmentPlanAttachment { + MetalRenderTarget* render_target = nullptr; + MTL::Texture* texture = nullptr; + bool bound = false; + bool needs_initial_clear = false; + bool load_action_safe = false; + }; + struct AttachmentPlan { + AttachmentPlanAttachment depth = {}; + std::array colors = + {}; + bool has_any_color_target = false; + uint32_t coverage_width = 0; + uint32_t coverage_height = 0; + uint32_t coverage_samples = 1; + }; + + std::unordered_map transfer_pipelines_; - std::unordered_map - transfer_tile_pipelines_; std::vector transfer_invocations_; MTL::Library* transfer_library_ = nullptr; - std::unordered_map + std::unordered_map transfer_clear_pipelines_; - static constexpr uint32_t kTransferInstanceBufferCount = 3; - std::array - transfer_tile_instance_buffers_ = {}; - std::array - transfer_tile_instance_buffer_sizes_ = {}; - std::array, kTransferInstanceBufferCount> - transfer_tile_instance_retired_buffers_ = {}; - uint64_t transfer_tile_instance_buffer_frame_id_ = 0; - size_t transfer_tile_instance_buffer_offset_ = 0; + std::array + pending_draw_pass_render_targets_ = {}; + std::array, 1 + xenos::kMaxColorRenderTargets> + pending_draw_pass_transfers_; + uint32_t pending_draw_pass_transfer_mask_ = 0; + uint32_t pending_draw_pass_full_overwrite_mask_ = 0; + uint32_t pending_draw_pass_load_dontcare_mask_ = 0; + mutable TelemetryStats telemetry_; MTL::DepthStencilState* transfer_depth_state_ = nullptr; MTL::DepthStencilState* transfer_depth_state_none_ = nullptr; MTL::DepthStencilState* transfer_depth_clear_state_ = nullptr; MTL::DepthStencilState* transfer_stencil_clear_state_ = nullptr; + MTL::DepthStencilState* transfer_stencil_output_state_ = nullptr; MTL::DepthStencilState* transfer_stencil_bit_states_[8] = {}; MTL::Buffer* transfer_dummy_buffer_ = nullptr; MTL::Texture* transfer_dummy_color_float_[3] = {}; @@ -411,6 +522,7 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { MTL::Texture* transfer_dummy_depth_[3] = {}; MTL::Texture* transfer_dummy_stencil_[3] = {}; bool msaa_2x_supported_ = true; + uint64_t bindless_resources_serial_ = 1; // Current render targets - updated by base class Update() call @@ -428,20 +540,15 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { MTL::RenderPassDescriptor* cached_render_pass_descriptor_ = nullptr; bool render_pass_descriptor_dirty_ = true; uint32_t cached_render_pass_descriptor_sample_count_ = 0; + bool cached_render_pass_descriptor_fallback_depth_required_ = false; - // Dummy render target for when no render targets are bound - struct DummyColorTargetEntry { - std::unique_ptr target; - uint64_t last_used_frame = 0; - uint64_t last_cleared_frame = 0; - }; - mutable std::unordered_map - dummy_color_targets_; + // Transient dummy color target used for passes with no bound color render + // targets, so the render pass/pipeline still has a matching color output. + // Cached by shape/format and recreated only when the required shape changes. + // (D3D12 needs no analog.) + std::unique_ptr dummy_color_target_owner_; + uint64_t dummy_color_target_shape_key_ = 0; mutable MetalRenderTarget* dummy_color_target_ = nullptr; - uint64_t frame_id_ = 0; - - // Track which render targets have been cleared this frame - std::unordered_set cleared_render_targets_this_frame_; // Debug helper to log a small region of the current color RT0. // Helper methods @@ -453,6 +560,8 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { MTL::Texture* CreateDepthTexture(uint32_t width, uint32_t height, xenos::DepthRenderTargetFormat format, uint32_t samples); + MTL::Texture* CreateTransientDepthTexture(uint32_t width, uint32_t height, + uint32_t samples); MTL::Texture* GetStencilTextureView(MetalRenderTarget* render_target); MTL::PixelFormat GetColorResourcePixelFormat( @@ -463,19 +572,60 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { xenos::ColorRenderTargetFormat format, bool* is_integer_out) const; MTL::PixelFormat GetDepthPixelFormat( xenos::DepthRenderTargetFormat format) const; + TransferShaderKey GetTransferShaderKey( + RenderTargetKey source_key, RenderTargetKey dest_key, + const RenderTargetKey* host_depth_source_key, + bool host_depth_source_is_copy, bool stencil_bit, + bool dest_sample_id_from_sample_default) const; + bool GetActiveTransferAttachmentFormats( + MTL::RenderPassDescriptor* pass_descriptor, + TransferAttachmentFormats& attachment_formats_out) const; + bool GetCurrentTransferAttachmentFormats( + TransferAttachmentFormats& attachment_formats_out) const; + bool CanQueueDrawPassTransfers(uint32_t render_target_index, + RenderTarget* const* render_targets, + const std::vector& transfers) const; + bool PendingDrawPassTransfersFullyOverwriteTarget( + uint32_t render_target_index, RenderTarget* render_target, + const std::vector& transfers) const; + bool BuildTransferRectanglePlans( + RenderTargetKey dest_key, const std::vector& transfers, + const Transfer::Rectangle* cutout, bool require_all_rectangles, + std::vector& transfer_rectangles_out) const; + bool PreflightPendingDrawPassTransfers( + const TransferAttachmentFormats& attachment_formats); + bool PreflightPendingDrawPassTransfers( + MTL::RenderPassDescriptor* pass_descriptor); + bool BuildCurrentAttachmentPlan(uint32_t expected_sample_count, + AttachmentPlan& plan_out); + void MarkRenderPassDescriptorDirty(); + bool IsRenderPassDescriptorCompatibleSlow( + MTL::RenderPassDescriptor* pass_descriptor, + uint32_t expected_sample_count, + bool fallback_depth_attachment_required) const; + void ClearPendingDrawPassTransfers(); // EDRAM compute shader setup bool InitializeEdramComputeShaders(); void ShutdownEdramComputeShaders(); + void InitializeDirectHostResolvePipelines(bool draw_resolution_scaled); + void ResetDirectHostResolvePipelines(bool release_existing); - // Transfer pipeline setup (host RT ownership transfers) - Metal analogue of - // D3D12RenderTargetCache::GetOrCreateTransferPipelines. + // Transfer pipeline setup for host RT ownership transfers. The shader keys + // intentionally mirror D3D12RenderTargetCache transfer modes. MTL::RenderPipelineState* GetOrCreateTransferPipelines( const TransferShaderKey& key, MTL::PixelFormat dest_format, - bool dest_is_uint, bool tile_instanced); + bool dest_is_uint, bool native_stencil_output, + uint32_t color_attachment_index = 0, + const TransferColorAttachmentFormats* color_attachment_formats = nullptr, + MTL::PixelFormat depth_attachment_format = MTL::PixelFormatInvalid, + MTL::PixelFormat stencil_attachment_format = MTL::PixelFormatInvalid); MTL::RenderPipelineState* GetOrCreateTransferClearPipeline( MTL::PixelFormat dest_format, bool dest_is_uint, bool is_depth, - uint32_t sample_count); + uint32_t sample_count, uint32_t color_attachment_index = 0, + const TransferColorAttachmentFormats* color_attachment_formats = nullptr, + MTL::PixelFormat depth_attachment_format = MTL::PixelFormatInvalid, + MTL::PixelFormat stencil_attachment_format = MTL::PixelFormatInvalid); MTL::Library* GetOrCreateTransferLibrary(); MTL::Texture* GetTransferDummyTexture(MTL::PixelFormat format, uint32_t sample_count); @@ -488,33 +638,58 @@ class MetalRenderTargetCache final : public gpu::RenderTargetCache { MTL::DepthStencilState* GetTransferNoDepthStencilState(); MTL::DepthStencilState* GetTransferDepthClearState(); MTL::DepthStencilState* GetTransferStencilClearState(); + MTL::DepthStencilState* GetTransferStencilOutputState(); MTL::DepthStencilState* GetTransferStencilBitState(uint32_t bit); + // Shared builder for the transfer depth/stencil states above. The stencil + // half (when enabled) is always Always/Keep/Keep/Replace with readMask 0xFF; + // callers vary only the depth compare/write and the stencil write mask. + MTL::DepthStencilState* BuildTransferDepthStencilState( + MTL::CompareFunction depth_compare, bool depth_write, bool stencil_enable, + uint32_t stencil_write_mask); - // EDRAM tile operations - - void LoadTiledData(MTL::CommandBuffer* command_buffer, MTL::Texture* texture, - uint32_t edram_base, uint32_t pitch_tiles, - uint32_t height_tiles, bool is_depth); - - void StoreTiledData(MTL::CommandBuffer* command_buffer, MTL::Texture* texture, - uint32_t edram_base, uint32_t pitch_tiles, - uint32_t height_tiles, bool is_depth); - - // Ownership transfer support - copies data between render targets when - // EDRAM regions are aliased between different RT configurations. - // This mirrors D3D12/Vulkan's PerformTransfersAndResolveClears. - void PerformTransfersAndResolveClears( + // Sole host-side transfer and resolve-clear execution entry point. + bool PerformTransfersAndResolveClears( uint32_t render_target_count, RenderTarget* const* render_targets, const std::vector* render_target_transfers, const uint64_t* render_target_resolve_clear_values = nullptr, const Transfer::Rectangle* resolve_clear_rectangle = nullptr, - MTL::CommandBuffer* command_buffer = nullptr); + MTL::CommandBuffer* command_buffer = nullptr, + MTL::RenderCommandEncoder* active_render_encoder = nullptr, + MTL::RenderPassDescriptor* active_render_pass_descriptor = nullptr, + DrawPassTransferEncoderMutationMask* mutations_out = nullptr); // Writes contents of host render targets within rectangles from // ResolveInfo::GetCopyEdramTileSpan to edram_buffer_. void DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch, - MTL::CommandBuffer* command_buffer = nullptr); + MTL::CommandBuffer* command_buffer = nullptr, + const char* encoder_label = nullptr); + + bool TryDirectHostResolveCopy( + const draw_util::ResolveInfo& resolve_info, + const draw_util::ResolveCopyShaderConstants& copy_constants, + draw_util::ResolveCopyShaderIndex copy_shader, uint32_t dump_base, + uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch, + MTL::CommandBuffer* command_buffer, uint32_t& written_address, + uint32_t& written_length); + struct ResolveDestinationBuffer { + MTL::Buffer* buffer = nullptr; + size_t offset = 0; + size_t length = 0; + }; + bool PrepareResolveDestinationBuffer( + const draw_util::ResolveInfo& resolve_info, bool draw_resolution_scaled, + ResolveDestinationBuffer& destination); + MTL::ComputePipelineState* GetResolvePipeline( + draw_util::ResolveCopyShaderIndex copy_shader, bool scaled) const; + MTL::ComputePipelineState* GetDirectHostResolvePipeline( + bool is_64bpp, xenos::MsaaSamples msaa_samples, bool scaled, + bool source_is_uint) const; + MTL::ComputePipelineState* GetDirectHostColorFullResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled, bool source_is_uint, + draw_util::ResolveCopyShaderIndex copy_shader) const; + MTL::ComputePipelineState* GetDirectHostDepthResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled) const; }; } // namespace metal diff --git a/src/xenia/gpu/metal/metal_shader.cc b/src/xenia/gpu/metal/metal_shader.cc new file mode 100644 index 000000000..dd3fe4ff7 --- /dev/null +++ b/src/xenia/gpu/metal/metal_shader.cc @@ -0,0 +1,295 @@ +/** + ****************************************************************************** + * 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.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef DISPATCH_DATA_DESTRUCTOR_NONE +#define DISPATCH_DATA_DESTRUCTOR_NONE DISPATCH_DATA_DESTRUCTOR_DEFAULT +#endif + +#include "xenia/base/assert.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/base/string.h" +#include "xenia/gpu/dxbc_shader.h" +#include "xenia/gpu/gpu_flags.h" +#include "xenia/gpu/metal/dxbc_to_dxil_converter.h" +#include "xenia/gpu/metal/metal_shader_converter.h" +#include "xenia/ui/metal/metal_api.h" + +namespace xe { +namespace gpu { +namespace metal { + +namespace { + +constexpr uint32_t kAllTranslatedCbvMask = + (uint32_t(1) + << (uint32_t(DxbcShaderTranslator::CbufferRegister::kDescriptorIndices) + + 1)) - + 1; +constexpr uint32_t kDescriptorIndicesCbvSizeBytes = 4096; + +void MarkFetchConstantDword(DxbcShader::FetchConstantDwordMask& mask, + uint32_t dword_index) { + if (dword_index >= DxbcShader::kFetchConstantDwordCount) { + assert_always(); + return; + } + mask[dword_index >> 5] |= uint32_t(1) << (dword_index & 31); +} + +void MarkVertexFetchConstant(DxbcShader::FetchConstantDwordMask& mask, + uint32_t fetch_constant_index) { + if (fetch_constant_index >= xenos::kVertexFetchConstantCount) { + assert_always(); + return; + } + const uint32_t dword_index = fetch_constant_index * 2; + MarkFetchConstantDword(mask, dword_index); + MarkFetchConstantDword(mask, dword_index + 1); +} + +void MarkTextureFetchConstant(DxbcShader::FetchConstantDwordMask& mask, + uint32_t fetch_constant_index) { + if (fetch_constant_index >= xenos::kTextureFetchConstantCount) { + assert_always(); + return; + } + const uint32_t dword_index = fetch_constant_index * 6; + for (uint32_t i = 0; i < 6; ++i) { + MarkFetchConstantDword(mask, dword_index + i); + } +} + +} // namespace + +MetalShader::MetalShader(xenos::ShaderType shader_type, + uint64_t ucode_data_hash, const uint32_t* ucode_dwords, + size_t ucode_dword_count, + std::endian ucode_source_endian) + : DxbcShader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count, + ucode_source_endian) {} + +const MetalShader::DrawConstantMetadata& MetalShader::GetDrawConstantMetadata() + const { + std::call_once(draw_constant_metadata_once_, [this]() { + DrawConstantMetadata metadata = {}; + + const Shader::ConstantRegisterMap& constant_map = 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); + MarkVertexFetchConstant(metadata.shader_fetch_constant_dword_mask, + i * 32 + bit_index); + } + } + + for (const Shader::VertexBinding& binding : vertex_bindings()) { + MarkVertexFetchConstant(metadata.shader_fetch_constant_dword_mask, + binding.fetch_constant); + } + for (const Shader::TextureBinding& binding : texture_bindings()) { + MarkTextureFetchConstant(metadata.shader_fetch_constant_dword_mask, + binding.fetch_constant); + } + + constexpr uint32_t kMaxDescriptorIndexWords = + kDescriptorIndicesCbvSizeBytes / sizeof(uint32_t); + metadata.descriptor_indices_word_count = 1; + for (const DxbcShader::TextureBinding& binding : + GetTextureBindingsAfterTranslation()) { + assert_true(binding.bindless_descriptor_index < kMaxDescriptorIndexWords); + if (binding.bindless_descriptor_index < kMaxDescriptorIndexWords) { + metadata.descriptor_indices_word_count = + std::max(metadata.descriptor_indices_word_count, + binding.bindless_descriptor_index + 1); + } + } + for (const DxbcShader::SamplerBinding& binding : + GetSamplerBindingsAfterTranslation()) { + assert_true(binding.bindless_descriptor_index < kMaxDescriptorIndexWords); + if (binding.bindless_descriptor_index < kMaxDescriptorIndexWords) { + metadata.descriptor_indices_word_count = + std::max(metadata.descriptor_indices_word_count, + binding.bindless_descriptor_index + 1); + } + } + + const uint32_t used_cbuffer_mask = GetUsedCbufferMaskAfterTranslation(); + metadata.active_cbv_mask = used_cbuffer_mask + ? (used_cbuffer_mask & kAllTranslatedCbvMask) + : kAllTranslatedCbvMask; + + draw_constant_metadata_ = metadata; + }); + return draw_constant_metadata_; +} + +MetalShader::MetalTranslation::~MetalTranslation() { + if (metal_function_) { + metal_function_->release(); + metal_function_ = nullptr; + } + if (metal_library_) { + metal_library_->release(); + metal_library_ = nullptr; + } +} + +bool MetalShader::MetalTranslation::InstallMetal( + MTL::Device* device, const MetalStageCompileResult& result, + uint64_t* out_new_library_ms, bool* out_new_library_created) { + if (out_new_library_ms) { + *out_new_library_ms = 0; + } + if (out_new_library_created) { + *out_new_library_created = false; + } + if (!device) { + XELOGE("MetalShader: No Metal device provided"); + return false; + } + std::lock_guard lock(metal_translation_mutex_); + if (metal_function_) { + return true; + } + if (!result.success || result.metallib_data.empty()) { + XELOGE("MetalShader: invalid stage compile result: {}", + result.error_message); + return false; + } + function_name_ = result.function_name; + metallib_data_ = result.metallib_data; + + // Debug: Dump shader artifacts (DXBC, DXIL, MetalLib) to files when enabled. + static std::atomic shader_dump_counter{0}; + if (!cvars::dump_shaders.empty()) { + std::filesystem::path base_dir = cvars::dump_shaders / "metal_shaders"; + + char filename[128]; + const char* type_str = + (shader().type() == xenos::ShaderType::kVertex) ? "vs" : "ps"; + int counter = shader_dump_counter++; + + // Dump DXBC (translated binary from DXBC translator) + const auto& dxbc_data = translated_binary(); + if (!dxbc_data.empty()) { + snprintf(filename, sizeof(filename), "shader_%d_%s.dxbc", counter, + type_str); + std::filesystem::path dxbc_path = base_dir / filename; + xe::filesystem::CreateParentFolder(dxbc_path); + FILE* f = xe::filesystem::OpenFile(dxbc_path, "wb"); + if (f) { + fwrite(dxbc_data.data(), 1, dxbc_data.size(), f); + fclose(f); + } + } + + // Dump DXIL + if (!dxil_data_.empty()) { + snprintf(filename, sizeof(filename), "shader_%d_%s.dxil", counter, + type_str); + std::filesystem::path dxil_path = base_dir / filename; + xe::filesystem::CreateParentFolder(dxil_path); + FILE* f = xe::filesystem::OpenFile(dxil_path, "wb"); + if (f) { + fwrite(dxil_data_.data(), 1, dxil_data_.size(), f); + fclose(f); + } + } + + // Dump MetalLib + if (!metallib_data_.empty()) { + snprintf(filename, sizeof(filename), "shader_%d_%s.metallib", counter, + type_str); + std::filesystem::path metallib_path = base_dir / filename; + xe::filesystem::CreateParentFolder(metallib_path); + FILE* f = xe::filesystem::OpenFile(metallib_path, "wb"); + if (f) { + fwrite(metallib_data_.data(), 1, metallib_data_.size(), f); + fclose(f); + } + } + } + + NS::Error* error = nullptr; + dispatch_data_t data = + dispatch_data_create(metallib_data_.data(), metallib_data_.size(), + nullptr, DISPATCH_DATA_DESTRUCTOR_NONE); + + auto library_start = std::chrono::steady_clock::now(); + metal_library_ = device->newLibrary(data, &error); + auto library_end = std::chrono::steady_clock::now(); + if (out_new_library_created) { + *out_new_library_created = true; + } + if (out_new_library_ms) { + *out_new_library_ms = + uint64_t(std::chrono::duration_cast( + library_end - library_start) + .count()); + } + dispatch_release(data); + + if (!metal_library_) { + if (error) { + XELOGE("MetalShader: Failed to create Metal library: {}", + error->localizedDescription()->utf8String()); + } else { + XELOGE("MetalShader: Failed to create Metal library (unknown error)"); + } + return false; + } + + NS::String* function_name = + NS::String::string(result.function_name.c_str(), NS::UTF8StringEncoding); + + metal_function_ = metal_library_->newFunction(function_name); + + if (!metal_function_) { + XELOGE("MetalShader: Function '{}' not found in metallib", function_name_); + return false; + } + + return true; +} + +void MetalShader::MetalTranslation::SetDxilData( + std::vector dxil_data) { + std::lock_guard lock(metal_translation_mutex_); + dxil_data_ = std::move(dxil_data); +} + +std::vector MetalShader::MetalTranslation::GetDxilDataCopy() const { + std::lock_guard lock(metal_translation_mutex_); + return dxil_data_; +} + +Shader::Translation* MetalShader::CreateTranslationInstance( + uint64_t modification) { + return new MetalTranslation(*this, modification); +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_shader.h b/src/xenia/gpu/metal/metal_shader.h new file mode 100644 index 000000000..5acabcd7a --- /dev/null +++ b/src/xenia/gpu/metal/metal_shader.h @@ -0,0 +1,126 @@ +/** + ****************************************************************************** + * 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_H_ +#define XENIA_GPU_METAL_METAL_SHADER_H_ + +#include +#include +#include + +#include "xenia/gpu/dxbc_shader.h" +#include "xenia/ui/metal/metal_api.h" + +namespace xe { +namespace gpu { +namespace metal { + +class DxbcToDxilConverter; +class MetalShaderConverter; +struct MetalStageCompileResult; + +class MetalShader : public DxbcShader { + public: + class MetalTranslation : public DxbcTranslation { + public: + MetalTranslation(MetalShader& shader, uint64_t modification) + : DxbcTranslation(shader, modification) {} + ~MetalTranslation(); + + // Install a stage-compile result as Metal library/function state. + bool InstallMetal(MTL::Device* device, + const MetalStageCompileResult& result, + uint64_t* out_new_library_ms = nullptr, + bool* out_new_library_created = nullptr); + + void SetDxilData(std::vector dxil_data); + std::vector GetDxilDataCopy() const; + + // Get the Metal library (contains compiled shader) + MTL::Library* metal_library() const { return metal_library_; } + + // Get the Metal function (shader entry point) + MTL::Function* metal_function() const { return metal_function_; } + + // Check if translation succeeded + bool is_valid() const { return metal_function_ != nullptr; } + + // Thread-safe translation claiming for async pipeline compilation. + // Returns true if this thread successfully claimed translation rights. + // Returns false if another thread already claimed it. + bool TryClaimTranslation() { + return !translation_claimed_.test_and_set(std::memory_order_acq_rel); + } + + // Get intermediate data for debugging + const std::vector& dxil_data() const { return dxil_data_; } + const std::vector& metallib_data() const { return metallib_data_; } + const std::string& function_name() const { return function_name_; } + + private: + std::atomic_flag translation_claimed_ = ATOMIC_FLAG_INIT; + mutable std::mutex metal_translation_mutex_; + MTL::Library* metal_library_ = nullptr; + MTL::Function* metal_function_ = nullptr; + std::vector dxil_data_; + std::vector metallib_data_; + std::string function_name_; + }; + + MetalShader(xenos::ShaderType shader_type, uint64_t ucode_data_hash, + const uint32_t* ucode_dwords, size_t ucode_dword_count, + std::endian ucode_source_endian = std::endian::big); + + struct DrawConstantMetadata { + DxbcShader::FetchConstantDwordMask shader_fetch_constant_dword_mask = {}; + uint32_t descriptor_indices_word_count = 1; + uint32_t active_cbv_mask = 0; + }; + + const DrawConstantMetadata& GetDrawConstantMetadata() const; + + // For owning subsystem like the pipeline cache, accessors for unique + // identifiers (used instead of hashes to make sure collisions can't happen) + // of binding layouts used by the shader, for invalidation if a shader with an + // incompatible layout was bound. + size_t GetTextureBindingLayoutUserUID() const { + return texture_binding_layout_user_uid_; + } + size_t GetSamplerBindingLayoutUserUID() const { + return sampler_binding_layout_user_uid_; + } + // Modifications of the same shader can be translated on different threads. + // The "set" function must only be called if "enter" returned true - these are + // set up only once. + bool EnterBindingLayoutUserUIDSetup() { + return !binding_layout_user_uids_set_up_.test_and_set(); + } + void SetTextureBindingLayoutUserUID(size_t uid) { + texture_binding_layout_user_uid_ = uid; + } + void SetSamplerBindingLayoutUserUID(size_t uid) { + sampler_binding_layout_user_uid_ = uid; + } + + protected: + Translation* CreateTranslationInstance(uint64_t modification) override; + + private: + std::atomic_flag binding_layout_user_uids_set_up_ = ATOMIC_FLAG_INIT; + size_t texture_binding_layout_user_uid_ = 0; + size_t sampler_binding_layout_user_uid_ = 0; + mutable std::once_flag draw_constant_metadata_once_; + mutable DrawConstantMetadata draw_constant_metadata_ = {}; +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_SHADER_H_ diff --git a/src/xenia/gpu/metal/metal_shader_cache.cc b/src/xenia/gpu/metal/metal_shader_cache.cc index a11f4f0a8..fe5c17c7c 100644 --- a/src/xenia/gpu/metal/metal_shader_cache.cc +++ b/src/xenia/gpu/metal/metal_shader_cache.cc @@ -10,236 +10,618 @@ #include "xenia/gpu/metal/metal_shader_cache.h" #include +#include #include #include -#include #include +#include #include "third_party/xxhash/xxhash.h" +#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" +#include "xenia/base/xxhash.h" namespace xe { namespace gpu { namespace metal { -std::unique_ptr g_metal_shader_cache = - std::make_unique(); +std::unique_ptr g_metal_artifact_store = + std::make_unique(); namespace { -constexpr uint32_t kCacheFileMagic = 0x4D4C4358; // 'XCLM' -constexpr uint32_t kCacheFileVersion = 1; +constexpr uint32_t kArtifactFileMagic = 0x46414D58; // 'XMAF' +constexpr uint32_t kArtifactRecordMagic = 0x52414D58; // 'XMAR' +constexpr uint32_t kMaxArtifactPayloadSize = 128u * 1024u * 1024u; -struct CacheFileHeader { +static constexpr size_t kStageCompileSerializedKeySize = + MetalStageCompileKey::kSerializedWordCount * sizeof(uint64_t); +static constexpr size_t kDxilBytecodeSerializedKeySize = + MetalDxilBytecodeKey::kSerializedWordCount * sizeof(uint64_t); + +XEPACKEDSTRUCT(ArtifactFileHeader, { uint32_t magic; uint32_t version; - uint64_t cache_key; - uint32_t function_name_length; - uint32_t metallib_size; -}; +}); -static_assert(sizeof(CacheFileHeader) == 24, "Unexpected header packing."); +XEPACKEDSTRUCT(ArtifactRecordHeader, { + uint32_t magic; + uint32_t version; + uint64_t primary_hash; + uint64_t modification; + uint32_t stage; + uint32_t kind; + uint32_t payload_size; + uint32_t reserved; + uint64_t payload_hash; +}); + +XEPACKEDSTRUCT(StageCompilePayloadHeader, { + uint32_t key_size; + uint32_t function_name_size; + uint32_t error_message_size; + uint32_t metallib_size; + uint32_t stage_in_metallib_size; + uint32_t success; + uint32_t has_reflection; + uint32_t has_mesh_stage; + uint32_t has_geometry_stage; + uint32_t vertex_output_size_in_bytes; + uint32_t vertex_input_count; + uint32_t gs_max_input_primitives_per_mesh_threadgroup; + uint32_t function_constant_count; + uint32_t has_hull_info; + uint32_t hs_max_patches_per_object_threadgroup; + uint32_t hs_max_object_threads_per_patch; + uint32_t hs_patch_constants_size; + uint32_t hs_input_control_point_count; + uint32_t hs_output_control_point_count; + uint32_t hs_output_control_point_size; + uint32_t hs_tessellator_domain; + uint32_t hs_tessellator_partitioning; + uint32_t hs_tessellator_output_primitive; + uint32_t hs_tessellation_type_half; + float hs_max_tessellation_factor; + uint32_t has_domain_info; + uint32_t ds_max_input_prims_per_mesh_threadgroup; + uint32_t ds_input_control_point_count; + uint32_t ds_input_control_point_size; + uint32_t ds_patch_constants_size; + uint32_t ds_tessellator_domain; + uint32_t ds_tessellation_type_half; +}); + +XEPACKEDSTRUCT(StageCompileStringRecord, { + uint32_t string_size; + uint32_t value; +}); + +XEPACKEDSTRUCT(DxilBytecodePayloadHeader, { + uint32_t key_size; + uint32_t dxil_size; +}); + +template +bool ReadPod(const uint8_t*& ptr, const uint8_t* end, T* out) { + if (size_t(end - ptr) < sizeof(T)) { + return false; + } + std::memcpy(out, ptr, sizeof(T)); + ptr += sizeof(T); + return true; +} + +bool ReadBytes(const uint8_t*& ptr, const uint8_t* end, void* out, + size_t size) { + if (size_t(end - ptr) < size) { + return false; + } + std::memcpy(out, ptr, size); + ptr += size; + return true; +} + +bool ReadString(const uint8_t*& ptr, const uint8_t* end, size_t size, + std::string* out) { + if (!out || size_t(end - ptr) < size) { + return false; + } + out->assign(reinterpret_cast(ptr), size); + ptr += size; + return true; +} + +void AppendBytes(std::vector& out, const void* data, size_t size) { + if (!size) { + return; + } + const uint8_t* bytes = static_cast(data); + out.insert(out.end(), bytes, bytes + size); +} + +void AppendStringRecord(std::vector& out, std::string_view value, + uint32_t metadata_value) { + StageCompileStringRecord record = {}; + record.string_size = uint32_t(value.size()); + record.value = metadata_value; + AppendBytes(out, &record, sizeof(record)); + AppendBytes(out, value.data(), value.size()); +} } // namespace -void MetalShaderCache::Initialize(const std::filesystem::path& cache_dir) { - std::lock_guard lock(mutex_); - Shutdown(); - cache_dir_ = cache_dir; - std::error_code ec; - std::filesystem::create_directories(cache_dir_, ec); - if (ec) { - XELOGW("MetalShaderCache: Failed to create cache directory {}: {}", - cache_dir_.string(), ec.message()); - initialized_ = false; - return; - } - initialized_ = true; +size_t MetalArtifactStore::ArtifactKeyHasher::operator()( + const ArtifactKey& key) const { + const uint64_t words[] = {key.primary_hash, key.modification, + uint64_t(key.stage), uint64_t(key.kind)}; + return size_t(XXH3_64bits(words, sizeof(words))); } -void MetalShaderCache::Shutdown() { - cache_.clear(); - cache_dir_.clear(); +MetalArtifactStore::ArtifactKey MetalArtifactStore::MakeStageCompileKey( + const MetalStageCompileKey& stage_key) { + const auto words = stage_key.SerializeWords(); + XXH128_hash_t hash = + XXH3_128bits(words.data(), words.size() * sizeof(uint64_t)); + ArtifactKey key; + key.primary_hash = hash.low64; + key.modification = hash.high64; + key.stage = stage_key.stage; + key.kind = ArtifactKind::kStageCompile; + return key; +} + +MetalArtifactStore::ArtifactKey MetalArtifactStore::MakeDxilBytecodeKey( + const MetalDxilBytecodeKey& dxil_key) { + const auto words = dxil_key.SerializeWords(); + XXH128_hash_t hash = + XXH3_128bits(words.data(), words.size() * sizeof(uint64_t)); + ArtifactKey key; + key.primary_hash = hash.low64; + key.modification = hash.high64; + key.stage = 0; + key.kind = ArtifactKind::kDxilBytecode; + return key; +} + +void MetalArtifactStore::Initialize( + const std::filesystem::path& artifact_path) { + std::lock_guard lock(mutex_); + Shutdown(); + artifact_path_ = artifact_path; + xe::filesystem::CreateParentFolder(artifact_path_); + artifact_file_ = xe::filesystem::OpenFile(artifact_path_, "a+b"); + if (!artifact_file_) { + XELOGW("MetalArtifactStore: failed to open {}", + xe::path_to_utf8(artifact_path_)); + return; + } + initialized_ = LoadIndex(); + if (!initialized_) { + std::fclose(artifact_file_); + artifact_file_ = nullptr; + index_.clear(); + artifact_path_.clear(); + } +} + +void MetalArtifactStore::Shutdown() { + if (artifact_file_) { + std::fflush(artifact_file_); + std::fclose(artifact_file_); + artifact_file_ = nullptr; + } + index_.clear(); + artifact_path_.clear(); initialized_ = false; } -uint64_t MetalShaderCache::GetCacheKey(uint64_t ucode_hash, - uint64_t modification, uint32_t stage) { - struct KeyData { - uint64_t ucode_hash; - uint64_t modification; - uint32_t stage; - uint32_t reserved; - } key_data = {ucode_hash, modification, stage, 0}; - return XXH3_64bits(&key_data, sizeof(key_data)); -} - -MetalShaderCache::CacheStats MetalShaderCache::GetStats() const { - std::lock_guard lock(mutex_); - CacheStats stats; - stats.entry_count = cache_.size(); - stats.memory_entry_count = cache_.size(); - for (const auto& it : cache_) { - stats.memory_total_bytes += it.second.metallib_data.size(); +bool MetalArtifactStore::LoadIndex() { + if (!artifact_file_) { + return false; } - stats.total_bytes = stats.memory_total_bytes; - return stats; + xe::filesystem::Seek(artifact_file_, 0, SEEK_SET); + + ArtifactFileHeader header = {}; + if (std::fread(&header, sizeof(header), 1, artifact_file_) != 1 || + header.magic != kArtifactFileMagic || header.version != kStorageVersion) { + xe::filesystem::TruncateStdioFile(artifact_file_, 0); + header.magic = kArtifactFileMagic; + header.version = kStorageVersion; + if (std::fwrite(&header, sizeof(header), 1, artifact_file_) != 1) { + return false; + } + std::fflush(artifact_file_); + return true; + } + + uint64_t valid_bytes = sizeof(header); + while (true) { + ArtifactRecordHeader record = {}; + if (std::fread(&record, sizeof(record), 1, artifact_file_) != 1) { + break; + } + if (record.magic != kArtifactRecordMagic || + record.version != kStorageVersion || + record.payload_size > kMaxArtifactPayloadSize) { + break; + } + const int64_t payload_offset = xe::filesystem::Tell(artifact_file_); + if (payload_offset < 0) { + break; + } + if (!xe::filesystem::Seek(artifact_file_, record.payload_size, SEEK_CUR)) { + break; + } + ArtifactKey key; + key.primary_hash = record.primary_hash; + key.modification = record.modification; + key.stage = record.stage; + key.kind = static_cast(record.kind); + index_[key] = RecordLocation{uint64_t(payload_offset), record.payload_size, + record.payload_hash}; + valid_bytes = uint64_t(payload_offset) + record.payload_size; + } + + xe::filesystem::TruncateStdioFile(artifact_file_, valid_bytes); + xe::filesystem::Seek(artifact_file_, 0, SEEK_END); + return true; } -bool MetalShaderCache::Load(uint64_t cache_key, CachedMetallib* out) { +bool MetalArtifactStore::ReadPayload(const RecordLocation& location, + std::vector* out) { + if (!artifact_file_ || !out || + location.payload_size > kMaxArtifactPayloadSize) { + return false; + } + out->resize(location.payload_size); + if (!xe::filesystem::Seek(artifact_file_, int64_t(location.payload_offset), + SEEK_SET)) { + return false; + } + if (location.payload_size && + std::fread(out->data(), location.payload_size, 1, artifact_file_) != 1) { + return false; + } + if (XXH3_64bits(out->data(), out->size()) != location.payload_hash) { + return false; + } + return true; +} + +void MetalArtifactStore::StorePayload(const ArtifactKey& key, + const uint8_t* payload, + size_t payload_size) { + if (!artifact_file_ || !payload || payload_size == 0 || + payload_size > kMaxArtifactPayloadSize) { + return; + } + ArtifactRecordHeader record = {}; + record.magic = kArtifactRecordMagic; + record.version = kStorageVersion; + record.primary_hash = key.primary_hash; + record.modification = key.modification; + record.stage = key.stage; + record.kind = uint32_t(key.kind); + record.payload_size = uint32_t(payload_size); + record.payload_hash = XXH3_64bits(payload, payload_size); + + xe::filesystem::Seek(artifact_file_, 0, SEEK_END); + if (std::fwrite(&record, sizeof(record), 1, artifact_file_) != 1) { + return; + } + const int64_t payload_offset = xe::filesystem::Tell(artifact_file_); + if (payload_offset < 0) { + return; + } + if (std::fwrite(payload, payload_size, 1, artifact_file_) != 1) { + return; + } + std::fflush(artifact_file_); + index_[key] = RecordLocation{uint64_t(payload_offset), uint32_t(payload_size), + record.payload_hash}; +} + +bool MetalArtifactStore::LoadStageCompile(const MetalStageCompileKey& key, + MetalStageCompileResult* out) { if (!out) { return false; } + std::vector payload; { std::lock_guard lock(mutex_); if (!initialized_) { return false; } - auto it = cache_.find(cache_key); - if (it != cache_.end()) { - out->function_name = it->second.function_name; - out->metallib_data = it->second.metallib_data; - return true; + ArtifactKey artifact_key = MakeStageCompileKey(key); + auto it = index_.find(artifact_key); + if (it == index_.end()) { + return false; + } + if (!ReadPayload(it->second, &payload)) { + return false; } } - CachedMetallib disk_entry; - if (!LoadFromDisk(cache_key, &disk_entry)) { + const uint8_t* ptr = payload.data(); + const uint8_t* end = ptr + payload.size(); + StageCompilePayloadHeader header = {}; + if (!ReadPod(ptr, end, &header)) { + return false; + } + if (header.key_size != kStageCompileSerializedKeySize || + header.vertex_input_count > 4096 || + header.function_constant_count > 4096) { return false; } - { - std::lock_guard lock(mutex_); - if (!initialized_) { - return false; - } - MemoryEntry mem; - mem.function_name = disk_entry.function_name; - mem.metallib_data = disk_entry.metallib_data; - cache_.emplace(cache_key, std::move(mem)); + std::array + stored_key_words = {}; + if (!ReadBytes(ptr, end, stored_key_words.data(), + kStageCompileSerializedKeySize) || + stored_key_words != key.SerializeWords()) { + return false; } - *out = std::move(disk_entry); + MetalStageCompileResult result; + result.success = header.success != 0; + result.has_reflection = header.has_reflection != 0; + result.has_mesh_stage = header.has_mesh_stage != 0; + result.has_geometry_stage = header.has_geometry_stage != 0; + if (!ReadString(ptr, end, header.function_name_size, &result.function_name)) { + return false; + } + if (!ReadString(ptr, end, header.error_message_size, &result.error_message)) { + return false; + } + result.metallib_data.resize(header.metallib_size); + if (header.metallib_size && + !ReadBytes(ptr, end, result.metallib_data.data(), header.metallib_size)) { + return false; + } + result.stage_in_metallib.resize(header.stage_in_metallib_size); + if (header.stage_in_metallib_size && + !ReadBytes(ptr, end, result.stage_in_metallib.data(), + header.stage_in_metallib_size)) { + return false; + } + + result.reflection.vertex_output_size_in_bytes = + header.vertex_output_size_in_bytes; + result.reflection.vertex_input_count = header.vertex_input_count; + result.reflection.gs_max_input_primitives_per_mesh_threadgroup = + header.gs_max_input_primitives_per_mesh_threadgroup; + result.reflection.has_hull_info = header.has_hull_info != 0; + result.reflection.hs_max_patches_per_object_threadgroup = + header.hs_max_patches_per_object_threadgroup; + result.reflection.hs_max_object_threads_per_patch = + header.hs_max_object_threads_per_patch; + result.reflection.hs_patch_constants_size = header.hs_patch_constants_size; + result.reflection.hs_input_control_point_count = + header.hs_input_control_point_count; + result.reflection.hs_output_control_point_count = + header.hs_output_control_point_count; + result.reflection.hs_output_control_point_size = + header.hs_output_control_point_size; + result.reflection.hs_tessellator_domain = header.hs_tessellator_domain; + result.reflection.hs_tessellator_partitioning = + header.hs_tessellator_partitioning; + result.reflection.hs_tessellator_output_primitive = + header.hs_tessellator_output_primitive; + result.reflection.hs_tessellation_type_half = + header.hs_tessellation_type_half != 0; + result.reflection.hs_max_tessellation_factor = + header.hs_max_tessellation_factor; + result.reflection.has_domain_info = header.has_domain_info != 0; + result.reflection.ds_max_input_prims_per_mesh_threadgroup = + header.ds_max_input_prims_per_mesh_threadgroup; + result.reflection.ds_input_control_point_count = + header.ds_input_control_point_count; + result.reflection.ds_input_control_point_size = + header.ds_input_control_point_size; + result.reflection.ds_patch_constants_size = header.ds_patch_constants_size; + result.reflection.ds_tessellator_domain = header.ds_tessellator_domain; + result.reflection.ds_tessellation_type_half = + header.ds_tessellation_type_half != 0; + + result.reflection.vertex_inputs.reserve(header.vertex_input_count); + for (uint32_t i = 0; i < header.vertex_input_count; ++i) { + StageCompileStringRecord record = {}; + if (!ReadPod(ptr, end, &record)) { + return false; + } + MetalShaderReflectionInput input; + input.attribute_index = uint8_t(record.value); + if (!ReadString(ptr, end, record.string_size, &input.name)) { + return false; + } + result.reflection.vertex_inputs.push_back(std::move(input)); + } + result.reflection.function_constants.reserve(header.function_constant_count); + for (uint32_t i = 0; i < header.function_constant_count; ++i) { + StageCompileStringRecord record = {}; + if (!ReadPod(ptr, end, &record)) { + return false; + } + MetalShaderFunctionConstant constant; + constant.type = record.value; + if (!ReadString(ptr, end, record.string_size, &constant.name)) { + return false; + } + result.reflection.function_constants.push_back(std::move(constant)); + } + + if (ptr != end) { + return false; + } + *out = std::move(result); return true; } -void MetalShaderCache::Store(uint64_t cache_key, std::string_view function_name, - const uint8_t* metallib_data, - size_t metallib_size) { - if (!metallib_data || metallib_size == 0) { +void MetalArtifactStore::StoreStageCompile( + const MetalStageCompileKey& key, const MetalStageCompileResult& result) { + if (!result.success || result.metallib_data.empty()) { return; } - CachedMetallib entry; - entry.function_name.assign(function_name.data(), function_name.size()); - entry.metallib_data.resize(metallib_size); - std::memcpy(entry.metallib_data.data(), metallib_data, metallib_size); + StageCompilePayloadHeader header = {}; + const auto key_words = key.SerializeWords(); + header.key_size = uint32_t(key_words.size() * sizeof(uint64_t)); + header.function_name_size = uint32_t(result.function_name.size()); + header.error_message_size = uint32_t(result.error_message.size()); + header.metallib_size = uint32_t(result.metallib_data.size()); + header.stage_in_metallib_size = uint32_t(result.stage_in_metallib.size()); + header.success = result.success ? 1u : 0u; + header.has_reflection = result.has_reflection ? 1u : 0u; + header.has_mesh_stage = result.has_mesh_stage ? 1u : 0u; + header.has_geometry_stage = result.has_geometry_stage ? 1u : 0u; + header.vertex_output_size_in_bytes = + result.reflection.vertex_output_size_in_bytes; + header.vertex_input_count = uint32_t(result.reflection.vertex_inputs.size()); + header.gs_max_input_primitives_per_mesh_threadgroup = + result.reflection.gs_max_input_primitives_per_mesh_threadgroup; + header.function_constant_count = + uint32_t(result.reflection.function_constants.size()); + header.has_hull_info = result.reflection.has_hull_info ? 1u : 0u; + header.hs_max_patches_per_object_threadgroup = + result.reflection.hs_max_patches_per_object_threadgroup; + header.hs_max_object_threads_per_patch = + result.reflection.hs_max_object_threads_per_patch; + header.hs_patch_constants_size = result.reflection.hs_patch_constants_size; + header.hs_input_control_point_count = + result.reflection.hs_input_control_point_count; + header.hs_output_control_point_count = + result.reflection.hs_output_control_point_count; + header.hs_output_control_point_size = + result.reflection.hs_output_control_point_size; + header.hs_tessellator_domain = result.reflection.hs_tessellator_domain; + header.hs_tessellator_partitioning = + result.reflection.hs_tessellator_partitioning; + header.hs_tessellator_output_primitive = + result.reflection.hs_tessellator_output_primitive; + header.hs_tessellation_type_half = + result.reflection.hs_tessellation_type_half ? 1u : 0u; + header.hs_max_tessellation_factor = + result.reflection.hs_max_tessellation_factor; + header.has_domain_info = result.reflection.has_domain_info ? 1u : 0u; + header.ds_max_input_prims_per_mesh_threadgroup = + result.reflection.ds_max_input_prims_per_mesh_threadgroup; + header.ds_input_control_point_count = + result.reflection.ds_input_control_point_count; + header.ds_input_control_point_size = + result.reflection.ds_input_control_point_size; + header.ds_patch_constants_size = result.reflection.ds_patch_constants_size; + header.ds_tessellator_domain = result.reflection.ds_tessellator_domain; + header.ds_tessellation_type_half = + result.reflection.ds_tessellation_type_half ? 1u : 0u; - { - std::lock_guard lock(mutex_); - if (!initialized_) { - return; - } - MemoryEntry mem; - mem.function_name = entry.function_name; - mem.metallib_data = entry.metallib_data; - cache_[cache_key] = std::move(mem); + std::vector payload; + payload.reserve(sizeof(header) + header.key_size + + result.function_name.size() + result.error_message.size() + + result.metallib_data.size() + + result.stage_in_metallib.size()); + AppendBytes(payload, &header, sizeof(header)); + AppendBytes(payload, key_words.data(), header.key_size); + AppendBytes(payload, result.function_name.data(), + result.function_name.size()); + AppendBytes(payload, result.error_message.data(), + result.error_message.size()); + AppendBytes(payload, result.metallib_data.data(), + result.metallib_data.size()); + AppendBytes(payload, result.stage_in_metallib.data(), + result.stage_in_metallib.size()); + for (const MetalShaderReflectionInput& input : + result.reflection.vertex_inputs) { + AppendStringRecord(payload, input.name, input.attribute_index); + } + for (const MetalShaderFunctionConstant& constant : + result.reflection.function_constants) { + AppendStringRecord(payload, constant.name, constant.type); } - StoreToDisk(cache_key, entry); + std::lock_guard lock(mutex_); + if (initialized_) { + StorePayload(MakeStageCompileKey(key), payload.data(), payload.size()); + } } -std::filesystem::path MetalShaderCache::GetDiskPath(uint64_t cache_key) const { - char name[32]; - std::snprintf(name, sizeof(name), "%016llX.metalshcache", - static_cast(cache_key)); - return cache_dir_ / name; -} - -bool MetalShaderCache::LoadFromDisk(uint64_t cache_key, CachedMetallib* out) { - std::filesystem::path path; +bool MetalArtifactStore::LoadDxilBytecode(const MetalDxilBytecodeKey& key, + std::vector* out) { + if (!out) { + return false; + } + std::vector payload; { std::lock_guard lock(mutex_); if (!initialized_) { return false; } - path = GetDiskPath(cache_key); - } - - std::ifstream file(path, std::ios::binary); - if (!file.is_open()) { - return false; - } - - CacheFileHeader hdr = {}; - file.read(reinterpret_cast(&hdr), sizeof(hdr)); - if (!file || hdr.magic != kCacheFileMagic || - hdr.version != kCacheFileVersion || hdr.cache_key != cache_key) { - return false; - } - - if (hdr.function_name_length > 4096 || - hdr.metallib_size > std::numeric_limits::max()) { - return false; - } - - std::string fn; - fn.resize(hdr.function_name_length); - file.read(fn.data(), hdr.function_name_length); - if (!file) { - return false; - } - - std::vector data; - data.resize(hdr.metallib_size); - file.read(reinterpret_cast(data.data()), hdr.metallib_size); - if (!file) { - return false; - } - - out->function_name = std::move(fn); - out->metallib_data = std::move(data); - return true; -} - -bool MetalShaderCache::StoreToDisk(uint64_t cache_key, - const CachedMetallib& in) { - std::filesystem::path path; - { - std::lock_guard lock(mutex_); - if (!initialized_) { + ArtifactKey artifact_key = MakeDxilBytecodeKey(key); + auto it = index_.find(artifact_key); + if (it == index_.end()) { + return false; + } + if (!ReadPayload(it->second, &payload)) { return false; } - path = GetDiskPath(cache_key); } - std::filesystem::path tmp_path = path; - tmp_path += ".tmp"; - - std::ofstream file(tmp_path, std::ios::binary | std::ios::trunc); - if (!file.is_open()) { + const uint8_t* ptr = payload.data(); + const uint8_t* end = ptr + payload.size(); + DxilBytecodePayloadHeader header = {}; + if (!ReadPod(ptr, end, &header)) { + return false; + } + if (header.key_size != kDxilBytecodeSerializedKeySize || + header.dxil_size > kMaxArtifactPayloadSize) { return false; } - CacheFileHeader hdr = {}; - hdr.magic = kCacheFileMagic; - hdr.version = kCacheFileVersion; - hdr.cache_key = cache_key; - hdr.function_name_length = static_cast(in.function_name.size()); - hdr.metallib_size = static_cast(in.metallib_data.size()); - file.write(reinterpret_cast(&hdr), sizeof(hdr)); - file.write(in.function_name.data(), in.function_name.size()); - file.write(reinterpret_cast(in.metallib_data.data()), - in.metallib_data.size()); - file.close(); - - std::error_code ec; - std::filesystem::rename(tmp_path, path, ec); - if (ec) { - std::filesystem::remove(tmp_path, ec); + std::array + stored_key_words = {}; + if (!ReadBytes(ptr, end, stored_key_words.data(), + kDxilBytecodeSerializedKeySize) || + stored_key_words != key.SerializeWords()) { return false; } - return true; + + out->resize(header.dxil_size); + if (header.dxil_size && !ReadBytes(ptr, end, out->data(), header.dxil_size)) { + return false; + } + return ptr == end; +} + +void MetalArtifactStore::StoreDxilBytecode( + const MetalDxilBytecodeKey& key, const std::vector& dxil_data) { + if (dxil_data.empty()) { + return; + } + + const auto key_words = key.SerializeWords(); + DxilBytecodePayloadHeader header = {}; + header.key_size = uint32_t(key_words.size() * sizeof(uint64_t)); + header.dxil_size = uint32_t(dxil_data.size()); + + std::vector payload; + payload.reserve(sizeof(header) + header.key_size + dxil_data.size()); + AppendBytes(payload, &header, sizeof(header)); + AppendBytes(payload, key_words.data(), header.key_size); + AppendBytes(payload, dxil_data.data(), dxil_data.size()); + + std::lock_guard lock(mutex_); + if (initialized_) { + StorePayload(MakeDxilBytecodeKey(key), payload.data(), payload.size()); + } +} + +MetalArtifactStore::Stats MetalArtifactStore::GetStats() const { + std::lock_guard lock(mutex_); + Stats stats; + stats.entry_count = index_.size(); + for (const auto& [key, location] : index_) { + stats.total_payload_bytes += location.payload_size; + } + return stats; } } // namespace metal diff --git a/src/xenia/gpu/metal/metal_shader_cache.h b/src/xenia/gpu/metal/metal_shader_cache.h index 308e0d7e7..9b8b4d317 100644 --- a/src/xenia/gpu/metal/metal_shader_cache.h +++ b/src/xenia/gpu/metal/metal_shader_cache.h @@ -10,7 +10,9 @@ #ifndef XENIA_GPU_METAL_METAL_SHADER_CACHE_H_ #define XENIA_GPU_METAL_METAL_SHADER_CACHE_H_ +#include #include +#include #include #include #include @@ -19,60 +21,109 @@ #include #include +#include "xenia/gpu/metal/metal_stage_compile_cache.h" + namespace xe { namespace gpu { namespace metal { -class MetalShaderCache { +struct MetalDxilBytecodeKey { + static constexpr size_t kSerializedWordCount = 7; + + uint32_t key_version = 1; + uint64_t dxbc_size = 0; + uint64_t dxbc_hash_low = 0; + uint64_t dxbc_hash_high = 0; + uint64_t converter_options_hash = 0; + uint32_t converter_version = 0; + uint32_t reserved = 0; + + std::array SerializeWords() const { + return {{ + key_version, + dxbc_size, + dxbc_hash_low, + dxbc_hash_high, + converter_options_hash, + converter_version, + reserved, + }}; + } +}; + +class MetalArtifactStore { public: - struct CachedMetallib { - std::string function_name; - std::vector metallib_data; + enum class ArtifactKind : uint32_t { + kStageCompile = 3, + kDxilBytecode = 4, }; - struct CacheStats { + struct ArtifactKey { + uint64_t primary_hash = 0; + uint64_t modification = 0; + uint32_t stage = 0; + ArtifactKind kind = ArtifactKind::kStageCompile; + + bool operator==(const ArtifactKey& other) const { + return primary_hash == other.primary_hash && + modification == other.modification && stage == other.stage && + kind == other.kind; + } + }; + + struct ArtifactKeyHasher { + size_t operator()(const ArtifactKey& key) const; + }; + + struct Stats { size_t entry_count = 0; - size_t total_bytes = 0; - size_t memory_entry_count = 0; - size_t memory_total_bytes = 0; + size_t total_payload_bytes = 0; }; - MetalShaderCache() = default; - ~MetalShaderCache() = default; + MetalArtifactStore() = default; + ~MetalArtifactStore() = default; - void Initialize(const std::filesystem::path& cache_dir); + void Initialize(const std::filesystem::path& artifact_path); void Shutdown(); bool IsInitialized() const { return initialized_; } - std::filesystem::path cache_dir() const { return cache_dir_; } + const std::filesystem::path& artifact_path() const { return artifact_path_; } - static uint64_t GetCacheKey(uint64_t ucode_hash, uint64_t modification, - uint32_t stage); + static constexpr uint32_t kStorageVersion = 3; - bool Load(uint64_t cache_key, CachedMetallib* out); - void Store(uint64_t cache_key, std::string_view function_name, - const uint8_t* metallib_data, size_t metallib_size); + static ArtifactKey MakeStageCompileKey(const MetalStageCompileKey& stage_key); + static ArtifactKey MakeDxilBytecodeKey(const MetalDxilBytecodeKey& dxil_key); + bool LoadStageCompile(const MetalStageCompileKey& key, + MetalStageCompileResult* out); + void StoreStageCompile(const MetalStageCompileKey& key, + const MetalStageCompileResult& result); + bool LoadDxilBytecode(const MetalDxilBytecodeKey& key, + std::vector* out); + void StoreDxilBytecode(const MetalDxilBytecodeKey& key, + const std::vector& dxil_data); - CacheStats GetStats() const; + Stats GetStats() const; private: - struct MemoryEntry { - std::string function_name; - std::vector metallib_data; + struct RecordLocation { + uint64_t payload_offset = 0; + uint32_t payload_size = 0; + uint64_t payload_hash = 0; }; - bool LoadFromDisk(uint64_t cache_key, CachedMetallib* out); - bool StoreToDisk(uint64_t cache_key, const CachedMetallib& in); - - std::filesystem::path GetDiskPath(uint64_t cache_key) const; + bool LoadIndex(); + bool ReadPayload(const RecordLocation& location, std::vector* out); + void StorePayload(const ArtifactKey& key, const uint8_t* payload, + size_t payload_size); mutable std::mutex mutex_; bool initialized_ = false; - std::filesystem::path cache_dir_; - std::unordered_map cache_; + std::filesystem::path artifact_path_; + FILE* artifact_file_ = nullptr; + std::unordered_map index_; }; -extern std::unique_ptr g_metal_shader_cache; +extern std::unique_ptr g_metal_artifact_store; } // namespace metal } // namespace gpu diff --git a/src/xenia/gpu/metal/metal_shader_converter.cc b/src/xenia/gpu/metal/metal_shader_converter.cc new file mode 100644 index 000000000..ffeeb694b --- /dev/null +++ b/src/xenia/gpu/metal/metal_shader_converter.cc @@ -0,0 +1,733 @@ +/** + ****************************************************************************** + * 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_converter.h" + +#include "metal_irconverter.h" + +#include "xenia/base/logging.h" +#include "xenia/gpu/gpu_flags.h" +#include "xenia/gpu/metal/metal_stage_compile_cache.h" + +namespace xe { +namespace gpu { +namespace metal { + +constexpr uint32_t kFunctionConstantRegisterSpace = 2147420894u; +constexpr uint32_t kDefaultCompatibilityFlags = + IRCompatibilityFlagForceTextureArray | IRCompatibilityFlagBoundsCheck | + IRCompatibilityFlagVertexPositionInfToNan; +constexpr uint32_t kUnsetCompilerOption = UINT32_MAX; + +MetalShaderConverter::MetalShaderConverter() + : stage_compile_cache_(std::make_unique()) {} + +MetalShaderConverter::~MetalShaderConverter() = default; + +void MetalShaderConverter::SetMinimumTarget(uint32_t gpu_family, uint32_t os, + const std::string& version) { + has_minimum_target_ = true; + minimum_gpu_family_ = gpu_family; + minimum_os_ = os; + minimum_os_version_ = version; +} + +void MetalShaderConverter::PopulateDefaultRequestOptions( + MetalStageCompileRequest& request) const { + if (!request.compatibility_flags) { + request.compatibility_flags = kDefaultCompatibilityFlags; + } + if (!request.function_constant_resource_space) { + request.function_constant_resource_space = kFunctionConstantRegisterSpace; + } + if (!request.has_minimum_target && has_minimum_target_) { + request.has_minimum_target = true; + request.minimum_gpu_family = minimum_gpu_family_; + request.minimum_os = minimum_os_; + request.minimum_os_version = minimum_os_version_; + } + if (!request.root_signature_abi_version) { + request.root_signature_abi_version = kMetalRootSignatureAbiVersion; + } +} + +std::shared_ptr +MetalShaderConverter::CompileStage(const MetalStageCompileRequest& request) { + MetalStageCompileRequest normalized = request; + PopulateDefaultRequestOptions(normalized); + if (!normalized.dxil_data) { + auto result = std::make_shared(); + result->success = false; + result->error_message = "Missing DXIL data"; + return result; + } + return stage_compile_cache_->GetOrCompile(normalized, [this, normalized]() { + return std::make_shared( + CompileStageUncached(normalized)); + }); +} + +bool MetalShaderConverter::Initialize() { + // Metal Shader Converter is a library that should be available + // at /usr/local/lib/libmetalirconverter.dylib + // The headers are at /usr/local/include/metal_irconverter/ + // or in third_party/metal-shader-converter/include/ + + // Test if we can create basic MSC objects + IRCompiler* test_compiler = IRCompilerCreate(); + if (!test_compiler) { + XELOGE( + "MetalShaderConverter: Failed to create IR compiler - MSC not " + "available"); + is_available_ = false; + return false; + } + IRCompilerDestroy(test_compiler); + + XELOGI("MetalShaderConverter: Initialized successfully"); + is_available_ = true; + return true; +} + +// Create Xbox 360 root signature matching xbox360_rootsig_helper.h. +void* MetalShaderConverter::CreateXbox360RootSignature( + bool bindless_resources_used) { + // Create root parameters for Xbox 360 shader resources. Descriptor tables + // keep the heap-backed texture/UAV/sampler resources, while shader constants + // use MSC root CBVs matching the D3D12 bindless root-signature shape. + IRDescriptorRange1 ranges[32] = {}; + IRRootDescriptorTable1 tables[32] = {}; + IRRootParameter1 params[32] = {}; + int range_count = 0; + int table_count = 0; + int param_count = 0; + + auto append_descriptor_table = + [&](IRDescriptorRangeType type, uint32_t descriptor_count, + uint32_t base_shader_register, uint32_t register_space, + IRShaderVisibility table_visibility) { + IRDescriptorRange1& range = ranges[range_count++]; + range.RangeType = type; + range.NumDescriptors = descriptor_count; + range.BaseShaderRegister = base_shader_register; + range.RegisterSpace = register_space; + range.Flags = IRDescriptorRangeFlagNone; + range.OffsetInDescriptorsFromTableStart = 0; + + IRRootDescriptorTable1& table = tables[table_count++]; + table.NumDescriptorRanges = 1; + table.pDescriptorRanges = ⦥ + + IRRootParameter1& param = params[param_count++]; + param.ParameterType = IRRootParameterTypeDescriptorTable; + param.DescriptorTable = table; + param.ShaderVisibility = table_visibility; + }; + + auto append_root_cbv = [&](uint32_t shader_register, + IRShaderVisibility cbv_visibility) { + IRRootParameter1& param = params[param_count++]; + param.ParameterType = IRRootParameterTypeCBV; + param.Descriptor.ShaderRegister = shader_register; + param.Descriptor.RegisterSpace = 0; + param.Descriptor.Flags = IRRootDescriptorFlagNone; + param.ShaderVisibility = cbv_visibility; + }; + + const uint32_t resource_descriptor_count = + bindless_resources_used ? UINT32_MAX : 1025; + const uint32_t sampler_descriptor_count = + bindless_resources_used ? UINT32_MAX : 257; + + // SRVs in spaces 0-3. + for (uint32_t space = 0; space < 4; ++space) { + append_descriptor_table(IRDescriptorRangeTypeSRV, resource_descriptor_count, + 0, space, IRShaderVisibilityAll); + } + + // SRV in space 10 for hull shaders. + append_descriptor_table(IRDescriptorRangeTypeSRV, resource_descriptor_count, + 0, 10, IRShaderVisibilityAll); + + // UAVs in spaces 0-3. + for (uint32_t space = 0; space < 4; ++space) { + append_descriptor_table(IRDescriptorRangeTypeUAV, resource_descriptor_count, + 0, space, IRShaderVisibilityAll); + } + + // Samplers in space 0. + append_descriptor_table(IRDescriptorRangeTypeSampler, + sampler_descriptor_count, 0, 0, + IRShaderVisibilityAll); + + // Xenia root ABI v2 uses five shader-constant CBVs in space 0: + // b0 = system constants + // b1 = float constants + // b2 = bool/loop constants + // b3 = fetch constants + // b4 = descriptor indices + // It keeps b0/b2 common and duplicates the volatile stage-local CBVs + // with disjoint graphics-stage visibility. This lets the runtime build one + // graphics root argument block while the DXIL-visible register numbers stay + // unchanged. + append_root_cbv(0, IRShaderVisibilityAll); + append_root_cbv(1, IRShaderVisibilityVertex); + append_root_cbv(2, IRShaderVisibilityAll); + append_root_cbv(3, IRShaderVisibilityVertex); + append_root_cbv(4, IRShaderVisibilityVertex); + constexpr IRShaderVisibility kGeneratedAndPixelVisibilities[] = { + IRShaderVisibilityHull, IRShaderVisibilityDomain, + IRShaderVisibilityPixel}; + for (IRShaderVisibility stage_visibility : kGeneratedAndPixelVisibilities) { + append_root_cbv(1, stage_visibility); + append_root_cbv(3, stage_visibility); + append_root_cbv(4, stage_visibility); + } + + // Function-constant CBV space for MSC. MSC documentation requires it to be + // declared when function constants are enabled, but the converter skips it + // when laying out the top-level argument buffer. + append_descriptor_table(IRDescriptorRangeTypeCBV, 1, 0, + kFunctionConstantRegisterSpace, + IRShaderVisibilityAll); + + // Create root signature descriptor + IRRootSignatureDescriptor1 desc = {}; + desc.NumParameters = param_count; + desc.pParameters = params; + desc.NumStaticSamplers = 0; + desc.pStaticSamplers = nullptr; + desc.Flags = IRRootSignatureFlagNone; + + IRVersionedRootSignatureDescriptor versionedDesc = {}; + versionedDesc.version = IRRootSignatureVersion_1_1; + versionedDesc.desc_1_1 = desc; + + // Create the root signature + IRError* error = nullptr; + IRRootSignature* rootSig = + IRRootSignatureCreateFromDescriptor(&versionedDesc, &error); + + if (error) { + const char* errMsg = (const char*)IRErrorGetPayload(error); + XELOGE("MetalShaderConverter: Failed to create root signature: {}", + errMsg ? errMsg : "unknown error"); + IRErrorDestroy(error); + return nullptr; + } + + return rootSig; +} + +bool MetalShaderConverter::Convert(xenos::ShaderType shader_type, + const std::vector& dxil_data, + MetalShaderConversionResult& result) { + MetalShaderStage stage; + switch (shader_type) { + case xenos::ShaderType::kVertex: + stage = MetalShaderStage::kVertex; + break; + case xenos::ShaderType::kPixel: + stage = MetalShaderStage::kFragment; + break; + default: + result.success = false; + result.error_message = "Unsupported shader type"; + return false; + } + + return ConvertWithStage(stage, dxil_data, result); +} + +bool MetalShaderConverter::ConvertWithStage( + MetalShaderStage stage, const std::vector& dxil_data, + MetalShaderConversionResult& result) { + return ConvertWithStageEx(stage, dxil_data, result, nullptr, nullptr, nullptr, + false, IRInputTopologyUndefined); +} + +bool MetalShaderConverter::ConvertWithStageEx( + MetalShaderStage stage, const std::vector& dxil_data, + MetalShaderConversionResult& result, MetalShaderReflectionInfo* reflection, + const IRVersionedInputLayoutDescriptor* input_layout, + std::vector* stage_in_metallib, bool enable_geometry_emulation, + int input_topology) { + auto dxil = std::make_shared>(dxil_data); + MetalStageCompileRequest request; + request.stage = stage; + request.dxil_data = std::move(dxil); + request.enable_geometry_emulation = enable_geometry_emulation; + request.input_topology = input_topology; + request.input_layout = input_layout; + request.requested_outputs = kMetalStageCompileOutputMetallib; + if (stage_in_metallib && input_layout) { + request.requested_outputs |= kMetalStageCompileOutputStageInMetallib; + } + + std::shared_ptr stage_result = + CompileStage(request); + result.success = stage_result && stage_result->success; + result.metallib_data = + stage_result ? stage_result->metallib_data : std::vector(); + result.error_message = + stage_result ? stage_result->error_message : "Stage compile failed"; + result.function_name = stage_result ? stage_result->function_name : ""; + result.has_mesh_stage = stage_result && stage_result->has_mesh_stage; + result.has_geometry_stage = stage_result && stage_result->has_geometry_stage; + if (reflection && stage_result && stage_result->has_reflection) { + *reflection = stage_result->reflection; + } + if (stage_in_metallib) { + stage_in_metallib->clear(); + if (stage_result) { + *stage_in_metallib = stage_result->stage_in_metallib; + } + } + return result.success; +} + +MetalStageCompileResult MetalShaderConverter::CompileStageUncached( + const MetalStageCompileRequest& request) { + MetalStageCompileResult result; + const MetalShaderStage stage = request.stage; + const bool enable_geometry_emulation = request.enable_geometry_emulation; + const int input_topology = request.input_topology; + if (!is_available_) { + result.success = false; + result.error_message = "MetalShaderConverter not initialized"; + return result; + } + if (!request.dxil_data) { + result.success = false; + result.error_message = "Missing DXIL data"; + return result; + } + + const auto& dxil_data = *request.dxil_data; + + if (dxil_data.empty()) { + result.success = false; + result.error_message = "Empty DXIL data"; + return result; + } + + // Create DXIL object from input data + IRObject* dxilObject = IRObjectCreateFromDXIL( + dxil_data.data(), dxil_data.size(), IRBytecodeOwnershipNone); + + if (!dxilObject) { + result.success = false; + result.error_message = "Failed to create DXIL object"; + return result; + } + + // Create compiler + IRCompiler* compiler = IRCompilerCreate(); + if (!compiler) { + IRObjectDestroy(dxilObject); + result.success = false; + result.error_message = "Failed to create IR compiler"; + return result; + } + + // Set compatibility flag to force texture array types + // This is required because: + // 1. Xenia's DXBC translator generates code expecting texture2d_array + // 2. MSC 3.0+ defaults to non-array texture types + // 3. Our Metal textures are created as MTLTextureType2DArray + IRCompilerSetCompatibilityFlags( + compiler, static_cast(request.compatibility_flags)); + if (request.validation_flags != kUnsetCompilerOption) { + IRCompilerSetValidationFlags( + compiler, + static_cast(request.validation_flags)); + } + if (request.stage_in_generation_mode != kUnsetCompilerOption) { + IRCompilerSetStageInGenerationMode(compiler, + static_cast( + request.stage_in_generation_mode)); + } + + if (input_topology != IRInputTopologyUndefined) { + IRCompilerSetInputTopology(compiler, + static_cast(input_topology)); + } + if (enable_geometry_emulation) { + IRCompilerEnableGeometryAndTessellationEmulation(compiler, true); + } + // Ignore embedded root signatures in DXIL; we provide our own. + IRCompilerIgnoreRootSignature(compiler, request.ignore_root_signature); + if (request.ignore_debug_information) { + IRCompilerIgnoreDebugInformation(compiler, true); + } + // Enable function-constant register space for MSC specialization. + IRCompilerSetFunctionConstantResourceSpace( + compiler, request.function_constant_resource_space); + if (request.has_minimum_target) { + IRCompilerSetMinimumGPUFamily( + compiler, static_cast(request.minimum_gpu_family)); + IRCompilerSetMinimumDeploymentTarget( + compiler, static_cast(request.minimum_os), + request.minimum_os_version.c_str()); + } + + // Create and set Xbox 360 root signature + IRRootSignature* rootSig = + static_cast(CreateXbox360RootSignature( + request.root_signature_bindless_resources_used)); + if (!rootSig) { + IRCompilerDestroy(compiler); + IRObjectDestroy(dxilObject); + result.success = false; + result.error_message = "Failed to create root signature"; + return result; + } + IRCompilerSetGlobalRootSignature(compiler, rootSig); + + // Compile DXIL to Metal + IRError* error = nullptr; + IRObject* metalObject = + IRCompilerAllocCompileAndLink(compiler, nullptr, dxilObject, &error); + + if (error) { + const char* errMsg = (const char*)IRErrorGetPayload(error); + result.success = false; + result.error_message = std::string("MSC compilation failed: ") + + (errMsg ? errMsg : "unknown error"); + XELOGE("MetalShaderConverter: {}", result.error_message); + IRErrorDestroy(error); + IRRootSignatureDestroy(rootSig); + IRCompilerDestroy(compiler); + IRObjectDestroy(dxilObject); + return result; + } + + if (!metalObject) { + result.success = false; + result.error_message = "MSC returned null object without error"; + IRRootSignatureDestroy(rootSig); + IRCompilerDestroy(compiler); + IRObjectDestroy(dxilObject); + return result; + } + + auto extract_metallib = [&](IRShaderStage ir_stage, + std::vector& out_bytes, + size_t* out_size) -> bool { + IRMetalLibBinary* metallib = IRMetalLibBinaryCreate(); + if (!metallib) { + if (out_size) { + *out_size = 0; + } + return false; + } + bool ok = IRObjectGetMetalLibBinary(metalObject, ir_stage, metallib); + size_t metallib_size = IRMetalLibGetBytecodeSize(metallib); + if (!ok || metallib_size == 0) { + IRMetalLibBinaryDestroy(metallib); + if (out_size) { + *out_size = 0; + } + return false; + } + out_bytes.resize(metallib_size); + IRMetalLibGetBytecode(metallib, out_bytes.data()); + IRMetalLibBinaryDestroy(metallib); + if (out_size) { + *out_size = metallib_size; + } + return true; + }; + + IRShaderStage ir_stage = IRShaderStageInvalid; + switch (stage) { + case MetalShaderStage::kVertex: + ir_stage = IRShaderStageVertex; + break; + case MetalShaderStage::kFragment: + ir_stage = IRShaderStageFragment; + break; + case MetalShaderStage::kCompute: + ir_stage = IRShaderStageCompute; + break; + case MetalShaderStage::kHull: + ir_stage = IRShaderStageHull; + break; + case MetalShaderStage::kDomain: + ir_stage = IRShaderStageDomain; + break; + case MetalShaderStage::kGeometry: + // We'll determine mesh/geometry below. + break; + default: + ir_stage = IRShaderStageInvalid; + break; + } + + result.has_mesh_stage = false; + result.has_geometry_stage = false; + size_t stage_size = 0; + if (stage == MetalShaderStage::kGeometry) { + std::vector mesh_bytes; + std::vector geom_bytes; + result.has_mesh_stage = + extract_metallib(IRShaderStageMesh, mesh_bytes, nullptr); + result.has_geometry_stage = + extract_metallib(IRShaderStageGeometry, geom_bytes, nullptr); + if (result.has_mesh_stage) { + result.metallib_data = std::move(mesh_bytes); + ir_stage = IRShaderStageMesh; + } else if (result.has_geometry_stage) { + result.metallib_data = std::move(geom_bytes); + ir_stage = IRShaderStageGeometry; + } + } else if (ir_stage != IRShaderStageInvalid) { + extract_metallib(ir_stage, result.metallib_data, &stage_size); + } + + if (result.metallib_data.empty()) { + auto stage_name = [](MetalShaderStage value) -> const char* { + switch (value) { + case MetalShaderStage::kVertex: + return "vertex"; + case MetalShaderStage::kFragment: + return "fragment"; + case MetalShaderStage::kGeometry: + return "geometry"; + case MetalShaderStage::kCompute: + return "compute"; + case MetalShaderStage::kHull: + return "hull"; + case MetalShaderStage::kDomain: + return "domain"; + default: + return "unknown"; + } + }; + result.success = false; + result.error_message = "Generated MetalLib has zero size"; + XELOGE( + "MetalShaderConverter: empty metallib (stage={}, ir_stage={}, " + "geom_emulation={}, input_topology={}, mesh_ok={}, geom_ok={}, " + "stage_size={})", + stage_name(stage), int(ir_stage), enable_geometry_emulation, + input_topology, result.has_mesh_stage, result.has_geometry_stage, + stage_size); + IRObjectDestroy(metalObject); + IRRootSignatureDestroy(rootSig); + IRCompilerDestroy(compiler); + IRObjectDestroy(dxilObject); + return result; + } + + MetalShaderReflectionInfo* reflection = &result.reflection; + if (reflection) { + reflection->vertex_inputs.clear(); + reflection->function_constants.clear(); + reflection->vertex_output_size_in_bytes = 0; + reflection->vertex_input_count = 0; + reflection->gs_max_input_primitives_per_mesh_threadgroup = 0; + reflection->has_hull_info = false; + reflection->hs_max_patches_per_object_threadgroup = 0; + reflection->hs_max_object_threads_per_patch = 0; + reflection->hs_patch_constants_size = 0; + reflection->hs_input_control_point_count = 0; + reflection->hs_output_control_point_count = 0; + reflection->hs_output_control_point_size = 0; + reflection->hs_tessellator_domain = 0; + reflection->hs_tessellator_partitioning = 0; + reflection->hs_tessellator_output_primitive = 0; + reflection->hs_tessellation_type_half = false; + reflection->hs_max_tessellation_factor = 0.0f; + reflection->has_domain_info = false; + reflection->ds_max_input_prims_per_mesh_threadgroup = 0; + reflection->ds_input_control_point_count = 0; + reflection->ds_input_control_point_size = 0; + reflection->ds_patch_constants_size = 0; + reflection->ds_tessellator_domain = 0; + reflection->ds_tessellation_type_half = false; + } + + IRShaderReflection* shader_reflection = IRShaderReflectionCreate(); + if (shader_reflection && ir_stage != IRShaderStageInvalid) { + if (IRObjectGetReflection(metalObject, ir_stage, shader_reflection)) { + result.has_reflection = true; + const char* entry_name = + IRShaderReflectionGetEntryPointFunctionName(shader_reflection); + if (entry_name) { + result.function_name = entry_name; + } + if (reflection) { + if (ir_stage == IRShaderStageVertex) { + IRVersionedVSInfo vs_info = {}; + vs_info.version = IRReflectionVersion_1_0; + if (IRShaderReflectionCopyVertexInfo( + shader_reflection, IRReflectionVersion_1_0, &vs_info)) { + reflection->vertex_output_size_in_bytes = + vs_info.info_1_0.vertex_output_size_in_bytes; + reflection->vertex_input_count = + static_cast(vs_info.info_1_0.num_vertex_inputs); + reflection->vertex_inputs.reserve( + vs_info.info_1_0.num_vertex_inputs); + for (size_t i = 0; i < vs_info.info_1_0.num_vertex_inputs; ++i) { + const auto& input = vs_info.info_1_0.vertex_inputs[i]; + MetalShaderReflectionInput out; + out.name = input.name ? input.name : ""; + out.attribute_index = input.attributeIndex; + reflection->vertex_inputs.push_back(std::move(out)); + } + IRShaderReflectionReleaseVertexInfo(&vs_info); + } + } else if (ir_stage == IRShaderStageGeometry || + ir_stage == IRShaderStageMesh) { + IRVersionedGSInfo gs_info = {}; + gs_info.version = IRReflectionVersion_1_0; + if (IRShaderReflectionCopyGeometryInfo( + shader_reflection, IRReflectionVersion_1_0, &gs_info)) { + reflection->gs_max_input_primitives_per_mesh_threadgroup = + gs_info.info_1_0.max_input_primitives_per_mesh_threadgroup; + IRShaderReflectionReleaseGeometryInfo(&gs_info); + } + } + + if (IRShaderReflectionNeedsFunctionConstants(shader_reflection)) { + size_t constant_count = + IRShaderReflectionGetFunctionConstantCount(shader_reflection); + if (constant_count) { + std::vector constants(constant_count); + IRShaderReflectionCopyFunctionConstants(shader_reflection, + constants.data()); + reflection->function_constants.reserve(constant_count); + for (const auto& constant : constants) { + MetalShaderFunctionConstant out; + out.name = constant.name ? constant.name : ""; + out.type = static_cast(constant.type); + reflection->function_constants.push_back(std::move(out)); + } + IRShaderReflectionReleaseFunctionConstants(constants.data(), + constant_count); + } + } + + if (ir_stage == IRShaderStageHull) { + IRVersionedHSInfo hs_info = {}; + hs_info.version = IRReflectionVersion_1_0; + if (IRShaderReflectionCopyHullInfo( + shader_reflection, IRReflectionVersion_1_0, &hs_info)) { + reflection->has_hull_info = true; + reflection->hs_max_patches_per_object_threadgroup = + hs_info.info_1_0.max_patches_per_object_threadgroup; + reflection->hs_max_object_threads_per_patch = + hs_info.info_1_0.max_object_threads_per_patch; + reflection->hs_patch_constants_size = + hs_info.info_1_0.patch_constants_size; + reflection->hs_input_control_point_count = + hs_info.info_1_0.input_control_point_count; + reflection->hs_output_control_point_count = + hs_info.info_1_0.output_control_point_count; + reflection->hs_output_control_point_size = + hs_info.info_1_0.output_control_point_size; + reflection->hs_tessellator_domain = + static_cast(hs_info.info_1_0.tessellator_domain); + reflection->hs_tessellator_partitioning = static_cast( + hs_info.info_1_0.tessellator_partitioning); + reflection->hs_tessellator_output_primitive = static_cast( + hs_info.info_1_0.tessellator_output_primitive); + reflection->hs_tessellation_type_half = + hs_info.info_1_0.tessellation_type_half; + reflection->hs_max_tessellation_factor = + hs_info.info_1_0.max_tessellation_factor; + IRShaderReflectionReleaseHullInfo(&hs_info); + } + } else if (ir_stage == IRShaderStageDomain) { + IRVersionedDSInfo ds_info = {}; + ds_info.version = IRReflectionVersion_1_0; + if (IRShaderReflectionCopyDomainInfo( + shader_reflection, IRReflectionVersion_1_0, &ds_info)) { + reflection->has_domain_info = true; + reflection->ds_max_input_prims_per_mesh_threadgroup = + ds_info.info_1_0.max_input_prims_per_mesh_threadgroup; + reflection->ds_input_control_point_count = + ds_info.info_1_0.input_control_point_count; + reflection->ds_input_control_point_size = + ds_info.info_1_0.input_control_point_size; + reflection->ds_patch_constants_size = + ds_info.info_1_0.patch_constants_size; + reflection->ds_tessellator_domain = + static_cast(ds_info.info_1_0.tessellator_domain); + reflection->ds_tessellation_type_half = + ds_info.info_1_0.tessellation_type_half; + IRShaderReflectionReleaseDomainInfo(&ds_info); + } + } + } + } + } + + if (result.function_name.empty()) { + switch (stage) { + case MetalShaderStage::kVertex: + result.function_name = "vertexMain"; + break; + case MetalShaderStage::kFragment: + result.function_name = "fragmentMain"; + break; + case MetalShaderStage::kCompute: + result.function_name = "computeMain"; + break; + case MetalShaderStage::kGeometry: + default: + result.function_name = "main"; + break; + } + } + + const IRVersionedInputLayoutDescriptor* input_layout = request.input_layout; + if (!input_layout && request.RequestsStageIn() && + request.stage_in_layout_builder) { + input_layout = request.stage_in_layout_builder(result.reflection); + } + if (stage == MetalShaderStage::kVertex && request.RequestsStageIn() && + input_layout && shader_reflection) { + IRMetalLibBinary* stage_in_lib = IRMetalLibBinaryCreate(); + if (stage_in_lib) { + if (IRMetalLibSynthesizeStageInFunction(compiler, shader_reflection, + input_layout, stage_in_lib)) { + size_t stage_in_size = IRMetalLibGetBytecodeSize(stage_in_lib); + if (stage_in_size) { + result.stage_in_metallib.resize(stage_in_size); + IRMetalLibGetBytecode(stage_in_lib, result.stage_in_metallib.data()); + } + } + IRMetalLibBinaryDestroy(stage_in_lib); + } + } + + if (shader_reflection) { + IRShaderReflectionDestroy(shader_reflection); + } + + XELOGD( + "MetalShaderConverter: Successfully converted {} bytes DXIL to {} bytes " + "MetalLib", + dxil_data.size(), result.metallib_data.size()); + + // Cleanup + IRObjectDestroy(metalObject); + IRRootSignatureDestroy(rootSig); + IRCompilerDestroy(compiler); + IRObjectDestroy(dxilObject); + + result.success = true; + return result; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_shader_converter.h b/src/xenia/gpu/metal/metal_shader_converter.h new file mode 100644 index 000000000..3b3c18776 --- /dev/null +++ b/src/xenia/gpu/metal/metal_shader_converter.h @@ -0,0 +1,203 @@ +/** + ****************************************************************************** + * 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_CONVERTER_H_ +#define XENIA_GPU_METAL_METAL_SHADER_CONVERTER_H_ + +#include +#include +#include +#include +#include + +struct IRVersionedInputLayoutDescriptor; + +#include "xenia/gpu/xenos.h" + +namespace xe { +namespace gpu { +namespace metal { + +class MetalStageCompileCache; + +constexpr uint32_t kMetalRootSignatureAbiVersion = 2; + +// Shader stage for Metal conversion +enum class MetalShaderStage { + kVertex, + kFragment, + kGeometry, + kCompute, + kHull, + kDomain +}; + +// Result of shader conversion +struct MetalShaderConversionResult { + bool success = false; + std::vector metallib_data; + std::string error_message; + std::string function_name; // Main function name in the metallib + bool has_mesh_stage = false; + bool has_geometry_stage = false; +}; + +struct MetalShaderReflectionInput { + std::string name; + uint8_t attribute_index = 0; +}; + +struct MetalShaderFunctionConstant { + std::string name; + uint32_t type = 0; +}; + +struct MetalShaderReflectionInfo { + uint32_t vertex_output_size_in_bytes = 0; + uint32_t vertex_input_count = 0; + std::vector vertex_inputs; + uint32_t gs_max_input_primitives_per_mesh_threadgroup = 0; + std::vector function_constants; + bool has_hull_info = false; + uint32_t hs_max_patches_per_object_threadgroup = 0; + uint32_t hs_max_object_threads_per_patch = 0; + uint32_t hs_patch_constants_size = 0; + uint32_t hs_input_control_point_count = 0; + uint32_t hs_output_control_point_count = 0; + uint32_t hs_output_control_point_size = 0; + uint32_t hs_tessellator_domain = 0; + uint32_t hs_tessellator_partitioning = 0; + uint32_t hs_tessellator_output_primitive = 0; + bool hs_tessellation_type_half = false; + float hs_max_tessellation_factor = 0.0f; + bool has_domain_info = false; + uint32_t ds_max_input_prims_per_mesh_threadgroup = 0; + uint32_t ds_input_control_point_count = 0; + uint32_t ds_input_control_point_size = 0; + uint32_t ds_patch_constants_size = 0; + uint32_t ds_tessellator_domain = 0; + bool ds_tessellation_type_half = false; +}; + +enum MetalStageCompileOutputFlags : uint32_t { + kMetalStageCompileOutputMetallib = 1u << 0, + kMetalStageCompileOutputStageInMetallib = 1u << 1, +}; + +struct MetalStageCompileRequest { + MetalShaderStage stage = MetalShaderStage::kVertex; + std::shared_ptr> dxil_data; + uint32_t requested_outputs = kMetalStageCompileOutputMetallib; + const IRVersionedInputLayoutDescriptor* input_layout = nullptr; + // Optional builder used when the stage-in layout depends on reflection from + // the same MSC compile. The callback is invoked before MSC objects are + // destroyed and must return a layout valid until CompileStageUncached + // returns. + std::function + stage_in_layout_builder; + uint64_t stage_in_layout_key = 0; + bool enable_geometry_emulation = false; + int input_topology = 0; + + uint32_t compatibility_flags = 0; + bool ignore_root_signature = true; + bool ignore_debug_information = false; + uint32_t validation_flags = UINT32_MAX; + uint32_t stage_in_generation_mode = UINT32_MAX; + uint32_t function_constant_resource_space = 0; + bool has_minimum_target = false; + uint32_t minimum_gpu_family = 0; + uint32_t minimum_os = 0; + std::string minimum_os_version; + + uint32_t root_signature_abi_version = kMetalRootSignatureAbiVersion; + bool root_signature_bindless_resources_used = true; + + bool RequestsStageIn() const { + return (requested_outputs & kMetalStageCompileOutputStageInMetallib) != 0; + } +}; + +struct MetalStageCompileResult { + bool success = false; + std::vector metallib_data; + std::string error_message; + std::string function_name; + MetalShaderReflectionInfo reflection; + bool has_reflection = false; + std::vector stage_in_metallib; + bool has_mesh_stage = false; + bool has_geometry_stage = false; +}; + +// Converts DXIL shaders to Metal IR using Apple's Metal Shader Converter +// Uses the correct Xbox 360 root signatures from xbox360_rootsig_helper.h +class MetalShaderConverter { + public: + MetalShaderConverter(); + ~MetalShaderConverter(); + + // Initialize the converter (loads MSC library) + bool Initialize(); + + // Check if the converter is available + bool IsAvailable() const { return is_available_; } + + // Convert DXIL to Metal IR + // shader_type: Xenia shader type (vertex or pixel) + // dxil_data: DXIL bytecode from dxbc2dxil + // result: Output conversion result with metallib data + bool Convert(xenos::ShaderType shader_type, + const std::vector& dxil_data, + MetalShaderConversionResult& result); + + // Convert with explicit stage specification + bool ConvertWithStage(MetalShaderStage stage, + const std::vector& dxil_data, + MetalShaderConversionResult& result); + + bool ConvertWithStageEx(MetalShaderStage stage, + const std::vector& dxil_data, + MetalShaderConversionResult& result, + MetalShaderReflectionInfo* reflection, + const IRVersionedInputLayoutDescriptor* input_layout, + std::vector* stage_in_metallib, + bool enable_geometry_emulation, int input_topology); + + std::shared_ptr CompileStage( + const MetalStageCompileRequest& request); + MetalStageCompileResult CompileStageUncached( + const MetalStageCompileRequest& request); + + void SetMinimumTarget(uint32_t gpu_family, uint32_t os, + const std::string& version); + + MetalStageCompileCache* stage_compile_cache() { + return stage_compile_cache_.get(); + } + + private: + void PopulateDefaultRequestOptions(MetalStageCompileRequest& request) const; + + bool is_available_ = false; + bool has_minimum_target_ = false; + uint32_t minimum_gpu_family_ = 0; + uint32_t minimum_os_ = 0; + std::string minimum_os_version_; + std::unique_ptr stage_compile_cache_; + + void* CreateXbox360RootSignature(bool bindless_resources_used); +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_SHADER_CONVERTER_H_ diff --git a/src/xenia/gpu/metal/metal_shared_memory.cc b/src/xenia/gpu/metal/metal_shared_memory.cc index 0e0c14331..51174f644 100644 --- a/src/xenia/gpu/metal/metal_shared_memory.cc +++ b/src/xenia/gpu/metal/metal_shared_memory.cc @@ -9,15 +9,23 @@ #include "xenia/gpu/metal/metal_shared_memory.h" +#include +#include +#include +#include +#include + +#include "xenia/base/cvar.h" #include "xenia/base/logging.h" +#include "xenia/base/math.h" #include "xenia/base/memory.h" -#include "xenia/gpu/gpu_flags.h" #include "xenia/gpu/metal/metal_command_processor.h" -DEFINE_bool(metal_shared_memory_zero_copy, true, - "Use MTLBuffer bytes-no-copy for guest memory on unified memory " - "devices when possible.", - "Metal"); +DEFINE_bool( + metal_shared_memory_direct_write, true, + "Directly copy safe Metal shared-memory uploads into the shared storage " + "buffer instead of staging them through a blit.", + "Metal"); namespace xe { namespace gpu { @@ -30,10 +38,9 @@ MetalSharedMemory::MetalSharedMemory(MetalCommandProcessor& command_processor, MetalSharedMemory::~MetalSharedMemory() { Shutdown(); } bool MetalSharedMemory::Initialize() { - // Try to alias guest memory on unified-memory devices and fall back to a - // dedicated shared buffer when not supported. - // Initialize base class - InitializeCommon(); + if (!InitializeCommon()) { + return false; + } const ui::metal::MetalProvider& provider = command_processor_.GetMetalProvider(); @@ -52,52 +59,170 @@ bool MetalSharedMemory::Initialize() { return false; } - if (cvars::metal_shared_memory_zero_copy && device->hasUnifiedMemory()) { - size_t system_page_size = xe::memory::page_size(); - if (reinterpret_cast(xbox_ram) % system_page_size == 0) { - buffer_ = device->newBuffer(xbox_ram, kBufferSize, - MTL::ResourceStorageModeShared, nullptr); - if (buffer_) { - use_zero_copy_ = true; - XELOGD("Metal shared memory: using bytes-no-copy buffer"); - } else { - XELOGW("Metal shared memory: bytes-no-copy buffer creation failed"); - } - } else { - XELOGW( - "Metal shared memory: Xbox RAM not page-aligned for bytes-no-copy"); - } - } - - if (!buffer_) { - buffer_ = device->newBuffer(kBufferSize, MTL::ResourceStorageModeShared); - } + buffer_ = device->newBuffer(kBufferSize, MTL::ResourceStorageModeShared); if (!buffer_) { XELOGE("Failed to create Metal shared memory buffer"); return false; } - // For trace dump, do initial full copy; UploadRanges handles incremental - // updates for normal runs. - if (!use_zero_copy_) { - if (xbox_ram) { - memcpy(buffer_->contents(), xbox_ram, kBufferSize); - } - } else { - XELOGD("Metal shared memory: skipping initial copy (zero-copy)"); - } + upload_buffer_pool_ = std::make_unique( + device, xe::align(ui::GraphicsUploadBufferPool::kDefaultPageSize, + size_t(1) << page_size_log2())); + page_last_main_gpu_access_submission_.assign(kBufferSize >> page_size_log2(), + 0); + page_standalone_gpu_access_counts_.assign(kBufferSize >> page_size_log2(), 0); return true; } -void MetalSharedMemory::ClearCache() { SharedMemory::ClearCache(); } +void MetalSharedMemory::ClearCache() { + SharedMemory::ClearCache(); + + if (upload_buffer_pool_) { + upload_buffer_pool_->ClearCache(); + } +} + +void MetalSharedMemory::MarkGpuAccess(uint32_t start, uint32_t length, + uint64_t submission_index) { + if (!length || !submission_index || + page_last_main_gpu_access_submission_.empty()) { + return; + } + if (start >= kBufferSize) { + return; + } + uint64_t end = std::min(uint64_t(start) + length, uint64_t(kBufferSize)); + if (end <= start) { + return; + } + uint32_t page_first = start >> page_size_log2(); + uint32_t page_last = static_cast((end - 1) >> page_size_log2()); + page_last = std::min( + page_last, + static_cast(page_last_main_gpu_access_submission_.size() - 1)); + for (uint32_t page = page_first; page <= page_last; ++page) { + page_last_main_gpu_access_submission_[page] = + std::max(page_last_main_gpu_access_submission_[page], submission_index); + } +} + +void MetalSharedMemory::TrackStandaloneGpuAccess( + MTL::CommandBuffer* command_buffer, + const std::pair* ranges, uint32_t range_count) { + if (!command_buffer || !ranges || !range_count || + page_standalone_gpu_access_counts_.empty()) { + return; + } + auto tracked_page_ranges = + std::make_shared>>(); + tracked_page_ranges->reserve(range_count); + for (uint32_t i = 0; i < range_count; ++i) { + uint32_t start = ranges[i].first; + uint32_t length = ranges[i].second; + if (!length || start >= kBufferSize) { + continue; + } + uint64_t end = std::min(uint64_t(start) + length, uint64_t(kBufferSize)); + if (end <= start) { + continue; + } + uint32_t page_first = start >> page_size_log2(); + uint32_t page_last = static_cast((end - 1) >> page_size_log2()); + page_last = std::min( + page_last, + static_cast(page_standalone_gpu_access_counts_.size() - 1)); + tracked_page_ranges->push_back({page_first, page_last}); + } + if (tracked_page_ranges->empty()) { + return; + } + { + std::lock_guard lock(standalone_gpu_access_mutex_); + for (const auto& page_range : *tracked_page_ranges) { + for (uint32_t page = page_range.first; page <= page_range.second; + ++page) { + ++page_standalone_gpu_access_counts_[page]; + } + } + } + MetalSharedMemory* shared_memory = this; + command_buffer->addCompletedHandler(^(MTL::CommandBuffer*) { + std::lock_guard lock( + shared_memory->standalone_gpu_access_mutex_); + for (const auto& page_range : *tracked_page_ranges) { + for (uint32_t page = page_range.first; page <= page_range.second; + ++page) { + if (page < shared_memory->page_standalone_gpu_access_counts_.size() && + shared_memory->page_standalone_gpu_access_counts_[page]) { + --shared_memory->page_standalone_gpu_access_counts_[page]; + } + } + } + }); +} + +MetalSharedMemory::UploadRouteInfo MetalSharedMemory::GetUploadRouteInfo( + const SharedMemory::Range* ranges, uint32_t range_count) { + UploadRouteInfo info; + if (!ranges || !range_count) { + return info; + } + + const uint32_t page_size = 1u << page_size_log2(); + const uint64_t completed_submission = + command_processor_.GetCompletedSubmission(); + const bool direct_write_enabled = ::cvars::metal_shared_memory_direct_write && + buffer_ && buffer_->contents() != nullptr; + + std::lock_guard standalone_lock(standalone_gpu_access_mutex_); + for (uint32_t i = 0; i < range_count; ++i) { + const SharedMemory::Range& range = ranges[i]; + if (!range.length || range.start >= kBufferSize) { + continue; + } + uint64_t end = + std::min(uint64_t(range.start) + range.length, uint64_t(kBufferSize)); + if (end <= range.start) { + continue; + } + uint32_t page_first = range.start >> page_size_log2(); + uint32_t page_last = static_cast((end - 1) >> page_size_log2()); + for (uint32_t page = page_first; page <= page_last; ++page) { + uint64_t page_start = uint64_t(page) << page_size_log2(); + uint32_t page_length = static_cast( + std::min(page_size, uint64_t(kBufferSize) - page_start)); + if (!page_length || + IsRangeValid(static_cast(page_start), page_length)) { + continue; + } + + info.upload_bytes += page_length; + bool direct_safe = direct_write_enabled; + if (page < page_standalone_gpu_access_counts_.size() && + page_standalone_gpu_access_counts_[page]) { + direct_safe = false; + } + uint64_t page_last_access = 0; + if (page < page_last_main_gpu_access_submission_.size()) { + page_last_access = page_last_main_gpu_access_submission_[page]; + } + if (page_last_access > completed_submission) { + direct_safe = false; + } + if (direct_safe) { + info.direct_bytes += page_length; + } else { + info.staged_bytes += page_length; + } + } + } + return info; +} bool MetalSharedMemory::UploadRanges( const std::pair* upload_page_ranges, uint32_t num_upload_ranges) { - // Copy modified ranges from Xbox memory to Metal buffer when not using - // bytes-no-copy shared memory. - static bool first_upload = true; if (first_upload) { first_upload = false; @@ -117,33 +242,213 @@ bool MetalSharedMemory::UploadRanges( return true; } - uint8_t* buffer_data = nullptr; - uint8_t* xbox_data = nullptr; - if (!use_zero_copy_) { - void* xbox_ram = memory().TranslatePhysical(0); - if (!xbox_ram) { - XELOGE("MetalSharedMemory::UploadRanges: Xbox RAM is null"); - return false; - } - buffer_data = static_cast(buffer_->contents()); - xbox_data = static_cast(xbox_ram); + void* xbox_ram = memory().TranslatePhysical(0); + if (!xbox_ram) { + XELOGE("MetalSharedMemory::UploadRanges: Xbox RAM is null"); + return false; } + uint8_t* xbox_data = static_cast(xbox_ram); const uint32_t page_size = 1u << page_size_log2(); + if (!upload_buffer_pool_) { + XELOGE("MetalSharedMemory::UploadRanges: upload buffer pool is null"); + return false; + } + upload_buffer_pool_->Reclaim(command_processor_.GetCompletedSubmission()); + + MTL::BlitCommandEncoder* blit_encoder = nullptr; + auto get_blit_encoder = [&]() -> MTL::BlitCommandEncoder* { + if (!blit_encoder) { + blit_encoder = command_processor_.GetSharedMemoryUploadBlitEncoder(); + } + return blit_encoder; + }; uint32_t merged_start = 0; uint32_t merged_end = 0; bool have_merged = false; - auto flush_merged_range = [&](uint32_t start, uint32_t end) { + auto copy_guest_bytes = [&](uint8_t* dest, uint32_t offset, size_t size) { + if (size < (1ULL << 32) && size > 8192) { + memory::vastcpy(dest, xbox_data + offset, static_cast(size)); + swcache::WriteFence(); + } else { + std::memcpy(dest, xbox_data + offset, size); + } + }; + + uint64_t direct_eligible_bytes = 0; + uint64_t staged_required_bytes = 0; + uint8_t* shared_buffer_contents = static_cast(buffer_->contents()); + const bool has_shared_buffer_contents = shared_buffer_contents != nullptr; + const uint64_t completed_submission = + command_processor_.GetCompletedSubmission(); + + auto record_direct_write_reject = + [&](MetalCommandProcessor::SharedMemoryDirectWriteRejectReason reason, + uint64_t bytes) { + command_processor_.RecordSharedMemoryDirectWriteReject(reason, bytes); + }; + + struct UploadRun { + uint32_t start; + uint32_t end; + bool direct_safe; + }; + + auto append_upload_run = [](std::vector& runs, uint32_t start, + uint32_t end, bool direct_safe) { if (end <= start) { return; } - uint32_t length = end - start; - MakeRangeValid(start, length, false); - if (!use_zero_copy_) { - memcpy(buffer_data + start, xbox_data + start, length); + if (!runs.empty() && runs.back().direct_safe == direct_safe && + runs.back().end == start) { + runs.back().end = end; + return; } + runs.push_back({start, end, direct_safe}); + }; + + auto build_upload_runs = [&](uint32_t start, uint32_t end, + std::vector& runs) { + if (end <= start) { + return; + } + if (!has_shared_buffer_contents) { + uint64_t bytes = uint64_t(end) - start; + staged_required_bytes += bytes; + record_direct_write_reject( + MetalCommandProcessor::SharedMemoryDirectWriteRejectReason:: + kNoSharedBufferContents, + bytes); + append_upload_run(runs, start, end, false); + return; + } + bool saw_direct = false; + bool saw_staged = false; + uint32_t page_first = start >> page_size_log2(); + uint32_t page_last = (end - 1) >> page_size_log2(); + std::lock_guard standalone_lock(standalone_gpu_access_mutex_); + for (uint32_t page = page_first; page <= page_last; ++page) { + uint64_t page_start = uint64_t(page) << page_size_log2(); + uint64_t page_end = page_start + page_size; + uint64_t byte_start = std::max(page_start, start); + uint64_t byte_end = std::min(page_end, end); + uint64_t bytes = byte_end - byte_start; + if (!bytes) { + continue; + } + + if (page < page_standalone_gpu_access_counts_.size() && + page_standalone_gpu_access_counts_[page]) { + staged_required_bytes += bytes; + saw_staged = true; + append_upload_run(runs, static_cast(byte_start), + static_cast(byte_end), false); + record_direct_write_reject( + MetalCommandProcessor::SharedMemoryDirectWriteRejectReason:: + kStandaloneAccessInFlight, + bytes); + continue; + } + + uint64_t page_last_access = 0; + if (page < page_last_main_gpu_access_submission_.size()) { + page_last_access = page_last_main_gpu_access_submission_[page]; + } + if (page_last_access > completed_submission) { + staged_required_bytes += bytes; + saw_staged = true; + append_upload_run(runs, static_cast(byte_start), + static_cast(byte_end), false); + record_direct_write_reject( + MetalCommandProcessor::SharedMemoryDirectWriteRejectReason:: + kMainGpuAccessInFlight, + bytes); + continue; + } + + direct_eligible_bytes += bytes; + saw_direct = true; + append_upload_run(runs, static_cast(byte_start), + static_cast(byte_end), true); + } + + if (saw_direct && saw_staged) { + record_direct_write_reject( + MetalCommandProcessor::SharedMemoryDirectWriteRejectReason:: + kMixedRangeSplit, + uint64_t(end) - start); + } + }; + + auto direct_write_run = [&](uint32_t start, uint32_t end) { + uint32_t size = end - start; + copy_guest_bytes(shared_buffer_contents + start, start, size); + swcache::WriteFence(); + MakeRangeValid(start, size, false); + command_processor_.RecordSharedMemoryUploadRoute( + MetalCommandProcessor::SharedMemoryUploadRoute::kDirectWrite, size); + }; + + auto stage_upload_run = [&](uint32_t start, uint32_t end) -> bool { + if (end <= start) { + return true; + } + uint32_t offset = start; + uint32_t remaining = end - start; + while (remaining) { + MTL::BlitCommandEncoder* encoder = get_blit_encoder(); + if (!encoder) { + XELOGE("MetalSharedMemory::UploadRanges: failed to get blit encoder"); + return false; + } + + MTL::Buffer* upload_buffer = nullptr; + size_t upload_offset = 0; + uint64_t upload_gpu_address = 0; + size_t upload_size = 0; + uint8_t* upload_mapping = upload_buffer_pool_->RequestPartial( + command_processor_.GetCurrentSubmission(), remaining, page_size, + &upload_buffer, upload_offset, upload_gpu_address, upload_size); + if (!upload_mapping || !upload_buffer || !upload_size) { + XELOGE( + "MetalSharedMemory::UploadRanges: failed to allocate upload " + "staging buffer"); + return false; + } + + MakeRangeValid(offset, static_cast(upload_size), false); + copy_guest_bytes(upload_mapping, offset, upload_size); + encoder->copyFromBuffer(upload_buffer, + static_cast(upload_offset), buffer_, + static_cast(offset), + static_cast(upload_size)); + command_processor_.RecordSharedMemoryUploadEncoderCopy(); + command_processor_.RecordSharedMemoryUploadRoute( + MetalCommandProcessor::SharedMemoryUploadRoute::kStagedBlit, + upload_size); + + offset += static_cast(upload_size); + remaining -= static_cast(upload_size); + } + return true; + }; + + auto flush_merged_range = [&](uint32_t start, uint32_t end) -> bool { + if (end <= start) { + return true; + } + std::vector upload_runs; + build_upload_runs(start, end, upload_runs); + for (const UploadRun& run : upload_runs) { + if (::cvars::metal_shared_memory_direct_write && run.direct_safe) { + direct_write_run(run.start, run.end); + } else if (!stage_upload_run(run.start, run.end)) { + return false; + } + } + return true; }; for (uint32_t i = 0; i < num_upload_ranges; ++i) { @@ -170,28 +475,40 @@ bool MetalSharedMemory::UploadRanges( merged_end = end; } } else { - flush_merged_range(merged_start, merged_end); + if (!flush_merged_range(merged_start, merged_end)) { + command_processor_.EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kUploadFailure); + return false; + } merged_start = start; merged_end = end; } } if (have_merged) { - flush_merged_range(merged_start, merged_end); + if (!flush_merged_range(merged_start, merged_end)) { + command_processor_.EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kUploadFailure); + return false; + } } - XELOGD("MetalSharedMemory::UploadRanges: Copied {} ranges to Metal buffer", + XELOGD("MetalSharedMemory::UploadRanges: Staged {} ranges to Metal buffer", num_upload_ranges); + command_processor_.RecordSharedMemoryDirectWriteEligibility( + direct_eligible_bytes, staged_required_bytes); return true; } void MetalSharedMemory::Shutdown() { + upload_buffer_pool_.reset(); if (buffer_) { buffer_->release(); buffer_ = nullptr; } - use_zero_copy_ = false; ShutdownCommon(); // Base class cleanup } diff --git a/src/xenia/gpu/metal/metal_shared_memory.h b/src/xenia/gpu/metal/metal_shared_memory.h index 114dc1536..577f8a390 100644 --- a/src/xenia/gpu/metal/metal_shared_memory.h +++ b/src/xenia/gpu/metal/metal_shared_memory.h @@ -10,9 +10,15 @@ #ifndef XENIA_GPU_METAL_METAL_SHARED_MEMORY_H_ #define XENIA_GPU_METAL_METAL_SHARED_MEMORY_H_ -// Metal shared memory attempts bytes-no-copy aliasing on unified-memory -// devices and falls back to staged uploads when unsupported. +// Metal shared memory keeps a Metal-owned guest-memory buffer and updates dirty +// guest ranges through submission-owned upload staging buffers. +#include +#include +#include +#include + +#include "xenia/gpu/metal/metal_upload_buffer_pool.h" #include "xenia/gpu/shared_memory.h" #include "xenia/ui/metal/metal_api.h" @@ -33,6 +39,18 @@ class MetalSharedMemory : public SharedMemory { const uint8_t* GetXboxRamBase() const { return static_cast(memory().TranslatePhysical(0)); } + void MarkGpuAccess(uint32_t start, uint32_t length, + uint64_t submission_index); + void TrackStandaloneGpuAccess(MTL::CommandBuffer* command_buffer, + const std::pair* ranges, + uint32_t range_count); + struct UploadRouteInfo { + uint64_t upload_bytes = 0; + uint64_t direct_bytes = 0; + uint64_t staged_bytes = 0; + }; + UploadRouteInfo GetUploadRouteInfo(const SharedMemory::Range* ranges, + uint32_t range_count); // For trace dump, simplified - just make buffer available for reading void UseForReading() { @@ -44,8 +62,11 @@ class MetalSharedMemory : public SharedMemory { private: MetalCommandProcessor& command_processor_; + std::unique_ptr upload_buffer_pool_; + std::vector page_last_main_gpu_access_submission_; + std::mutex standalone_gpu_access_mutex_; + std::vector page_standalone_gpu_access_counts_; MTL::Buffer* buffer_ = nullptr; - bool use_zero_copy_ = false; }; } // namespace metal diff --git a/src/xenia/gpu/metal/metal_stage_compile_cache.cc b/src/xenia/gpu/metal/metal_stage_compile_cache.cc new file mode 100644 index 000000000..c065cc672 --- /dev/null +++ b/src/xenia/gpu/metal/metal_stage_compile_cache.cc @@ -0,0 +1,342 @@ +/** + ****************************************************************************** + * 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_stage_compile_cache.h" + +#include +#include +#include +#include + +#include "metal_irconverter.h" + +#include "third_party/xxhash/xxhash.h" +#include "xenia/base/logging.h" +#include "xenia/gpu/metal/metal_shader_cache.h" + +namespace xe { +namespace gpu { +namespace metal { + +namespace { + +uint64_t HashString(const std::string& value) { + return value.empty() ? 0 : XXH3_64bits(value.data(), value.size()); +} + +const char* StageName(uint32_t stage) { + switch (MetalShaderStage(stage)) { + case MetalShaderStage::kVertex: + return "vertex"; + case MetalShaderStage::kFragment: + return "fragment"; + case MetalShaderStage::kGeometry: + return "geometry"; + case MetalShaderStage::kCompute: + return "compute"; + case MetalShaderStage::kHull: + return "hull"; + case MetalShaderStage::kDomain: + return "domain"; + default: + return "unknown"; + } +} + +void HashInputLayout(const IRVersionedInputLayoutDescriptor& layout, + uint64_t* out_low, uint64_t* out_high) { + if (!out_low || !out_high) { + return; + } + XXH3_state_t* state = XXH3_createState(); + if (!state) { + *out_low = 0; + *out_high = 0; + return; + } + XXH3_128bits_reset(state); + XXH3_128bits_update(state, &layout.version, sizeof(layout.version)); + if (layout.version == IRInputLayoutDescriptorVersion_1) { + const auto& desc = layout.desc_1_0; + XXH3_128bits_update(state, &desc.numElements, sizeof(desc.numElements)); + const uint32_t count = std::min(desc.numElements, 31); + for (uint32_t i = 0; i < count; ++i) { + const char* semantic = desc.semanticNames[i]; + uint32_t semantic_size = semantic ? uint32_t(std::strlen(semantic)) : 0; + XXH3_128bits_update(state, &semantic_size, sizeof(semantic_size)); + if (semantic_size) { + XXH3_128bits_update(state, semantic, semantic_size); + } + XXH3_128bits_update(state, &desc.inputElementDescs[i], + sizeof(desc.inputElementDescs[i])); + } + } + XXH128_hash_t hash = XXH3_128bits_digest(state); + XXH3_freeState(state); + *out_low = hash.low64; + *out_high = hash.high64; +} + +void AtomicMax(std::atomic& target, uint64_t value) { + uint64_t current = target.load(std::memory_order_relaxed); + while (current < value && !target.compare_exchange_weak( + current, value, std::memory_order_relaxed, + std::memory_order_relaxed)) { + } +} + +} // namespace + +size_t MetalStageCompileKeyHasher::operator()( + const MetalStageCompileKey& key) const { + const auto words = key.SerializeWords(); + return size_t(XXH3_64bits(words.data(), words.size() * sizeof(uint64_t))); +} + +std::shared_ptr +MetalStageCompileCache::PendingStageCompile::Wait() { + std::unique_lock lock(mutex_); + ready_cv_.wait(lock, [this]() { return ready_; }); + return result_; +} + +void MetalStageCompileCache::PendingStageCompile::Publish( + std::shared_ptr result) { + { + std::lock_guard lock(mutex_); + result_ = std::move(result); + ready_ = true; + } + ready_cv_.notify_all(); +} + +MetalStageCompileKey MetalStageCompileCache::BuildKey( + const MetalStageCompileRequest& request) { + MetalStageCompileKey key = {}; + key.key_version = 2; + key.stage = uint32_t(request.stage); + if (request.dxil_data) { + key.dxil_size = request.dxil_data->size(); + if (!request.dxil_data->empty()) { + XXH128_hash_t dxil_hash = + XXH3_128bits(request.dxil_data->data(), request.dxil_data->size()); + key.dxil_hash_low = dxil_hash.low64; + key.dxil_hash_high = dxil_hash.high64; + } + } + key.compatibility_flags = request.compatibility_flags; + key.validation_flags = request.validation_flags; + key.stage_in_generation_mode = request.stage_in_generation_mode; + key.function_constant_resource_space = + request.function_constant_resource_space; + if (request.has_minimum_target) { + key.minimum_gpu_family = request.minimum_gpu_family; + key.minimum_os = request.minimum_os; + key.minimum_os_version_hash = HashString(request.minimum_os_version); + } + key.root_signature_abi_version = request.root_signature_abi_version; + key.root_signature_stage = uint32_t(request.stage); + key.root_signature_bindless_resources_used = + request.root_signature_bindless_resources_used ? 1u : 0u; + key.ignore_root_signature = request.ignore_root_signature ? 1u : 0u; + key.ignore_debug_information = request.ignore_debug_information ? 1u : 0u; + key.input_topology = uint32_t(request.input_topology); + key.geometry_or_tessellation_emulation_enabled = + request.enable_geometry_emulation ? 1u : 0u; + key.stage_in_metallib_requested = request.RequestsStageIn() ? 1u : 0u; + if (request.RequestsStageIn()) { + if (request.input_layout) { + HashInputLayout(*request.input_layout, &key.input_layout_hash_low, + &key.input_layout_hash_high); + } else { + key.input_layout_hash_low = request.stage_in_layout_key; + key.input_layout_hash_high = 0; + } + } + return key; +} + +std::shared_ptr +MetalStageCompileCache::GetOrCompile(const MetalStageCompileRequest& request, + CompileFn compile_fn) { + requests_.fetch_add(1, std::memory_order_relaxed); + if (request.dxil_data) { + dxil_bytes_.fetch_add(request.dxil_data->size(), std::memory_order_relaxed); + } + + MetalStageCompileKey key = BuildKey(request); + std::shared_ptr pending; + bool owner = false; + { + std::lock_guard lock(mutex_); + auto it = entries_.find(key); + if (it != entries_.end()) { + pending = it->second; + memory_hits_.fetch_add(1, std::memory_order_relaxed); + } else { + pending = std::make_shared(); + entries_.emplace(key, pending); + owner = true; + memory_misses_.fetch_add(1, std::memory_order_relaxed); + } + } + + if (!owner) { + waits_.fetch_add(1, std::memory_order_relaxed); + auto wait_start = std::chrono::steady_clock::now(); + auto result = pending->Wait(); + auto wait_end = std::chrono::steady_clock::now(); + AddWaitTime(uint64_t(std::chrono::duration_cast( + wait_end - wait_start) + .count())); + return result; + } + + if (std::shared_ptr persistent_result = + LoadPersistent(key)) { + metallib_bytes_.fetch_add(persistent_result->metallib_data.size(), + std::memory_order_relaxed); + pending->Publish(persistent_result); + return persistent_result; + } + + auto compile_start = std::chrono::steady_clock::now(); + std::shared_ptr result = compile_fn(); + auto compile_end = std::chrono::steady_clock::now(); + AddOwnerCompileTime( + uint64_t(std::chrono::duration_cast( + compile_end - compile_start) + .count())); + + if (result) { + metallib_bytes_.fetch_add(result->metallib_data.size(), + std::memory_order_relaxed); + } + if (!result || !result->success) { + failures_.fetch_add(1, std::memory_order_relaxed); + } + + pending->Publish(result); + + if (result && result->success) { + StorePersistent(key, *result); + } + + if (!result || !result->success) { + std::lock_guard lock(mutex_); + auto it = entries_.find(key); + if (it != entries_.end() && it->second == pending) { + entries_.erase(it); + } + } + + return result; +} + +std::shared_ptr +MetalStageCompileCache::LoadPersistent( + const MetalStageCompileRequest& request) { + return LoadPersistent(BuildKey(request)); +} + +std::shared_ptr +MetalStageCompileCache::LoadPersistent(const MetalStageCompileKey& key) { + if (!g_metal_artifact_store || !g_metal_artifact_store->IsInitialized()) { + return nullptr; + } + MetalStageCompileResult result; + if (!g_metal_artifact_store->LoadStageCompile(key, &result)) { + persistent_misses_.fetch_add(1, std::memory_order_relaxed); + uint64_t log_index = + persistent_miss_log_count_.fetch_add(1, std::memory_order_relaxed); + if (log_index < 64) { + XELOGI( + "MetalStageCompileCache: persistent miss #{} stage={} " + "dxil={:016X}{:016X}/{} root_abi={} compat={:#x} " + "validation={:#x} stage_in_mode={:#x} min_target={}/{}:{:016X} " + "topology={} geom_tess={} stage_in={} layout={:016X}{:016X}", + log_index + 1, StageName(key.stage), key.dxil_hash_high, + key.dxil_hash_low, key.dxil_size, key.root_signature_abi_version, + key.compatibility_flags, key.validation_flags, + key.stage_in_generation_mode, key.minimum_gpu_family, key.minimum_os, + key.minimum_os_version_hash, key.input_topology, + key.geometry_or_tessellation_emulation_enabled, + key.stage_in_metallib_requested, key.input_layout_hash_high, + key.input_layout_hash_low); + } + return nullptr; + } + persistent_hits_.fetch_add(1, std::memory_order_relaxed); + return std::make_shared(std::move(result)); +} + +void MetalStageCompileCache::StorePersistent( + const MetalStageCompileKey& key, const MetalStageCompileResult& result) { + if (!result.success || !g_metal_artifact_store || + !g_metal_artifact_store->IsInitialized()) { + return; + } + g_metal_artifact_store->StoreStageCompile(key, result); +} + +void MetalStageCompileCache::AddOwnerCompileTime(uint64_t ms) { + owner_compile_ms_total_.fetch_add(ms, std::memory_order_relaxed); + AtomicMax(owner_compile_ms_max_, ms); +} + +void MetalStageCompileCache::AddWaitTime(uint64_t ms) { + wait_ms_total_.fetch_add(ms, std::memory_order_relaxed); + AtomicMax(wait_ms_max_, ms); +} + +MetalStageCompileCacheStats MetalStageCompileCache::GetStats() const { + MetalStageCompileCacheStats stats; + stats.requests = requests_.load(std::memory_order_relaxed); + stats.memory_hits = memory_hits_.load(std::memory_order_relaxed); + stats.memory_misses = memory_misses_.load(std::memory_order_relaxed); + stats.waits = waits_.load(std::memory_order_relaxed); + stats.failures = failures_.load(std::memory_order_relaxed); + stats.persistent_hits = persistent_hits_.load(std::memory_order_relaxed); + stats.persistent_misses = persistent_misses_.load(std::memory_order_relaxed); + stats.dxil_bytes = dxil_bytes_.load(std::memory_order_relaxed); + stats.metallib_bytes = metallib_bytes_.load(std::memory_order_relaxed); + stats.owner_compile_ms_total = + owner_compile_ms_total_.load(std::memory_order_relaxed); + stats.owner_compile_ms_max = + owner_compile_ms_max_.load(std::memory_order_relaxed); + stats.wait_ms_total = wait_ms_total_.load(std::memory_order_relaxed); + stats.wait_ms_max = wait_ms_max_.load(std::memory_order_relaxed); + return stats; +} + +MetalStageCompileCacheStats MetalStageCompileCache::GetAndResetStats() { + MetalStageCompileCacheStats stats; + stats.requests = requests_.exchange(0, std::memory_order_relaxed); + stats.memory_hits = memory_hits_.exchange(0, std::memory_order_relaxed); + stats.memory_misses = memory_misses_.exchange(0, std::memory_order_relaxed); + stats.waits = waits_.exchange(0, std::memory_order_relaxed); + stats.failures = failures_.exchange(0, std::memory_order_relaxed); + stats.persistent_hits = + persistent_hits_.exchange(0, std::memory_order_relaxed); + stats.persistent_misses = + persistent_misses_.exchange(0, std::memory_order_relaxed); + stats.dxil_bytes = dxil_bytes_.exchange(0, std::memory_order_relaxed); + stats.metallib_bytes = metallib_bytes_.exchange(0, std::memory_order_relaxed); + stats.owner_compile_ms_total = + owner_compile_ms_total_.exchange(0, std::memory_order_relaxed); + stats.owner_compile_ms_max = + owner_compile_ms_max_.exchange(0, std::memory_order_relaxed); + stats.wait_ms_total = wait_ms_total_.exchange(0, std::memory_order_relaxed); + stats.wait_ms_max = wait_ms_max_.exchange(0, std::memory_order_relaxed); + return stats; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_stage_compile_cache.h b/src/xenia/gpu/metal/metal_stage_compile_cache.h new file mode 100644 index 000000000..65d97ede5 --- /dev/null +++ b/src/xenia/gpu/metal/metal_stage_compile_cache.h @@ -0,0 +1,197 @@ +/** + ****************************************************************************** + * 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_STAGE_COMPILE_CACHE_H_ +#define XENIA_GPU_METAL_METAL_STAGE_COMPILE_CACHE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xenia/gpu/metal/metal_shader_converter.h" + +namespace xe { +namespace gpu { +namespace metal { + +struct MetalStageCompileCacheStats { + uint64_t requests = 0; + uint64_t memory_hits = 0; + uint64_t memory_misses = 0; + uint64_t waits = 0; + uint64_t failures = 0; + uint64_t persistent_hits = 0; + uint64_t persistent_misses = 0; + uint64_t dxil_bytes = 0; + uint64_t metallib_bytes = 0; + uint64_t owner_compile_ms_total = 0; + uint64_t owner_compile_ms_max = 0; + uint64_t wait_ms_total = 0; + uint64_t wait_ms_max = 0; +}; + +struct MetalStageCompileKey { + static constexpr size_t kSerializedWordCount = 23; + + uint32_t key_version = 2; + uint32_t stage = 0; + uint64_t dxil_size = 0; + uint64_t dxil_hash_low = 0; + uint64_t dxil_hash_high = 0; + uint32_t compatibility_flags = 0; + uint32_t validation_flags = 0; + uint32_t stage_in_generation_mode = 0; + uint32_t function_constant_resource_space = 0; + uint32_t minimum_gpu_family = 0; + uint32_t minimum_os = 0; + uint64_t minimum_os_version_hash = 0; + uint32_t root_signature_abi_version = 0; + uint32_t root_signature_stage = 0; + uint32_t root_signature_bindless_resources_used = 0; + uint32_t ignore_root_signature = 0; + uint32_t ignore_debug_information = 0; + uint32_t input_topology = 0; + uint32_t geometry_or_tessellation_emulation_enabled = 0; + uint32_t stage_in_metallib_requested = 0; + uint32_t reserved = 0; + uint64_t input_layout_hash_low = 0; + uint64_t input_layout_hash_high = 0; + + std::array SerializeWords() const { + return {{ + key_version, + stage, + dxil_size, + dxil_hash_low, + dxil_hash_high, + compatibility_flags, + validation_flags, + stage_in_generation_mode, + function_constant_resource_space, + minimum_gpu_family, + minimum_os, + minimum_os_version_hash, + root_signature_abi_version, + root_signature_stage, + root_signature_bindless_resources_used, + ignore_root_signature, + ignore_debug_information, + input_topology, + geometry_or_tessellation_emulation_enabled, + stage_in_metallib_requested, + reserved, + input_layout_hash_low, + input_layout_hash_high, + }}; + } + + bool operator==(const MetalStageCompileKey& other) const { + return key_version == other.key_version && stage == other.stage && + dxil_size == other.dxil_size && + dxil_hash_low == other.dxil_hash_low && + dxil_hash_high == other.dxil_hash_high && + compatibility_flags == other.compatibility_flags && + validation_flags == other.validation_flags && + stage_in_generation_mode == other.stage_in_generation_mode && + function_constant_resource_space == + other.function_constant_resource_space && + minimum_gpu_family == other.minimum_gpu_family && + minimum_os == other.minimum_os && + minimum_os_version_hash == other.minimum_os_version_hash && + root_signature_abi_version == other.root_signature_abi_version && + root_signature_stage == other.root_signature_stage && + root_signature_bindless_resources_used == + other.root_signature_bindless_resources_used && + ignore_root_signature == other.ignore_root_signature && + ignore_debug_information == other.ignore_debug_information && + input_topology == other.input_topology && + geometry_or_tessellation_emulation_enabled == + other.geometry_or_tessellation_emulation_enabled && + stage_in_metallib_requested == other.stage_in_metallib_requested && + reserved == other.reserved && + input_layout_hash_low == other.input_layout_hash_low && + input_layout_hash_high == other.input_layout_hash_high; + } +}; + +struct MetalStageCompileKeyHasher { + size_t operator()(const MetalStageCompileKey& key) const; +}; + +class MetalStageCompileCache { + public: + using CompileFn = + std::function()>; + + MetalStageCompileCache() = default; + ~MetalStageCompileCache() = default; + + MetalStageCompileCache(const MetalStageCompileCache&) = delete; + MetalStageCompileCache& operator=(const MetalStageCompileCache&) = delete; + + std::shared_ptr GetOrCompile( + const MetalStageCompileRequest& request, CompileFn compile_fn); + std::shared_ptr LoadPersistent( + const MetalStageCompileRequest& request); + + MetalStageCompileCacheStats GetStats() const; + MetalStageCompileCacheStats GetAndResetStats(); + + private: + class PendingStageCompile { + public: + std::shared_ptr Wait(); + void Publish(std::shared_ptr result); + + private: + std::mutex mutex_; + std::condition_variable ready_cv_; + bool ready_ = false; + std::shared_ptr result_; + }; + + static MetalStageCompileKey BuildKey(const MetalStageCompileRequest& request); + std::shared_ptr LoadPersistent( + const MetalStageCompileKey& key); + void StorePersistent(const MetalStageCompileKey& key, + const MetalStageCompileResult& result); + void AddOwnerCompileTime(uint64_t ms); + void AddWaitTime(uint64_t ms); + + mutable std::mutex mutex_; + std::unordered_map, + MetalStageCompileKeyHasher> + entries_; + + std::atomic requests_{0}; + std::atomic memory_hits_{0}; + std::atomic memory_misses_{0}; + std::atomic waits_{0}; + std::atomic failures_{0}; + std::atomic persistent_hits_{0}; + std::atomic persistent_misses_{0}; + std::atomic persistent_miss_log_count_{0}; + std::atomic dxil_bytes_{0}; + std::atomic metallib_bytes_{0}; + std::atomic owner_compile_ms_total_{0}; + std::atomic owner_compile_ms_max_{0}; + std::atomic wait_ms_total_{0}; + std::atomic wait_ms_max_{0}; +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_STAGE_COMPILE_CACHE_H_ diff --git a/src/xenia/gpu/metal/metal_texture_cache.cc b/src/xenia/gpu/metal/metal_texture_cache.cc index 2803fb744..d9a88f7f1 100644 --- a/src/xenia/gpu/metal/metal_texture_cache.cc +++ b/src/xenia/gpu/metal/metal_texture_cache.cc @@ -18,12 +18,13 @@ #include #include #include +#include #include #include #include +#include #include -#include "third_party/stb/stb_image_write.h" #include "xenia/base/assert.h" #include "xenia/base/autorelease_pool_mac.h" #include "xenia/base/bit_stream.h" @@ -91,6 +92,7 @@ #include "xenia/gpu/shaders/bytecode/metal/texture_load_rgba16_snorm_float_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/metal/texture_load_rgba16_unorm_float_cs.h" #include "xenia/gpu/shaders/bytecode/metal/texture_load_rgba16_unorm_float_scaled_cs.h" +#include "xenia/gpu/shaders/bytecode/metal/texture_upload_repack.h" #include "xenia/gpu/texture_info.h" #include "xenia/gpu/texture_util.h" #include "xenia/gpu/xenos.h" @@ -105,7 +107,6 @@ DEFINE_bool(metal_texture_upload_via_blit, true, "Upload textures via staging buffers and GPU blit copies instead " "of CPU replaceRegion.", "Metal"); - DECLARE_bool(metal_use_heaps); DECLARE_int32(metal_heap_min_bytes); @@ -121,6 +122,7 @@ constexpr uint64_t kScaledResolveRetiredMaxBytes = 64ull * 1024ull * 1024ull; constexpr uint64_t kUploadBufferPoolMaxBytes = 512ull * 1024ull * 1024ull; constexpr uint64_t kScaledResolveRetiredMaxBytes = 256ull * 1024ull * 1024ull; #endif +constexpr uint32_t kViewBindlessHeapPressureThreshold = 65536; struct MetalLoadConstants { uint32_t is_tiled_3d_endian_scale; @@ -136,6 +138,20 @@ struct MetalLoadConstants { }; static_assert(sizeof(MetalLoadConstants) == 64); +struct MetalTextureUploadRepackConstants { + uint32_t source_offset; + uint32_t dest_offset; + uint32_t source_row_pitch; + uint32_t dest_row_pitch; + uint32_t source_image_pitch; + uint32_t dest_image_pitch; + uint32_t row_bytes; + uint32_t row_count; + uint32_t depth; + uint32_t padding[3]; +}; +static_assert(sizeof(MetalTextureUploadRepackConstants) == 48); + class ScopedAutoreleasePool { public: ScopedAutoreleasePool() : pool_(NS::AutoreleasePool::alloc()->init()) {} @@ -152,6 +168,13 @@ class ScopedAutoreleasePool { NS::AutoreleasePool* pool_; }; +void SetEncoderLabel(MTL::CommandEncoder* encoder, const char* label) { + if (!encoder || !label) { + return; + } + encoder->setLabel(NS::String::string(label, NS::UTF8StringEncoding)); +} + bool SupportsPixelFormat(MTL::Device* device, MTL::PixelFormat format) { if (!device || format == MTL::PixelFormatInvalid) { return false; @@ -296,22 +319,18 @@ class MetalTextureCache::UploadBufferPool { std::lock_guard lock(mutex_); ++usage_tick_; - size_t best_index = entries_.size(); - size_t best_size = std::numeric_limits::max(); - for (size_t i = 0; i < entries_.size(); ++i) { - Entry& entry = entries_[i]; - if (entry.in_use || entry.size < size) { - continue; + auto available_it = available_entries_by_size_.lower_bound(size); + while (available_it != available_entries_by_size_.end()) { + const size_t entry_index = available_it->second; + Entry& entry = entries_[entry_index]; + if (entry.buffer && !entry.in_use && entry.size >= size) { + available_entries_by_size_.erase(available_it); + entry.in_use = true; + entry.last_used_tick = usage_tick_; + return entry.buffer; } - if (entry.size < best_size) { - best_index = i; - best_size = entry.size; - } - } - if (best_index < entries_.size()) { - entries_[best_index].in_use = true; - entries_[best_index].last_used_tick = usage_tick_; - return entries_[best_index].buffer; + // Defensive cleanup if an old entry was left in the size index. + available_it = available_entries_by_size_.erase(available_it); } can_pool = pooled_bytes_ + size <= max_pooled_bytes_; } @@ -325,7 +344,9 @@ class MetalTextureCache::UploadBufferPool ++usage_tick_; if (can_pool && pooled_bytes_ + size <= max_pooled_bytes_) { pooled_bytes_ += size; + const size_t entry_index = entries_.size(); entries_.push_back({buffer, size, true, true, usage_tick_}); + entry_indices_by_buffer_[buffer] = entry_index; return buffer; } ++transient_allocations_; @@ -337,20 +358,60 @@ class MetalTextureCache::UploadBufferPool if (!buffer) { return; } - bool release_transient = true; + MTL::Buffer* buffers[] = {buffer}; + ReleaseImmediateBatch(buffers, 1); + } + + void ReleaseImmediateBatch(MTL::Buffer* const* buffers, size_t count) { + if (!buffers || !count) { + return; + } + std::vector transient_buffers; { std::lock_guard lock(mutex_); ++usage_tick_; - for (Entry& entry : entries_) { - if (entry.buffer == buffer) { - entry.in_use = false; - entry.last_used_tick = usage_tick_; - release_transient = false; - break; + const uint64_t release_tick = usage_tick_; + for (size_t buffer_index = 0; buffer_index < count; ++buffer_index) { + MTL::Buffer* buffer = buffers[buffer_index]; + if (!buffer) { + continue; + } + bool release_transient = true; + auto entry_it = entry_indices_by_buffer_.find(buffer); + if (entry_it != entry_indices_by_buffer_.end()) { + Entry& entry = entries_[entry_it->second]; + if (entry.buffer == buffer) { + if (entry.in_use) { + entry.in_use = false; + entry.last_used_tick = release_tick; + available_entries_by_size_.emplace(entry.size, entry_it->second); + } + release_transient = false; + } + } + // Compatibility with any pool state created before the index existed. + if (release_transient) { + for (size_t i = 0; i < entries_.size(); ++i) { + Entry& entry = entries_[i]; + if (entry.buffer != buffer) { + continue; + } + entry_indices_by_buffer_[buffer] = i; + if (entry.in_use) { + entry.in_use = false; + entry.last_used_tick = release_tick; + available_entries_by_size_.emplace(entry.size, i); + } + release_transient = false; + break; + } + } + if (release_transient) { + transient_buffers.push_back(buffer); } } } - if (release_transient) { + for (MTL::Buffer* buffer : transient_buffers) { buffer->release(); } } @@ -387,6 +448,8 @@ class MetalTextureCache::UploadBufferPool } pooled_bytes_ = 0; entries_.clear(); + available_entries_by_size_.clear(); + entry_indices_by_buffer_.clear(); } size_t GetEntryCount() const { @@ -434,6 +497,8 @@ class MetalTextureCache::UploadBufferPool mutable std::mutex mutex_; std::vector entries_; + std::multimap available_entries_by_size_; + std::unordered_map entry_indices_by_buffer_; MTL::Device* device_ = nullptr; uint64_t max_pooled_bytes_ = 0; uint64_t pooled_bytes_ = 0; @@ -454,8 +519,25 @@ void MetalTextureCache::UploadBufferPool::HandleCommandBufferCompleted( releases = std::move(it->second); pending_releases.erase(it); } - for (auto& release : releases) { - release.pool->ReleaseImmediate(release.buffer); + std::sort(releases.begin(), releases.end(), + [](const PendingRelease& a, const PendingRelease& b) { + return a.pool.get() < b.pool.get(); + }); + std::vector buffers; + buffers.reserve(releases.size()); + for (size_t i = 0; i < releases.size();) { + std::shared_ptr pool = releases[i].pool; + buffers.clear(); + size_t j = i; + for (; j < releases.size() && releases[j].pool.get() == pool.get(); ++j) { + if (releases[j].buffer) { + buffers.push_back(releases[j].buffer); + } + } + if (pool && !buffers.empty()) { + pool->ReleaseImmediateBatch(buffers.data(), buffers.size()); + } + i = j; } } @@ -482,16 +564,6 @@ bool MetalTextureCache::ShouldUploadViaBlit() const { return ::cvars::metal_texture_upload_via_blit; } -bool MetalTextureCache::CanUseCurrentCommandBufferForTextureUploads() const { - if (!ShouldUploadViaBlit() || !command_processor_) { - return false; - } - if (!command_processor_->GetCurrentCommandBuffer()) { - return false; - } - return !command_processor_->HasActiveRenderEncoder(); -} - void MetalTextureCache::BeginUploadCommandBufferBatch() { ++upload_batch_depth_; if (upload_batch_depth_ != 1) { @@ -504,22 +576,18 @@ void MetalTextureCache::BeginUploadCommandBufferBatch() { // buffer is already active in the command processor. Keeping upload work on // a separate command buffer in that state can reorder with in-flight render // setup and lead to startup rendering regressions. - if (command_processor_->GetCurrentCommandBuffer()) { + if (command_processor_->HasActiveSubmission()) { return; } - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!queue) { - return; - } - MTL::CommandBuffer* cmd = queue->commandBuffer(); + MTL::CommandBuffer* cmd = + command_processor_->CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=texture-upload-batch"); if (!cmd) { return; } - cmd->retain(); - cmd->setLabel( - NS::String::string("XeniaTextureUploadBatch", NS::UTF8StringEncoding)); upload_batch_command_buffer_ = cmd; upload_batch_command_buffer_has_work_ = false; + upload_batch_command_buffer_shared_memory_ranges_.clear(); } void MetalTextureCache::EndUploadCommandBufferBatch() { @@ -533,7 +601,10 @@ void MetalTextureCache::EndUploadCommandBufferBatch() { MTL::CommandBuffer* cmd = upload_batch_command_buffer_; upload_batch_command_buffer_ = nullptr; bool has_work = upload_batch_command_buffer_has_work_; + auto shared_memory_ranges = + std::move(upload_batch_command_buffer_shared_memory_ranges_); upload_batch_command_buffer_has_work_ = false; + upload_batch_command_buffer_shared_memory_ranges_.clear(); if (!cmd) { return; } @@ -541,17 +612,27 @@ void MetalTextureCache::EndUploadCommandBufferBatch() { cmd->release(); return; } - cmd->addCompletedHandler(^(MTL::CommandBuffer* completed_cmd) { - completed_cmd->release(); - }); - cmd->commit(); + if (command_processor_) { + if (!shared_memory_ranges.empty()) { + static_cast(shared_memory()) + .TrackStandaloneGpuAccess( + cmd, shared_memory_ranges.data(), + static_cast(shared_memory_ranges.size())); + } + command_processor_->CommitStandaloneAsync(cmd); + } else { + cmd->release(); + } } void MetalTextureCache::AbortUploadCommandBufferBatch(bool commit_if_has_work) { MTL::CommandBuffer* cmd = upload_batch_command_buffer_; upload_batch_command_buffer_ = nullptr; bool has_work = upload_batch_command_buffer_has_work_; + auto shared_memory_ranges = + std::move(upload_batch_command_buffer_shared_memory_ranges_); upload_batch_command_buffer_has_work_ = false; + upload_batch_command_buffer_shared_memory_ranges_.clear(); if (!cmd) { return; } @@ -559,10 +640,450 @@ void MetalTextureCache::AbortUploadCommandBufferBatch(bool commit_if_has_work) { cmd->release(); return; } - cmd->addCompletedHandler(^(MTL::CommandBuffer* completed_cmd) { - completed_cmd->release(); - }); - cmd->commit(); + if (command_processor_) { + if (!shared_memory_ranges.empty()) { + static_cast(shared_memory()) + .TrackStandaloneGpuAccess( + cmd, shared_memory_ranges.data(), + static_cast(shared_memory_ranges.size())); + } + command_processor_->CommitStandaloneAsync(cmd); + } else { + cmd->release(); + } +} + +void MetalTextureCache::BeginDeferredUploadEncoderBatch() { + ++deferred_upload_batch_depth_; +} + +bool MetalTextureCache::EndDeferredUploadEncoderBatch() { + if (!deferred_upload_batch_depth_) { + return true; + } + --deferred_upload_batch_depth_; + if (deferred_upload_batch_depth_ != 0) { + return true; + } + if (!deferred_upload_compute_encoder_ && deferred_upload_copies_.empty()) { + deferred_upload_command_buffer_ = nullptr; + return true; + } + return FlushDeferredUploadEncoderBatch(); +} + +bool MetalTextureCache::FlushDeferredUploadEncoderBatch() { + MTL::CommandBuffer* cmd = deferred_upload_command_buffer_; + if (!deferred_upload_compute_encoder_ && deferred_upload_copies_.empty()) { + deferred_upload_command_buffer_ = nullptr; + return true; + } + if (deferred_upload_compute_encoder_) { + MTL::ComputeCommandEncoder* compute_encoder = + deferred_upload_compute_encoder_; + deferred_upload_compute_encoder_ = nullptr; + compute_encoder->endEncoding(); + compute_encoder->release(); + } + bool success = true; + if (cmd && !deferred_upload_copies_.empty()) { + if (command_processor_ && + cmd == command_processor_->GetCurrentCommandBuffer()) { + command_processor_->EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kTextureDeferredBlit); + } + MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); + if (!blit) { + XELOGE("Metal texture upload: failed to create deferred blit encoder"); + success = false; + } else { + for (const DeferredUploadCopy& copy : deferred_upload_copies_) { + blit->copyFromBuffer( + copy.source_buffer, copy.source_offset_bytes, copy.source_row_pitch, + copy.source_bytes_per_image, + MTL::Size::Make(copy.width, copy.height, copy.depth), + copy.destination_texture, copy.destination_slice, + copy.destination_level, MTL::Origin::Make(0, 0, 0)); + } + blit->endEncoding(); + } + } + deferred_upload_copies_.clear(); + deferred_upload_command_buffer_ = nullptr; + return success; +} + +bool MetalTextureCache::FlushPendingUploadEncodersForCommandEncoderBoundary() { + if (!deferred_upload_compute_encoder_ && deferred_upload_copies_.empty()) { + return true; + } + return FlushDeferredUploadEncoderBatch(); +} + +void MetalTextureCache::BeginTextureUploadBatch() { + BeginUploadCommandBufferBatch(); + BeginDeferredUploadEncoderBatch(); +} + +bool MetalTextureCache::EndTextureUploadBatch() { + bool success = EndDeferredUploadEncoderBatch(); + if (!success) { + XELOGE("Metal texture upload: failed to flush deferred upload batch"); + ResetTextureBindings(); + } + EndUploadCommandBufferBatch(); + return success; +} + +bool MetalTextureCache::PrepareTextureDataLoadRanges( + Texture** textures, uint32_t texture_count, uint64_t base_outdated_mask, + uint64_t mips_outdated_mask) { + assert_true(texture_count <= 64); + if (!textures || !texture_count || + (!base_outdated_mask && !mips_outdated_mask)) { + return true; + } + + std::vector ranges; + ranges.reserve(texture_count * 2); + auto add_range_if_needed = [&](uint32_t start, uint32_t length) { + if (!shared_memory().IsRangeValid(start, length)) { + ranges.push_back({start, length}); + } + }; + for (uint32_t i = 0; i < texture_count; ++i) { + Texture* texture = textures[i]; + if (!texture) { + continue; + } + TextureKey texture_key = texture->key(); + if (base_outdated_mask & (UINT64_C(1) << i)) { + add_range_if_needed(static_cast(texture_key.base_page << 12), + xe::align(texture->GetGuestBaseSize(), UINT32_C(16))); + } + if (mips_outdated_mask & (UINT64_C(1) << i)) { + add_range_if_needed(static_cast(texture_key.mip_page << 12), + xe::align(texture->GetGuestMipsSize(), UINT32_C(16))); + } + } + if (ranges.empty()) { + return true; + } + + // Shared-memory uploads use a Metal blit encoder. Make sure no deferred + // texture upload encoder is still open before requesting residency. + if ((deferred_upload_compute_encoder_ || !deferred_upload_copies_.empty()) && + !FlushDeferredUploadEncoderBatch()) { + if (command_processor_) { + command_processor_->RecordSharedMemoryRequestOutcome( + MetalCommandProcessor::SharedMemoryRequestOutcome:: + kTextureDeferredUploadFlush); + } + return false; + } + + // Request the guest memory needed by this texture upload before loading the + // texture data for the current draw. + const MetalCommandProcessor::SharedMemoryRequestReason reason = + base_outdated_mask && mips_outdated_mask + ? MetalCommandProcessor::SharedMemoryRequestReason:: + kTextureBaseAndMips + : base_outdated_mask + ? MetalCommandProcessor::SharedMemoryRequestReason::kTextureBase + : MetalCommandProcessor::SharedMemoryRequestReason::kTextureMips; + if (!command_processor_) { + XELOGE("Metal texture data residency requires a command processor"); + return false; + } + return command_processor_->RequestSharedMemoryRanges( + reason, ranges.data(), static_cast(ranges.size())); +} + +bool MetalTextureCache::RequestTextureDataRange(Texture&, + TextureDataRangeSource source, + uint32_t start, + uint32_t length) { + if (shared_memory().IsRangeValid(start, length)) { + return true; + } + static bool range_not_resident_logged = false; + if (!range_not_resident_logged) { + range_not_resident_logged = true; + XELOGE( + "Metal texture load range was not resident after residency request " + "(source={} start=0x{:08X} length={}); skipping texture load", + source == TextureDataRangeSource::kBase ? "base" : "mips", start, + length); + } + return false; +} + +MTL::ComputeCommandEncoder* MetalTextureCache::GetDeferredUploadComputeEncoder( + MTL::CommandBuffer* command_buffer) { + if (!deferred_upload_batch_depth_ || !command_buffer) { + return nullptr; + } + if (deferred_upload_command_buffer_ && + deferred_upload_command_buffer_ != command_buffer && + !FlushDeferredUploadEncoderBatch()) { + return nullptr; + } + deferred_upload_command_buffer_ = command_buffer; + if (!deferred_upload_compute_encoder_) { + if (command_processor_ && + command_buffer == command_processor_->GetCurrentCommandBuffer()) { + command_processor_->EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kTextureCompute); + } + deferred_upload_compute_encoder_ = command_buffer->computeCommandEncoder(); + if (deferred_upload_compute_encoder_) { + SetEncoderLabel(deferred_upload_compute_encoder_, + "XeniaTextureUploadDeferredComputeEncoder"); + deferred_upload_compute_encoder_->retain(); + } + } + return deferred_upload_compute_encoder_; +} + +void MetalTextureCache::QueueDeferredUploadCopy( + MTL::Buffer* source_buffer, size_t source_offset_bytes, + size_t source_row_pitch, size_t source_bytes_per_image, + MTL::Texture* destination_texture, uint32_t destination_slice, + uint32_t destination_level, uint32_t width, uint32_t height, + uint32_t depth) { + assert_not_zero(deferred_upload_batch_depth_); + assert_not_null(deferred_upload_command_buffer_); + assert_not_null(source_buffer); + assert_not_null(destination_texture); + assert_not_zero(width); + assert_not_zero(height); + assert_not_zero(depth); + DeferredUploadCopy copy = {}; + copy.source_buffer = source_buffer; + copy.source_offset_bytes = source_offset_bytes; + copy.source_row_pitch = source_row_pitch; + copy.source_bytes_per_image = source_bytes_per_image; + copy.destination_texture = destination_texture; + copy.destination_slice = destination_slice; + copy.destination_level = destination_level; + copy.width = width; + copy.height = height; + copy.depth = depth; + deferred_upload_copies_.push_back(copy); +} + +class MetalTextureCache::UploadBatchScope { + public: + explicit UploadBatchScope(MetalTextureCache& texture_cache) + : texture_cache_(texture_cache) { + texture_cache_.BeginTextureUploadBatch(); + } + + UploadBatchScope(const UploadBatchScope&) = delete; + UploadBatchScope& operator=(const UploadBatchScope&) = delete; + + ~UploadBatchScope() { End(); } + + bool End() { + if (ended_) { + return success_; + } + success_ = texture_cache_.EndTextureUploadBatch(); + ended_ = true; + return success_; + } + + private: + MetalTextureCache& texture_cache_; + bool ended_ = false; + bool success_ = true; +}; + +bool MetalTextureCache::PrepareTextureMaterialization( + const RegisterFile& regs, uint32_t used_texture_mask, + TextureMaterializationPlan& plan) { + SCOPE_profile_cpu_f("gpu"); + plan.Reset(); + if (!used_texture_mask) { + return true; + } + + auto append_texture = [&](TextureKey key) { + ++plan.request_count; + Texture* texture = FindOrCreateTexture(key); + bool base_outdated = texture ? texture->base_outdated_lockless() : false; + bool mips_outdated = texture ? texture->mips_outdated_lockless() : false; + if (!texture || (!base_outdated && !mips_outdated)) { + return; + } + for (const TextureMaterializationPlan::TextureLoad& planned_load : + plan.texture_loads_) { + if (planned_load.texture == texture) { + return; + } + } + const TextureKey& texture_key = texture->key(); + const uint32_t base_start = texture_key.base_page << 12; + const uint32_t mips_start = texture_key.mip_page << 12; + const uint32_t base_length = + xe::align(texture->GetGuestBaseSize(), UINT32_C(16)); + const uint32_t mips_length = + xe::align(texture->GetGuestMipsSize(), UINT32_C(16)); + + enum class SourceRangeState { + kNone, + kInvalid, + kValid, + kMixed, + }; + auto source_range_state = [&](bool needed, uint32_t start, + uint32_t length) { + if (!needed || !length) { + return SourceRangeState::kNone; + } + if (shared_memory().IsRangeInvalid(start, length)) { + return SourceRangeState::kInvalid; + } + if (shared_memory().IsRangeValid(start, length)) { + return SourceRangeState::kValid; + } + return SourceRangeState::kMixed; + }; + const SourceRangeState base_state = + source_range_state(base_outdated, base_start, base_length); + const SourceRangeState mips_state = + source_range_state(mips_outdated, mips_start, mips_length); + const bool base_needs_upload = base_state == SourceRangeState::kInvalid || + base_state == SourceRangeState::kMixed; + const bool mips_needs_upload = mips_state == SourceRangeState::kInvalid || + mips_state == SourceRangeState::kMixed; + const bool base_cpu_source = base_state == SourceRangeState::kInvalid; + const bool mips_cpu_source = mips_state == SourceRangeState::kInvalid; + + bool use_cpu_source = !texture_key.scaled_resolve; + bool has_cpu_source_range = false; + if (base_outdated && base_length) { + use_cpu_source &= base_cpu_source; + has_cpu_source_range |= base_cpu_source; + } + if (mips_outdated && mips_length) { + use_cpu_source &= mips_cpu_source; + has_cpu_source_range |= mips_cpu_source; + } + use_cpu_source &= has_cpu_source_range; + if (!use_cpu_source) { + if (command_processor_) { + MetalCommandProcessor::TextureUploadSourceFallbackReason + fallback_reason = MetalCommandProcessor:: + TextureUploadSourceFallbackReason::kUnknown; + if (texture_key.scaled_resolve) { + fallback_reason = MetalCommandProcessor:: + TextureUploadSourceFallbackReason::kScaledResolve; + } else if (base_state == SourceRangeState::kMixed || + mips_state == SourceRangeState::kMixed) { + fallback_reason = MetalCommandProcessor:: + TextureUploadSourceFallbackReason::kMixedValidity; + } else if (base_state == SourceRangeState::kValid || + mips_state == SourceRangeState::kValid) { + fallback_reason = MetalCommandProcessor:: + TextureUploadSourceFallbackReason::kSourceAlreadyResident; + } + command_processor_->RecordTextureUploadSourceFallback(fallback_reason); + } + if (base_needs_upload) { + plan.source_ranges.push_back({base_start, base_length}); + } + if (mips_needs_upload) { + plan.source_ranges.push_back({mips_start, mips_length}); + } + } + + plan.texture_loads_.push_back({ + texture, + base_outdated, + mips_outdated, + use_cpu_source, + }); + ++plan.planned_load_count; + }; + + uint32_t remaining_bits = used_texture_mask; + uint32_t index = 0; + while (xe::bit_scan_forward(remaining_bits, &index)) { + remaining_bits = xe::clear_lowest_bit(remaining_bits); + + TextureKey key; + uint8_t swizzled_signs = kSwizzledSignsUnsigned; + BindingInfoFromFetchConstant(regs.GetTextureFetch(index), key, + &swizzled_signs); + if (!key.is_valid) { + continue; + } + + if (IsSignedVersionSeparateForFormat(key)) { + if (texture_util::IsAnySignNotSigned(swizzled_signs)) { + append_texture(key); + } + if (texture_util::IsAnySignSigned(swizzled_signs)) { + TextureKey signed_key = key; + signed_key.signed_separate = 1; + append_texture(signed_key); + } + } else { + append_texture(key); + } + } + + return true; +} + +bool MetalTextureCache::ExecuteTextureMaterialization( + TextureMaterializationPlan& plan) { + if (plan.texture_loads_.empty()) { + return true; + } + uint64_t load_calls_before = loaded_texture_data_count_; + bool success = true; + + std::vector fallback_textures; + fallback_textures.reserve(plan.texture_loads_.size()); + { + UploadBatchScope upload_batch(*this); + for (const TextureMaterializationPlan::TextureLoad& load : + plan.texture_loads_) { + if (!load.texture) { + continue; + } + if (!load.use_cpu_source) { + fallback_textures.push_back(load.texture); + continue; + } + if (!LoadTextureDataFromCpuGuestMemory(*load.texture, load.load_base, + load.load_mips)) { + if (command_processor_) { + command_processor_->RecordTextureUploadSourceFallback( + MetalCommandProcessor::TextureUploadSourceFallbackReason:: + kCpuSourceLoadFailed); + } + fallback_textures.push_back(load.texture); + } + } + + if (!fallback_textures.empty()) { + LoadTexturesData(fallback_textures.data(), + static_cast(fallback_textures.size())); + } + success = upload_batch.End(); + } + plan.executed_load_count = + static_cast(loaded_texture_data_count_ - load_calls_before); + plan.texture_loads_.clear(); + if (!success) { + ResetTextureBindings(); + } + return success; } bool MetalTextureCache::IsDecompressionNeededForKey(TextureKey key) const { @@ -574,8 +1095,8 @@ bool MetalTextureCache::IsDecompressionNeededForKey(TextureKey key) const { if (::cvars::metal_force_bc_decompress) { return true; } - // BC support is GPU-family dependent on iOS. Creating BC textures on a - // device that doesn't support them may trip Metal validation and abort. + // BC support is GPU-family dependent on Apple GPUs. Creating BC textures + // on a device that doesn't support them may trip Metal validation. if (!supports_bc_texture_compression_) { return true; } @@ -617,7 +1138,7 @@ TextureCache::LoadShaderIndex MetalTextureCache::GetLoadShaderIndexForKey( case xenos::TextureFormat::k_2_10_10_10: return kLoadShaderIndex32bpb; case xenos::TextureFormat::k_4_4_4_4: - return kLoadShaderIndexRGBA4ToBGRA4; + return kLoadShaderIndexRGBA4ToARGB4; case xenos::TextureFormat::k_10_11_11: return key.signed_separate ? kLoadShaderIndexR11G11B10ToRGBA16SNorm : kLoadShaderIndexR11G11B10ToRGBA16; @@ -638,7 +1159,7 @@ TextureCache::LoadShaderIndex MetalTextureCache::GetLoadShaderIndexForKey( case xenos::TextureFormat::k_DXT5A: return kLoadShaderIndexDXT5AToR8; case xenos::TextureFormat::k_DXT3A_AS_1_1_1_1: - return kLoadShaderIndexDXT3AAs1111ToBGRA4; + return kLoadShaderIndexDXT3AAs1111ToARGB4; case xenos::TextureFormat::k_CTX1: return kLoadShaderIndexCTX1; @@ -694,6 +1215,12 @@ TextureCache::LoadShaderIndex MetalTextureCache::GetLoadShaderIndexForKey( case xenos::TextureFormat::k_32_32_32_32_FLOAT: return kLoadShaderIndex128bpb; + case xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP: + // Metal has no native packed YUV format; always decompress to RGB8. + return kLoadShaderIndexGBGR8ToRGB8; + case xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP: + return kLoadShaderIndexBGRG8ToRGB8; + case xenos::TextureFormat::k_8_B: return kLoadShaderIndex8bpb; case xenos::TextureFormat::k_8_8_8_8_A: @@ -783,6 +1310,11 @@ MTL::PixelFormat MetalTextureCache::GetPixelFormatForKey(TextureKey key) const { case xenos::TextureFormat::k_24_8_FLOAT: return MTL::PixelFormatR32Float; + case xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP: + case xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP: + // Decompressed from packed YUV to RGBA8 by the load shader. + return MTL::PixelFormatRGBA8Unorm; + case xenos::TextureFormat::k_8_B: return MTL::PixelFormatR8Unorm; case xenos::TextureFormat::k_8_8_8_8_A: @@ -801,12 +1333,12 @@ MTL::PixelFormat MetalTextureCache::GetPixelFormatForKey(TextureKey key) const { } bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, - bool load_mips) { + bool load_mips, + TextureLoadSourceMode source_mode) { MetalTexture* metal_texture = static_cast(&texture); if (!metal_texture || !metal_texture->metal_texture()) { return false; } - const TextureKey& key = texture.key(); bool texture_resolution_scaled = key.scaled_resolve && IsDrawResolutionScaled(); @@ -816,6 +1348,11 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, texture_resolution_scaled ? draw_resolution_scale_y() : 1; uint32_t texture_resolution_scale_area = texture_resolution_scale_x * texture_resolution_scale_y; + const bool use_cpu_guest_source = + source_mode == TextureLoadSourceMode::kCpuGuestMemory; + if (use_cpu_guest_source && texture_resolution_scaled) { + return false; + } const texture_util::TextureGuestLayout& guest_layout = texture.guest_layout(); xenos::DataDimension dimension = key.dimension; @@ -858,12 +1395,6 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, const TextureCache::LoadShaderInfo& load_shader_info = GetLoadShaderInfo(load_shader); - if (texture_resolution_scaled) { - static uint32_t scaled_load_log_count = 0; - if (scaled_load_log_count < 8) { - ++scaled_load_log_count; - } - } bool is_block_compressed_format = key.format == xenos::TextureFormat::k_DXT1 || @@ -971,16 +1502,18 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, if (!device) { return false; } - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!queue) { + if (!command_processor_->GetMetalCommandQueue()) { return false; } - MetalSharedMemory& metal_shared_memory = - static_cast(shared_memory()); - MTL::Buffer* shared_buffer = metal_shared_memory.GetBuffer(); - if (!shared_buffer) { - return false; + MTL::Buffer* shared_buffer = nullptr; + if (!use_cpu_guest_source) { + MetalSharedMemory& metal_shared_memory = + static_cast(shared_memory()); + shared_buffer = metal_shared_memory.GetBuffer(); + if (!shared_buffer) { + return false; + } } std::shared_ptr buffer_pool; @@ -1040,6 +1573,32 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, } const bool use_blit_upload = ShouldUploadViaBlit(); + MetalCommandProcessor::SharedMemoryReadDependency shared_memory_dependency; + MetalCommandProcessor::SharedMemoryRange shared_memory_ranges[2] = {}; + uint32_t shared_memory_range_count = 0; + if (!use_cpu_guest_source && !texture_resolution_scaled && + command_processor_) { + if (load_base) { + shared_memory_ranges[shared_memory_range_count++] = { + base_guest_address, + xe::align(texture.GetGuestBaseSize(), UINT32_C(16)), + }; + } + if (load_mips) { + shared_memory_ranges[shared_memory_range_count++] = { + mips_guest_address, + xe::align(texture.GetGuestMipsSize(), UINT32_C(16)), + }; + } + if (shared_memory_range_count && + !command_processor_->PrepareSharedMemoryComputeReadDependency( + shared_memory_ranges, shared_memory_range_count, use_blit_upload, + &shared_memory_dependency)) { + release_buffer_immediate(constants_buffer, constants_buffer_size); + release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); + return false; + } + } auto find_stored_level = [&](bool is_base_storage, @@ -1052,16 +1611,15 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, return nullptr; }; - MTL::CommandBuffer* current_command_buffer = - command_processor_ ? command_processor_->GetCurrentCommandBuffer() - : nullptr; - bool use_upload_batch = use_blit_upload && upload_batch_command_buffer_ && - command_processor_ && !current_command_buffer; - // Reuse the current command buffer whenever no render pass encoder is active. - // This keeps copy/resolve and texture-upload ordering within one submission. + bool has_active_submission = + command_processor_ && command_processor_->HasActiveSubmission(); + bool can_join_active_submission = + command_processor_ && + command_processor_->CanJoinActiveSubmissionForTransfer(); bool use_current_command_buffer = - use_blit_upload && command_processor_ && current_command_buffer && - !command_processor_->HasActiveRenderEncoder(); + use_blit_upload && can_join_active_submission; + bool use_upload_batch = use_blit_upload && upload_batch_command_buffer_ && + command_processor_ && !has_active_submission; if (use_upload_batch && texture_resolution_scaled) { bool needs_base_scaled_range = false; bool needs_mips_scaled_range = false; @@ -1084,14 +1642,25 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, } } + enum class UploadCommandBufferMode { + kStandalone, + kUploadBatch, + kCurrentSubmission, + }; + ScopedAutoreleasePool autorelease_pool; MTL::CommandBuffer* cmd = nullptr; + UploadCommandBufferMode upload_command_buffer_mode = + UploadCommandBufferMode::kStandalone; if (use_upload_batch) { cmd = upload_batch_command_buffer_; + upload_command_buffer_mode = UploadCommandBufferMode::kUploadBatch; } else if (use_current_command_buffer) { - cmd = current_command_buffer; + cmd = command_processor_->GetCurrentCommandBuffer(); + upload_command_buffer_mode = UploadCommandBufferMode::kCurrentSubmission; } else { - cmd = queue->commandBuffer(); + cmd = command_processor_->CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=texture-upload"); } if (!cmd) { release_buffer_immediate(constants_buffer, constants_buffer_size); @@ -1099,161 +1668,221 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, return false; } bool command_buffer_has_work = false; + auto is_current_submission = [&]() { + return upload_command_buffer_mode == + UploadCommandBufferMode::kCurrentSubmission; + }; + auto is_upload_batch = [&]() { + return upload_command_buffer_mode == UploadCommandBufferMode::kUploadBatch; + }; + auto is_standalone = [&]() { + return upload_command_buffer_mode == UploadCommandBufferMode::kStandalone; + }; + if (!use_cpu_guest_source && shared_memory_range_count && + is_current_submission()) { + uint64_t submission = command_processor_->GetCurrentSubmission(); + MetalSharedMemory& metal_shared_memory = + static_cast(shared_memory()); + for (uint32_t i = 0; i < shared_memory_range_count; ++i) { + metal_shared_memory.MarkGpuAccess(shared_memory_ranges[i].start, + shared_memory_ranges[i].length, + submission); + } + } + struct TransientTextureSourceRange { + bool valid = false; + uint32_t guest_start = 0; + uint32_t guest_length = 0; + uint32_t copy_start = 0; + uint32_t copy_length = 0; + size_t buffer_length = 0; + size_t guest_buffer_offset = 0; + size_t buffer_offset = 0; + }; + MTL::Buffer* transient_source_buffer = nullptr; + size_t transient_source_size = 0; + TransientTextureSourceRange transient_base_source; + TransientTextureSourceRange transient_mips_source; + bool using_deferred_upload_encoder = false; auto handle_upload_failure = [&](bool abort_batch) { - if ((use_upload_batch || use_current_command_buffer) && + if ((is_upload_batch() || is_current_submission()) && command_buffer_has_work) { + release_buffer_after(cmd, transient_source_buffer, transient_source_size); + transient_source_buffer = nullptr; release_buffer_after(cmd, constants_buffer, constants_buffer_size); release_buffer_after(cmd, dest_buffer, size_t(dest_buffer_size)); - if (use_upload_batch) { + if (is_upload_batch()) { upload_batch_command_buffer_has_work_ = true; - AbortUploadCommandBufferBatch(); + if (!deferred_upload_batch_depth_) { + AbortUploadCommandBufferBatch(); + } } return; } + release_buffer_immediate(transient_source_buffer, transient_source_size); + transient_source_buffer = nullptr; release_buffer_immediate(constants_buffer, constants_buffer_size); release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - if (use_upload_batch && abort_batch) { + if (is_upload_batch() && abort_batch && !deferred_upload_batch_depth_) { AbortUploadCommandBufferBatch(); } + if (is_standalone()) { + cmd->release(); + } }; - MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); - if (!encoder) { - handle_upload_failure(true); - return false; - } - encoder->setComputePipelineState(pipeline); - if (!texture_resolution_scaled) { - encoder->setBuffer(shared_buffer, 0, 2); - } - - uint32_t guest_x_blocks_per_group_log2 = - load_shader_info.GetGuestXBlocksPerGroupLog2(); - MTL::Size threads_per_group = - MTL::Size::Make(UINT32_C(1) << kLoadGuestXThreadsPerGroupLog2, - UINT32_C(1) << kLoadGuestYBlocksPerGroupLog2, 1); - - bool scaled_mips_source_set_up = false; - MTL::Buffer* source_buffer = shared_buffer; - size_t source_buffer_offset = 0; - size_t source_buffer_length = 0; - - size_t dispatch_index = 0; - for (const StoredLevelHostLayout& stored_level : stored_levels) { - bool is_base_storage = stored_level.is_base; - const texture_util::TextureGuestLayout::Level& level_guest_layout = - is_base_storage ? guest_layout.base - : guest_layout.mips[stored_level.level]; - - if (texture_resolution_scaled && - (is_base_storage || !scaled_mips_source_set_up)) { - uint32_t guest_address = - is_base_storage ? base_guest_address : mips_guest_address; - uint32_t guest_size_unscaled = is_base_storage - ? texture.GetGuestBaseSize() - : texture.GetGuestMipsSize(); - if (!MakeScaledResolveRangeCurrent(guest_address, guest_size_unscaled, - 4) || - !GetCurrentScaledResolveBuffer(source_buffer, source_buffer_offset, - source_buffer_length)) { - handle_upload_failure(false); + if (use_cpu_guest_source) { + auto append_transient_source = [&](bool needed, uint32_t guest_start, + uint32_t guest_length, + TransientTextureSourceRange& range) { + if (!needed || !guest_length) { + return true; + } + if (!shared_memory().IsRangeInvalid(guest_start, guest_length)) { return false; } - encoder->setBuffer(source_buffer, source_buffer_offset, 2); - if (!is_base_storage) { - scaled_mips_source_set_up = true; + constexpr uint32_t kSharedMemoryPageSize = UINT32_C(4096); + constexpr uint32_t kSharedMemoryPageMask = kSharedMemoryPageSize - 1; + static_assert((kSharedMemoryPageSize & kSharedMemoryPageMask) == 0); + if (guest_start >= SharedMemory::kBufferSize) { + return false; } - } - - uint32_t level_guest_offset = 0; - if (!texture_resolution_scaled) { - level_guest_offset = - is_base_storage ? base_guest_address : mips_guest_address; - } - if (!is_base_storage) { - uint32_t mip_offset = guest_layout.mip_offsets_bytes[stored_level.level]; - if (texture_resolution_scaled) { - mip_offset *= texture_resolution_scale_area; + uint64_t guest_end = uint64_t(guest_start) + guest_length; + if (guest_end > SharedMemory::kBufferSize) { + return false; } - level_guest_offset += mip_offset; - } - // Use guest layout pitch (blocks) - new XeSL expects blocks for both tiled - // and linear - uint32_t guest_pitch_aligned = - level_guest_layout.row_pitch_bytes / bytes_per_block; - - uint32_t size_blocks_x = - (stored_level.width_texels + (block_width - 1)) / block_width; - uint32_t size_blocks_y = - (stored_level.height_texels + (block_height - 1)) / block_height; - size_blocks_x *= texture_resolution_scale_x; - size_blocks_y *= texture_resolution_scale_y; - - uint32_t group_count_x = - (size_blocks_x + - ((UINT32_C(1) << guest_x_blocks_per_group_log2) - 1)) >> - guest_x_blocks_per_group_log2; - uint32_t group_count_y = - (size_blocks_y + - ((UINT32_C(1) << kLoadGuestYBlocksPerGroupLog2) - 1)) >> - kLoadGuestYBlocksPerGroupLog2; - MTL::Size threadgroups = MTL::Size::Make(group_count_x, group_count_y, - stored_level.depth_slices); - - for (uint32_t slice = 0; slice < array_size; ++slice) { - MetalLoadConstants constants = {}; - constants.is_tiled_3d_endian_scale = - uint32_t(key.tiled) | (uint32_t(is_3d_tiling) << 1) | - (uint32_t(key.endianness) << 2) | (texture_resolution_scale_x << 4) | - (texture_resolution_scale_y << 7); - constants.guest_offset = level_guest_offset; - if (!is_3d) { - uint32_t slice_stride = level_guest_layout.array_slice_stride_bytes; - if (texture_resolution_scaled) { - slice_stride *= texture_resolution_scale_area; - } - constants.guest_offset += slice * slice_stride; + uint32_t copy_start = guest_start & ~kSharedMemoryPageMask; + uint32_t copy_end = static_cast( + xe::align(guest_end, uint64_t(kSharedMemoryPageSize))); + copy_end = std::min(copy_end, SharedMemory::kBufferSize); + if (copy_end <= copy_start) { + return false; } - constants.guest_pitch_aligned = guest_pitch_aligned; - constants.guest_z_stride_block_rows_aligned = - level_guest_layout.z_slice_stride_block_rows; - constants.size_blocks[0] = size_blocks_x; - constants.size_blocks[1] = size_blocks_y; - constants.size_blocks[2] = stored_level.depth_slices; - constants.padding0 = 0; - constants.host_offset = 0; - constants.host_pitch = stored_level.row_pitch_bytes; - constants.height_texels = stored_level.height_texels; - - uint8_t* constants_ptr = - static_cast(constants_buffer->contents()) + - dispatch_index * constants_size; - std::memcpy(constants_ptr, &constants, sizeof(constants)); - - encoder->setBuffer(constants_buffer, dispatch_index * constants_size, 0); - encoder->setBuffer(dest_buffer, - stored_level.dest_offset_bytes + - slice * stored_level.slice_size_bytes, - 1); - encoder->dispatchThreadgroups(threadgroups, threads_per_group); - command_buffer_has_work = true; - ++dispatch_index; - } - } - - encoder->endEncoding(); - - MTL::Texture* mtl_texture = metal_texture->metal_texture(); - if (use_blit_upload) { - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (!blit) { + size_t buffer_offset = xe::align(transient_source_size, size_t(16)); + // The texture load shaders intentionally vectorize/local-X over the + // nominal source extent. The resident path reads from the full shared + // memory buffer; give the transient source a clean guard so those reads + // don't observe recycled upload-buffer contents or run past the buffer. + constexpr size_t kTextureLoadSourceGuardBytes = kSharedMemoryPageSize; + size_t copy_length = size_t(copy_end - copy_start); + size_t buffer_length = copy_length + kTextureLoadSourceGuardBytes; + if (buffer_offset > SIZE_MAX - buffer_length) { + return false; + } + range.valid = true; + range.guest_start = guest_start; + range.guest_length = guest_length; + range.copy_start = copy_start; + range.copy_length = static_cast(copy_length); + range.buffer_length = buffer_length; + range.buffer_offset = buffer_offset; + range.guest_buffer_offset = + buffer_offset + size_t(guest_start - copy_start); + transient_source_size = buffer_offset + buffer_length; + return true; + }; + if (!append_transient_source( + load_base, base_guest_address, + xe::align(texture.GetGuestBaseSize(), UINT32_C(16)), + transient_base_source) || + !append_transient_source( + load_mips, mips_guest_address, + xe::align(texture.GetGuestMipsSize(), UINT32_C(16)), + transient_mips_source) || + !transient_source_size) { handle_upload_failure(true); return false; } + transient_source_buffer = acquire_buffer(transient_source_size); + if (!transient_source_buffer || !transient_source_buffer->contents()) { + handle_upload_failure(true); + return false; + } + const uint8_t* xbox_ram = + static_cast(shared_memory()).GetXboxRamBase(); + if (!xbox_ram) { + handle_upload_failure(true); + return false; + } + uint8_t* source_data = + static_cast(transient_source_buffer->contents()); + auto copy_transient_source = [&](const TransientTextureSourceRange& range) { + if (!range.valid) { + return; + } + std::memcpy(source_data + range.buffer_offset, + xbox_ram + range.copy_start, range.copy_length); + std::memset(source_data + range.buffer_offset + range.copy_length, 0, + range.buffer_length - range.copy_length); + }; + copy_transient_source(transient_base_source); + copy_transient_source(transient_mips_source); + } + + struct PendingDirectUpload { + size_t source_offset_bytes = 0; + size_t source_row_pitch = 0; + size_t source_bytes_per_image = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 0; + uint32_t destination_slice = 0; + uint32_t destination_level = 0; + }; + struct PendingRepackUpload { + MTL::Buffer* staging_buffer = nullptr; + size_t staging_size = 0; + MetalTextureUploadRepackConstants constants = {}; + uint32_t blit_row_pitch = 0; + size_t blit_bytes_per_image = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 0; + uint32_t destination_slice = 0; + uint32_t destination_level = 0; + }; + std::vector direct_uploads; + std::vector repack_uploads; + + auto release_repack_staging_immediate = [&]() { + for (PendingRepackUpload& upload : repack_uploads) { + if (!upload.staging_buffer) { + continue; + } + release_buffer_immediate(upload.staging_buffer, upload.staging_size); + upload.staging_buffer = nullptr; + } + }; + auto release_repack_staging_after = [&]() { + for (PendingRepackUpload& upload : repack_uploads) { + if (!upload.staging_buffer) { + continue; + } + release_buffer_after(cmd, upload.staging_buffer, upload.staging_size); + upload.staging_buffer = nullptr; + } + }; + auto release_repack_staging_for_encoded_failure = [&]() { + if ((is_upload_batch() || is_current_submission()) && + command_buffer_has_work) { + release_repack_staging_after(); + } else { + release_repack_staging_immediate(); + } + }; + + if (use_blit_upload) { uint32_t bytes_per_host_block = load_shader_info.bytes_per_host_block; const uint32_t blit_alignment = 256; + direct_uploads.reserve((level_last - level_first + 1) * array_size); + repack_uploads.reserve((level_last - level_first + 1) * array_size); + + auto fits_u32 = [](size_t value) { + return value <= size_t(std::numeric_limits::max()); + }; + for (uint32_t level = level_first; level <= level_last; ++level) { uint32_t stored_level = std::min(level, level_packed); bool is_base_storage = @@ -1328,62 +1957,365 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, bool requires_staging = (source_offset_bytes % blit_alignment) != 0; if (requires_staging) { + if (!texture_upload_repack_pipeline_ || + (level_depth != 0 && + blit_bytes_per_image > + std::numeric_limits::max() / level_depth)) { + release_repack_staging_immediate(); + handle_upload_failure(true); + return false; + } size_t staging_size = blit_bytes_per_image * level_depth; + if (!fits_u32(source_offset_bytes) || !fits_u32(bytes_per_image) || + !fits_u32(blit_bytes_per_image) || !fits_u32(staging_size)) { + release_repack_staging_immediate(); + handle_upload_failure(true); + return false; + } MTL::Buffer* staging_buffer = acquire_buffer(staging_size); if (!staging_buffer) { - blit->endEncoding(); + release_repack_staging_immediate(); handle_upload_failure(true); return false; } - for (uint32_t z = 0; z < level_depth; ++z) { - size_t src_z_offset = - source_offset_bytes + size_t(z) * bytes_per_image; - size_t dst_z_offset = size_t(z) * blit_bytes_per_image; - for (uint32_t y = 0; y < upload_row_count; ++y) { - size_t src_row_offset = - src_z_offset + size_t(y) * stored_layout->row_pitch_bytes; - size_t dst_row_offset = dst_z_offset + size_t(y) * blit_row_pitch; - blit->copyFromBuffer(dest_buffer, src_row_offset, staging_buffer, - dst_row_offset, upload_row_bytes); - } - } - - blit->copyFromBuffer( - staging_buffer, 0, blit_row_pitch, blit_bytes_per_image, - MTL::Size::Make(upload_width_texels, upload_height_texels, - level_depth), - mtl_texture, is_3d ? 0 : slice, level, - MTL::Origin::Make(0, 0, 0)); - - release_buffer_after(cmd, staging_buffer, staging_size); + PendingRepackUpload upload = {}; + upload.staging_buffer = staging_buffer; + upload.staging_size = staging_size; + upload.constants.source_offset = uint32_t(source_offset_bytes); + upload.constants.dest_offset = 0; + upload.constants.source_row_pitch = stored_layout->row_pitch_bytes; + upload.constants.dest_row_pitch = blit_row_pitch; + upload.constants.source_image_pitch = uint32_t(bytes_per_image); + upload.constants.dest_image_pitch = uint32_t(blit_bytes_per_image); + upload.constants.row_bytes = upload_row_bytes; + upload.constants.row_count = upload_row_count; + upload.constants.depth = level_depth; + upload.blit_row_pitch = blit_row_pitch; + upload.blit_bytes_per_image = blit_bytes_per_image; + upload.width = upload_width_texels; + upload.height = upload_height_texels; + upload.depth = level_depth; + upload.destination_slice = is_3d ? 0 : slice; + upload.destination_level = level; + repack_uploads.push_back(upload); } else { - blit->copyFromBuffer( - dest_buffer, source_offset_bytes, stored_layout->row_pitch_bytes, - bytes_per_image, - MTL::Size::Make(upload_width_texels, upload_height_texels, - level_depth), - mtl_texture, is_3d ? 0 : slice, level, - MTL::Origin::Make(0, 0, 0)); + PendingDirectUpload upload = {}; + upload.source_offset_bytes = source_offset_bytes; + upload.source_row_pitch = stored_layout->row_pitch_bytes; + upload.source_bytes_per_image = bytes_per_image; + upload.width = upload_width_texels; + upload.height = upload_height_texels; + upload.depth = level_depth; + upload.destination_slice = is_3d ? 0 : slice; + upload.destination_level = level; + direct_uploads.push_back(upload); } } } + } - blit->endEncoding(); + const bool can_defer_upload_blits = + use_blit_upload && (is_upload_batch() || is_current_submission()) && + deferred_upload_batch_depth_ != 0; + MTL::ComputeCommandEncoder* encoder = nullptr; + if (can_defer_upload_blits) { + encoder = GetDeferredUploadComputeEncoder(cmd); + using_deferred_upload_encoder = encoder != nullptr; + } + if (!encoder) { + if (command_processor_ && + cmd == command_processor_->GetCurrentCommandBuffer()) { + command_processor_->EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kTextureCompute); + } + encoder = cmd->computeCommandEncoder(); + } + if (!encoder) { + release_repack_staging_immediate(); + handle_upload_failure(true); + return false; + } + if (!using_deferred_upload_encoder) { + SetEncoderLabel(encoder, repack_uploads.empty() + ? "XeniaTextureLoadEncoder" + : "XeniaTextureLoadRepackEncoder"); + } + bool compute_encoder_open = true; + auto end_local_compute_encoder = [&]() { + if (!compute_encoder_open || using_deferred_upload_encoder) { + return; + } + encoder->endEncoding(); + compute_encoder_open = false; + }; + if (command_processor_ && + !command_processor_->EncodeSharedMemoryComputeReadDependency( + encoder, shared_memory_dependency, shared_memory_ranges, + shared_memory_range_count)) { + end_local_compute_encoder(); + release_repack_staging_immediate(); + handle_upload_failure(true); + return false; + } + encoder->setComputePipelineState(pipeline); + MTL::Buffer* shader_source_buffer = + use_cpu_guest_source ? transient_source_buffer : shared_buffer; + if (!texture_resolution_scaled) { + encoder->setBuffer(shader_source_buffer, 0, 2); + } + + uint32_t guest_x_blocks_per_group_log2 = + load_shader_info.GetGuestXBlocksPerGroupLog2(); + MTL::Size threads_per_group = + MTL::Size::Make(UINT32_C(1) << kLoadGuestXThreadsPerGroupLog2, + UINT32_C(1) << kLoadGuestYBlocksPerGroupLog2, 1); + + bool scaled_mips_source_set_up = false; + MTL::Buffer* source_buffer = shader_source_buffer; + size_t source_buffer_offset = 0; + size_t source_buffer_length = 0; + + size_t dispatch_index = 0; + for (const StoredLevelHostLayout& stored_level : stored_levels) { + bool is_base_storage = stored_level.is_base; + const texture_util::TextureGuestLayout::Level& level_guest_layout = + is_base_storage ? guest_layout.base + : guest_layout.mips[stored_level.level]; + + if (texture_resolution_scaled && + (is_base_storage || !scaled_mips_source_set_up)) { + uint32_t guest_address = + is_base_storage ? base_guest_address : mips_guest_address; + uint32_t guest_size_unscaled = is_base_storage + ? texture.GetGuestBaseSize() + : texture.GetGuestMipsSize(); + if (!MakeScaledResolveRangeCurrent(guest_address, guest_size_unscaled, + 4) || + !GetCurrentScaledResolveBuffer(source_buffer, source_buffer_offset, + source_buffer_length)) { + end_local_compute_encoder(); + release_repack_staging_for_encoded_failure(); + handle_upload_failure(false); + return false; + } + encoder->setBuffer(source_buffer, source_buffer_offset, 2); + if (!is_base_storage) { + scaled_mips_source_set_up = true; + } + } + + uint32_t level_guest_offset = 0; + if (!texture_resolution_scaled) { + if (use_cpu_guest_source) { + const TransientTextureSourceRange& source_range = + is_base_storage ? transient_base_source : transient_mips_source; + if (!source_range.valid || + source_range.guest_buffer_offset > + size_t(std::numeric_limits::max())) { + end_local_compute_encoder(); + release_repack_staging_for_encoded_failure(); + handle_upload_failure(false); + return false; + } + level_guest_offset = uint32_t(source_range.guest_buffer_offset); + } else { + level_guest_offset = + is_base_storage ? base_guest_address : mips_guest_address; + } + } + if (!is_base_storage) { + uint32_t mip_offset = guest_layout.mip_offsets_bytes[stored_level.level]; + if (texture_resolution_scaled) { + mip_offset *= texture_resolution_scale_area; + } + if (mip_offset > + std::numeric_limits::max() - level_guest_offset) { + end_local_compute_encoder(); + release_repack_staging_for_encoded_failure(); + handle_upload_failure(false); + return false; + } + level_guest_offset += mip_offset; + } + // Use guest layout pitch (blocks) - new XeSL expects blocks for both tiled + // and linear + uint32_t guest_pitch_aligned = + level_guest_layout.row_pitch_bytes / bytes_per_block; + + uint32_t size_blocks_x = + (stored_level.width_texels + (block_width - 1)) / block_width; + uint32_t size_blocks_y = + (stored_level.height_texels + (block_height - 1)) / block_height; + size_blocks_x *= texture_resolution_scale_x; + size_blocks_y *= texture_resolution_scale_y; + + uint32_t group_count_x = + (size_blocks_x + + ((UINT32_C(1) << guest_x_blocks_per_group_log2) - 1)) >> + guest_x_blocks_per_group_log2; + uint32_t group_count_y = + (size_blocks_y + + ((UINT32_C(1) << kLoadGuestYBlocksPerGroupLog2) - 1)) >> + kLoadGuestYBlocksPerGroupLog2; + MTL::Size threadgroups = MTL::Size::Make(group_count_x, group_count_y, + stored_level.depth_slices); + + for (uint32_t slice = 0; slice < array_size; ++slice) { + MetalLoadConstants constants = {}; + constants.is_tiled_3d_endian_scale = + uint32_t(key.tiled) | (uint32_t(is_3d_tiling) << 1) | + (uint32_t(key.endianness) << 2) | (texture_resolution_scale_x << 4) | + (texture_resolution_scale_y << 7); + constants.guest_offset = level_guest_offset; + if (!is_3d) { + uint32_t slice_stride = level_guest_layout.array_slice_stride_bytes; + if (texture_resolution_scaled) { + slice_stride *= texture_resolution_scale_area; + } + constants.guest_offset += slice * slice_stride; + } + constants.guest_pitch_aligned = guest_pitch_aligned; + constants.guest_z_stride_block_rows_aligned = + level_guest_layout.z_slice_stride_block_rows; + constants.size_blocks[0] = size_blocks_x; + constants.size_blocks[1] = size_blocks_y; + constants.size_blocks[2] = stored_level.depth_slices; + constants.padding0 = 0; + constants.host_offset = 0; + constants.host_pitch = stored_level.row_pitch_bytes; + constants.height_texels = stored_level.height_texels; + + uint8_t* constants_ptr = + static_cast(constants_buffer->contents()) + + dispatch_index * constants_size; + std::memcpy(constants_ptr, &constants, sizeof(constants)); + + encoder->setBuffer(constants_buffer, dispatch_index * constants_size, 0); + encoder->setBuffer(dest_buffer, + stored_level.dest_offset_bytes + + slice * stored_level.slice_size_bytes, + 1); + encoder->dispatchThreadgroups(threadgroups, threads_per_group); + command_buffer_has_work = true; + ++dispatch_index; + } + } + + if (!repack_uploads.empty()) { + encoder->memoryBarrier(MTL::BarrierScopeBuffers); + encoder->pushDebugGroup( + NS::String::string("XeniaTextureUploadRepack", NS::UTF8StringEncoding)); + encoder->setComputePipelineState(texture_upload_repack_pipeline_); + encoder->setBuffer(dest_buffer, 0, 1); + MTL::Size repack_threads_per_group = MTL::Size::Make(128, 1, 1); + for (const PendingRepackUpload& upload : repack_uploads) { + uint32_t dword_count = (upload.constants.row_bytes + 3) >> 2; + uint32_t group_count_x = (dword_count + 127) >> 7; + encoder->setBytes(&upload.constants, sizeof(upload.constants), 0); + encoder->setBuffer(upload.staging_buffer, 0, 2); + encoder->dispatchThreadgroups( + MTL::Size::Make(group_count_x, upload.constants.row_count, + upload.constants.depth), + repack_threads_per_group); + command_buffer_has_work = true; + } + encoder->popDebugGroup(); + } + + end_local_compute_encoder(); + + MTL::Texture* mtl_texture = metal_texture->metal_texture(); + if (use_blit_upload) { + if (!direct_uploads.empty() || !repack_uploads.empty()) { + if (using_deferred_upload_encoder) { + for (const PendingDirectUpload& upload : direct_uploads) { + QueueDeferredUploadCopy( + dest_buffer, upload.source_offset_bytes, upload.source_row_pitch, + upload.source_bytes_per_image, mtl_texture, + upload.destination_slice, upload.destination_level, upload.width, + upload.height, upload.depth); + command_buffer_has_work = true; + } + for (const PendingRepackUpload& upload : repack_uploads) { + QueueDeferredUploadCopy( + upload.staging_buffer, 0, upload.blit_row_pitch, + upload.blit_bytes_per_image, mtl_texture, + upload.destination_slice, upload.destination_level, upload.width, + upload.height, upload.depth); + command_buffer_has_work = true; + } + } else { + if (command_processor_ && + cmd == command_processor_->GetCurrentCommandBuffer()) { + command_processor_->EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kTextureBlit); + } + MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); + if (!blit) { + release_repack_staging_for_encoded_failure(); + handle_upload_failure(true); + return false; + } + + for (const PendingDirectUpload& upload : direct_uploads) { + blit->copyFromBuffer( + dest_buffer, upload.source_offset_bytes, upload.source_row_pitch, + upload.source_bytes_per_image, + MTL::Size::Make(upload.width, upload.height, upload.depth), + mtl_texture, upload.destination_slice, upload.destination_level, + MTL::Origin::Make(0, 0, 0)); + command_buffer_has_work = true; + } + for (const PendingRepackUpload& upload : repack_uploads) { + blit->copyFromBuffer( + upload.staging_buffer, 0, upload.blit_row_pitch, + upload.blit_bytes_per_image, + MTL::Size::Make(upload.width, upload.height, upload.depth), + mtl_texture, upload.destination_slice, upload.destination_level, + MTL::Origin::Make(0, 0, 0)); + command_buffer_has_work = true; + } + + blit->endEncoding(); + } + } + + release_repack_staging_after(); + release_buffer_after(cmd, transient_source_buffer, transient_source_size); + transient_source_buffer = nullptr; release_buffer_after(cmd, constants_buffer, constants_buffer_size); release_buffer_after(cmd, dest_buffer, size_t(dest_buffer_size)); - if (use_upload_batch) { + if (is_upload_batch()) { + if (!use_cpu_guest_source && shared_memory_range_count) { + for (uint32_t i = 0; i < shared_memory_range_count; ++i) { + upload_batch_command_buffer_shared_memory_ranges_.push_back( + {shared_memory_ranges[i].start, shared_memory_ranges[i].length}); + } + } upload_batch_command_buffer_has_work_ = true; - } else if (!use_current_command_buffer) { - cmd->retain(); - cmd->addCompletedHandler(^(MTL::CommandBuffer* cb) { - cb->release(); - }); - cmd->commit(); + } else if (is_standalone()) { + if (!use_cpu_guest_source && shared_memory_range_count) { + std::pair standalone_ranges[2] = {}; + for (uint32_t i = 0; i < shared_memory_range_count; ++i) { + standalone_ranges[i] = {shared_memory_ranges[i].start, + shared_memory_ranges[i].length}; + } + static_cast(shared_memory()) + .TrackStandaloneGpuAccess(cmd, standalone_ranges, + shared_memory_range_count); + } + command_processor_->CommitStandaloneAsync(cmd); } } else { - cmd->commit(); - cmd->waitUntilCompleted(); + if (is_standalone()) { + command_processor_->CommitStandaloneAndWait(cmd); + } else { + cmd->commit(); + cmd->waitUntilCompleted(); + } uint8_t* dest_data = static_cast(dest_buffer->contents()); if (!dest_data) { @@ -1488,6 +2420,8 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, } if (!use_blit_upload) { + release_buffer_immediate(transient_source_buffer, transient_source_size); + transient_source_buffer = nullptr; release_buffer_immediate(constants_buffer, constants_buffer_size); release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); } @@ -1495,85 +2429,6 @@ bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, return true; } -void MetalTextureCache::DumpTextureToFile(MTL::Texture* texture, - const std::string& filename, - uint32_t width, uint32_t height) { - if (!texture) { - XELOGE("DumpTextureToFile: null texture"); - return; - } - - MTL::Device* device = command_processor_->GetMetalDevice(); - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!device || !queue) { - XELOGE("DumpTextureToFile: missing Metal device or command queue"); - return; - } - - // Calculate bytes per row (align for blit requirements). - size_t bytes_per_pixel = 4; // Assuming RGBA8 - size_t bytes_per_row_unaligned = width * bytes_per_pixel; - size_t bytes_per_row = xe::align(bytes_per_row_unaligned, size_t(256)); - size_t buffer_size = bytes_per_row * height; - - MTL::Buffer* readback = - device->newBuffer(buffer_size, MTL::ResourceStorageModeShared); - if (!readback) { - XELOGE("DumpTextureToFile: failed to allocate readback buffer"); - return; - } - - ScopedAutoreleasePool autorelease_pool; - MTL::CommandBuffer* cmd = queue->commandBuffer(); - if (!cmd) { - readback->release(); - XELOGE("DumpTextureToFile: failed to create command buffer"); - return; - } - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (!blit) { - readback->release(); - XELOGE("DumpTextureToFile: failed to create blit encoder"); - return; - } - - blit->copyFromTexture(texture, 0, 0, MTL::Origin::Make(0, 0, 0), - MTL::Size::Make(width, height, 1), readback, 0, - bytes_per_row, 0); - blit->endEncoding(); - cmd->commit(); - cmd->waitUntilCompleted(); - - // Allocate buffer for packed texture data (tightly packed rows). - std::vector data(bytes_per_row_unaligned * height); - const uint8_t* src = static_cast(readback->contents()); - if (!src) { - readback->release(); - XELOGE("DumpTextureToFile: failed to map readback buffer"); - return; - } - for (uint32_t y = 0; y < height; ++y) { - std::memcpy(data.data() + y * bytes_per_row_unaligned, - src + y * bytes_per_row, bytes_per_row_unaligned); - } - - readback->release(); - - if (texture->pixelFormat() == MTL::PixelFormatBGRA8Unorm) { - // Convert BGRA to RGBA for stb_image_write - for (size_t i = 0; i < data.size(); i += 4) { - std::swap(data[i], data[i + 2]); - } - } - - // Write PNG file - if (stbi_write_png(filename.c_str(), width, height, 4, data.data(), - bytes_per_row)) { - } else { - XELOGE("Failed to write texture to: {}", filename); - } -} - bool MetalTextureCache::Initialize() { SCOPE_profile_cpu_f("gpu"); XE_SCOPED_AUTORELEASE_POOL("MetalTextureCache::Initialize"); @@ -1607,6 +2462,11 @@ bool MetalTextureCache::Initialize() { size_t min_heap_bytes = std::max(0, ::cvars::metal_heap_min_bytes); texture_heap_pool_ = std::make_unique( device, GetCacheTextureStorageMode(), min_heap_bytes, "XeniaTex"); + texture_heap_pool_->SetHeapCreatedCallback([this](MTL::Heap* heap) { + if (command_processor_) { + command_processor_->AddResidencySetHeap(heap); + } + }); } InitializeNorm16Selection(device); @@ -1621,6 +2481,71 @@ bool MetalTextureCache::Initialize() { return false; } + // Allocate persistent bindless heap slots for null textures so that + // bindless draws can fall back to a valid descriptor index. + null_texture_2d_bindless_index_ = + command_processor_->AllocateViewBindlessIndex(); + if (null_texture_2d_bindless_index_ == UINT32_MAX) { + XELOGE("Failed to allocate bindless SRV slot for null 2D texture"); + return false; + } + if (auto* e = command_processor_->GetViewBindlessHeapEntry( + null_texture_2d_bindless_index_)) { + IRDescriptorTableSetTexture(e, null_texture_2d_, 0.0f, 0); + } + null_texture_3d_bindless_index_ = + command_processor_->AllocateViewBindlessIndex(); + if (null_texture_3d_bindless_index_ == UINT32_MAX) { + XELOGE("Failed to allocate bindless SRV slot for null 3D texture"); + return false; + } + if (auto* e = command_processor_->GetViewBindlessHeapEntry( + null_texture_3d_bindless_index_)) { + IRDescriptorTableSetTexture(e, null_texture_3d_, 0.0f, 0); + } + null_texture_cube_bindless_index_ = + command_processor_->AllocateViewBindlessIndex(); + if (null_texture_cube_bindless_index_ == UINT32_MAX) { + XELOGE("Failed to allocate bindless SRV slot for null cube texture"); + return false; + } + if (auto* e = command_processor_->GetViewBindlessHeapEntry( + null_texture_cube_bindless_index_)) { + IRDescriptorTableSetTexture(e, null_texture_cube_, 0.0f, 0); + } + + // Allocate a persistent bindless slot for the null sampler. + { + MTL::SamplerDescriptor* desc = MTL::SamplerDescriptor::alloc()->init(); + desc->setMinFilter(MTL::SamplerMinMagFilterLinear); + desc->setMagFilter(MTL::SamplerMinMagFilterLinear); + desc->setMipFilter(MTL::SamplerMipFilterLinear); + desc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); + desc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); + desc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); + desc->setSupportArgumentBuffers(true); + null_sampler_bindless_ = + command_processor_->GetMetalDevice()->newSamplerState(desc); + desc->release(); + if (null_sampler_bindless_) { + null_sampler_bindless_index_ = + command_processor_->AllocateSamplerBindlessIndex(); + if (null_sampler_bindless_index_ == UINT32_MAX) { + XELOGE("Failed to allocate bindless sampler slot for null sampler"); + null_sampler_bindless_->release(); + null_sampler_bindless_ = nullptr; + return false; + } + if (auto* e = command_processor_->GetSamplerBindlessHeapEntry( + null_sampler_bindless_index_)) { + IRDescriptorTableSetSampler(e, null_sampler_bindless_, 0.0f); + } + } else { + XELOGE("Failed to create bindless null sampler"); + return false; + } + } + if (!InitializeLoadPipelines()) { XELOGE("Metal texture cache: Failed to initialize texture_load pipelines"); return false; @@ -1685,6 +2610,8 @@ bool MetalTextureCache::InitializeLoadPipelines() { const uint8_t* data, size_t size) -> void { load_pipelines_scaled_[index] = create_pipeline_from_metallib(data, size); }; + texture_upload_repack_pipeline_ = create_pipeline_from_metallib( + texture_upload_repack_metallib, sizeof(texture_upload_repack_metallib)); init_pipeline(TextureCache::kLoadShaderIndex8bpb, texture_load_8bpb_cs_metallib, @@ -1880,7 +2807,8 @@ bool MetalTextureCache::InitializeLoadPipelines() { // Require at least the common loaders. return load_pipelines_[TextureCache::kLoadShaderIndex32bpb] != nullptr && load_pipelines_[TextureCache::kLoadShaderIndex16bpb] != nullptr && - load_pipelines_[TextureCache::kLoadShaderIndex8bpb] != nullptr; + load_pipelines_[TextureCache::kLoadShaderIndex8bpb] != nullptr && + texture_upload_repack_pipeline_ != nullptr; } void MetalTextureCache::InitializeNorm16Selection(MTL::Device* device) { @@ -1903,10 +2831,17 @@ void MetalTextureCache::InitializeNorm16Selection(MTL::Device* device) { void MetalTextureCache::Shutdown() { SCOPE_profile_cpu_f("gpu"); + deferred_upload_batch_depth_ = 0; + FlushDeferredUploadEncoderBatch(); AbortUploadCommandBufferBatch(false); ClearCache(); + if (texture_upload_repack_pipeline_) { + texture_upload_repack_pipeline_->release(); + texture_upload_repack_pipeline_ = nullptr; + } + for (size_t i = 0; i < kLoadShaderCount; ++i) { if (load_pipelines_[i]) { load_pipelines_[i]->release(); @@ -1918,6 +2853,38 @@ void MetalTextureCache::Shutdown() { } } + // Release persistent bindless heap slots for null textures/samplers. + if (null_texture_2d_bindless_index_ != UINT32_MAX) { + command_processor_->ReleaseViewBindlessIndex( + null_texture_2d_bindless_index_); + null_texture_2d_bindless_index_ = UINT32_MAX; + } + if (null_texture_3d_bindless_index_ != UINT32_MAX) { + command_processor_->ReleaseViewBindlessIndex( + null_texture_3d_bindless_index_); + null_texture_3d_bindless_index_ = UINT32_MAX; + } + if (null_texture_cube_bindless_index_ != UINT32_MAX) { + command_processor_->ReleaseViewBindlessIndex( + null_texture_cube_bindless_index_); + null_texture_cube_bindless_index_ = UINT32_MAX; + } + if (null_sampler_bindless_index_ != UINT32_MAX) { + command_processor_->ReleaseSamplerBindlessIndex( + null_sampler_bindless_index_); + null_sampler_bindless_index_ = UINT32_MAX; + } + if (null_sampler_bindless_) { + null_sampler_bindless_->release(); + null_sampler_bindless_ = nullptr; + } + for (auto& retired_sampler : retired_sampler_states_) { + if (retired_sampler.sampler) { + retired_sampler.sampler->release(); + } + } + retired_sampler_states_.clear(); + // Follow existing shutdown pattern - explicit null checks and release if (null_texture_2d_) { null_texture_2d_->release(); @@ -1974,33 +2941,65 @@ void MetalTextureCache::ClearScaledResolveBuffers() { void MetalTextureCache::CompletedSubmissionUpdated( uint64_t completed_submission_index) { TextureCache::CompletedSubmissionUpdated(completed_submission_index); - if (scaled_resolve_retired_buffers_.empty()) { - return; - } - for (auto it = scaled_resolve_retired_buffers_.begin(); - it != scaled_resolve_retired_buffers_.end();) { - if (it->submission_id <= completed_submission_index) { - if (it->buffer) { - it->buffer->release(); + if (!scaled_resolve_retired_buffers_.empty()) { + for (auto it = scaled_resolve_retired_buffers_.begin(); + it != scaled_resolve_retired_buffers_.end();) { + if (it->submission_id <= completed_submission_index) { + if (it->buffer) { + it->buffer->release(); + } + scaled_resolve_retired_bytes_ = + scaled_resolve_retired_bytes_ > it->length_scaled + ? (scaled_resolve_retired_bytes_ - it->length_scaled) + : 0; + it = scaled_resolve_retired_buffers_.erase(it); + } else { + ++it; } - scaled_resolve_retired_bytes_ = - scaled_resolve_retired_bytes_ > it->length_scaled - ? (scaled_resolve_retired_bytes_ - it->length_scaled) - : 0; - it = scaled_resolve_retired_buffers_.erase(it); + } + } + for (auto it = retired_sampler_states_.begin(); + it != retired_sampler_states_.end();) { + if (it->submission_id <= completed_submission_index) { + if (it->sampler) { + it->sampler->release(); + } + it = retired_sampler_states_.erase(it); } else { ++it; } } } +bool MetalTextureCache::TrimViewBindlessPressure(uint32_t needed_slot_count) { + if (!command_processor_) { + return false; + } + bool destroyed_any = false; + uint64_t completed_submission = command_processor_->GetCompletedSubmission(); + while (command_processor_->GetViewBindlessHeapAvailableCount() < + needed_slot_count) { + if (!DestroyOldestTextureIfUnused(completed_submission)) { + break; + } + destroyed_any = true; + } + return destroyed_any; +} + void MetalTextureCache::ClearCache() { SCOPE_profile_cpu_f("gpu"); + TextureCache::ClearCache(); + + // Release persistent bindless sampler slots before destroying samplers. + for (auto& [param_key, sampler_index] : sampler_bindless_indices_) { + command_processor_->ReleaseSamplerBindlessIndex(sampler_index); + } + sampler_bindless_indices_.clear(); + for (auto& sampler_pair : sampler_cache_) { - if (sampler_pair.second) { - sampler_pair.second->release(); - } + ReleaseOrRetireSamplerState(sampler_pair.second); } sampler_cache_.clear(); ClearScaledResolveBuffers(); @@ -2008,83 +3007,49 @@ void MetalTextureCache::ClearCache() { XELOGD("Metal texture cache: Cache cleared"); } -// Legacy method - kept for compatibility but now uses base class texture -// management -bool MetalTextureCache::UploadTexture2D(const TextureInfo& texture_info) { - XELOGD("UploadTexture2D: Legacy method called - delegating to base class"); - // The base class RequestTextures will handle texture creation and loading - return true; -} - -// Legacy method - kept for compatibility but now uses base class texture -// management -bool MetalTextureCache::UploadTextureCube(const TextureInfo& texture_info) { - XELOGD("UploadTextureCube: Legacy method called - delegating to base class"); - // The base class RequestTextures will handle texture creation and loading - return true; -} - -MTL::PixelFormat MetalTextureCache::ConvertXenosFormat( - xenos::TextureFormat format, xenos::Endian endian) { - // Convert Xbox 360 texture formats to Metal pixel formats - // This is a simplified mapping - the full implementation would handle all - // Xbox 360 formats - switch (format) { - case xenos::TextureFormat::k_8_8_8_8: - // Xbox 360 k_8_8_8_8 is stored as ARGB in big-endian. After k_8in32 - // endian swap on little-endian, the byte order is BGRA, and we swizzle - // to RGBA for Metal. - return MTL::PixelFormatRGBA8Unorm; - case xenos::TextureFormat::k_1_5_5_5: - return MTL::PixelFormatA1BGR5Unorm; - case xenos::TextureFormat::k_5_6_5: - return MTL::PixelFormatB5G6R5Unorm; - case xenos::TextureFormat::k_8: - return MTL::PixelFormatR8Unorm; - case xenos::TextureFormat::k_8_8: - return MTL::PixelFormatRG8Unorm; - case xenos::TextureFormat::k_DXT1: - return MTL::PixelFormatBC1_RGBA; - case xenos::TextureFormat::k_DXT2_3: - return MTL::PixelFormatBC2_RGBA; - case xenos::TextureFormat::k_DXT4_5: - return MTL::PixelFormatBC3_RGBA; - case xenos::TextureFormat::k_16_16_16_16: - return MTL::PixelFormatRGBA16Unorm; - case xenos::TextureFormat::k_2_10_10_10: - return MTL::PixelFormatRGB10A2Unorm; - case xenos::TextureFormat::k_16_FLOAT: - return MTL::PixelFormatR16Float; - case xenos::TextureFormat::k_16_16_FLOAT: - return MTL::PixelFormatRG16Float; - case xenos::TextureFormat::k_16_16_16_16_FLOAT: - return MTL::PixelFormatRGBA16Float; - case xenos::TextureFormat::k_32_FLOAT: - return MTL::PixelFormatR32Float; - case xenos::TextureFormat::k_32_32_FLOAT: - return MTL::PixelFormatRG32Float; - case xenos::TextureFormat::k_32_32_32_32_FLOAT: - return MTL::PixelFormatRGBA32Float; - case xenos::TextureFormat::k_DXN: // BC5 - return MTL::PixelFormatBC5_RGUnorm; - default: - // Don't log here - caller will log the error with more context - return MTL::PixelFormatInvalid; +void MetalTextureCache::ReleaseOrRetireSamplerState( + MTL::SamplerState* sampler) { + if (!sampler) { + return; } + uint64_t current_submission = + command_processor_ ? command_processor_->GetLatestSubmissionStarted() : 0; + uint64_t completed_submission = + command_processor_ ? command_processor_->GetCompletedSubmission() : 0; + if (!current_submission || completed_submission >= current_submission) { + sampler->release(); + return; + } + retired_sampler_states_.push_back({sampler, current_submission}); +} + +MTL::Texture* MetalTextureCache::CreateTexture( + MTL::TextureDescriptor* descriptor) { + MTL::Device* device = command_processor_->GetMetalDevice(); + if (!device) { + XELOGE( + "Metal texture cache: Failed to get Metal device from command " + "processor"); + descriptor->release(); + return nullptr; + } + + MTL::Texture* texture = nullptr; + if (texture_heap_pool_ && + descriptor->storageMode() == MTL::StorageModePrivate) { + texture = texture_heap_pool_->CreateTexture(descriptor); + } + if (!texture) { + texture = device->newTexture(descriptor); + } + descriptor->release(); + return texture; } MTL::Texture* MetalTextureCache::CreateTexture2D( uint32_t width, uint32_t height, uint32_t array_length, MTL::PixelFormat format, MTL::TextureSwizzleChannels swizzle, uint32_t mip_levels) { - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device from command " - "processor"); - return nullptr; - } - // Always create 2D array textures (even with a single layer) so that the // Metal texture type matches the shader expectation of texture2d_array, // mirroring the D3D12 backend which uses TEXTURE2DARRAY SRVs for 1D/2D @@ -2104,17 +3069,7 @@ MTL::Texture* MetalTextureCache::CreateTexture2D( descriptor->setStorageMode(GetCacheTextureStorageMode()); descriptor->setSwizzle(swizzle); - MTL::Texture* texture = nullptr; - if (texture_heap_pool_ && - descriptor->storageMode() == MTL::StorageModePrivate) { - texture = texture_heap_pool_->CreateTexture(descriptor); - } - if (!texture) { - texture = device->newTexture(descriptor); - } - - descriptor->release(); - + MTL::Texture* texture = CreateTexture(descriptor); if (!texture) { XELOGE( "Metal texture cache: Failed to create 2D array texture {}x{} (layers " @@ -2129,14 +3084,6 @@ MTL::Texture* MetalTextureCache::CreateTexture2D( MTL::Texture* MetalTextureCache::CreateTexture3D( uint32_t width, uint32_t height, uint32_t depth, MTL::PixelFormat format, MTL::TextureSwizzleChannels swizzle, uint32_t mip_levels) { - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device from command " - "processor"); - return nullptr; - } - depth = std::max(depth, 1u); MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); @@ -2152,17 +3099,7 @@ MTL::Texture* MetalTextureCache::CreateTexture3D( descriptor->setStorageMode(GetCacheTextureStorageMode()); descriptor->setSwizzle(swizzle); - MTL::Texture* texture = nullptr; - if (texture_heap_pool_ && - descriptor->storageMode() == MTL::StorageModePrivate) { - texture = texture_heap_pool_->CreateTexture(descriptor); - } - if (!texture) { - texture = device->newTexture(descriptor); - } - - descriptor->release(); - + MTL::Texture* texture = CreateTexture(descriptor); if (!texture) { XELOGE("Metal texture cache: Failed to create 3D texture {}x{}x{}", width, height, depth); @@ -2176,14 +3113,6 @@ MTL::Texture* MetalTextureCache::CreateTextureCube( uint32_t width, MTL::PixelFormat format, MTL::TextureSwizzleChannels swizzle, uint32_t mip_levels, uint32_t cube_count) { - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device from command " - "processor"); - return nullptr; - } - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); // Use cube for single-cube textures to match non-array cube bindings in the // translated MSL, and cube-array only when multiple cubes are present. @@ -2200,17 +3129,7 @@ MTL::Texture* MetalTextureCache::CreateTextureCube( descriptor->setStorageMode(GetCacheTextureStorageMode()); descriptor->setSwizzle(swizzle); - MTL::Texture* texture = nullptr; - if (texture_heap_pool_ && - descriptor->storageMode() == MTL::StorageModePrivate) { - texture = texture_heap_pool_->CreateTexture(descriptor); - } - if (!texture) { - texture = device->newTexture(descriptor); - } - - descriptor->release(); - + MTL::Texture* texture = CreateTexture(descriptor); if (!texture) { XELOGE("Metal texture cache: Failed to create Cube texture {}x{}", width, width); @@ -2223,12 +3142,6 @@ MTL::Texture* MetalTextureCache::CreateTextureCube( MTL::Texture* MetalTextureCache::CreateNullTexture2D() { SCOPE_profile_cpu_f("gpu"); - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE("Metal texture cache: Failed to get Metal device for null texture"); - return nullptr; - } - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); // Null 2D textures are created as 2D arrays with a single layer so they can // be bound wherever shaders expect texture2d_array. @@ -2241,12 +3154,10 @@ MTL::Texture* MetalTextureCache::CreateNullTexture2D() { MTL::TextureUsagePixelFormatView); descriptor->setStorageMode(MTL::StorageModeShared); - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - + MTL::Texture* texture = CreateTexture(descriptor); if (texture) { - // Initialize with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; + // Match D3D12 null-SRV semantics: missing textures should sample zero. + uint32_t default_color = 0x00000000; MTL::Region region = MTL::Region::Make2D(0, 0, 1, 1); texture->replaceRegion(region, 0, &default_color, 4); } else { @@ -2259,13 +3170,6 @@ MTL::Texture* MetalTextureCache::CreateNullTexture2D() { MTL::Texture* MetalTextureCache::CreateNullTexture3D() { SCOPE_profile_cpu_f("gpu"); - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device for null 3D texture"); - return nullptr; - } - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); descriptor->setTextureType(MTL::TextureType3D); descriptor->setPixelFormat(MTL::PixelFormatRGBA8Unorm); @@ -2276,12 +3180,10 @@ MTL::Texture* MetalTextureCache::CreateNullTexture3D() { MTL::TextureUsagePixelFormatView); descriptor->setStorageMode(MTL::StorageModeShared); - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - + MTL::Texture* texture = CreateTexture(descriptor); if (texture) { - // Initialize with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; + // Match D3D12 null-SRV semantics: missing textures should sample zero. + uint32_t default_color = 0x00000000; MTL::Region region = MTL::Region::Make3D(0, 0, 0, 1, 1, 1); texture->replaceRegion(region, 0, 0, &default_color, 4, 4); } else { @@ -2294,14 +3196,6 @@ MTL::Texture* MetalTextureCache::CreateNullTexture3D() { MTL::Texture* MetalTextureCache::CreateNullTextureCube() { SCOPE_profile_cpu_f("gpu"); - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device for null cube " - "texture"); - return nullptr; - } - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); // Null cube texture must match non-array cube bindings in translated MSL. descriptor->setTextureType(MTL::TextureTypeCube); @@ -2314,12 +3208,10 @@ MTL::Texture* MetalTextureCache::CreateNullTextureCube() { MTL::TextureUsagePixelFormatView); descriptor->setStorageMode(MTL::StorageModeShared); - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - + MTL::Texture* texture = CreateTexture(descriptor); if (texture) { - // Initialize all 6 faces with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; + // Match D3D12 null-SRV semantics: missing textures should sample zero. + uint32_t default_color = 0x00000000; MTL::Region region = MTL::Region::Make2D(0, 0, 1, 1); for (uint32_t face = 0; face < 6; ++face) { texture->replaceRegion(region, 0, face, &default_color, 4, 0); @@ -2334,24 +3226,78 @@ MTL::Texture* MetalTextureCache::CreateNullTextureCube() { // RequestTextures override - integrates with standard texture binding pipeline void MetalTextureCache::RequestTextures(uint32_t used_texture_mask) { SCOPE_profile_cpu_f("gpu"); + const bool may_load_data = MayRequestTexturesLoadData(used_texture_mask); + uint64_t load_calls_before = loaded_texture_data_count_; - BeginUploadCommandBufferBatch(); - - // Call base class implementation first - TextureCache::RequestTextures(used_texture_mask); - - EndUploadCommandBufferBatch(); + if (may_load_data) { + UploadBatchScope upload_batch(*this); + TextureCache::RequestTextures(used_texture_mask); + upload_batch.End(); + } else { + TextureCache::RequestTextures(used_texture_mask); + } + uint64_t load_calls_delta = loaded_texture_data_count_ - load_calls_before; + if (!may_load_data) { + assert_zero(load_calls_delta); + } // Intentionally no Metal-specific per-fetch logging here - invalid fetch // constants are already reported by the shared TextureCache logic. } -MTL::Texture* MetalTextureCache::GetTextureForBinding( - uint32_t fetch_constant, xenos::FetchOpDimension dimension, - bool is_signed) { - static std::array logged_missing_binding{}; - static std::array logged_missing_texture{}; +void MetalTextureCache::RequestTexturesWithoutLoading( + uint32_t used_texture_mask) { + SCOPE_profile_cpu_f("gpu"); + uint64_t load_calls_before = loaded_texture_data_count_; + TextureCache::RequestTextures(used_texture_mask, false); + assert_true(loaded_texture_data_count_ == load_calls_before); +} +bool MetalTextureCache::AreActiveTextureSRVKeysUpToDate( + const TextureSRVKey* keys, + const DxbcShader::TextureBinding* host_shader_bindings, + size_t host_shader_binding_count) const { + for (size_t i = 0; i < host_shader_binding_count; ++i) { + const TextureSRVKey& key = keys[i]; + const TextureBinding* binding = + GetValidTextureBinding(host_shader_bindings[i].fetch_constant); + if (!binding) { + if (key.key.is_valid) { + return false; + } + continue; + } + if ((key.key != binding->key) || + (key.host_swizzle != binding->host_swizzle) || + (key.swizzled_signs != binding->swizzled_signs)) { + return false; + } + } + return true; +} + +void MetalTextureCache::WriteActiveTextureSRVKeys( + TextureSRVKey* keys, const DxbcShader::TextureBinding* host_shader_bindings, + size_t host_shader_binding_count) const { + for (size_t i = 0; i < host_shader_binding_count; ++i) { + TextureSRVKey& key = keys[i]; + const TextureBinding* binding = + GetValidTextureBinding(host_shader_bindings[i].fetch_constant); + if (!binding) { + key.key.MakeInvalid(); + key.host_swizzle = xenos::XE_GPU_TEXTURE_SWIZZLE_0000; + key.swizzled_signs = kSwizzledSignsUnsigned; + continue; + } + key.key = binding->key; + key.host_swizzle = binding->host_swizzle; + key.swizzled_signs = binding->swizzled_signs; + } +} + +uint32_t MetalTextureCache::GetBindlessSRVIndexForBinding( + uint32_t fetch_constant, xenos::FetchOpDimension dimension, bool is_signed, + MTL::Texture** texture_for_encoder_out) { auto get_null_texture_for_dimension = [&]() -> MTL::Texture* { switch (dimension) { case xenos::FetchOpDimension::k1D: @@ -2365,104 +3311,88 @@ MTL::Texture* MetalTextureCache::GetTextureForBinding( return null_texture_2d_; } }; + auto get_null_index_for_dimension = [&]() -> uint32_t { + switch (dimension) { + case xenos::FetchOpDimension::k1D: + case xenos::FetchOpDimension::k2D: + return null_texture_2d_bindless_index_; + case xenos::FetchOpDimension::k3DOrStacked: + return null_texture_3d_bindless_index_; + case xenos::FetchOpDimension::kCube: + return null_texture_cube_bindless_index_; + default: + return null_texture_2d_bindless_index_; + } + }; + auto return_null_index_for_dimension = [&]() -> uint32_t { + if (texture_for_encoder_out) { + *texture_for_encoder_out = get_null_texture_for_dimension(); + } + return get_null_index_for_dimension(); + }; const TextureBinding* binding = GetValidTextureBinding(fetch_constant); if (!binding) { - if (fetch_constant < logged_missing_binding.size() && - !logged_missing_binding[fetch_constant]) { - xenos::xe_gpu_texture_fetch_t fetch = - register_file().GetTextureFetch(fetch_constant); - TextureKey decoded_key; - uint8_t swizzled_signs = 0; - BindingInfoFromFetchConstant(fetch, decoded_key, &swizzled_signs); - XELOGW( - "GetTextureForBinding: No valid binding for fetch {} (type={}, " - "format={}, swizzle=0x{:08X}, dwords={:08X} {:08X} {:08X} {:08X} " - "{:08X} {:08X})", - fetch_constant, uint32_t(fetch.type), uint32_t(fetch.format), - fetch.swizzle, fetch.dword_0, fetch.dword_1, fetch.dword_2, - fetch.dword_3, fetch.dword_4, fetch.dword_5); - if (decoded_key.is_valid) { - const char* format_name = FormatInfo::GetName(decoded_key.format); - XELOGW( - "GetTextureForBinding: Decoded fetch {} -> format={} ({}), " - "size={}x{}x{}, pitch={}, mips={}, tiled={}, packed_mips={}, " - "endian={}, swizzled_signs=0x{:02X}", - fetch_constant, uint32_t(decoded_key.format), format_name, - decoded_key.GetWidth(), decoded_key.GetHeight(), - decoded_key.GetDepthOrArraySize(), decoded_key.pitch, - decoded_key.mip_max_level + 1, decoded_key.tiled ? 1 : 0, - decoded_key.packed_mips ? 1 : 0, uint32_t(decoded_key.endianness), - swizzled_signs); - } - logged_missing_binding[fetch_constant] = true; - } - return get_null_texture_for_dimension(); + return return_null_index_for_dimension(); } - if (!AreDimensionsCompatible(dimension, binding->key.dimension)) { - return get_null_texture_for_dimension(); + return return_null_index_for_dimension(); } Texture* texture = nullptr; if (is_signed) { - bool needs_signed_components = - texture_util::IsAnySignSigned(binding->swizzled_signs); - if (needs_signed_components && - IsSignedVersionSeparateForFormat(binding->key)) { - texture = - binding->texture_signed ? binding->texture_signed : binding->texture; - } else { - texture = binding->texture; - } - } else { - bool has_unsigned_components = - texture_util::IsAnySignNotSigned(binding->swizzled_signs); - if (has_unsigned_components) { - texture = binding->texture; - } else if (!has_unsigned_components && binding->texture_signed != nullptr) { - texture = binding->texture_signed; - } else { - texture = binding->texture; + if (texture_util::IsAnySignSigned(binding->swizzled_signs)) { + texture = IsSignedVersionSeparateForFormat(binding->key) + ? binding->texture_signed + : binding->texture; } + } else if (texture_util::IsAnySignNotSigned(binding->swizzled_signs)) { + texture = binding->texture; } if (!texture) { - if (fetch_constant < logged_missing_texture.size() && - !logged_missing_texture[fetch_constant]) { - XELOGW("GetTextureForBinding: No texture object for fetch {}", - fetch_constant); - logged_missing_texture[fetch_constant] = true; - } - return get_null_texture_for_dimension(); + return return_null_index_for_dimension(); } texture->MarkAsUsed(); auto* metal_texture = static_cast(texture); - MTL::Texture* result = nullptr; - const bool use_3d_as_2d = - binding->key.dimension == xenos::DataDimension::k3D && - (dimension == xenos::FetchOpDimension::k1D || - dimension == xenos::FetchOpDimension::k2D); - if (metal_texture) { - if (use_3d_as_2d) { - result = metal_texture->GetOrCreate3DAs2DView(binding->host_swizzle, - dimension, is_signed); - if (!result) { - return get_null_texture_for_dimension(); - } - } else { - result = metal_texture->GetOrCreateView(binding->host_swizzle, dimension, - is_signed); - } + if (!metal_texture) { + return return_null_index_for_dimension(); } - return result ? result : get_null_texture_for_dimension(); + MTL::Texture* texture_for_encoder = nullptr; + uint32_t srv_index = metal_texture->GetOrCreateBindlessSRVIndexAndView( + binding->host_swizzle, dimension, is_signed, + texture_for_encoder_out ? &texture_for_encoder : nullptr); + if (srv_index == UINT32_MAX) { + return return_null_index_for_dimension(); + } + if (texture_for_encoder_out) { + *texture_for_encoder_out = texture_for_encoder + ? texture_for_encoder + : get_null_texture_for_dimension(); + } + return srv_index; +} + +uint32_t MetalTextureCache::GetBindlessSamplerIndexForBinding( + const DxbcShader::SamplerBinding& binding) { + SamplerParameters parameters = GetSamplerParameters(binding); + // GetOrCreateSampler will create the sampler and allocate a persistent + // bindless slot if it doesn't exist yet. + MTL::SamplerState* sampler_state = GetOrCreateSampler(parameters); + if (!sampler_state) { + return null_sampler_bindless_index_; + } + auto it = sampler_bindless_indices_.find(parameters.value); + if (it != sampler_bindless_indices_.end()) { + return it->second; + } + return null_sampler_bindless_index_; } MTL::Texture* MetalTextureCache::RequestSwapTexture( uint32_t& width_scaled_out, uint32_t& height_scaled_out, xenos::TextureFormat& format_out) { - static bool logged_valid = false; static bool logged_invalid = false; enum class SwapFailure : uint8_t { kCreateTexture = 0, @@ -2580,9 +3510,6 @@ MTL::Texture* MetalTextureCache::RequestSwapTexture( height_scaled_out = key.GetHeight() * (key.scaled_resolve ? draw_resolution_scale_y() : 1); format_out = key.format; - if (!logged_valid) { - logged_valid = true; - } return view; } @@ -2623,51 +3550,60 @@ static MetalTextureCache::SamplerParameters BuildSamplerParametersFromFetch( parameters.border_color = xenos::BorderColor::k_ABGR_Black; } - uint32_t mip_min_level; - texture_util::GetSubresourcesFromFetchConstant(fetch, nullptr, nullptr, - nullptr, nullptr, nullptr, - &mip_min_level, nullptr); + uint32_t mip_min_level, mip_max_level; + texture_util::GetSubresourcesFromFetchConstant( + fetch, nullptr, nullptr, nullptr, nullptr, nullptr, &mip_min_level, + &mip_max_level); parameters.mip_min_level = mip_min_level; + bool has_mips = mip_max_level > mip_min_level; + + xenos::TextureFilter mag_filter = + req_mag_filter == xenos::TextureFilter::kUseFetchConst ? fetch.mag_filter + : req_mag_filter; + xenos::TextureFilter min_filter = + req_min_filter == xenos::TextureFilter::kUseFetchConst ? fetch.min_filter + : req_min_filter; + xenos::TextureFilter mip_filter = + req_mip_filter == xenos::TextureFilter::kUseFetchConst ? fetch.mip_filter + : req_mip_filter; + bool min_mag_linear = (mag_filter == xenos::TextureFilter::kLinear) && + (min_filter == xenos::TextureFilter::kLinear); + bool mip_filter_bilinear_or_trilinear = + mip_filter == xenos::TextureFilter::kPoint || + mip_filter == xenos::TextureFilter::kLinear; + bool mip_base_map = mip_filter == xenos::TextureFilter::kBaseMap; xenos::AnisoFilter aniso_filter = req_aniso_filter == xenos::AnisoFilter::kUseFetchConst ? fetch.aniso_filter : req_aniso_filter; + // Apply anisotropic override, but only for mipmapped textures + // that are already using bilinear/trilinear filtering. + if (cvars::anisotropic_override > -1 && cvars::anisotropic_override < 6 && + has_mips && !mip_base_map && min_mag_linear && + mip_filter_bilinear_or_trilinear) { + aniso_filter = xenos::AnisoFilter(cvars::anisotropic_override); + } aniso_filter = std::min(aniso_filter, xenos::AnisoFilter::kMax_16_1); parameters.aniso_filter = aniso_filter; - xenos::TextureFilter mip_filter = - req_mip_filter == xenos::TextureFilter::kUseFetchConst ? fetch.mip_filter - : req_mip_filter; - if (aniso_filter != xenos::AnisoFilter::kDisabled) { parameters.mag_linear = 1; parameters.min_linear = 1; parameters.mip_linear = 1; } else { - xenos::TextureFilter mag_filter = - req_mag_filter == xenos::TextureFilter::kUseFetchConst - ? fetch.mag_filter - : req_mag_filter; parameters.mag_linear = mag_filter == xenos::TextureFilter::kLinear; - - xenos::TextureFilter min_filter = - req_min_filter == xenos::TextureFilter::kUseFetchConst - ? fetch.min_filter - : req_min_filter; parameters.min_linear = min_filter == xenos::TextureFilter::kLinear; - parameters.mip_linear = mip_filter == xenos::TextureFilter::kLinear; } - parameters.mip_base_map = - mip_filter == xenos::TextureFilter::kBaseMap ? 1 : 0; + parameters.mip_base_map = mip_base_map ? 1 : 0; return parameters; } MetalTextureCache::SamplerParameters MetalTextureCache::GetSamplerParameters( - const SpirvShader::SamplerBinding& binding) const { + const DxbcShader::SamplerBinding& binding) const { return BuildSamplerParametersFromFetch( register_file(), binding.fetch_constant, binding.mag_filter, binding.min_filter, binding.mip_filter, binding.aniso_filter); @@ -2727,6 +3663,10 @@ MTL::SamplerState* MetalTextureCache::GetOrCreateSampler( desc->setBorderColor(MTL::SamplerBorderColorOpaqueWhite); break; case xenos::BorderColor::k_ABGR_Black: + case xenos::BorderColor::k_ACBYCR_Black: + case xenos::BorderColor::k_ACBCRY_Black: + // Metal doesn't support custom border colors. The YUV black variants + // still need alpha 0 to stay closer to D3D12's null/border semantics. desc->setBorderColor(MTL::SamplerBorderColorTransparentBlack); break; default: @@ -2757,6 +3697,26 @@ MTL::SamplerState* MetalTextureCache::GetOrCreateSampler( } sampler_cache_.emplace(parameters.value, sampler_state); + + // Allocate a persistent bindless sampler slot and write the descriptor. + uint32_t sampler_index = command_processor_->AllocateSamplerBindlessIndex(); + if (sampler_index == UINT32_MAX) { + XELOGE("Failed to allocate persistent bindless sampler slot"); + sampler_state->release(); + sampler_cache_.erase(parameters.value); + return nullptr; + } + if (auto* e = + command_processor_->GetSamplerBindlessHeapEntry(sampler_index)) { + IRDescriptorTableSetSampler(e, sampler_state, 0.0f); + } else { + command_processor_->ReleaseSamplerBindlessIndex(sampler_index); + sampler_state->release(); + sampler_cache_.erase(parameters.value); + return nullptr; + } + sampler_bindless_indices_.emplace(parameters.value, sampler_index); + return sampler_state; } @@ -2792,8 +3752,14 @@ uint32_t MetalTextureCache::GetHostFormatSwizzle(TextureKey key) const { case xenos::TextureFormat::k_5_6_5: case xenos::TextureFormat::k_10_11_11: case xenos::TextureFormat::k_11_11_10: + case xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP: + case xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP: return xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB; + case xenos::TextureFormat::k_6_5_5: + // On the host, green bits in blue, blue bits in green. + return XE_GPU_MAKE_TEXTURE_SWIZZLE(R, B, G, G); + case xenos::TextureFormat::k_8_8_8_8: case xenos::TextureFormat::k_8_8_8_8_A: case xenos::TextureFormat::k_2_10_10_10: @@ -3034,9 +4000,7 @@ bool MetalTextureCache::EnsureScaledResolveBufferRange(uint64_t start_scaled, for (size_t overlap_index : overlap_indices) { overlap_total_bytes += scaled_resolve_buffers_[overlap_index].length_scaled; } - MTL::CommandBuffer* current_cmd = - command_processor_->GetCurrentCommandBuffer(); - bool retain_overlaps = current_cmd != nullptr; + bool retain_overlaps = command_processor_->HasActiveSubmission(); if (retain_overlaps) { bool exceeds_retired_budget = overlap_total_bytes > kScaledResolveRetiredMaxBytes || @@ -3048,22 +4012,31 @@ bool MetalTextureCache::EnsureScaledResolveBufferRange(uint64_t start_scaled, } if (!overlap_indices.empty()) { - MTL::CommandBuffer* cmd = retain_overlaps ? current_cmd : nullptr; + bool standalone = false; + MTL::CommandBuffer* cmd = + retain_overlaps ? command_processor_->GetCurrentCommandBuffer() + : nullptr; if (!cmd) { - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!queue) { - new_buffer->release(); - return false; - } - cmd = queue->commandBuffer(); + cmd = command_processor_->CreateStandaloneTransferCommandBuffer( + "XeniaCB reason=scaled-resolve-blit"); if (!cmd) { new_buffer->release(); return false; } + standalone = true; } + if (!standalone && command_processor_ && + cmd == command_processor_->GetCurrentCommandBuffer()) { + command_processor_->EndSharedMemoryUploadBlitEncoder( + MetalCommandProcessor::SharedMemoryUploadEncoderEndReason:: + kScaledResolveBlit); + } MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); if (!blit) { + if (standalone) { + cmd->release(); + } new_buffer->release(); return false; } @@ -3079,9 +4052,8 @@ bool MetalTextureCache::EnsureScaledResolveBufferRange(uint64_t start_scaled, } blit->endEncoding(); - if (cmd != current_cmd) { - cmd->commit(); - cmd->waitUntilCompleted(); + if (standalone) { + command_processor_->CommitStandaloneAndWait(cmd); } } @@ -3235,7 +4207,11 @@ std::unique_ptr MetalTextureCache::CreateTexture( return nullptr; } - // Create MetalTexture wrapper + EnsureViewBindlessHeadroom(kViewBindlessHeapPressureThreshold); + + // Create the MetalTexture wrapper. The canonical bindless descriptor is + // allocated lazily on first bind so unused cached textures don't pin a view + // slot permanently. return std::make_unique(*this, key, metal_texture); } @@ -3244,6 +4220,7 @@ bool MetalTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, bool load_mips) { SCOPE_profile_cpu_f("gpu"); + ++loaded_texture_data_count_; MetalTexture* metal_texture = static_cast(&texture); if (!metal_texture || !metal_texture->metal_texture()) { @@ -3251,25 +4228,148 @@ bool MetalTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, return false; } + // Upload work references the destination texture before normal binding does, + // so keep it protected from LRU eviction like the D3D12 and Vulkan paths do. + texture.MarkAsUsed(); + // GPU-based loading path for Metal texture_load_* shaders only (parity with // D3D12/Vulkan; no CPU untile fallback). - return TryGpuLoadTexture(texture, load_base, load_mips); + if (!TryGpuLoadTexture(texture, load_base, load_mips)) { + return false; + } + if (command_processor_) { + const TextureKey& texture_key = texture.key(); + const uint64_t upload_bytes = + (load_base + ? uint64_t(xe::align(texture.GetGuestBaseSize(), UINT32_C(16))) + : 0) + + (load_mips + ? uint64_t(xe::align(texture.GetGuestMipsSize(), UINT32_C(16))) + : 0); + command_processor_->RecordTextureUploadSourceRoute( + texture_key.scaled_resolve + ? MetalCommandProcessor::TextureUploadSourceRoute::kScaledResolve + : MetalCommandProcessor::TextureUploadSourceRoute:: + kResidentSharedMemory, + upload_bytes); + } + return true; +} + +bool MetalTextureCache::LoadTextureDataFromCpuGuestMemory(Texture& texture, + bool load_base, + bool load_mips) { + SCOPE_profile_cpu_f("gpu"); + if (!load_base && !load_mips) { + return true; + } + const TextureKey& texture_key = texture.key(); + if (texture_key.scaled_resolve) { + return false; + } + + bool base_outdated = false; + bool mips_outdated = false; + { + auto global_lock = AcquireGlobalLock(); + base_outdated = load_base && texture.base_outdated(global_lock); + mips_outdated = load_mips && texture.mips_outdated(global_lock); + } + if (!base_outdated && !mips_outdated) { + return true; + } + + const uint32_t base_length = + xe::align(texture.GetGuestBaseSize(), UINT32_C(16)); + const uint32_t mips_length = + xe::align(texture.GetGuestMipsSize(), UINT32_C(16)); + auto has_cpu_source = [&](bool needed, uint32_t start, uint32_t length) { + if (!needed || !length) { + return true; + } + return shared_memory().IsRangeInvalid(start, length); + }; + if (!has_cpu_source(base_outdated, texture_key.base_page << 12, + base_length) || + !has_cpu_source(mips_outdated, texture_key.mip_page << 12, mips_length)) { + return false; + } + MetalTexture* metal_texture = static_cast(&texture); + if (!metal_texture || !metal_texture->metal_texture()) { + XELOGE("LoadTextureDataFromCpuGuestMemory: Invalid Metal texture"); + return false; + } + + // Upload work references the destination texture before normal binding does, + // so keep it protected from LRU eviction like the resident-memory path. + texture.MarkAsUsed(); + ++loaded_texture_data_count_; + + if (!TryGpuLoadTexture(texture, base_outdated, mips_outdated, + TextureLoadSourceMode::kCpuGuestMemory)) { + return false; + } + + { + auto global_lock = AcquireGlobalLock(); + texture.MakeLoadedDataUpToDateAndWatch(global_lock, base_outdated, + mips_outdated); + } + if (command_processor_) { + command_processor_->RecordTextureUploadSourceRoute( + MetalCommandProcessor::TextureUploadSourceRoute::kCpuGuestMemory, + (base_outdated ? uint64_t(base_length) : 0) + + (mips_outdated ? uint64_t(mips_length) : 0)); + } + texture.LogAction("Loaded"); + return true; +} + +bool MetalTextureCache::EnsureViewBindlessHeadroom( + uint32_t target_free_slots) const { + if (!command_processor_) { + return false; + } + if (command_processor_->GetViewBindlessHeapAvailableCount() >= + target_free_slots) { + return true; + } + const_cast(this)->TrimViewBindlessPressure( + target_free_slots); + return command_processor_->GetViewBindlessHeapAvailableCount() >= + target_free_slots; } // MetalTexture implementation MetalTextureCache::MetalTexture::MetalTexture(MetalTextureCache& texture_cache, const TextureKey& key, MTL::Texture* metal_texture, - bool track_usage) + bool track_usage, + bool is_3d_as_2d_wrapper) : Texture(texture_cache, key, track_usage), texture_cache_(texture_cache), - metal_texture_(metal_texture) { + metal_texture_(metal_texture), + is_3d_as_2d_wrapper_(is_3d_as_2d_wrapper) { if (metal_texture_) { SetHostMemoryUsage(EstimateTextureBytes(metal_texture_)); } } MetalTextureCache::MetalTexture::~MetalTexture() { + // Release persistent bindless SRV slots before destroying the texture. + if (bindless_srv_index_ != UINT32_MAX) { + texture_cache_.command_processor_->ReleaseViewBindlessIndex( + bindless_srv_index_); + bindless_srv_index_ = UINT32_MAX; + } + for (auto& [key, bindless_srv_index] : swizzled_view_bindless_srv_indices_) { + if (bindless_srv_index != UINT32_MAX) { + texture_cache_.command_processor_->ReleaseViewBindlessIndex( + bindless_srv_index); + } + } + swizzled_view_bindless_srv_indices_.clear(); + uint64_t views_released = 0; for (auto& entry : swizzled_view_cache_) { if (entry.second) { @@ -3283,68 +4383,105 @@ MetalTextureCache::MetalTexture::~MetalTexture() { } } +uint64_t MetalTextureCache::MetalTexture::GetViewKey( + uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed, + MTL::PixelFormat view_format) const { + return uint64_t(host_swizzle) | (uint64_t(dimension) << 32) | + (uint64_t(is_signed) << 40) | (uint64_t(view_format) << 48); +} + +MTL::PixelFormat MetalTextureCache::MetalTexture::GetViewPixelFormat( + bool is_signed) const { + if (!metal_texture_ || !is_signed) { + return metal_texture_ ? metal_texture_->pixelFormat() + : MTL::PixelFormatInvalid; + } + switch (key().format) { + case xenos::TextureFormat::k_8: + case xenos::TextureFormat::k_8_A: + case xenos::TextureFormat::k_8_B: + return MTL::PixelFormatR8Snorm; + case xenos::TextureFormat::k_8_8: + return MTL::PixelFormatRG8Snorm; + case xenos::TextureFormat::k_8_8_8_8: + case xenos::TextureFormat::k_8_8_8_8_A: + return MTL::PixelFormatRGBA8Snorm; + case xenos::TextureFormat::k_16: + return MTL::PixelFormatR16Snorm; + case xenos::TextureFormat::k_16_16: + return MTL::PixelFormatRG16Snorm; + case xenos::TextureFormat::k_16_16_16_16: + return MTL::PixelFormatRGBA16Snorm; + default: + return metal_texture_->pixelFormat(); + } +} + +MTL::TextureType MetalTextureCache::MetalTexture::GetViewType( + xenos::FetchOpDimension dimension) const { + if (!metal_texture_) { + return MTL::TextureType2D; + } + switch (dimension) { + case xenos::FetchOpDimension::kCube: + return MTL::TextureTypeCube; + case xenos::FetchOpDimension::k3DOrStacked: + return key().dimension == xenos::DataDimension::k3D + ? MTL::TextureType3D + : MTL::TextureType2DArray; + default: + return MTL::TextureType2DArray; + } +} + +uint32_t +MetalTextureCache::MetalTexture::GetOrCreateBindlessSRVIndexForResolvedView( + uint64_t view_key, MTL::Texture* view) { + if (!view) { + return UINT32_MAX; + } + + texture_cache_.EnsureViewBindlessHeadroom(kViewBindlessHeapPressureThreshold); + + auto existing = swizzled_view_bindless_srv_indices_.find(view_key); + if (existing != swizzled_view_bindless_srv_indices_.end()) { + return existing->second; + } + + uint32_t bindless_srv_index = + texture_cache_.command_processor_->AllocateViewBindlessIndex(); + if (bindless_srv_index == UINT32_MAX) { + return UINT32_MAX; + } + auto* entry = texture_cache_.command_processor_->GetViewBindlessHeapEntry( + bindless_srv_index); + if (!entry) { + texture_cache_.command_processor_->ReleaseViewBindlessIndex( + bindless_srv_index); + return UINT32_MAX; + } + IRDescriptorTableSetTexture(entry, view, 0.0f, 0); + swizzled_view_bindless_srv_indices_.emplace(view_key, bindless_srv_index); + return bindless_srv_index; +} + MTL::Texture* MetalTextureCache::MetalTexture::GetOrCreateView( uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed) { if (!metal_texture_) { return nullptr; } - auto get_view_pixel_format = - [&](const TextureKey& key, bool view_signed, - MTL::PixelFormat base_format) -> MTL::PixelFormat { - if (!view_signed) { - return base_format; - } - switch (key.format) { - case xenos::TextureFormat::k_8: - case xenos::TextureFormat::k_8_A: - case xenos::TextureFormat::k_8_B: - return MTL::PixelFormatR8Snorm; - case xenos::TextureFormat::k_8_8: - return MTL::PixelFormatRG8Snorm; - case xenos::TextureFormat::k_8_8_8_8: - case xenos::TextureFormat::k_8_8_8_8_A: - return MTL::PixelFormatRGBA8Snorm; - case xenos::TextureFormat::k_16: - return MTL::PixelFormatR16Snorm; - case xenos::TextureFormat::k_16_16: - return MTL::PixelFormatRG16Snorm; - case xenos::TextureFormat::k_16_16_16_16: - return MTL::PixelFormatRGBA16Snorm; - default: - return base_format; - } - }; + MTL::PixelFormat view_format = GetViewPixelFormat(is_signed); + MTL::TextureType view_type = GetViewType(dimension); - MTL::PixelFormat view_format = - get_view_pixel_format(key(), is_signed, metal_texture_->pixelFormat()); - - MTL::TextureType view_type = metal_texture_->textureType(); - switch (dimension) { - case xenos::FetchOpDimension::kCube: - // SPIRV-Cross translates cube fetches to non-array cube textures. - view_type = MTL::TextureTypeCube; - break; - case xenos::FetchOpDimension::k3DOrStacked: - view_type = key().dimension == xenos::DataDimension::k3D - ? MTL::TextureType3D - : MTL::TextureType2DArray; - break; - default: - view_type = MTL::TextureType2DArray; - break; + if (host_swizzle == xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA && + view_format == metal_texture_->pixelFormat() && + metal_texture_->textureType() == view_type) { + return metal_texture_; } - if (host_swizzle == xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA) { - if ((!is_signed || view_format == metal_texture_->pixelFormat()) && - metal_texture_->textureType() == view_type) { - return metal_texture_; - } - } - - uint64_t view_key = uint64_t(host_swizzle) | (uint64_t(dimension) << 32) | - (uint64_t(is_signed) << 40) | - (uint64_t(view_format) << 48); + uint64_t view_key = + GetViewKey(host_swizzle, dimension, is_signed, view_format); auto found = swizzled_view_cache_.find(view_key); if (found != swizzled_view_cache_.end()) { return found->second; @@ -3377,15 +4514,90 @@ MTL::Texture* MetalTextureCache::MetalTexture::GetOrCreateView( MTL::Texture* view = metal_texture_->newTextureView( view_format, view_type, level_range, slice_range, swizzle); if (!view) { - // Returning a mismatched base type can trigger Metal validation asserts. - return metal_texture_->textureType() == view_type ? metal_texture_ - : nullptr; + return nullptr; } swizzled_view_cache_.emplace(view_key, view); return view; } +uint32_t MetalTextureCache::MetalTexture::GetOrCreateBindlessSRVIndexAndView( + uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed, + MTL::Texture** view_out) { + if (view_out) { + *view_out = nullptr; + } + if (!metal_texture_) { + return UINT32_MAX; + } + + MetalTexture* resolved_texture = this; + MTL::Texture* view = nullptr; + if (!is_3d_as_2d_wrapper_ && key().dimension == xenos::DataDimension::k3D && + dimension == xenos::FetchOpDimension::k2D) { + view = GetOrCreate3DAs2DView(host_swizzle, dimension, is_signed); + if (!view || !texture_3d_as_2d_) { + return UINT32_MAX; + } + resolved_texture = texture_3d_as_2d_.get(); + } else { + view = GetOrCreateView(host_swizzle, dimension, is_signed); + if (!view) { + return UINT32_MAX; + } + } + + MTL::PixelFormat view_format = + resolved_texture->GetViewPixelFormat(is_signed); + MTL::TextureType view_type = resolved_texture->GetViewType(dimension); + uint32_t srv_index = UINT32_MAX; + if (host_swizzle == xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA && + view_format == resolved_texture->metal_texture_->pixelFormat() && + resolved_texture->metal_texture_->textureType() == view_type) { + if (resolved_texture->bindless_srv_index_ == UINT32_MAX) { + texture_cache_.EnsureViewBindlessHeadroom( + kViewBindlessHeapPressureThreshold); + resolved_texture->bindless_srv_index_ = + texture_cache_.command_processor_->AllocateViewBindlessIndex(); + if (resolved_texture->bindless_srv_index_ == UINT32_MAX) { + XELOGE("MetalTexture: failed to allocate default bindless SRV index"); + return UINT32_MAX; + } + auto* entry = texture_cache_.command_processor_->GetViewBindlessHeapEntry( + resolved_texture->bindless_srv_index_); + if (!entry) { + texture_cache_.command_processor_->ReleaseViewBindlessIndex( + resolved_texture->bindless_srv_index_); + resolved_texture->bindless_srv_index_ = UINT32_MAX; + return UINT32_MAX; + } + IRDescriptorTableSetTexture(entry, resolved_texture->metal_texture_, 0.0f, + 0); + } + srv_index = resolved_texture->bindless_srv_index_; + } else { + uint64_t view_key = resolved_texture->GetViewKey(host_swizzle, dimension, + is_signed, view_format); + auto existing = + resolved_texture->swizzled_view_bindless_srv_indices_.find(view_key); + if (existing != + resolved_texture->swizzled_view_bindless_srv_indices_.end()) { + srv_index = existing->second; + } else { + srv_index = resolved_texture->GetOrCreateBindlessSRVIndexForResolvedView( + view_key, view); + } + } + + if (srv_index == UINT32_MAX) { + return UINT32_MAX; + } + if (view_out) { + *view_out = view; + } + return srv_index; +} + MTL::Texture* MetalTextureCache::MetalTexture::GetOrCreate3DAs2DView( uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed) { if (!metal_texture_ || key().dimension != xenos::DataDimension::k3D) { @@ -3417,7 +4629,7 @@ MTL::Texture* MetalTextureCache::MetalTexture::GetOrCreate3DAs2DView( } texture_3d_as_2d_ = std::make_unique(texture_cache_, key_2d, - texture_2d, false); + texture_2d, false, true); texture_3d_as_2d_->SetForceLoad3DTiling(true); if (!texture_cache_.LoadTextureData(*texture_3d_as_2d_)) { XELOGE("MetalTexture: Failed to load 3D-as-2D texture data"); diff --git a/src/xenia/gpu/metal/metal_texture_cache.h b/src/xenia/gpu/metal/metal_texture_cache.h index 72265d351..c3616beef 100644 --- a/src/xenia/gpu/metal/metal_texture_cache.h +++ b/src/xenia/gpu/metal/metal_texture_cache.h @@ -16,10 +16,11 @@ #include #include #include +#include #include +#include "xenia/gpu/dxbc_shader.h" #include "xenia/gpu/register_file.h" -#include "xenia/gpu/spirv_shader.h" #include "xenia/gpu/texture_cache.h" #include "xenia/gpu/texture_info.h" #include "xenia/gpu/xenos.h" @@ -48,24 +49,37 @@ class MetalTextureCache : public TextureCache { void Shutdown(); void ClearCache() override; void CompletedSubmissionUpdated(uint64_t completed_submission_index) override; + bool TrimViewBindlessPressure(uint32_t needed_slot_count = 1); - bool UploadTexture2D(const TextureInfo& texture_info); - bool UploadTextureCube(const TextureInfo& texture_info); + // Bindless persistent heap index lookups. + // Returns the persistent view_bindless_heap_ slot for the texture that + // would be resolved for the given binding parameters. Falls back to the + // null texture's persistent slot when no valid texture is found. If + // texture_for_encoder_out is provided, also returns the Metal texture/view + // that must be marked as used by the active render encoder. + uint32_t GetBindlessSRVIndexForBinding( + uint32_t fetch_constant, xenos::FetchOpDimension dimension, + bool is_signed, MTL::Texture** texture_for_encoder_out = nullptr); + // Returns the persistent sampler_bindless_heap_ slot for the sampler that + // would be created for the given binding. Falls back to + // null_sampler_bindless_index_ when no sampler can be resolved. + uint32_t GetBindlessSamplerIndexForBinding( + const DxbcShader::SamplerBinding& binding); - // Pixel format conversion - MTL::PixelFormat ConvertXenosFormat( - xenos::TextureFormat format, - xenos::Endian endian = xenos::Endian::k8in32); + struct TextureSRVKey { + TextureKey key; + uint32_t host_swizzle; + uint8_t swizzled_signs; + }; - // Null texture accessors for invalid bindings (following D3D12/Vulkan - // pattern) - MTL::Texture* GetNullTexture2D() const { return null_texture_2d_; } - MTL::Texture* GetNullTexture3D() const { return null_texture_3d_; } - MTL::Texture* GetNullTextureCube() const { return null_texture_cube_; } - - MTL::Texture* GetTextureForBinding(uint32_t fetch_constant, - xenos::FetchOpDimension dimension, - bool is_signed); + bool AreActiveTextureSRVKeysUpToDate( + const TextureSRVKey* keys, + const DxbcShader::TextureBinding* host_shader_bindings, + size_t host_shader_binding_count) const; + void WriteActiveTextureSRVKeys( + TextureSRVKey* keys, + const DxbcShader::TextureBinding* host_shader_bindings, + size_t host_shader_binding_count) const; MTL::Texture* RequestSwapTexture(uint32_t& width_scaled_out, uint32_t& height_scaled_out, @@ -96,11 +110,48 @@ class MetalTextureCache : public TextureCache { }; SamplerParameters GetSamplerParameters( - const SpirvShader::SamplerBinding& binding) const; + const DxbcShader::SamplerBinding& binding) const; MTL::SamplerState* GetOrCreateSampler(SamplerParameters parameters); // TextureCache virtual method overrides void RequestTextures(uint32_t used_texture_mask) override; + void RequestTexturesWithoutLoading(uint32_t used_texture_mask); + struct TextureMaterializationPlan { + std::vector source_ranges; + uint32_t request_count = 0; + uint32_t planned_load_count = 0; + uint32_t executed_load_count = 0; + + void Reset() { + source_ranges.clear(); + texture_loads_.clear(); + request_count = 0; + planned_load_count = 0; + executed_load_count = 0; + } + bool NeedsTextureUpload() const { return !texture_loads_.empty(); } + + private: + friend class MetalTextureCache; + struct TextureLoad { + Texture* texture = nullptr; + bool load_base = false; + bool load_mips = false; + bool use_cpu_source = false; + }; + std::vector texture_loads_; + }; + bool PrepareTextureMaterialization(const RegisterFile& regs, + uint32_t used_texture_mask, + TextureMaterializationPlan& plan); + bool ExecuteTextureMaterialization(TextureMaterializationPlan& plan); + void BeginTextureUploadBatch(); + bool EndTextureUploadBatch(); + bool PrepareTextureDataLoadRanges(Texture** textures, uint32_t texture_count, + uint64_t base_outdated_mask, + uint64_t mips_outdated_mask) override; + bool RequestTextureDataRange(Texture& texture, TextureDataRangeSource source, + uint32_t start, uint32_t length) override; bool IsSignedVersionSeparateForFormat(TextureKey key) const override; bool IsScaledResolveSupportedForFormat(TextureKey key) const override; @@ -113,12 +164,6 @@ class MetalTextureCache : public TextureCache { bool GetCurrentScaledResolveBuffer(MTL::Buffer*& buffer_out, size_t& buffer_offset_out, size_t& buffer_length_out) const; - uint64_t GetCurrentScaledResolveRangeStartScaled() const { - return scaled_resolve_current_range_start_scaled_; - } - uint64_t GetCurrentScaledResolveRangeLengthScaled() const { - return scaled_resolve_current_range_length_scaled_; - } uint32_t GetHostFormatSwizzle(TextureKey key) const override; uint32_t GetMaxHostTextureWidthHeight( xenos::DataDimension dimension) const override; @@ -127,18 +172,39 @@ class MetalTextureCache : public TextureCache { std::unique_ptr CreateTexture(TextureKey key) override; bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, bool load_mips) override; - // Whether GPU texture uploads can be encoded into the currently open command - // buffer (without creating a separate upload command buffer). - bool CanUseCurrentCommandBufferForTextureUploads() const; + bool FlushPendingUploadEncodersForCommandEncoderBoundary(); private: + bool EnsureViewBindlessHeadroom(uint32_t target_free_slots) const; + enum class TextureLoadSourceMode { + kResidentMemory, + kCpuGuestMemory, + }; // GPU-based texture loading entry point. Returns true on success. - bool TryGpuLoadTexture(Texture& texture, bool load_base, bool load_mips); + bool TryGpuLoadTexture(Texture& texture, bool load_base, bool load_mips, + TextureLoadSourceMode source_mode = + TextureLoadSourceMode::kResidentMemory); + bool LoadTextureDataFromCpuGuestMemory(Texture& texture, bool load_base, + bool load_mips); MTL::StorageMode GetCacheTextureStorageMode() const; bool ShouldUploadViaBlit() const; void BeginUploadCommandBufferBatch(); void EndUploadCommandBufferBatch(); void AbortUploadCommandBufferBatch(bool commit_if_has_work = true); + void BeginDeferredUploadEncoderBatch(); + bool EndDeferredUploadEncoderBatch(); + bool FlushDeferredUploadEncoderBatch(); + class UploadBatchScope; + MTL::ComputeCommandEncoder* GetDeferredUploadComputeEncoder( + MTL::CommandBuffer* command_buffer); + void QueueDeferredUploadCopy(MTL::Buffer* source_buffer, + size_t source_offset_bytes, + size_t source_row_pitch, + size_t source_bytes_per_image, + MTL::Texture* destination_texture, + uint32_t destination_slice, + uint32_t destination_level, uint32_t width, + uint32_t height, uint32_t depth); // Format / load shader mapping for Metal texture loading. bool IsDecompressionNeededForKey(TextureKey key) const; @@ -159,13 +225,18 @@ class MetalTextureCache : public TextureCache { // resolution-scaled variants), indexed by TextureCache::LoadShaderIndex. MTL::ComputePipelineState* load_pipelines_[kLoadShaderCount] = {}; MTL::ComputePipelineState* load_pipelines_scaled_[kLoadShaderCount] = {}; + MTL::ComputePipelineState* texture_upload_repack_pipeline_ = nullptr; + // Number of LoadTextureDataFromResidentMemoryImpl calls. Used only to derive + // the per-request load count reported to command-processor telemetry. + uint64_t loaded_texture_data_count_ = 0; // Metal-specific Texture implementation class MetalTexture : public Texture { public: MetalTexture(MetalTextureCache& texture_cache, const TextureKey& key, - MTL::Texture* metal_texture, bool track_usage = true); + MTL::Texture* metal_texture, bool track_usage = true, + bool is_3d_as_2d_wrapper = false); ~MetalTexture() override; MTL::Texture* metal_texture() const { return metal_texture_; } @@ -175,16 +246,31 @@ class MetalTextureCache : public TextureCache { MTL::Texture* GetOrCreate3DAs2DView(uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed); + uint32_t GetOrCreateBindlessSRVIndexAndView( + uint32_t host_swizzle, xenos::FetchOpDimension dimension, + bool is_signed, MTL::Texture** view_out); private: + uint64_t GetViewKey(uint32_t host_swizzle, + xenos::FetchOpDimension dimension, bool is_signed, + MTL::PixelFormat view_format) const; + MTL::PixelFormat GetViewPixelFormat(bool is_signed) const; + MTL::TextureType GetViewType(xenos::FetchOpDimension dimension) const; + uint32_t GetOrCreateBindlessSRVIndexForResolvedView(uint64_t view_key, + MTL::Texture* view); + MetalTextureCache& texture_cache_; MTL::Texture* metal_texture_; + uint32_t bindless_srv_index_ = UINT32_MAX; std::unique_ptr texture_3d_as_2d_; + bool is_3d_as_2d_wrapper_ = false; std::unordered_map swizzled_view_cache_; + std::unordered_map swizzled_view_bindless_srv_indices_; }; private: // Metal texture creation helpers + MTL::Texture* CreateTexture(MTL::TextureDescriptor* descriptor); MTL::Texture* CreateTexture2D(uint32_t width, uint32_t height, uint32_t array_length, MTL::PixelFormat format, MTL::TextureSwizzleChannels swizzle, @@ -197,9 +283,6 @@ class MetalTextureCache : public TextureCache { MTL::TextureSwizzleChannels swizzle, uint32_t mip_levels = 1, uint32_t cube_count = 1); - void DumpTextureToFile(MTL::Texture* texture, const std::string& filename, - uint32_t width, uint32_t height); - struct ScaledResolveBuffer { MTL::Buffer* buffer = nullptr; uint64_t base_scaled = 0; @@ -210,6 +293,10 @@ class MetalTextureCache : public TextureCache { uint64_t submission_id = 0; uint64_t length_scaled = 0; }; + struct RetiredSamplerState { + MTL::SamplerState* sampler = nullptr; + uint64_t submission_id = 0; + }; bool GetScaledResolveRange(uint32_t start_unscaled, uint32_t length_unscaled, uint32_t length_scaled_alignment_log2, @@ -221,6 +308,7 @@ class MetalTextureCache : public TextureCache { bool EnsureScaledResolveBufferRange(uint64_t start_scaled, uint64_t length_scaled); void ClearScaledResolveBuffers(); + void ReleaseOrRetireSamplerState(MTL::SamplerState* sampler); // Null texture factory methods (following existing CreateTexture pattern) MTL::Texture* CreateNullTexture2D(); @@ -237,25 +325,53 @@ class MetalTextureCache : public TextureCache { MTL::Texture* null_texture_3d_ = nullptr; MTL::Texture* null_texture_cube_ = nullptr; + // Persistent bindless heap indices for null textures/samplers. + // Allocated during Initialize, released during Shutdown. + uint32_t null_texture_2d_bindless_index_ = UINT32_MAX; + uint32_t null_texture_3d_bindless_index_ = UINT32_MAX; + uint32_t null_texture_cube_bindless_index_ = UINT32_MAX; + uint32_t null_sampler_bindless_index_ = UINT32_MAX; + MTL::SamplerState* null_sampler_bindless_ = nullptr; + Norm16Selection r16_selection_; Norm16Selection rg16_selection_; Norm16Selection rgba16_selection_; std::unordered_map sampler_cache_; + // Maps sampler parameter key -> persistent bindless heap index. + std::unordered_map sampler_bindless_indices_; class UploadBufferPool; + struct DeferredUploadCopy { + MTL::Buffer* source_buffer = nullptr; + size_t source_offset_bytes = 0; + size_t source_row_pitch = 0; + size_t source_bytes_per_image = 0; + MTL::Texture* destination_texture = nullptr; + uint32_t destination_slice = 0; + uint32_t destination_level = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 0; + }; + mutable std::mutex upload_buffer_pool_mutex_; std::shared_ptr upload_buffer_pool_; MTL::CommandBuffer* upload_batch_command_buffer_ = nullptr; bool upload_batch_command_buffer_has_work_ = false; + std::vector> + upload_batch_command_buffer_shared_memory_ranges_; uint32_t upload_batch_depth_ = 0; - MetalTexture* bindless_used_first_ = nullptr; - MetalTexture* bindless_used_last_ = nullptr; + MTL::CommandBuffer* deferred_upload_command_buffer_ = nullptr; + MTL::ComputeCommandEncoder* deferred_upload_compute_encoder_ = nullptr; + std::vector deferred_upload_copies_; + uint32_t deferred_upload_batch_depth_ = 0; std::unique_ptr texture_heap_pool_; bool supports_bc_texture_compression_ = false; std::vector scaled_resolve_buffers_; std::vector scaled_resolve_retired_buffers_; + std::vector retired_sampler_states_; uint64_t scaled_resolve_retired_bytes_ = 0; size_t scaled_resolve_current_buffer_index_ = size_t(-1); uint64_t scaled_resolve_current_range_start_scaled_ = 0; diff --git a/src/xenia/gpu/metal/metal_upload_buffer_pool.cc b/src/xenia/gpu/metal/metal_upload_buffer_pool.cc new file mode 100644 index 000000000..3fdaa39a9 --- /dev/null +++ b/src/xenia/gpu/metal/metal_upload_buffer_pool.cc @@ -0,0 +1,102 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#include "xenia/gpu/metal/metal_upload_buffer_pool.h" + +#include + +#include "xenia/base/logging.h" +#include "xenia/base/math.h" + +namespace xe { +namespace gpu { +namespace metal { + +MetalUploadBufferPool::MetalUploadBufferPool(MTL::Device* device, + size_t page_size) + : GraphicsUploadBufferPool(page_size), device_(device) {} + +MetalUploadBufferPool::~MetalUploadBufferPool() = default; + +void MetalUploadBufferPool::SetPageCreatedCallback( + PageCreatedCallback callback) { + page_created_callback_ = std::move(callback); +} + +MetalUploadBufferPool::MetalPage::MetalPage(MTL::Buffer* buffer, void* mapping) + : buffer_(buffer), mapping_(mapping), gpu_address_(buffer->gpuAddress()) {} + +MetalUploadBufferPool::MetalPage::~MetalPage() { + if (buffer_) { + buffer_->release(); + } +} + +ui::GraphicsUploadBufferPool::Page* +MetalUploadBufferPool::CreatePageImplementation() { + MTL::Buffer* buffer = device_->newBuffer( + page_size_, + MTL::ResourceStorageModeShared | MTL::ResourceCPUCacheModeWriteCombined); + if (!buffer) { + XELOGE("MetalUploadBufferPool: Failed to allocate {} byte page", + page_size_); + return nullptr; + } + void* mapping = buffer->contents(); + if (page_created_callback_) { + page_created_callback_(buffer); + } + return new MetalPage(buffer, mapping); +} + +uint8_t* MetalUploadBufferPool::Request(uint64_t submission_index, size_t size, + size_t alignment, + MTL::Buffer** buffer_out, + size_t& offset_out, + uint64_t& gpu_address_out) { + size_t offset; + const MetalPage* page = + static_cast(GraphicsUploadBufferPool::Request( + submission_index, size, alignment, offset)); + if (!page) { + return nullptr; + } + if (buffer_out) { + *buffer_out = page->buffer_; + } + offset_out = offset; + gpu_address_out = page->gpu_address_ + offset; + return reinterpret_cast(page->mapping_) + offset; +} + +uint8_t* MetalUploadBufferPool::RequestPartial(uint64_t submission_index, + size_t size, size_t alignment, + MTL::Buffer** buffer_out, + size_t& offset_out, + uint64_t& gpu_address_out, + size_t& size_out) { + size_t offset, size_obtained; + const MetalPage* page = + static_cast(GraphicsUploadBufferPool::RequestPartial( + submission_index, size, alignment, offset, size_obtained)); + if (!page) { + return nullptr; + } + if (buffer_out) { + *buffer_out = page->buffer_; + } + offset_out = offset; + size_out = size_obtained; + gpu_address_out = page->gpu_address_ + offset; + return reinterpret_cast(page->mapping_) + offset; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_upload_buffer_pool.h b/src/xenia/gpu/metal/metal_upload_buffer_pool.h new file mode 100644 index 000000000..3adfa3258 --- /dev/null +++ b/src/xenia/gpu/metal/metal_upload_buffer_pool.h @@ -0,0 +1,66 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_GPU_METAL_METAL_UPLOAD_BUFFER_POOL_H_ +#define XENIA_GPU_METAL_METAL_UPLOAD_BUFFER_POOL_H_ + +#include + +#include "xenia/ui/graphics_upload_buffer_pool.h" +#include "xenia/ui/metal/metal_api.h" + +namespace xe { +namespace gpu { +namespace metal { + +class MetalUploadBufferPool : public ui::GraphicsUploadBufferPool { + public: + MetalUploadBufferPool(MTL::Device* device, + size_t page_size = kDefaultPageSize); + ~MetalUploadBufferPool() override; + + using PageCreatedCallback = std::function; + void SetPageCreatedCallback(PageCreatedCallback callback); + + // Request a region of the upload buffer. Returns CPU-writable pointer. + // buffer_out receives the MTL::Buffer* for encoder binding. + // offset_out receives the byte offset within that buffer. + // gpu_address_out receives the GPU virtual address (buffer gpuAddress + + // offset). + uint8_t* Request(uint64_t submission_index, size_t size, size_t alignment, + MTL::Buffer** buffer_out, size_t& offset_out, + uint64_t& gpu_address_out); + + // Partial request (may return less than requested). + uint8_t* RequestPartial(uint64_t submission_index, size_t size, + size_t alignment, MTL::Buffer** buffer_out, + size_t& offset_out, uint64_t& gpu_address_out, + size_t& size_out); + + protected: + Page* CreatePageImplementation() override; + + private: + struct MetalPage : public Page { + MetalPage(MTL::Buffer* buffer, void* mapping); + ~MetalPage() override; + MTL::Buffer* buffer_; + void* mapping_; + uint64_t gpu_address_; + }; + + MTL::Device* device_; + PageCreatedCallback page_created_callback_; +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_UPLOAD_BUFFER_POOL_H_ diff --git a/src/xenia/gpu/metal/metal_zpd_visibility_pool.cc b/src/xenia/gpu/metal/metal_zpd_visibility_pool.cc new file mode 100644 index 000000000..7bc75545b --- /dev/null +++ b/src/xenia/gpu/metal/metal_zpd_visibility_pool.cc @@ -0,0 +1,132 @@ +/** + ****************************************************************************** + * 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_zpd_visibility_pool.h" + +#include + +#include "xenia/base/assert.h" +#include "xenia/base/logging.h" + +// metal-cpp +#include "third_party/metal-cpp/Metal/Metal.hpp" + +namespace xe { +namespace gpu { +namespace metal { + +bool MetalZPDVisibilityPool::EnsureInitialized(MTL::Device* device, + uint32_t requested_capacity) { + if (is_initialized() && capacity_ == requested_capacity) { + return true; + } + + if (is_initialized()) { + // Can't resize while resolves may be in flight. Caller must ensure + // the pool is idle before requesting a different capacity. + assert_true(free_indices_.size() == capacity_); + Shutdown(); + } + + if (requested_capacity == 0) { + return false; + } + + size_t buffer_size = + static_cast(requested_capacity) * sizeof(uint64_t); + visibility_buffer_ = + device->newBuffer(buffer_size, MTL::ResourceStorageModeShared); + if (!visibility_buffer_) { + XELOGW( + "MetalZPDVisibilityPool: Failed to allocate the ZPD visibility buffer " + "(size={}), falling back to fake sample counts.", + buffer_size); + return false; + } + visibility_buffer_->setLabel( + NS::String::string("XeniaZPDVisibility", NS::UTF8StringEncoding)); + + visibility_mapping_ = + reinterpret_cast(visibility_buffer_->contents()); + std::memset(visibility_mapping_, 0, buffer_size); + + capacity_ = requested_capacity; + + free_indices_.clear(); + free_indices_.reserve(requested_capacity); + for (uint32_t i = requested_capacity; i > 0; --i) { + free_indices_.push_back(i - 1); + } + generations_.assign(requested_capacity, 0); + + return true; +} + +void MetalZPDVisibilityPool::Shutdown() { + free_indices_.clear(); + generations_.clear(); + + capacity_ = 0; + visibility_mapping_ = nullptr; + + if (visibility_buffer_) { + visibility_buffer_->release(); + visibility_buffer_ = nullptr; + } +} + +bool MetalZPDVisibilityPool::Acquire(uint32_t& index, uint32_t& generation, + size_t& offset) { + if (free_indices_.empty()) { + index = UINT32_MAX; + generation = 0; + offset = 0; + return false; + } + + index = free_indices_.back(); + free_indices_.pop_back(); + + assert_true(index < generations_.size()); + generation = generations_[index]; + offset = static_cast(index) * sizeof(uint64_t); + visibility_mapping_[index] = 0; + return true; +} + +void MetalZPDVisibilityPool::Release(uint32_t index, uint32_t generation) { + if (index >= capacity_) { + return; + } + + if (!IsGenerationCurrent(index, generation)) { + XELOGW("MetalZPDVisibilityPool: stale release index={} gen={}", index, + generation); + return; + } + + ++generations_[index]; + free_indices_.push_back(index); +} + +bool MetalZPDVisibilityPool::IsGenerationCurrent(uint32_t index, + uint32_t generation) const { + return index < generations_.size() && generations_[index] == generation; +} + +uint64_t MetalZPDVisibilityPool::Read(uint32_t index) const { + if (index >= capacity_ || !visibility_mapping_) { + return 0; + } + return visibility_mapping_[index]; +} + +} // namespace metal +} // namespace gpu +} // namespace xe diff --git a/src/xenia/gpu/metal/metal_zpd_visibility_pool.h b/src/xenia/gpu/metal/metal_zpd_visibility_pool.h new file mode 100644 index 000000000..06215af4b --- /dev/null +++ b/src/xenia/gpu/metal/metal_zpd_visibility_pool.h @@ -0,0 +1,74 @@ +/** + ****************************************************************************** + * 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_ZPD_VISIBILITY_POOL_H_ +#define XENIA_GPU_METAL_METAL_ZPD_VISIBILITY_POOL_H_ + +#include +#include +#include + +namespace MTL { +class Buffer; +class Device; +} // namespace MTL + +namespace xe { +namespace gpu { +namespace metal { + +// Small virtual query pool over one Metal visibility result buffer. Each +// acquired slot maps to one 8-byte visibility-buffer offset and one physical +// ZPD query segment, never to a guest ZPD address. +class MetalZPDVisibilityPool { + public: + MetalZPDVisibilityPool() = default; + MetalZPDVisibilityPool(const MetalZPDVisibilityPool&) = delete; + MetalZPDVisibilityPool& operator=(const MetalZPDVisibilityPool&) = delete; + ~MetalZPDVisibilityPool() { Shutdown(); } + + bool EnsureInitialized(MTL::Device* device, uint32_t requested_capacity); + void Shutdown(); + + bool is_initialized() const { + return visibility_buffer_ != nullptr && visibility_mapping_ != nullptr && + capacity_ != 0; + } + + uint32_t capacity() const { return capacity_; } + + MTL::Buffer* visibility_buffer() const { return visibility_buffer_; } + + bool has_free_indices() const { return !free_indices_.empty(); } + + bool Acquire(uint32_t& index, uint32_t& generation, size_t& offset); + void Release(uint32_t index, uint32_t generation); + bool IsGenerationCurrent(uint32_t index, uint32_t generation) const; + + // Read the 64-bit sample count from the shared buffer at the given index. + // The command buffer containing this slot's work must have completed before + // calling this. + uint64_t Read(uint32_t index) const; + + private: + MTL::Buffer* visibility_buffer_ = nullptr; + uint64_t* visibility_mapping_ = nullptr; + uint32_t capacity_ = 0; + + std::vector free_indices_; + + // Bumped on release so stale readbacks from a recycled slot get dropped. + std::vector generations_; +}; + +} // namespace metal +} // namespace gpu +} // namespace xe + +#endif // XENIA_GPU_METAL_METAL_ZPD_VISIBILITY_POOL_H_ diff --git a/src/xenia/gpu/metal/msl_bindings.h b/src/xenia/gpu/metal/msl_bindings.h deleted file mode 100644 index 14f5d6a8b..000000000 --- a/src/xenia/gpu/metal/msl_bindings.h +++ /dev/null @@ -1,86 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_GPU_METAL_MSL_BINDINGS_H_ -#define XENIA_GPU_METAL_MSL_BINDINGS_H_ - -#include - -namespace xe { -namespace gpu { -namespace metal { - -// Metal buffer/texture/sampler binding indices for the SPIRV-Cross MSL path. -// These must match the remappings applied in msl_shader.cc AddResourceBindings. -// -// The SPIR-V translator uses 4 descriptor sets: -// Set 0: Shared memory and EDRAM (storage buffers) -// Set 1: Constant buffers (system, float, bool/loop, fetch, clip, tess) -// Set 2: Vertex stage textures -// Set 3: Pixel stage textures -// -// SPIRV-Cross with argument_buffers=false maps each binding to a direct -// Metal buffer/texture/sampler index. We control these indices via -// add_msl_resource_binding(). - -namespace MslBufferIndex { -// Buffer indices for vertex and fragment shaders. -constexpr uint32_t kSharedMemory = 0; -constexpr uint32_t kSystemConstants = 1; -constexpr uint32_t kFloatConstantsVertex = 2; -constexpr uint32_t kFloatConstantsPixel = 3; -constexpr uint32_t kBoolLoopConstants = 4; -constexpr uint32_t kFetchConstants = 5; -constexpr uint32_t kClipPlaneConstants = 6; -constexpr uint32_t kTessellationConstants = 7; -// Buffer index for the shared argument buffer (textures + samplers) when -// SPIRV-Cross argument buffers are enabled. -constexpr uint32_t kArgumentBufferTexturesSamplers = 8; - -// Total constant buffer slots used, for computing uniform buffer offsets. -constexpr uint32_t kConstantBufferCount = 8; - -// Size of each constant buffer slice in the uniforms buffer (4KB, matching -// the D3D12/MSC layout for easy migration). -constexpr uint32_t kCbvSizeBytes = 4096; - -// Total size of the uniforms buffer region for one stage (one draw). -// system(4KB) + float(4KB) + bool_loop(4KB) + fetch(4KB) + clip(4KB) + -// tessellation(4KB) = 24KB. -constexpr uint32_t kUniformsBytesPerStage = 6 * kCbvSizeBytes; -} // namespace MslBufferIndex - -namespace MslTextureIndex { -// Textures start at index 0 within each shader stage. -constexpr uint32_t kBase = 0; -// Maximum textures per stage for the SPIRV-Cross path. -// The SPIR-V translator may create multiple texture bindings per fetch -// constant (for signed/unsigned and 2D/3D variants), so this can exceed 32. -constexpr uint32_t kMaxPerStage = 128; -} // namespace MslTextureIndex - -namespace MslSamplerIndex { -// Samplers start at index 0 within each shader stage. -constexpr uint32_t kBase = 0; -// Maximum samplers per stage. -constexpr uint32_t kMaxPerStage = 16; -} // namespace MslSamplerIndex - -// Resource IDs used within the argument buffer `[[id(N)]]` namespace when -// SPIRV-Cross argument buffers are enabled. -namespace MslArgumentBufferId { -// Reserve [0, kSamplerBase) for textures, and place samplers after. -constexpr uint32_t kSamplerBase = MslTextureIndex::kMaxPerStage; -} // namespace MslArgumentBufferId - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_MSL_BINDINGS_H_ diff --git a/src/xenia/gpu/metal/msl_shader.cc b/src/xenia/gpu/metal/msl_shader.cc deleted file mode 100644 index 8b1ba0443..000000000 --- a/src/xenia/gpu/metal/msl_shader.cc +++ /dev/null @@ -1,1442 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/gpu/metal/msl_shader.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "spirv_msl.hpp" -#include "third_party/fmt/include/fmt/format.h" -#include "third_party/xxhash/xxhash.h" - -#include "xenia/base/assert.h" -#include "xenia/base/filesystem.h" -#include "xenia/base/logging.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/metal/msl_bindings.h" - -DEFINE_bool( - metal_spirvcross_strip_nocontract_optnone, true, - "When using the Metal SPIRV-Cross path, strip [[clang::optnone]] from " - "NoContraction float helper wrappers (spvFAdd/spvFSub/spvFMul and matrix " - "variants). This avoids severe strict-math performance regressions on some " - "Apple GPUs while keeping NoContraction semantics enabled.", - "Metal"); - -DEFINE_bool(metal_shader_disk_cache, true, - "Cache compiled Metal shader libraries (metallib) to disk when " - "store_shaders is enabled.", - "Metal"); - -namespace xe { -namespace gpu { -namespace metal { - -namespace { - -constexpr uint32_t kMslSourceCacheMagic = 0x5843534D; // 'MSCX' -constexpr uint32_t kMslSourceCacheVersion = 1; -constexpr uint32_t kMslSourceCacheMaxBytes = 16 * 1024 * 1024; -// Bump when SPIRV-Cross -> MSL resource remapping or codegen-affecting options -// change so stale on-disk MSL source can't be reused with incompatible runtime -// bindings. -constexpr uint32_t kMslSourceCacheSchemaVersion = 5; - -std::mutex g_msl_source_cache_mutex; -std::filesystem::path g_msl_source_cache_directory; - -std::filesystem::path GetMslSourceCachePathLocked(uint64_t cache_key) { - char name[32]; - std::snprintf(name, sizeof(name), "%016llX.mslsrcache", - static_cast(cache_key)); - return g_msl_source_cache_directory / name; -} - -bool LoadCachedMslSource(uint64_t cache_key, std::string* source_out) { - if (!source_out) { - return false; - } - std::filesystem::path path; - { - std::lock_guard lock(g_msl_source_cache_mutex); - if (g_msl_source_cache_directory.empty()) { - return false; - } - path = GetMslSourceCachePathLocked(cache_key); - } - - std::ifstream file(path, std::ios::binary); - if (!file.is_open()) { - return false; - } - - uint32_t magic = 0; - uint32_t version = 0; - uint64_t key = 0; - uint32_t source_size = 0; - file.read(reinterpret_cast(&magic), sizeof(magic)); - file.read(reinterpret_cast(&version), sizeof(version)); - file.read(reinterpret_cast(&key), sizeof(key)); - file.read(reinterpret_cast(&source_size), sizeof(source_size)); - if (!file || magic != kMslSourceCacheMagic || - version != kMslSourceCacheVersion || key != cache_key || - source_size > kMslSourceCacheMaxBytes) { - return false; - } - - source_out->resize(source_size); - file.read(source_out->data(), source_size); - return bool(file); -} - -bool StoreCachedMslSource(uint64_t cache_key, std::string_view source) { - if (source.empty() || source.size() > kMslSourceCacheMaxBytes) { - return false; - } - - std::filesystem::path path; - { - std::lock_guard lock(g_msl_source_cache_mutex); - if (g_msl_source_cache_directory.empty()) { - return false; - } - path = GetMslSourceCachePathLocked(cache_key); - } - std::filesystem::path tmp_path = path; - tmp_path += ".tmp"; - - std::ofstream file(tmp_path, std::ios::binary | std::ios::trunc); - if (!file.is_open()) { - return false; - } - uint32_t magic = kMslSourceCacheMagic; - uint32_t version = kMslSourceCacheVersion; - uint64_t key = cache_key; - uint32_t source_size = static_cast(source.size()); - file.write(reinterpret_cast(&magic), sizeof(magic)); - file.write(reinterpret_cast(&version), sizeof(version)); - file.write(reinterpret_cast(&key), sizeof(key)); - file.write(reinterpret_cast(&source_size), sizeof(source_size)); - file.write(source.data(), source.size()); - file.close(); - - std::error_code ec; - std::filesystem::rename(tmp_path, path, ec); - if (ec) { - std::filesystem::remove(tmp_path, ec); - return false; - } - return true; -} - -size_t ReplaceAllInPlace(std::string& text, std::string_view from, - std::string_view to) { - if (from.empty()) { - return 0; - } - size_t replacement_count = 0; - size_t position = 0; - while ((position = text.find(from, position)) != std::string::npos) { - text.replace(position, from.size(), to); - position += to.size(); - ++replacement_count; - } - return replacement_count; -} - -size_t StripNoContractionOptnoneWrappers(std::string& msl_source) { - // Keep this narrowly scoped to the NoContraction helper wrappers that are - // known to cause significant slowdowns with strict math. - static constexpr std::pair kPatterns[] = { - {"[[clang::optnone]] T spvFAdd(T l, T r)", "T spvFAdd(T l, T r)"}, - {"[[clang::optnone]] T spvFSub(T l, T r)", "T spvFSub(T l, T r)"}, - {"[[clang::optnone]] T spvFMul(T l, T r)", "T spvFMul(T l, T r)"}, - {"[[clang::optnone]] vec spvFMulVectorMatrix(vec v, " - "matrix m)", - "vec spvFMulVectorMatrix(vec v, matrix " - "m)"}, - {"[[clang::optnone]] vec spvFMulMatrixVector(matrix m, vec v)", - "vec spvFMulMatrixVector(matrix m, vec " - "v)"}, - {"[[clang::optnone]] matrix spvFMulMatrixMatrix(" - "matrix l, matrix r)", - "matrix spvFMulMatrixMatrix(matrix l, " - "matrix r)"}, - }; - size_t replacements = 0; - for (const auto& pattern : kPatterns) { - replacements += - ReplaceAllInPlace(msl_source, pattern.first, pattern.second); - } - return replacements; -} - -uint64_t GetMslSourceCacheKey(const MslShader::MslTranslation& translation, - bool is_ios, uint32_t msl_major, - uint32_t msl_minor, - bool ios_support_base_vertex_instance, - bool emulate_cube_array, - bool ios_use_simdgroup_functions, - bool use_argument_buffers, - uint8_t argument_buffers_tier) { - uint64_t translated_spirv_hash = 0; - const auto& translated_spirv = translation.translated_binary(); - if (!translated_spirv.empty()) { - translated_spirv_hash = - XXH3_64bits(translated_spirv.data(), translated_spirv.size()); - } - - struct KeyData { - uint64_t shader_hash; - uint64_t modification; - uint64_t translated_spirv_hash; - uint32_t stage; - uint8_t is_ios; - uint8_t msl_major; - uint8_t msl_minor; - uint8_t ios_support_base_vertex_instance; - uint8_t emulate_cube_array; - uint8_t ios_use_simdgroup_functions; - uint8_t use_argument_buffers; - uint8_t argument_buffers_tier; - uint16_t reserved0; - uint32_t cache_schema_version; - } key_data = {}; - key_data.shader_hash = translation.shader().ucode_data_hash(); - key_data.modification = translation.modification(); - key_data.translated_spirv_hash = translated_spirv_hash; - key_data.stage = static_cast(translation.shader().type()); - key_data.is_ios = is_ios ? 1 : 0; - key_data.msl_major = static_cast(msl_major); - key_data.msl_minor = static_cast(msl_minor); - key_data.ios_support_base_vertex_instance = - ios_support_base_vertex_instance ? 1 : 0; - key_data.emulate_cube_array = emulate_cube_array ? 1 : 0; - key_data.ios_use_simdgroup_functions = ios_use_simdgroup_functions ? 1 : 0; - key_data.use_argument_buffers = use_argument_buffers ? 1 : 0; - key_data.argument_buffers_tier = argument_buffers_tier; - key_data.cache_schema_version = kMslSourceCacheSchemaVersion; - return XXH3_64bits(&key_data, sizeof(key_data)); -} - -} // namespace - -void SetMslShaderSourceCacheDirectory(const std::filesystem::path& cache_dir) { - std::lock_guard lock(g_msl_source_cache_mutex); - g_msl_source_cache_directory = cache_dir; - if (g_msl_source_cache_directory.empty()) { - return; - } - std::error_code ec; - std::filesystem::create_directories(g_msl_source_cache_directory, ec); - if (ec) { - XELOGW("MslShader: Failed to create source cache directory {}: {}", - g_msl_source_cache_directory.string(), ec.message()); - g_msl_source_cache_directory.clear(); - } -} - -void ClearMslShaderSourceCacheDirectory() { - std::lock_guard lock(g_msl_source_cache_mutex); - g_msl_source_cache_directory.clear(); -} - -// SPIR-V descriptor set indices matching SpirvShaderTranslator's layout. -// These values must stay in sync with the enum in spirv_shader_translator.h. -namespace SpirvSets { -constexpr uint32_t kSharedMemoryAndEdram = 0; -constexpr uint32_t kConstants = 1; -constexpr uint32_t kTexturesVertex = 2; -constexpr uint32_t kTexturesPixel = 3; -} // namespace SpirvSets - -// SPIR-V constant buffer binding indices within descriptor set 1. -// Must match SpirvShaderTranslator::ConstantBuffer enum. -namespace SpirvCbv { -constexpr uint32_t kSystem = 0; -constexpr uint32_t kFloatVertex = 1; -constexpr uint32_t kFloatPixel = 2; -constexpr uint32_t kBoolLoop = 3; -constexpr uint32_t kFetch = 4; -constexpr uint32_t kClipPlanes = 5; -constexpr uint32_t kTessellation = 6; -} // namespace SpirvCbv - -MslShader::MslShader(xenos::ShaderType shader_type, uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, size_t ucode_dword_count, - std::endian ucode_source_endian) - : SpirvShader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count, - ucode_source_endian) {} - -Shader::Translation* MslShader::CreateTranslationInstance( - uint64_t modification) { - return new MslTranslation(*this, modification); -} - -MslShader::MslTranslation::~MslTranslation() { - if (argument_encoder_) { - argument_encoder_->release(); - argument_encoder_ = nullptr; - } - if (metal_function_) { - metal_function_->release(); - metal_function_ = nullptr; - } - if (metal_library_) { - metal_library_->release(); - metal_library_ = nullptr; - } -} - -// Metal buffer binding indices for resources. -// These must match the SPIRV-Cross remapping done below. -namespace MslBindings { -// Descriptor Set 0: Shared memory and EDRAM. -constexpr uint32_t kSharedMemory = 0; -// Descriptor Set 1: Constant buffers. -constexpr uint32_t kSystemConstants = 1; -constexpr uint32_t kFloatConstantsVertex = 2; -constexpr uint32_t kFloatConstantsPixel = 3; -constexpr uint32_t kBoolLoopConstants = 4; -constexpr uint32_t kFetchConstants = 5; -constexpr uint32_t kClipPlaneConstants = 6; -constexpr uint32_t kTessellationConstants = 7; -// Descriptor Set 2/3: Textures start at this index. -constexpr uint32_t kTextureBase = 0; -// Samplers start at this index. -constexpr uint32_t kSamplerBase = 0; -} // namespace MslBindings - -static const char* GetExecutionModelName(spv::ExecutionModel stage) { - switch (stage) { - case spv::ExecutionModelVertex: - return "vertex"; - case spv::ExecutionModelFragment: - return "fragment"; - case spv::ExecutionModelTessellationControl: - return "tess_control"; - case spv::ExecutionModelTessellationEvaluation: - return "tess_evaluation"; - case spv::ExecutionModelGLCompute: - return "compute"; - default: - return "other"; - } -} - -static std::string JoinStringList(const std::vector& values) { - std::string joined; - for (size_t i = 0; i < values.size(); ++i) { - if (i) { - joined += ", "; - } - joined += values[i]; - } - return joined; -} - -static void AddEntryPointCandidatesFromMslSource( - const std::string& msl_source, xenos::ShaderType shader_type, - std::vector* candidates) { - if (!candidates || msl_source.empty()) { - return; - } - const char* expected_stage = - shader_type == xenos::ShaderType::kVertex ? "vertex" : "fragment"; - // Match typical MSL stage entry declarations generated by SPIRV-Cross: - // vertex (...) - // fragment (...) - static const std::regex kStageEntryRegex( - R"((?:^|\n)\s*(vertex|fragment)\s+[^\n\(\{;]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*\()", - std::regex::optimize); - for (std::sregex_iterator - it(msl_source.begin(), msl_source.end(), kStageEntryRegex), - end; - it != end; ++it) { - if (it->size() < 3) { - continue; - } - const std::string stage = (*it)[1].str(); - if (stage != expected_stage) { - continue; - } - const std::string name = (*it)[2].str(); - if (name.empty()) { - continue; - } - if (std::find(candidates->begin(), candidates->end(), name) == - candidates->end()) { - candidates->push_back(name); - } - } - // Match alternate style: - // (...) [[vertex]] - // (...) [[fragment]] - static const std::regex kAttributedEntryRegex( - R"((?:^|\n)\s*[^\n\(\{;]*?\b([A-Za-z_][A-Za-z0-9_]*)\s*\([^;\{\)]*\)\s*\[\[\s*(vertex|fragment)\s*\]\])", - std::regex::optimize); - for (std::sregex_iterator - it(msl_source.begin(), msl_source.end(), kAttributedEntryRegex), - end; - it != end; ++it) { - if (it->size() < 3) { - continue; - } - const std::string name = (*it)[1].str(); - const std::string stage = (*it)[2].str(); - if (name.empty() || stage != expected_stage) { - continue; - } - if (std::find(candidates->begin(), candidates->end(), name) == - candidates->end()) { - candidates->push_back(name); - } - } -} - -static void DumpMslResolveFailureSource(const std::string& msl_source, - uint64_t shader_hash, - uint64_t modification, - xenos::ShaderType shader_type) { - if (msl_source.empty()) { - return; - } - const char* stage_tag = - shader_type == xenos::ShaderType::kVertex ? "vs" : "ps"; - std::filesystem::path dump_dir; - if (!cvars::dump_shaders.empty()) { - dump_dir = std::filesystem::absolute(cvars::dump_shaders) / "msl_shaders" / - "failures"; - } else { - dump_dir = std::filesystem::temp_directory_path() / "xenia_msl_failures"; - } - std::error_code ec; - std::filesystem::create_directories(dump_dir, ec); - if (ec) { - XELOGW("MslShader: Failed to create unresolved-entry dump directory {}: {}", - dump_dir.string(), ec.message()); - return; - } - std::filesystem::path dump_path = - dump_dir / fmt::format("{}_{:016X}_mod{:016X}_entry_resolve_fail.metal", - stage_tag, shader_hash, modification); - FILE* f = xe::filesystem::OpenFile(dump_path, "w"); - if (!f) { - XELOGW("MslShader: Failed to open unresolved-entry dump file {}", - dump_path.string()); - return; - } - fwrite(msl_source.data(), 1, msl_source.size(), f); - fclose(f); - XELOGW("MslShader: Dumped unresolved-entry MSL source to {}", - dump_path.string()); -} - -static void AddResourceBindings( - spirv_cross::CompilerMSL& compiler, spv::ExecutionModel stage, - uint32_t texture_descriptor_set, uint64_t shader_hash, - bool use_argument_buffers, - std::vector* sampler_spv_bindings_msl_order) { - using MSLBinding = spirv_cross::MSLResourceBinding; - - // Set 0: Shared memory (storage buffer at binding 0). - { - MSLBinding binding; - binding.stage = stage; - binding.desc_set = SpirvSets::kSharedMemoryAndEdram; - binding.binding = 0; - binding.msl_buffer = MslBindings::kSharedMemory; - binding.msl_texture = 0; - binding.msl_sampler = 0; - compiler.add_msl_resource_binding(binding); - } - - // Set 0 binding 1: EDRAM (only used in FSI mode — skip for host RT path). - // We still add a dummy mapping so SPIRV-Cross doesn't complain if the - // shader happens to reference it (it shouldn't for kHostRenderTargets). - { - MSLBinding binding; - binding.stage = stage; - binding.desc_set = SpirvSets::kSharedMemoryAndEdram; - binding.binding = 1; - binding.msl_buffer = 30; // High index, unused. - binding.msl_texture = 0; - binding.msl_sampler = 0; - compiler.add_msl_resource_binding(binding); - } - - // Set 1: Constant buffers. - struct CbvMapping { - uint32_t spirv_binding; - uint32_t msl_buffer; - }; - static const CbvMapping cbv_mappings[] = { - {SpirvCbv::kSystem, MslBindings::kSystemConstants}, - {SpirvCbv::kFloatVertex, MslBindings::kFloatConstantsVertex}, - {SpirvCbv::kFloatPixel, MslBindings::kFloatConstantsPixel}, - {SpirvCbv::kBoolLoop, MslBindings::kBoolLoopConstants}, - {SpirvCbv::kFetch, MslBindings::kFetchConstants}, - {SpirvCbv::kClipPlanes, MslBindings::kClipPlaneConstants}, - {SpirvCbv::kTessellation, MslBindings::kTessellationConstants}, - }; - for (const auto& cbv : cbv_mappings) { - MSLBinding binding; - binding.stage = stage; - binding.desc_set = SpirvSets::kConstants; - binding.binding = cbv.spirv_binding; - binding.msl_buffer = cbv.msl_buffer; - binding.msl_texture = 0; - binding.msl_sampler = 0; - compiler.add_msl_resource_binding(binding); - } - - // Set 2 (vertex textures) and Set 3 (pixel textures): texture bindings. - // - // The SPIR-V translator places sampler bindings AFTER texture bindings in - // the same descriptor set (sampler i gets SPIR-V binding texture_count + i). - // Metal only supports 16 samplers per stage, so we must remap sampler - // bindings to compact indices 0..M-1 regardless of their SPIR-V binding. - // - // Use a single MSLResourceBinding entry per SPIR-V binding to avoid - // overwrite issues (SPIRV-Cross keys entries by {stage, set, binding}). - // - // Step 1: Map all 32 possible texture bindings with identity mapping. - // Set msl_sampler = 0 as a safe default (overridden for real - // samplers in step 2). - for (uint32_t set = SpirvSets::kTexturesVertex; - set <= SpirvSets::kTexturesPixel; ++set) { - if (use_argument_buffers) { - // Map the argument buffer pointer itself to a Metal buffer slot. - MSLBinding argbuf_binding; - argbuf_binding.stage = stage; - argbuf_binding.desc_set = set; - argbuf_binding.binding = spirv_cross::kArgumentBufferBinding; - argbuf_binding.msl_buffer = - MslBufferIndex::kArgumentBufferTexturesSamplers; - argbuf_binding.msl_texture = 0; - argbuf_binding.msl_sampler = 0; - compiler.add_msl_resource_binding(argbuf_binding); - } - for (uint32_t i = 0; i < MslTextureIndex::kMaxPerStage; ++i) { - MSLBinding binding; - binding.stage = stage; - binding.desc_set = set; - binding.binding = i; - binding.msl_buffer = 0; - binding.msl_texture = MslBindings::kTextureBase + i; - binding.msl_sampler = 0; - compiler.add_msl_resource_binding(binding); - } - } - - // Step 2: Query the actual SPIR-V resources to find sampler bindings - // and remap them to compact Metal sampler indices 0..M-1. - // This handles the case where SPIR-V sampler bindings exceed 15 - // (which would violate Metal's 16-sampler-per-stage limit). - auto resources = compiler.get_shader_resources(); - struct SamplerSpvBinding { - uint32_t set; - uint32_t binding; - }; - std::vector sampler_spv_bindings; - sampler_spv_bindings.reserve(resources.separate_samplers.size()); - for (const auto& samp : resources.separate_samplers) { - uint32_t set = - compiler.get_decoration(samp.id, spv::DecorationDescriptorSet); - if (set != texture_descriptor_set) { - continue; - } - uint32_t spv_binding = - compiler.get_decoration(samp.id, spv::DecorationBinding); - sampler_spv_bindings.push_back({set, spv_binding}); - } - bool sampler_order_was_unsorted = false; - for (size_t i = 1; i < sampler_spv_bindings.size(); ++i) { - const auto& prev = sampler_spv_bindings[i - 1]; - const auto& curr = sampler_spv_bindings[i]; - if (curr.set < prev.set || - (curr.set == prev.set && curr.binding < prev.binding)) { - sampler_order_was_unsorted = true; - break; - } - } - std::sort(sampler_spv_bindings.begin(), sampler_spv_bindings.end(), - [](const SamplerSpvBinding& a, const SamplerSpvBinding& b) { - if (a.set != b.set) { - return a.set < b.set; - } - return a.binding < b.binding; - }); - if (sampler_order_was_unsorted) { - XELOGD( - "MslShader: Reordered sampler SPIR-V bindings by set/binding for " - "stable Metal remap shader={:016X} stage={}", - shader_hash, GetExecutionModelName(stage)); - } - uint32_t sampler_msl_index = 0; - std::ostringstream sampler_remap_log; - uint32_t sampler_remap_count = 0; - for (size_t i = 0; i < sampler_spv_bindings.size(); ++i) { - uint32_t set = sampler_spv_bindings[i].set; - uint32_t spv_binding = sampler_spv_bindings[i].binding; - if (i > 0 && set == sampler_spv_bindings[i - 1].set && - spv_binding == sampler_spv_bindings[i - 1].binding) { - XELOGW( - "MslShader: Duplicate sampler SPIR-V binding at set={} binding={} " - "shader={:016X}", - set, spv_binding, shader_hash); - continue; - } - if (sampler_msl_index >= 16) { - XELOGW( - "MslShader: Too many samplers ({} >= 16), sampler at set={} " - "binding={} will not be bound", - sampler_msl_index, set, spv_binding); - break; - } - // Overwrite the entry for this binding with the compact sampler index. - // For standalone samplers, don't propagate the SPIR-V binding to - // msl_texture; sampler SPIR-V bindings are after image bindings and may - // exceed the per-stage Metal texture slot limit. - MSLBinding binding; - binding.stage = stage; - binding.desc_set = set; - binding.binding = spv_binding; - binding.msl_buffer = 0; - binding.msl_texture = MslTextureIndex::kBase; - binding.msl_sampler = - (use_argument_buffers ? MslArgumentBufferId::kSamplerBase - : MslBindings::kSamplerBase) + - sampler_msl_index; - compiler.add_msl_resource_binding(binding); - if (sampler_spv_bindings_msl_order) { - sampler_spv_bindings_msl_order->push_back(spv_binding); - } - sampler_remap_log << "[set=" << set << ",binding=" << spv_binding - << "->msl_sampler=" << sampler_msl_index << "] "; - sampler_msl_index++; - sampler_remap_count++; - } - if (sampler_remap_count) { - XELOGD("MslShader: Sampler remap table shader={:016X} stage={} count={} {}", - shader_hash, GetExecutionModelName(stage), sampler_remap_count, - sampler_remap_log.str()); - } else { - XELOGD("MslShader: Sampler remap table shader={:016X} stage={} count=0", - shader_hash, GetExecutionModelName(stage)); - } -} - -bool MslShader::MslTranslation::CompileToMsl(MTL::Device* device, bool is_ios) { - if (!device) { - XELOGE("MslShader: No Metal device provided"); - return false; - } - if (argument_encoder_) { - argument_encoder_->release(); - argument_encoder_ = nullptr; - } - uses_argument_buffers_ = false; - argument_encoder_alignment_ = 0; - argument_encoder_encoded_length_ = 0; - - const std::vector& spirv_data = translated_binary(); - if (spirv_data.empty()) { - XELOGE("MslShader: No translated SPIR-V data available"); - return false; - } - const size_t spirv_size_bytes = spirv_data.size(); - if (spirv_size_bytes >= 64 * 1024) { - XELOGW( - "MslShader: Large SPIR-V module detected ({:016X}, {} bytes, stage={})", - shader().ucode_data_hash(), spirv_size_bytes, - shader().type() == xenos::ShaderType::kVertex ? "vertex" : "fragment"); - } - const auto compile_begin = std::chrono::steady_clock::now(); - auto to_ms = - [](const std::chrono::steady_clock::duration& duration) -> double { - return std::chrono::duration(duration).count(); - }; - double spirv_cross_ms = 0.0; - double metal_library_ms = 0.0; - double function_resolve_ms = 0.0; - - // SPIR-V data is a vector of bytes, but SPIRV-Cross expects uint32_t words. - if (spirv_data.size() % sizeof(uint32_t) != 0) { - XELOGE("MslShader: SPIR-V data size is not aligned to 4 bytes"); - return false; - } - const uint32_t* spirv_words = - reinterpret_cast(spirv_data.data()); - size_t spirv_word_count = spirv_data.size() / sizeof(uint32_t); - - // SPIRV-Cross throws exceptions on translation errors. We catch them - // below and return false so the draw can be skipped gracefully instead - // of crashing the process. - spirv_cross::CompilerMSL compiler(spirv_words, spirv_word_count); - - // Configure MSL options. - auto opts = compiler.get_msl_options(); - if (is_ios) { - opts.platform = spirv_cross::CompilerMSL::Options::iOS; - } else { - opts.platform = spirv_cross::CompilerMSL::Options::macOS; - } - bool ios_supports_base_vertex_instance = false; - bool ios_supports_cube_array = true; - bool ios_supports_simdgroup_scope_ops = false; - bool ios_supports_metal3 = false; - bool ios_supports_apple9 = false; - bool ios_supports_apple10 = false; - if (is_ios) { - // Apple docs: base vertex/instance drawing is Apple3+, cube array textures - // are Apple4+, SIMD-scoped permute is Apple6+. - ios_supports_base_vertex_instance = - device->supportsFamily(MTL::GPUFamilyApple3); - ios_supports_cube_array = device->supportsFamily(MTL::GPUFamilyApple4); - ios_supports_simdgroup_scope_ops = - device->supportsFamily(MTL::GPUFamilyApple6); - // Apple Metal Feature Set Tables (2025-10-20): Metal 3+ starts at Apple7, - // Apple9/Apple10 map to newer iPhone GPU families (A17/A18 and A19). - ios_supports_metal3 = device->supportsFamily(MTL::GPUFamilyApple7); - ios_supports_apple9 = device->supportsFamily(MTL::GPUFamilyApple9); - ios_supports_apple10 = device->supportsFamily(MTL::GPUFamilyApple10); - } - // iOS: prefer Metal 3 language levels on Apple7+ devices. - // Keep legacy fallback for older iOS GPUs. - uint32_t msl_major = 2; - uint32_t msl_minor = is_ios ? 3u : 4u; - MTL::LanguageVersion language_version = - is_ios ? MTL::LanguageVersion2_3 : MTL::LanguageVersion2_4; - if (is_ios && ios_supports_metal3) { - msl_major = 3; - if (ios_supports_apple10) { - msl_minor = 2; - language_version = MTL::LanguageVersion3_2; - } else if (ios_supports_apple9) { - msl_minor = 1; - language_version = MTL::LanguageVersion3_1; - } else { - msl_minor = 0; - language_version = MTL::LanguageVersion3_0; - } - } - opts.msl_version = - spirv_cross::CompilerMSL::Options::make_msl_version(msl_major, msl_minor); - // Use argument buffers for SPIRV-Cross texture/sampler bindings to reduce - // per-draw CPU binding overhead. - uses_argument_buffers_ = true; - opts.argument_buffers = uses_argument_buffers_; - uint8_t argument_buffers_tier_key = 0; - if (uses_argument_buffers_) { - if (device->argumentBuffersSupport() >= MTL::ArgumentBuffersTier2) { - opts.argument_buffers_tier = - spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier2; - argument_buffers_tier_key = 1; - } else { - opts.argument_buffers_tier = - spirv_cross::CompilerMSL::Options::ArgumentBuffersTier::Tier1; - argument_buffers_tier_key = 0; - } - // Keep argument-buffer layouts stable even if SPIRV-Cross marks specific - // resources inactive in some variants. - opts.force_active_argument_buffer_resources = true; - } - // Match iOS feature toggles to documented GPU-family availability. - opts.ios_support_base_vertex_instance = ios_supports_base_vertex_instance; - const bool emulate_cube_array = is_ios && !ios_supports_cube_array; - opts.emulate_cube_array = emulate_cube_array; - // Keep iOS aligned with the conservative cross-platform path by default. - // Enabling simdgroup translation on some iOS shader/compiler combinations can - // yield libraries with no exported stage entry points (`functionNames=[]`). - const bool ios_use_simdgroup_functions = - is_ios && ios_supports_simdgroup_scope_ops; - opts.ios_use_simdgroup_functions = ios_use_simdgroup_functions; - // Force sample rate shading to be available if needed. - opts.force_sample_rate_shading = false; - // Keep native component counts for fragment outputs to preserve shader- - // defined alpha semantics and avoid over-constraining RT writes. - opts.pad_fragment_output_components = false; - const uint64_t msl_source_cache_key = GetMslSourceCacheKey( - *this, is_ios, msl_major, msl_minor, ios_supports_base_vertex_instance, - emulate_cube_array, ios_use_simdgroup_functions, uses_argument_buffers_, - argument_buffers_tier_key); - compiler.set_msl_options(opts); - if (uses_argument_buffers_) { - // Keep shared memory/storage buffers and constant buffers as discrete - // bindings; put textures + samplers in argument buffers. - compiler.add_discrete_descriptor_set(SpirvSets::kSharedMemoryAndEdram); - compiler.add_discrete_descriptor_set(SpirvSets::kConstants); - } - - // Remap SPIR-V descriptor sets/bindings to Metal buffer/texture/sampler - // indices. - // Query the actual execution model from the SPIR-V binary rather than - // assuming kVertex always maps to spv::ExecutionModelVertex. The - // SpirvShaderTranslator already sets the correct model — domain shaders - // (tessellation evaluation) are emitted as TessellationEvaluation, not - // Vertex — so we must honour that for SPIRV-Cross resource remapping and - // entry-point lookup. - std::string spirv_entry_point_name = "main"; - std::vector spirv_entry_point_descriptions; - spv::ExecutionModel execution_model; - { - auto entry_points = compiler.get_entry_points_and_stages(); - if (!entry_points.empty()) { - spirv_entry_point_descriptions.reserve(entry_points.size()); - for (const auto& entry_point : entry_points) { - spirv_entry_point_descriptions.push_back( - fmt::format("{}:{}", entry_point.name, - GetExecutionModelName(entry_point.execution_model))); - } - spv::ExecutionModel preferred_model = - shader().type() == xenos::ShaderType::kVertex - ? spv::ExecutionModelVertex - : spv::ExecutionModelFragment; - const spirv_cross::EntryPoint* selected_entry = &entry_points.front(); - for (const auto& entry_point : entry_points) { - if (entry_point.execution_model == preferred_model) { - selected_entry = &entry_point; - break; - } - } - execution_model = selected_entry->execution_model; - if (!selected_entry->name.empty()) { - spirv_entry_point_name = selected_entry->name; - } - } else { - // Fallback: infer from shader type (pre-tessellation behavior). - execution_model = shader().type() == xenos::ShaderType::kVertex - ? spv::ExecutionModelVertex - : spv::ExecutionModelFragment; - XELOGW( - "MslShader: SPIR-V module has no declared entry points " - "(shader={:016X}, expected_stage={})", - shader().ucode_data_hash(), GetExecutionModelName(execution_model)); - } - } - try { - compiler.set_entry_point(spirv_entry_point_name, execution_model); - } catch (const std::exception& e) { - XELOGW( - "MslShader: Failed to set SPIR-V entry point '{}' (stage={}) " - "shader={:016X}: {}", - spirv_entry_point_name, GetExecutionModelName(execution_model), - shader().ucode_data_hash(), e.what()); - } - sampler_binding_indices_for_msl_slots_.clear(); - texture_binding_indices_for_msl_slots_.clear(); - // Domain shaders use tessellation-evaluation execution model, but still use - // the vertex texture descriptor set in SpirvShaderTranslator. - uint32_t texture_descriptor_set = - shader().type() == xenos::ShaderType::kVertex ? SpirvSets::kTexturesVertex - : SpirvSets::kTexturesPixel; - std::vector sampler_spv_bindings_msl_order; - AddResourceBindings(compiler, execution_model, texture_descriptor_set, - shader().ucode_data_hash(), uses_argument_buffers_, - &sampler_spv_bindings_msl_order); - auto resources = compiler.get_shader_resources(); - - // Build the runtime texture binding lookup for Metal slots. - // Slots are direct (msl_texture = SPIR-V binding). Seed with translator - // order, then refine with reflection where possible. - const MslShader& msl_shader = static_cast(shader()); - const auto& shader_texture_bindings = - msl_shader.GetTextureBindingsAfterTranslation(); - auto find_runtime_texture_binding_index = - [&](uint32_t fetch_constant, xenos::FetchOpDimension dimension, - bool is_signed) -> int32_t { - for (size_t i = 0; i < shader_texture_bindings.size(); ++i) { - const auto& binding = shader_texture_bindings[i]; - if (binding.fetch_constant == fetch_constant && - binding.dimension == dimension && binding.is_signed == is_signed) { - return int32_t(i); - } - } - return -1; - }; - auto parse_texture_resource_name = - [](const std::string& name, uint32_t* fetch_constant_out, - xenos::FetchOpDimension* dimension_out, bool* is_signed_out) -> bool { - static constexpr const char kPrefix[] = "xe_texture"; - if (!fetch_constant_out || !dimension_out || !is_signed_out || - name.size() <= sizeof(kPrefix) - 1 || - name.compare(0, sizeof(kPrefix) - 1, kPrefix) != 0) { - return false; - } - size_t offset = sizeof(kPrefix) - 1; - uint32_t fetch_constant = 0; - bool has_digit = false; - while (offset < name.size() && - std::isdigit(static_cast(name[offset]))) { - has_digit = true; - fetch_constant = fetch_constant * 10 + uint32_t(name[offset] - '0'); - ++offset; - } - if (!has_digit || offset >= name.size() || name[offset] != '_') { - return false; - } - ++offset; - - size_t dim_end = name.find('_', offset); - if (dim_end == std::string::npos || dim_end <= offset) { - return false; - } - std::string dim_token = name.substr(offset, dim_end - offset); - xenos::FetchOpDimension dimension = xenos::FetchOpDimension::k2D; - if (dim_token == "2d") { - dimension = xenos::FetchOpDimension::k2D; - } else if (dim_token == "3d") { - dimension = xenos::FetchOpDimension::k3DOrStacked; - } else if (dim_token == "cube") { - dimension = xenos::FetchOpDimension::kCube; - } else { - return false; - } - - offset = dim_end + 1; - if (offset >= name.size()) { - return false; - } - char sign_char = name[offset]; - if (sign_char != 's' && sign_char != 'u') { - return false; - } - - *fetch_constant_out = fetch_constant; - *dimension_out = dimension; - *is_signed_out = sign_char == 's'; - return true; - }; - texture_binding_indices_for_msl_slots_.reserve(std::min( - shader_texture_bindings.size(), MslTextureIndex::kMaxPerStage)); - for (uint32_t i = 0; - i < std::min(uint32_t(shader_texture_bindings.size()), - MslTextureIndex::kMaxPerStage); - ++i) { - texture_binding_indices_for_msl_slots_.push_back(int32_t(i)); - } - bool has_reflected_texture_bindings = false; - bool texture_reflection_incomplete = false; - auto register_texture_binding = - [&](const spirv_cross::Resource& resource) -> void { - uint32_t set = - compiler.get_decoration(resource.id, spv::DecorationDescriptorSet); - if (set != texture_descriptor_set) { - return; - } - has_reflected_texture_bindings = true; - uint32_t spv_binding = - compiler.get_decoration(resource.id, spv::DecorationBinding); - if (spv_binding >= MslTextureIndex::kMaxPerStage) { - XELOGW( - "MslShader: Reflected texture binding {} exceeds Metal slot limit {} " - "shader={:016X}", - spv_binding, MslTextureIndex::kMaxPerStage, - shader().ucode_data_hash()); - return; - } - int32_t runtime_binding_index = -1; - uint32_t parsed_fetch_constant = 0; - xenos::FetchOpDimension parsed_dimension = xenos::FetchOpDimension::k2D; - bool parsed_is_signed = false; - if (parse_texture_resource_name(resource.name, &parsed_fetch_constant, - &parsed_dimension, &parsed_is_signed)) { - runtime_binding_index = find_runtime_texture_binding_index( - parsed_fetch_constant, parsed_dimension, parsed_is_signed); - if (runtime_binding_index < 0) { - XELOGW( - "MslShader: Unable to resolve texture resource '{}' in runtime " - "bindings shader={:016X}", - resource.name, shader().ucode_data_hash()); - } - } - if (runtime_binding_index < 0) { - // Fall back to SPIR-V decoration binding index when reflection names are - // unavailable or don't match translator naming. - if (spv_binding >= shader_texture_bindings.size()) { - XELOGW( - "MslShader: Reflected texture binding {} is out of runtime range " - "{} " - "shader={:016X}", - spv_binding, shader_texture_bindings.size(), - shader().ucode_data_hash()); - texture_reflection_incomplete = true; - return; - } - runtime_binding_index = int32_t(spv_binding); - } - - if (texture_binding_indices_for_msl_slots_.size() <= spv_binding) { - texture_binding_indices_for_msl_slots_.resize(spv_binding + 1, -1); - } - int32_t& slot_binding = texture_binding_indices_for_msl_slots_[spv_binding]; - if (slot_binding == -1) { - slot_binding = runtime_binding_index; - } else if (slot_binding != runtime_binding_index) { - XELOGW( - "MslShader: Conflicting texture remap at slot {} ({} vs {}) " - "shader={:016X}", - spv_binding, slot_binding, runtime_binding_index, - shader().ucode_data_hash()); - texture_reflection_incomplete = true; - } - }; - for (const auto& image : resources.separate_images) { - register_texture_binding(image); - } - for (const auto& image : resources.sampled_images) { - register_texture_binding(image); - } - if (!has_reflected_texture_bindings && !shader_texture_bindings.empty()) { - XELOGW( - "MslShader: No reflected texture bindings for shader={:016X} stage={} " - "(runtime={}), using translator-order texture remap", - shader().ucode_data_hash(), GetExecutionModelName(execution_model), - shader_texture_bindings.size()); - } else if (texture_reflection_incomplete) { - XELOGW( - "MslShader: Incomplete texture reflection remap for shader={:016X} " - "stage={}, keeping translator-order mappings for unresolved slots", - shader().ucode_data_hash(), GetExecutionModelName(execution_model)); - } - - // Build the runtime sampler binding lookup for Metal slots. - // This is needed because SPIRV-Cross may drop / reorder separate samplers, - // while runtime sampler parameters are stored in translator binding order. - uint32_t shader_texture_binding_count = static_cast( - msl_shader.GetTextureBindingsAfterTranslation().size()); - const auto& shader_sampler_bindings = - msl_shader.GetSamplerBindingsAfterTranslation(); - std::vector sampler_binding_mapped(shader_sampler_bindings.size(), - false); - sampler_binding_indices_for_msl_slots_.reserve( - sampler_spv_bindings_msl_order.size()); - for (uint32_t spv_binding : sampler_spv_bindings_msl_order) { - if (spv_binding < shader_texture_binding_count) { - XELOGW( - "MslShader: Reflected sampler binding {} is below texture binding " - "base {} shader={:016X}", - spv_binding, shader_texture_binding_count, - shader().ucode_data_hash()); - continue; - } - uint32_t sampler_binding_index = spv_binding - shader_texture_binding_count; - if (sampler_binding_index >= shader_sampler_bindings.size()) { - XELOGW( - "MslShader: Reflected sampler binding {} maps to out-of-range " - "sampler index {} (available {}) shader={:016X}", - spv_binding, sampler_binding_index, shader_sampler_bindings.size(), - shader().ucode_data_hash()); - continue; - } - if (sampler_binding_index < sampler_binding_mapped.size() && - sampler_binding_mapped[sampler_binding_index]) { - continue; - } - sampler_binding_indices_for_msl_slots_.push_back(sampler_binding_index); - if (sampler_binding_index < sampler_binding_mapped.size()) { - sampler_binding_mapped[sampler_binding_index] = true; - } - } - size_t sampler_mapped_count = sampler_binding_indices_for_msl_slots_.size(); - if (!shader_sampler_bindings.empty() && - sampler_mapped_count < shader_sampler_bindings.size()) { - for (uint32_t i = 0; i < shader_sampler_bindings.size(); ++i) { - if (!sampler_binding_mapped[i]) { - sampler_binding_indices_for_msl_slots_.push_back(i); - } - } - } - bool sampler_reflection_incomplete = - sampler_spv_bindings_msl_order.size() > sampler_mapped_count; - if (!shader_sampler_bindings.empty() && sampler_reflection_incomplete) { - XELOGW( - "MslShader: Sampler reflection remap incomplete; preserving mapped " - "samplers and appending unmapped samplers in translator order " - "shader={:016X} stage={} (reflected={} mapped={} runtime={})", - shader().ucode_data_hash(), GetExecutionModelName(execution_model), - sampler_spv_bindings_msl_order.size(), sampler_mapped_count, - shader_sampler_bindings.size()); - } else if (sampler_spv_bindings_msl_order.empty() && - !shader_sampler_bindings.empty()) { - XELOGW( - "MslShader: No reflected sampler bindings for shader={:016X} stage={}, " - "using translator-order sampler remap", - shader().ucode_data_hash(), GetExecutionModelName(execution_model)); - } - if (sampler_binding_indices_for_msl_slots_.size() > - MslSamplerIndex::kMaxPerStage) { - sampler_binding_indices_for_msl_slots_.resize( - MslSamplerIndex::kMaxPerStage); - } - - // Validate resource counts against Metal limits before compilation. - bool has_argument_buffer_resources = false; - { - bool expects_tessellation_constants = false; - for (const auto& uniform_buffer : resources.uniform_buffers) { - uint32_t set = compiler.get_decoration(uniform_buffer.id, - spv::DecorationDescriptorSet); - uint32_t binding = - compiler.get_decoration(uniform_buffer.id, spv::DecorationBinding); - if (set == SpirvSets::kConstants && binding == SpirvCbv::kTessellation) { - expects_tessellation_constants = true; - break; - } - } - size_t texture_count = - resources.sampled_images.size() + resources.separate_images.size(); - size_t sampler_count = resources.separate_samplers.size(); - has_argument_buffer_resources = (texture_count + sampler_count) != 0; - XELOGD( - "MslShader: Resource summary shader={:016X} stage={} textures={} " - "samplers={} uniform_buffers={} tessellation_cbv_expected={}", - shader().ucode_data_hash(), GetExecutionModelName(execution_model), - texture_count, sampler_count, resources.uniform_buffers.size(), - expects_tessellation_constants ? 1 : 0); - if (expects_tessellation_constants) { - XELOGW( - "MslShader: Shader {:016X} stage={} declares set={},binding={} " - "tessellation constants (msl_buffer={})", - shader().ucode_data_hash(), GetExecutionModelName(execution_model), - SpirvSets::kConstants, SpirvCbv::kTessellation, - MslBindings::kTessellationConstants); - } - if (texture_count > MslTextureIndex::kMaxPerStage) { - XELOGE( - "MslShader: Shader uses {} textures, exceeding Metal's {}-per-stage " - "limit in this backend", - texture_count, MslTextureIndex::kMaxPerStage); - return false; - } - if (sampler_count > 16) { - XELOGE( - "MslShader: Shader uses {} samplers, exceeding Metal's 16-per-stage " - "limit — translation should be considered failed", - sampler_count); - return false; - } - } - - // Compile to MSL. SPIRV-Cross throws on translation errors. - const auto spirv_cross_begin = std::chrono::steady_clock::now(); - bool loaded_source_from_cache = false; - if (cvars::metal_shader_disk_cache) { - loaded_source_from_cache = - LoadCachedMslSource(msl_source_cache_key, &msl_source_); - if (loaded_source_from_cache) { - XELOGD("MslShader: Loaded cached MSL source ({:016X})", - shader().ucode_data_hash()); - } - } - if (!loaded_source_from_cache) { - try { - msl_source_ = compiler.compile(); - } catch (const std::exception& e) { - XELOGE("MslShader: SPIRV-Cross compilation failed: {}", e.what()); - return false; - } - if (cvars::metal_shader_disk_cache) { - StoreCachedMslSource(msl_source_cache_key, msl_source_); - } - } - spirv_cross_ms = to_ms(std::chrono::steady_clock::now() - spirv_cross_begin); - if (msl_source_.empty()) { - XELOGE("MslShader: SPIRV-Cross compilation produced empty output"); - return false; - } - if (cvars::metal_spirvcross_strip_nocontract_optnone) { - size_t stripped = StripNoContractionOptnoneWrappers(msl_source_); - if (stripped != 0) { - XELOGD( - "MslShader: Stripped {} NoContraction optnone wrapper signature(s) " - "for shader {:016X}", - stripped, shader().ucode_data_hash()); - } - } - - // Get the entry point name that SPIRV-Cross chose. - std::vector entry_point_candidates; - auto add_entry_point_candidate = [&](const std::string& name) { - if (name.empty()) { - return; - } - if (std::find(entry_point_candidates.begin(), entry_point_candidates.end(), - name) == entry_point_candidates.end()) { - entry_point_candidates.push_back(name); - } - }; - add_entry_point_candidate(spirv_entry_point_name); - try { - add_entry_point_candidate(compiler.get_cleansed_entry_point_name( - spirv_entry_point_name, execution_model)); - } catch (const std::exception& e) { - XELOGW( - "MslShader: SPIRV-Cross entry point lookup failed for '{}' " - "(stage={}): {}", - spirv_entry_point_name, GetExecutionModelName(execution_model), - e.what()); - } - if (spirv_entry_point_name != "main") { - try { - add_entry_point_candidate( - compiler.get_cleansed_entry_point_name("main", execution_model)); - } catch (const std::exception&) { - } - } - add_entry_point_candidate("main0"); - add_entry_point_candidate("main"); - add_entry_point_candidate("vertexMain"); - add_entry_point_candidate("fragmentMain"); - if (entry_point_candidates.empty()) { - entry_point_candidates.push_back("main0"); - } - AddEntryPointCandidatesFromMslSource(msl_source_, shader().type(), - &entry_point_candidates); - entry_point_name_ = entry_point_candidates.front(); - XELOGD("MslShader: Compiled SPIR-V to MSL ({} bytes, entries: {})", - msl_source_.size(), JoinStringList(entry_point_candidates)); - - // Dump MSL source when --dump_shaders is set (matching Vulkan dump pattern). - if (!cvars::dump_shaders.empty()) { - auto base_path = std::filesystem::absolute(cvars::dump_shaders); - auto msl_dir = base_path / "msl_shaders"; - std::filesystem::create_directories(msl_dir); - const char* type_str = - (shader().type() == xenos::ShaderType::kVertex) ? "vs" : "ps"; - auto msl_path = - msl_dir / fmt::format("{}_{:016X}_mod{:016X}.metal", type_str, - shader().ucode_data_hash(), modification()); - FILE* f = xe::filesystem::OpenFile(msl_path, "w"); - if (f) { - fwrite(msl_source_.data(), 1, msl_source_.size(), f); - fclose(f); - XELOGD("MslShader: Dumped MSL to {}", msl_path.string()); - } - } - - // Log SPIRV-Cross resolved SystemConstants member offsets for diagnostic - // comparison with the C++ struct layout. - try { - auto resources = compiler.get_shader_resources(); - for (const auto& ubo : resources.uniform_buffers) { - uint32_t set = - compiler.get_decoration(ubo.id, spv::DecorationDescriptorSet); - uint32_t binding = - compiler.get_decoration(ubo.id, spv::DecorationBinding); - if (set == SpirvSets::kConstants && binding == SpirvCbv::kSystem) { - auto& base_type = compiler.get_type(ubo.base_type_id); - uint32_t member_count = - static_cast(base_type.member_types.size()); - XELOGD( - "MslShader: SystemConstants reflection shader={:016X} " - "members={}", - shader().ucode_data_hash(), member_count); - for (uint32_t m = 0; m < member_count; ++m) { - uint32_t offset = compiler.get_member_decoration( - base_type.self, m, spv::DecorationOffset); - const std::string& name = compiler.get_member_name(base_type.self, m); - XELOGD(" [{}] '{}' offset={}", m, name, offset); - } - break; - } - } - } catch (const std::exception& e) { - XELOGW("MslShader: SystemConstants reflection failed: {}", e.what()); - } - - // Compile MSL source to a Metal library. - // Use a local autorelease pool to ensure NS::String factory objects and any - // NS::Error autoreleased by the Metal driver are cleaned up promptly. - NS::AutoreleasePool* pool = NS::AutoreleasePool::alloc()->init(); - - NS::Error* error = nullptr; - auto* source_str = - NS::String::string(msl_source_.c_str(), NS::UTF8StringEncoding); - auto* compile_options = MTL::CompileOptions::alloc()->init(); - // Use fast math for better performance (matches Xbox 360 behavior better - // than strict IEEE — the 360's ALU doesn't fully conform to IEEE anyway). - compile_options->setFastMathEnabled(true); - // Match the Metal compiler language version to SPIRV-Cross output. - compile_options->setLanguageVersion(language_version); - - const auto metal_library_begin = std::chrono::steady_clock::now(); - metal_library_ = device->newLibrary(source_str, compile_options, &error); - metal_library_ms = - to_ms(std::chrono::steady_clock::now() - metal_library_begin); - compile_options->release(); - - if (error && error->localizedDescription() && - error->localizedDescription()->utf8String()) { - // Metal may return warnings alongside a non-null library. - XELOGW("MslShader: Metal compile diagnostics: {}", - error->localizedDescription()->utf8String()); - } - - if (!metal_library_) { - if (error) { - XELOGE("MslShader: Metal library compilation failed: {}", - error->localizedDescription()->utf8String()); - } else { - XELOGE("MslShader: Metal library compilation failed (unknown error)"); - } - pool->release(); - return false; - } - - // Get the entry point function. - std::vector attempted_function_names; - std::vector available_function_names; - const auto function_resolve_begin = std::chrono::steady_clock::now(); - auto try_bind_function = [&](const std::string& function_name) -> bool { - if (function_name.empty()) { - return false; - } - if (std::find(attempted_function_names.begin(), - attempted_function_names.end(), - function_name) != attempted_function_names.end()) { - return false; - } - attempted_function_names.push_back(function_name); - auto* fn_name = - NS::String::string(function_name.c_str(), NS::UTF8StringEncoding); - MTL::Function* function = metal_library_->newFunction(fn_name); - if (!function) { - return false; - } - metal_function_ = function; - entry_point_name_ = function_name; - return true; - }; - for (const auto& candidate : entry_point_candidates) { - if (try_bind_function(candidate)) { - break; - } - } - if (!metal_function_) { - NS::Array* function_names = metal_library_->functionNames(); - if (function_names) { - for (NS::UInteger i = 0; i < function_names->count(); ++i) { - auto* name = static_cast(function_names->object(i)); - if (!name || !name->utf8String()) { - continue; - } - available_function_names.emplace_back(name->utf8String()); - } - } - if (available_function_names.size() == 1) { - try_bind_function(available_function_names.front()); - } - if (!metal_function_) { - for (const auto& available_name : available_function_names) { - if (available_name.find("main") != std::string::npos && - try_bind_function(available_name)) { - break; - } - } - } - if (!metal_function_) { - const char* stage_hint = - shader().type() == xenos::ShaderType::kVertex ? "vertex" : "fragment"; - for (const auto& available_name : available_function_names) { - if (available_name.find(stage_hint) != std::string::npos && - try_bind_function(available_name)) { - break; - } - } - } - } - if (!metal_function_) { - DumpMslResolveFailureSource(msl_source_, shader().ucode_data_hash(), - modification(), shader().type()); - XELOGE( - "MslShader: Could not resolve function in compiled library " - "(shader={:016X}, stage={}, spirv_entry='{}', candidates=[{}], " - "attempted=[{}], available=[{}], spirv_entries=[{}])", - shader().ucode_data_hash(), GetExecutionModelName(execution_model), - spirv_entry_point_name, JoinStringList(entry_point_candidates), - JoinStringList(attempted_function_names), - JoinStringList(available_function_names), - JoinStringList(spirv_entry_point_descriptions)); - metal_library_->release(); - metal_library_ = nullptr; - pool->release(); - return false; - } - function_resolve_ms = - to_ms(std::chrono::steady_clock::now() - function_resolve_begin); - - if (uses_argument_buffers_ && has_argument_buffer_resources) { - argument_encoder_ = metal_function_->newArgumentEncoder( - MslBufferIndex::kArgumentBufferTexturesSamplers); - if (argument_encoder_) { - argument_encoder_alignment_ = - static_cast(argument_encoder_->alignment()); - argument_encoder_encoded_length_ = - static_cast(argument_encoder_->encodedLength()); - } else { - XELOGE("MslShader: Failed to create argument encoder for shader {:016X}", - shader().ucode_data_hash()); - metal_function_->release(); - metal_function_ = nullptr; - metal_library_->release(); - metal_library_ = nullptr; - pool->release(); - return false; - } - } else { - argument_encoder_alignment_ = 0; - argument_encoder_encoded_length_ = 0; - } - - pool->release(); - - const double total_compile_ms = - to_ms(std::chrono::steady_clock::now() - compile_begin); - if (total_compile_ms >= 100.0 || spirv_size_bytes >= 64 * 1024) { - XELOGW( - "MslShader: Compile timings shader={:016X} stage={} spirv={}B " - "msl={}B total={:.2f}ms (spirv-cross={:.2f}ms, metal-library={:.2f}ms, " - "entry-resolve={:.2f}ms)", - shader().ucode_data_hash(), - shader().type() == xenos::ShaderType::kVertex ? "vertex" : "fragment", - spirv_size_bytes, msl_source_.size(), total_compile_ms, spirv_cross_ms, - metal_library_ms, function_resolve_ms); - } else { - XELOGD( - "MslShader: Compile timings shader={:016X} stage={} total={:.2f}ms " - "(spirv-cross={:.2f}ms, metal-library={:.2f}ms, " - "entry-resolve={:.2f}ms)", - shader().ucode_data_hash(), - shader().type() == xenos::ShaderType::kVertex ? "vertex" : "fragment", - total_compile_ms, spirv_cross_ms, metal_library_ms, - function_resolve_ms); - } - - XELOGD("MslShader: Successfully created Metal function '{}'", - entry_point_name_); - return true; -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/msl_shader.h b/src/xenia/gpu/metal/msl_shader.h deleted file mode 100644 index 0d712c89c..000000000 --- a/src/xenia/gpu/metal/msl_shader.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_GPU_METAL_MSL_SHADER_H_ -#define XENIA_GPU_METAL_MSL_SHADER_H_ - -#include -#include -#include -#include - -#include "xenia/gpu/spirv_shader.h" -#include "xenia/ui/metal/metal_api.h" - -namespace xe { -namespace gpu { -namespace metal { - -// Metal shader translated via SPIR-V -> SPIRV-Cross -> MSL path. -// Inherits from SpirvShader (not DxbcShader), removing the MSC dependency. -class MslShader : public SpirvShader { - public: - MslShader(xenos::ShaderType shader_type, uint64_t ucode_data_hash, - const uint32_t* ucode_dwords, size_t ucode_dword_count, - std::endian ucode_source_endian = std::endian::big); - - // Per-modification translated shader for Metal. - class MslTranslation : public SpirvTranslation { - public: - MslTranslation(MslShader& shader, uint64_t modification) - : SpirvTranslation(shader, modification) {} - ~MslTranslation(); - - // Compile the SPIR-V binary (already in translated_binary()) to MSL - // via SPIRV-Cross and create a Metal library + function. - bool CompileToMsl(MTL::Device* device, bool is_ios = false); - - MTL::Library* metal_library() const { return metal_library_; } - MTL::Function* metal_function() const { return metal_function_; } - const std::string& msl_source() const { return msl_source_; } - const std::string& entry_point_name() const { return entry_point_name_; } - bool is_valid() const { return metal_function_ != nullptr; } - bool uses_argument_buffers() const { return uses_argument_buffers_; } - MTL::ArgumentEncoder* argument_encoder() const { return argument_encoder_; } - uint32_t argument_encoder_alignment() const { - return argument_encoder_alignment_; - } - uint32_t argument_encoder_encoded_length() const { - return argument_encoder_encoded_length_; - } - const std::vector& texture_binding_indices_for_msl_slots() const { - return texture_binding_indices_for_msl_slots_; - } - const std::vector& sampler_binding_indices_for_msl_slots() const { - return sampler_binding_indices_for_msl_slots_; - } - - private: - MTL::Library* metal_library_ = nullptr; - MTL::Function* metal_function_ = nullptr; - bool uses_argument_buffers_ = false; - MTL::ArgumentEncoder* argument_encoder_ = nullptr; - uint32_t argument_encoder_alignment_ = 0; - uint32_t argument_encoder_encoded_length_ = 0; - std::string msl_source_; - std::string entry_point_name_; - std::vector texture_binding_indices_for_msl_slots_; - std::vector sampler_binding_indices_for_msl_slots_; - }; - - protected: - Translation* CreateTranslationInstance(uint64_t modification) override; -}; - -// MSL source cache root used by SPIRV-Cross translations. -void SetMslShaderSourceCacheDirectory(const std::filesystem::path& cache_dir); -void ClearMslShaderSourceCacheDirectory(); - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_MSL_SHADER_H_ diff --git a/src/xenia/gpu/metal/msl_tess_factor_kernels.h b/src/xenia/gpu/metal/msl_tess_factor_kernels.h deleted file mode 100644 index 2598bd41c..000000000 --- a/src/xenia/gpu/metal/msl_tess_factor_kernels.h +++ /dev/null @@ -1,224 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_GPU_METAL_MSL_TESS_FACTOR_KERNELS_H_ -#define XENIA_GPU_METAL_MSL_TESS_FACTOR_KERNELS_H_ - -// Embedded MSL compute kernels for tessellation factor generation. -// These are compiled at runtime by MetalCommandProcessor::InitializeMsl- -// Tessellation() and dispatched before drawPatches() for tessellated draws. -// -// NOTE: This header is #include'd inside namespace xe::gpu::metal in -// metal_command_processor.cc — do NOT add namespace declarations here. - -// -------------------------------------------------------------------- -// Uniform tessellation factor kernels (discrete / continuous modes). -// All patches receive the same factor from xe_tessellation_factor_range.y. -// -------------------------------------------------------------------- - -static const char* kMslTessFactorUniformTriangle = R"msl( -#include -using namespace metal; - -struct TessFactorParams { - float edge_factor; - float inside_factor; - uint patch_count; -}; - -kernel void tess_factor_triangle( - device MTLTriangleTessellationFactorsHalf* factors [[buffer(0)]], - constant TessFactorParams& params [[buffer(1)]], - uint tid [[thread_position_in_grid]]) -{ - if (tid >= params.patch_count) return; - half ef = half(params.edge_factor); - half inf = half(params.inside_factor); - factors[tid].edgeTessellationFactor[0] = ef; - factors[tid].edgeTessellationFactor[1] = ef; - factors[tid].edgeTessellationFactor[2] = ef; - factors[tid].insideTessellationFactor = inf; -} -)msl"; - -static const char* kMslTessFactorUniformQuad = R"msl( -#include -using namespace metal; - -struct TessFactorParams { - float edge_factor; - float inside_factor; - uint patch_count; -}; - -kernel void tess_factor_quad( - device MTLQuadTessellationFactorsHalf* factors [[buffer(0)]], - constant TessFactorParams& params [[buffer(1)]], - uint tid [[thread_position_in_grid]]) -{ - if (tid >= params.patch_count) return; - half ef = half(params.edge_factor); - half inf = half(params.inside_factor); - factors[tid].edgeTessellationFactor[0] = ef; - factors[tid].edgeTessellationFactor[1] = ef; - factors[tid].edgeTessellationFactor[2] = ef; - factors[tid].edgeTessellationFactor[3] = ef; - factors[tid].insideTessellationFactor[0] = inf; - factors[tid].insideTessellationFactor[1] = inf; -} -)msl"; - -// -------------------------------------------------------------------- -// Adaptive tessellation factor kernels. -// Per-patch edge factors are read from shared memory (the guest "index -// buffer" repurposed as a factor buffer for adaptive tessellation). -// -// Each factor is a big-endian float32. The kernel endian-swaps, adds -// 1.0 (Xbox 360 convention), clamps to [factor_min, factor_max], maps -// the Xbox 360 edge order to Metal's edge order, and computes inside -// factors as the minimum of opposing (quad) or all (triangle) edges. -// -// References: -// - adaptive_triangle.hs.glsl / adaptive_quad.hs.glsl (Vulkan path) -// - https://www.slideshare.net/blackdevilvikas/ -// next-generation-graphics-programming-on-xbox-360 -// - http://www.uraldev.ru/files/download/21/ -// Real-Time_Tessellation_on_GPU.pdf (Code Listing 15) -// -------------------------------------------------------------------- - -static const char* kMslTessFactorAdaptiveTriangle = R"msl( -#include -using namespace metal; - -struct AdaptiveTessParams { - uint factor_base_dwords; // Offset into shared_memory (in uint32 words). - uint patch_count; - float factor_min; // Clamping range (1.0 already added on CPU). - float factor_max; - uint vertex_index_endian; // 0 = none, 1 = 8in16, 2 = 8in32, 3 = 16in32. -}; - -// Endian-swap a 32-bit word per the Xenos endian mode. -uint endian_swap(uint v, uint mode) { - switch (mode) { - case 1u: // 8-in-16 - return ((v & 0x00FF00FFu) << 8u) | ((v & 0xFF00FF00u) >> 8u); - case 2u: // 8-in-32 - return ((v & 0x000000FFu) << 24u) | ((v & 0x0000FF00u) << 8u) | - ((v & 0x00FF0000u) >> 8u) | ((v & 0xFF000000u) >> 24u); - case 3u: // 16-in-32 - return ((v & 0x0000FFFFu) << 16u) | ((v & 0xFFFF0000u) >> 16u); - default: // 0 = none - return v; - } -} - -kernel void tess_factor_adaptive_triangle( - device const uint* shared_memory [[buffer(0)]], - device MTLTriangleTessellationFactorsHalf* factors [[buffer(1)]], - constant AdaptiveTessParams& params [[buffer(2)]], - uint tid [[thread_position_in_grid]]) -{ - if (tid >= params.patch_count) return; - - // Read 3 edge factors (big-endian float32) from shared memory. - float edge_factors[3]; - for (uint i = 0; i < 3; i++) { - uint raw = shared_memory[params.factor_base_dwords + tid * 3u + i]; - raw = endian_swap(raw, params.vertex_index_endian); - // Add 1.0 per Xbox 360 convention, then clamp. - float f = as_type(raw) + 1.0f; - edge_factors[i] = clamp(f, params.factor_min, params.factor_max); - } - - // Map Xbox 360 edge order to Metal (same as D3D12/Vulkan): - // Xbox 360: [0] = v0->v1, [1] = v1->v2, [2] = v2->v0 - // Metal: edge[0] = U0 (v1->v2), edge[1] = V0 (v2->v0), - // edge[2] = W0 (v0->v1) - factors[tid].edgeTessellationFactor[0] = half(edge_factors[1]); // v1->v2 - factors[tid].edgeTessellationFactor[1] = half(edge_factors[2]); // v2->v0 - factors[tid].edgeTessellationFactor[2] = half(edge_factors[0]); // v0->v1 - - // Inside factor = minimum of all edge factors. - factors[tid].insideTessellationFactor = half(min(min( - edge_factors[0], edge_factors[1]), edge_factors[2])); -} -)msl"; - -static const char* kMslTessFactorAdaptiveQuad = R"msl( -#include -using namespace metal; - -struct AdaptiveTessParams { - uint factor_base_dwords; - uint patch_count; - float factor_min; - float factor_max; - uint vertex_index_endian; -}; - -uint endian_swap(uint v, uint mode) { - switch (mode) { - case 1u: - return ((v & 0x00FF00FFu) << 8u) | ((v & 0xFF00FF00u) >> 8u); - case 2u: - return ((v & 0x000000FFu) << 24u) | ((v & 0x0000FF00u) << 8u) | - ((v & 0x00FF0000u) >> 8u) | ((v & 0xFF000000u) >> 24u); - case 3u: - return ((v & 0x0000FFFFu) << 16u) | ((v & 0xFFFF0000u) >> 16u); - default: - return v; - } -} - -kernel void tess_factor_adaptive_quad( - device const uint* shared_memory [[buffer(0)]], - device MTLQuadTessellationFactorsHalf* factors [[buffer(1)]], - constant AdaptiveTessParams& params [[buffer(2)]], - uint tid [[thread_position_in_grid]]) -{ - if (tid >= params.patch_count) return; - - // Read 4 edge factors (big-endian float32) from shared memory. - float edge_factors[4]; - for (uint i = 0; i < 4; i++) { - uint raw = shared_memory[params.factor_base_dwords + tid * 4u + i]; - raw = endian_swap(raw, params.vertex_index_endian); - float f = as_type(raw) + 1.0f; - edge_factors[i] = clamp(f, params.factor_min, params.factor_max); - } - - // Map Xbox 360 edge order to Metal (same as D3D12/Vulkan): - // Xbox 360 factors go around the perimeter: - // [0] = U0V0->U1V0, [1] = U1V0->U1V1, - // [2] = U1V1->U0V1, [3] = U0V1->U0V0 - // Metal/Vulkan: edge[i] = input[(i+3) & 3] - // edge[0] = between U0V1 and U0V0 - // edge[1] = between U0V0 and U1V0 - // edge[2] = between U1V0 and U1V1 - // edge[3] = between U1V1 and U0V1 - factors[tid].edgeTessellationFactor[0] = half(edge_factors[3]); - factors[tid].edgeTessellationFactor[1] = half(edge_factors[0]); - factors[tid].edgeTessellationFactor[2] = half(edge_factors[1]); - factors[tid].edgeTessellationFactor[3] = half(edge_factors[2]); - - // Inside factors: minimum of opposing edge pairs. - // Vulkan/Metal: inside[0] along U, inside[1] along V. - float mapped_edge_1 = edge_factors[0]; // Metal edge[1] - float mapped_edge_3 = edge_factors[2]; // Metal edge[3] - float mapped_edge_0 = edge_factors[3]; // Metal edge[0] - float mapped_edge_2 = edge_factors[1]; // Metal edge[2] - factors[tid].insideTessellationFactor[0] = - half(min(mapped_edge_1, mapped_edge_3)); - factors[tid].insideTessellationFactor[1] = - half(min(mapped_edge_0, mapped_edge_2)); -} -)msl"; - -#endif // XENIA_GPU_METAL_MSL_TESS_FACTOR_KERNELS_H_ diff --git a/src/xenia/gpu/primitive_processor.cc b/src/xenia/gpu/primitive_processor.cc index 406e01ee5..b716c32a7 100644 --- a/src/xenia/gpu/primitive_processor.cc +++ b/src/xenia/gpu/primitive_processor.cc @@ -1073,8 +1073,9 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { ProcessedIndexBufferType::kHostBuiltinForDMA) { // Request the index buffer memory. // TODO(Triang3l): Shared memory request cache. - if (!shared_memory_.RequestRange(guest_index_base, - guest_index_buffer_needed_bytes)) { + if (!RequestGuestIndexSharedMemoryRange(guest_index_base, + guest_index_buffer_needed_bytes, + cacheable.index_buffer_type)) { XELOGE( "PrimitiveProcessor: Failed to request index buffer 0x{:08X}, 0x{:X} " "bytes needed, in the shared memory", diff --git a/src/xenia/gpu/primitive_processor.h b/src/xenia/gpu/primitive_processor.h index 7ef107f73..14e69bcd9 100644 --- a/src/xenia/gpu/primitive_processor.h +++ b/src/xenia/gpu/primitive_processor.h @@ -323,6 +323,15 @@ class PrimitiveProcessor { xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd, uint32_t coalignment_original_address, size_t& backend_handle_out) = 0; + protected: + virtual bool RequestGuestIndexSharedMemoryRange( + uint32_t guest_index_base, uint32_t guest_index_buffer_needed_bytes, + ProcessedIndexBufferType index_buffer_type) { + (void)index_buffer_type; + return shared_memory_.RequestRange(guest_index_base, + guest_index_buffer_needed_bytes); + } + private: #if XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE #if XE_ARCH_AMD64 diff --git a/src/xenia/gpu/shaders/resolve_host_color.xesli b/src/xenia/gpu/shaders/resolve_host_color.xesli new file mode 100644 index 000000000..13df18e97 --- /dev/null +++ b/src/xenia/gpu/shaders/resolve_host_color.xesli @@ -0,0 +1,1704 @@ +/** + ****************************************************************************** + * 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_SHADERS_RESOLVE_HOST_COLOR_XESLI_ +#define XENIA_GPU_SHADERS_RESOLVE_HOST_COLOR_XESLI_ + +#include "endian.xesli" +#include "resolve.xesli" + +#if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + #if SHADING_LANGUAGE_GLSL_XE + #define texture_2d_host_color_uint_xe utexture2D + #define texture_2d_ms_host_color_uint_xe utexture2DMS + #elif SHADING_LANGUAGE_HLSL_XE + #define texture_2d_host_color_uint_xe Texture2D + #define texture_2d_ms_host_color_uint_xe Texture2DMS + #elif SHADING_LANGUAGE_MSL_XE + #define texture_2d_host_color_uint_xe texture2d + #define texture_2d_ms_host_color_uint_xe texture2d_ms + #else + #error Host color uint textures not defined for the target language. + #endif +#endif + +const_buffer_begin_xe(xe_resolve_host_color, set=0, binding=1, b1, space0) + uint xe_resolve_host_color_dispatch_offset; + uint xe_resolve_host_color_dump_base; + uint xe_resolve_host_color_dump_pitch_tiles; + uint xe_resolve_host_color_source_base_tiles; + uint xe_resolve_host_color_source_pitch_tiles; + uint xe_resolve_host_color_thread_count_x; + uint xe_resolve_host_color_thread_count_y; + uint xe_resolve_host_color_height_scaled; + uint xe_resolve_host_color_msaa_2x_sample_0; + uint xe_resolve_host_color_msaa_2x_sample_1; + uint xe_resolve_host_color_flags; +const_buffer_end_xe(xe_resolve_host_color) + +#define XE_RESOLVE_HOST_COLOR_CONSTANTS_BINDING \ + const_buffer_binding_xe(xe_resolve_host_color, buffer(2)) + +#if defined(XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP) && \ + XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 8 && \ + XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 4 + byte_buffer_align4_wo_declare_xe(xe_resolve_dest, set=1, binding=0, u0, + space0) +#elif defined(XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP) && \ + XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP <= 16 + byte_buffer_align8_wo_declare_xe(xe_resolve_dest, set=1, binding=0, u0, + space0) +#else + byte_buffer_align16_wo_declare_xe(xe_resolve_dest, set=1, binding=0, u0, + space0) +#endif + +#if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_host_color_uint_xe, \ + xe_resolve_host_color_source, set=2, binding=0, t0, \ + space0, texture(0)) + #else + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_ms_host_color_uint_xe, \ + xe_resolve_host_color_source, set=2, binding=0, t0, \ + space0, texture(0)) + #endif +#else + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_xe, xe_resolve_host_color_source, set=2, \ + binding=0, t0, space0, texture(0)) + #else + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_ms_xe, xe_resolve_host_color_source, \ + set=2, binding=0, t0, space0, texture(0)) + #endif +#endif + +#if SHADING_LANGUAGE_MSL_XE + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_BINDING \ + XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE +#else + XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_DECLARE + #define XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_BINDING +#endif + +#if SHADING_LANGUAGE_MSL_XE + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + #define param_texture_xe(name) texture2d name + #else + #define param_texture_xe(name) texture2d_ms name + #endif + #else + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + #define param_texture_xe(name) texture2d name + #else + #define param_texture_xe(name) texture2d_ms name + #endif + #endif + #define param_next_after_texture_xe , + #define pass_texture_xe(name) name + #define pass_next_after_texture_xe , +#endif +#ifndef param_texture_xe + #define param_texture_xe(name) +#endif +#ifndef param_next_after_texture_xe + #define param_next_after_texture_xe +#endif +#ifndef pass_texture_xe + #define pass_texture_xe(name) +#endif +#ifndef pass_next_after_texture_xe + #define pass_next_after_texture_xe +#endif + +uint XeResolveHostColorTileSizeX(XeResolveInfo resolve_info) { + #ifdef XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP + return (80u >> resolve_info.edram_format_ints_log2) * + resolve_info.resolution_scale.x; + #else + #if XE_RESOLVE_HOST_COLOR_BPP == 64 + return 40u * resolve_info.resolution_scale.x; + #else + return 80u * resolve_info.resolution_scale.x; + #endif + #endif +} + +uint XeResolveHostColorTileSizeY(XeResolveInfo resolve_info) { + return 16u * resolve_info.resolution_scale.y; +} + +uint XeResolveHostColorTilePixelSizeX(XeResolveInfo resolve_info) { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 4 + return XeResolveHostColorTileSizeX(resolve_info) >> 1u; + #else + return XeResolveHostColorTileSizeX(resolve_info); + #endif +} + +uint XeResolveHostColorTilePixelSizeY(XeResolveInfo resolve_info) { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES >= 2 + return XeResolveHostColorTileSizeY(resolve_info) >> 1u; + #else + return XeResolveHostColorTileSizeY(resolve_info); + #endif +} + +uint XeResolveHostColorPixelsPerThread() { + #ifdef XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP + #if XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 8 + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 4 + return 4u; + #else + return 8u; + #endif + #elif XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 128 + return 2u; + #else + return 4u; + #endif + #else + #if XE_RESOLVE_HOST_COLOR_BPP == 64 + return 4u; + #else + return 8u; + #endif + #endif +} + +uint XeResolveHostColorPackUnorm(float value, float scale) { + return uint(saturate_xe(value) * scale + 0.5f); +} + +uint XeResolveHostColorPackSnorm16(float value) { + float clamped = min(max(value, -1.0f), 1.0f); + float bias = clamped >= 0.0f ? 0.5f : -0.5f; + return uint(int(clamped * 32767.0f + bias)) & 0xFFFFu; +} + +uint XeResolveHostColorPreClampedFloat32To7e3(float value) { + uint f32 = float_bits_to_uint_xe(value); + uint biased_f32; + if (f32 < 0x3E800000u) { + uint f32_exp = f32 >> 23u; + uint shift = min(125u - f32_exp, 24u); + uint mantissa = (f32 & 0x7FFFFFu) | 0x800000u; + biased_f32 = mantissa >> shift; + } else { + biased_f32 = f32 + 0xC2000000u; + } + uint round_bit = (biased_f32 >> 16u) & 1u; + uint f10 = biased_f32 + 0x7FFFu + round_bit; + return (f10 >> 16u) & 0x3FFu; +} + +uint XeResolveHostColorFloat32To7e3(float value) { + return XeResolveHostColorPreClampedFloat32To7e3( + min(max(value, 0.0f), 31.875f)); +} + +uint XeResolveHostColorPack32Float(float4_xe color, uint format) { + switch (format) { + case kXenosColorRenderTargetFormat_8_8_8_8: + case kXenosColorRenderTargetFormat_8_8_8_8_GAMMA: + return XePackR8G8B8A8UNorm(color); + case kXenosColorRenderTargetFormat_2_10_10_10: + case kXenosColorRenderTargetFormat_2_10_10_10_AS_10_10_10_10: + return XePackR10G10B10A2UNorm(color); + case kXenosColorRenderTargetFormat_2_10_10_10_FLOAT: + case kXenosColorRenderTargetFormat_2_10_10_10_FLOAT_AS_16_16_16_16: + return XeResolveHostColorFloat32To7e3(color.r) | + (XeResolveHostColorFloat32To7e3(color.g) << 10u) | + (XeResolveHostColorFloat32To7e3(color.b) << 20u) | + (XeResolveHostColorPackUnorm(color.a, 3.0f) << 30u); + case kXenosColorRenderTargetFormat_16_16: + return XeResolveHostColorPackSnorm16(color.r) | + (XeResolveHostColorPackSnorm16(color.g) << 16u); + case kXenosColorRenderTargetFormat_16_16_FLOAT: + return pack_half_2x16_xe(color.rg); + default: + return float_bits_to_uint_xe(color.r); + } +} + +uint XeResolveHostColorPack32Uint(uint4_xe color, uint format) { + switch (format) { + case kXenosColorRenderTargetFormat_16_16: + case kXenosColorRenderTargetFormat_16_16_FLOAT: + return (color.r & 0xFFFFu) | ((color.g & 0xFFFFu) << 16u); + case kXenosColorRenderTargetFormat_32_FLOAT: + return color.r; + default: + return color.r; + } +} + +uint2_xe XeResolveHostColorPack64Float(float4_xe color, uint format) { + switch (format) { + case kXenosColorRenderTargetFormat_16_16_16_16: + return uint2_xe(XeResolveHostColorPackSnorm16(color.r) | + (XeResolveHostColorPackSnorm16(color.g) << 16u), + XeResolveHostColorPackSnorm16(color.b) | + (XeResolveHostColorPackSnorm16(color.a) << 16u)); + case kXenosColorRenderTargetFormat_16_16_16_16_FLOAT: + return uint2_xe(pack_half_2x16_xe(color.rg), + pack_half_2x16_xe(color.ba)); + default: + return float_bits_to_uint_xe(color.rg); + } +} + +uint2_xe XeResolveHostColorPack64Uint(uint4_xe color, uint format) { + switch (format) { + case kXenosColorRenderTargetFormat_16_16_16_16: + case kXenosColorRenderTargetFormat_16_16_16_16_FLOAT: + return uint2_xe((color.r & 0xFFFFu) | ((color.g & 0xFFFFu) << 16u), + (color.b & 0xFFFFu) | ((color.a & 0xFFFFu) << 16u)); + case kXenosColorRenderTargetFormat_32_32_FLOAT: + return color.rg; + default: + return color.rg; + } +} + +uint2_xe XeResolveHostColorSourceSampleToTexel( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + uint tile_size_x = XeResolveHostColorTileSizeX(resolve_info); + uint tile_size_y = XeResolveHostColorTileSizeY(resolve_info); + + uint source_tile_x = source_sample.x / tile_size_x; + uint source_tile_y = source_sample.y / tile_size_y; + uint sample_in_tile_x = source_sample.x - source_tile_x * tile_size_x; + uint sample_in_tile_y = source_sample.y - source_tile_y * tile_size_y; + uint source_local_tile = + source_tile_y * + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_dump_pitch_tiles) + + source_tile_x; + uint nonwrapped_tile = + constant_xe(xe_resolve_host_color, xe_resolve_host_color_dump_base) + + source_local_tile; + uint source_linear_tile = + nonwrapped_tile - + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_source_base_tiles); + uint source_pitch_tiles = + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_source_pitch_tiles); + uint source_rt_tile_y = source_linear_tile / source_pitch_tiles; + uint source_rt_tile_x = + source_linear_tile - source_rt_tile_y * source_pitch_tiles; + return uint2_xe(source_rt_tile_x * tile_size_x + sample_in_tile_x, + source_rt_tile_y * tile_size_y + sample_in_tile_y); +} + +uint2_xe XeResolveHostColorSampleOffsetForIndex(uint sample_index) { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + return uint_x2_xe(0u); + #else + return (uint_x2_xe(sample_index) >> uint2_xe(1u, 0u)) & 1u; + #endif +} + +uint XeResolveHostColorGet2xSampleId( + uint source_sample_y + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + return ((source_sample_y & 1u) != 0u) + ? constant_xe(xe_resolve_host_color, + xe_resolve_host_color_msaa_2x_sample_1) + : constant_xe(xe_resolve_host_color, + xe_resolve_host_color_msaa_2x_sample_0); +} + +// Fetches a source render-target sample given an already-resolved host texel +// (the output of XeResolveHostColorSourceSampleToTexel). Split out of the +// per-sample fetch so the expensive tile->texel mapping can be computed once +// per thread and reused across the thread's lanes (see +// XeResolveHostColorLaneTexel). +#if XE_RESOLVE_HOST_COLOR_SOURCE_UINT +uint4_xe XeResolveHostColorFetchUintTexel( + uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + return texel_fetch_2d_xe(xe_resolve_host_color_source, + int2_xe(source_texel), 0); + #elif XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 2 + uint sample_id = XeResolveHostColorGet2xSampleId( + source_texel.y + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + return texel_fetch_2d_ms_xe( + xe_resolve_host_color_source, + int2_xe(source_texel.x, source_texel.y >> 1u), int(sample_id)); + #else + uint sample_id = (source_texel.x & 1u) | ((source_texel.y & 1u) << 1u); + return texel_fetch_2d_ms_xe( + xe_resolve_host_color_source, + int2_xe(source_texel.x >> 1u, source_texel.y >> 1u), int(sample_id)); + #endif +} + +uint4_xe XeResolveHostColorFetchUint( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + return XeResolveHostColorFetchUintTexel( + XeResolveHostColorSourceSampleToTexel( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); +} +#else +float4_xe XeResolveHostColorFetchFloatTexel( + uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + return texel_fetch_2d_xe(xe_resolve_host_color_source, + int2_xe(source_texel), 0); + #elif XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 2 + uint sample_id = XeResolveHostColorGet2xSampleId( + source_texel.y + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + return texel_fetch_2d_ms_xe( + xe_resolve_host_color_source, + int2_xe(source_texel.x, source_texel.y >> 1u), int(sample_id)); + #else + uint sample_id = (source_texel.x & 1u) | ((source_texel.y & 1u) << 1u); + return texel_fetch_2d_ms_xe( + xe_resolve_host_color_source, + int2_xe(source_texel.x >> 1u, source_texel.y >> 1u), int(sample_id)); + #endif +} + +float4_xe XeResolveHostColorFetchFloat( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + return XeResolveHostColorFetchFloatTexel( + XeResolveHostColorSourceSampleToTexel( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); +} +#endif + +bool XeResolveHostColorGetPixelIndex( + XeResolveInfo resolve_info, uint2_xe thread_index, + out_param_xe(uint2_xe, pixel_index), + out_param_xe(uint, local_tile_out), + out_param_xe(uint2_xe, pixel_in_tile_out) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + if (thread_index.x >= + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_thread_count_x) || + thread_index.y >= + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_thread_count_y)) { + return false; + } + + uint pixels_per_thread = XeResolveHostColorPixelsPerThread(); + uint tile_pixel_size_x = XeResolveHostColorTilePixelSizeX(resolve_info); + uint tile_pixel_size_y = XeResolveHostColorTilePixelSizeY(resolve_info); + uint dispatch_pixel_x = thread_index.x * pixels_per_thread; + uint dispatch_pixel_y = thread_index.y; + uint dispatch_tile_x = dispatch_pixel_x / tile_pixel_size_x; + uint dispatch_tile_y = dispatch_pixel_y / tile_pixel_size_y; + uint pixel_in_tile_x = + dispatch_pixel_x - dispatch_tile_x * tile_pixel_size_x; + uint pixel_in_tile_y = + dispatch_pixel_y - dispatch_tile_y * tile_pixel_size_y; + + uint local_tile = + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_dispatch_offset) + + dispatch_tile_y * + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_dump_pitch_tiles) + + dispatch_tile_x; + // Hand the tile decomposition to XeResolveHostColorComputeSourceBase so the + // source texel base can be derived without re-dividing by the tile size. + local_tile_out = local_tile; + pixel_in_tile_out = uint2_xe(pixel_in_tile_x, pixel_in_tile_y); + uint dump_pitch_tiles = + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_dump_pitch_tiles); + uint dump_tile_y = local_tile / dump_pitch_tiles; + uint dump_tile_x = local_tile - dump_tile_y * dump_pitch_tiles; + uint2_xe dump_pixel = + uint2_xe(dump_tile_x * tile_pixel_size_x + pixel_in_tile_x, + dump_tile_y * tile_pixel_size_y + pixel_in_tile_y); + + if (dump_pixel.x < resolve_info.edram_offset_scaled.x || + dump_pixel.y < resolve_info.edram_offset_scaled.y) { + return false; + } + pixel_index = dump_pixel - resolve_info.edram_offset_scaled; + if (pixel_index.x >= (resolve_info.width_div_8_scaled << 3u) || + pixel_index.y >= + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_height_scaled)) { + return false; + } + return true; +} + +uint2_xe XeResolveHostColorSourceSampleForSampleIndex( + XeResolveInfo resolve_info, uint2_xe pixel_index, uint lane, + uint sample_index) { + uint2_xe source_pixel = + uint2_xe(pixel_index.x + lane, + max(pixel_index.y, + resolve_info.half_pixel_offset_fill_source.y)) + + resolve_info.edram_offset_scaled; + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + return source_pixel; + #elif XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 2 + return (source_pixel << uint2_xe(0u, 1u)) + + XeResolveHostColorSampleOffsetForIndex(sample_index); + #else + return (source_pixel << uint_x2_xe(1u)) + + XeResolveHostColorSampleOffsetForIndex(sample_index); + #endif +} + +// The lanes of a thread cover source samples at consecutive X (same Y), so the +// source texel for lane N is the lane-0 texel offset by N * stride in X. The +// stride is the per-lane step of source_sample.x: 1 for 1x/2x MSAA, 2 for 4x +// (XeResolveHostColorSourceSampleForSampleIndex shifts the pixel left by 1 for +// 4x). Y is lane-invariant, and so is the derived MSAA sample id, so a single +// XeResolveHostColorSourceSampleToTexel (with its integer divisions) is shared +// across the whole thread instead of recomputed per lane. The lane span is +// guaranteed by the dispatch's tile alignment to stay within one source +// render-target tile row, where host texel X is linear in source sample X. +uint XeResolveHostColorLaneTexelStrideX() { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 4 + return 2u; + #else + return 1u; + #endif +} + +uint2_xe XeResolveHostColorLaneTexel(uint2_xe base_texel, uint lane) { + return uint2_xe(base_texel.x + lane * XeResolveHostColorLaneTexelStrideX(), + base_texel.y); +} + +uint2_xe XeResolveHostColorSourceTexelBaseForSampleIndex( + XeResolveInfo resolve_info, uint2_xe pixel_index, uint sample_index + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + return XeResolveHostColorSourceSampleToTexel( + resolve_info, + XeResolveHostColorSourceSampleForSampleIndex(resolve_info, pixel_index, + 0u, sample_index) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); +} + +// Per-thread source addressing for the host texel base, derived from the tile +// decomposition XeResolveHostColorGetPixelIndex already produced instead of +// re-running XeResolveHostColorSourceSampleToTexel (and its tile-size divides) +// for every sample pass. +// +// For pixel_index.y >= half_pixel_offset_fill_source.y the lane-0 source pixel +// equals this thread's dump pixel, so the source sample lands in the same EDRAM +// tile GetPixelIndex found (local_tile), and the within-tile remainder reduces +// to (pixel_in_tile << msaa_shift) + sample_offset. Only the source render- +// target tile wrap (the divide by source_pitch_tiles) remains, and it depends +// solely on local_tile, so it is resolved once per thread in +// XeResolveHostColorComputeSourceBase rather than per sample pass. +// +// When pixel_index.y < half_pixel_offset_fill_source.y the Y clamp in +// XeResolveHostColorSourceSampleForSampleIndex moves the source row out of this +// tile and breaks the identity, so use_fallback routes those threads back +// through the exact XeResolveHostColorSourceTexelBaseForSampleIndex path. For +// non-scaled resolves half_pixel_offset_fill_source.y is zero, so the fallback +// never fires there. +struct XeResolveHostColorSourceBase { + uint2_xe tile_origin; + uint2_xe pixel_in_tile; + bool use_fallback; +}; + +// Per-MSAA shift taking a within-tile pixel offset to its within-tile sample +// offset: X doubles only at 4x, Y doubles at 2x and 4x. Mirrors the pixel-> +// sample shift in XeResolveHostColorSourceSampleForSampleIndex. +uint2_xe XeResolveHostColorSampleInTileShift() { + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 1 + return uint_x2_xe(0u); + #elif XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 2 + return uint2_xe(0u, 1u); + #else + return uint_x2_xe(1u); + #endif +} + +XeResolveHostColorSourceBase XeResolveHostColorComputeSourceBase( + XeResolveInfo resolve_info, uint2_xe pixel_index, uint local_tile, + uint2_xe pixel_in_tile + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + uint source_linear_tile = + constant_xe(xe_resolve_host_color, xe_resolve_host_color_dump_base) + + local_tile - + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_source_base_tiles); + uint source_pitch_tiles = + constant_xe(xe_resolve_host_color, + xe_resolve_host_color_source_pitch_tiles); + uint source_rt_tile_y = source_linear_tile / source_pitch_tiles; + uint source_rt_tile_x = + source_linear_tile - source_rt_tile_y * source_pitch_tiles; + XeResolveHostColorSourceBase source_base; + source_base.tile_origin = + uint2_xe(source_rt_tile_x * XeResolveHostColorTileSizeX(resolve_info), + source_rt_tile_y * XeResolveHostColorTileSizeY(resolve_info)); + source_base.pixel_in_tile = pixel_in_tile; + source_base.use_fallback = + pixel_index.y < resolve_info.half_pixel_offset_fill_source.y; + return source_base; +} + +uint2_xe XeResolveHostColorBaseTexelForSampleIndex( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index, uint sample_index + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + dont_flatten_xe if (source_base.use_fallback) { + return XeResolveHostColorSourceTexelBaseForSampleIndex( + resolve_info, pixel_index, sample_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + } + return source_base.tile_origin + + (source_base.pixel_in_tile << XeResolveHostColorSampleInTileShift()) + + XeResolveHostColorSampleOffsetForIndex(sample_index); +} + +uint2_xe XeResolveHostColorBaseTexelForLane( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color)) { + return XeResolveHostColorBaseTexelForSampleIndex( + resolve_info, source_base, pixel_index, + XeResolveFirstSampleIndex(resolve_info.sample_select) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); +} + +void XeResolveHostColorApplyHalfPixelOffset32( + XeResolveInfo resolve_info, uint2_xe pixel_index, + inout_param_xe(uint4_xe, pixels_0123)) { + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + pixels_0123.z = pixels_0123.w; + } + pixels_0123.y = pixels_0123.z; + } + pixels_0123.x = pixels_0123.y; + } +} + +void XeResolveHostColorApplyHalfPixelOffset64( + XeResolveInfo resolve_info, uint2_xe pixel_index, + inout_param_xe(uint4_xe, pixels_01), + inout_param_xe(uint4_xe, pixels_23)) { + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + pixels_23.xy = pixels_23.zw; + } + pixels_01.zw = pixels_23.xy; + } + pixels_01.xy = pixels_01.zw; + } +} + +#ifdef XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP + +uint XeResolveHostColorFetchPacked32( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + return XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUint( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + return XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloat( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif +} + +uint2_xe XeResolveHostColorFetchPacked64( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + return XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUint( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + return XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloat( + resolve_info, source_sample + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif +} + +uint XeResolveHostColorFetchPacked32ByTexel( + XeResolveInfo resolve_info, uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + return XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + return XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif +} + +uint2_xe XeResolveHostColorFetchPacked64ByTexel( + XeResolveInfo resolve_info, uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + return XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUintTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + return XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloatTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif +} + +void XeResolveHostColorFullLoad2RGBAUnswappedSamples( + XeResolveInfo resolve_info, uint2_xe pixel_index, uint sample_index, + out_param_xe(float4_xe, pixel_0), out_param_xe(float4_xe, pixel_1) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint2_xe base_texel = XeResolveHostColorSourceTexelBaseForSampleIndex( + resolve_info, pixel_index, sample_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + dont_flatten_xe if (resolve_info.edram_format_ints_log2 != 0u) { + uint2_xe packed_0 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_1 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + XeResolveUnpack64bpp2Samples( + uint4_xe(packed_0.x, packed_0.y, packed_1.x, packed_1.y), + resolve_info.edram_format, pixel_0, pixel_1); + } else { + uint2_xe packed; + packed.x = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed.y = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + XeResolveUnpack32bpp2Samples(packed, resolve_info.edram_format, pixel_0, + pixel_1); + } +} + +void XeResolveHostColorFullLoad4RGBAUnswappedSamples( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index, uint sample_index, + out_param_xe(float4_xe, pixel_0), out_param_xe(float4_xe, pixel_1), + out_param_xe(float4_xe, pixel_2), out_param_xe(float4_xe, pixel_3) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint2_xe base_texel = XeResolveHostColorBaseTexelForSampleIndex( + resolve_info, source_base, pixel_index, sample_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + dont_flatten_xe if (resolve_info.edram_format_ints_log2 != 0u) { + uint2_xe packed_0 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_1 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_2 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_3 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + XeResolveUnpack64bpp4Samples( + uint4_xe(packed_0.x, packed_0.y, packed_1.x, packed_1.y), + uint4_xe(packed_2.x, packed_2.y, packed_3.x, packed_3.y), + resolve_info.edram_format, pixel_0, pixel_1, pixel_2, pixel_3); + } else { + uint4_xe packed; + packed.x = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed.y = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed.z = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed.w = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + XeResolveUnpack32bpp4Samples(packed, resolve_info.edram_format, pixel_0, + pixel_1, pixel_2, pixel_3); + } +} + +void XeResolveHostColorFullLoad8RedSamples( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index, uint sample_index, + out_param_xe(float4_xe, pixels_0123), + out_param_xe(float4_xe, pixels_4567) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint2_xe base_texel = XeResolveHostColorBaseTexelForSampleIndex( + resolve_info, source_base, pixel_index, sample_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + dont_flatten_xe if (resolve_info.edram_format_ints_log2 != 0u) { + uint4_xe packed_0123, packed_4567; + uint2_xe packed_0 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_1 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_2 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_3 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_4 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 4u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_5 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 5u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_6 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 6u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed_7 = XeResolveHostColorFetchPacked64ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 7u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + dont_flatten_xe if (resolve_info.dest_swap) { + packed_0123 = uint4_xe(packed_0.y, packed_1.y, packed_2.y, packed_3.y); + packed_4567 = uint4_xe(packed_4.y, packed_5.y, packed_6.y, packed_7.y); + } else { + packed_0123 = uint4_xe(packed_0.x, packed_1.x, packed_2.x, packed_3.x); + packed_4567 = uint4_xe(packed_4.x, packed_5.x, packed_6.x, packed_7.x); + } + XeResolveUnpack64bpp8RedUnswappedSamples( + packed_0123, packed_4567, resolve_info.edram_format, pixels_0123, + pixels_4567); + } else { + uint4_xe packed_0123, packed_4567; + packed_0123.x = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_0123.y = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_0123.z = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_0123.w = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_4567.x = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 4u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_4567.y = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 5u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_4567.z = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 6u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + packed_4567.w = XeResolveHostColorFetchPacked32ByTexel( + resolve_info, XeResolveHostColorLaneTexel(base_texel, 7u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + XeResolveUnpack32bpp8RedSamples( + packed_0123, packed_4567, resolve_info.edram_format, + resolve_info.dest_swap, pixels_0123, pixels_4567); + } +} + +void XeResolveHostColorApplyExpBiasAndSwap2( + XeResolveInfo resolve_info, float exp_bias, + inout_param_xe(float4_xe, pixel_0), + inout_param_xe(float4_xe, pixel_1)) { + dont_flatten_xe if ((resolve_info.edram_format == + kXenosColorRenderTargetFormat_2_10_10_10_FLOAT || + resolve_info.edram_format == + kXenosColorRenderTargetFormat_2_10_10_10_FLOAT_AS_16_16_16_16) && + resolve_info.dest_format != kXenosFormat_16_16_16_16_FLOAT && + resolve_info.dest_format != kXenosFormat_32_32_32_32_FLOAT) { + pixel_0.xyz *= exp_bias; + pixel_1.xyz *= exp_bias; + } else { + pixel_0 *= exp_bias; + pixel_1 *= exp_bias; + } + dont_flatten_xe if (resolve_info.dest_swap) { + pixel_0 = pixel_0.bgra; + pixel_1 = pixel_1.bgra; + } +} + +void XeResolveHostColorApplyExpBiasAndSwap4( + XeResolveInfo resolve_info, float exp_bias, + inout_param_xe(float4_xe, pixel_0), + inout_param_xe(float4_xe, pixel_1), + inout_param_xe(float4_xe, pixel_2), + inout_param_xe(float4_xe, pixel_3)) { + dont_flatten_xe if ((resolve_info.edram_format == + kXenosColorRenderTargetFormat_2_10_10_10_FLOAT || + resolve_info.edram_format == + kXenosColorRenderTargetFormat_2_10_10_10_FLOAT_AS_16_16_16_16) && + resolve_info.dest_format != kXenosFormat_16_16_16_16_FLOAT && + resolve_info.dest_format != kXenosFormat_32_32_32_32_FLOAT) { + pixel_0.xyz *= exp_bias; + pixel_1.xyz *= exp_bias; + pixel_2.xyz *= exp_bias; + pixel_3.xyz *= exp_bias; + } else { + pixel_0 *= exp_bias; + pixel_1 *= exp_bias; + pixel_2 *= exp_bias; + pixel_3 *= exp_bias; + } + dont_flatten_xe if (resolve_info.dest_swap) { + pixel_0 = pixel_0.bgra; + pixel_1 = pixel_1.bgra; + pixel_2 = pixel_2.bgra; + pixel_3 = pixel_3.bgra; + } +} + +void XeResolveHostColorFullLoad2RGBAColors( + XeResolveInfo resolve_info, uint2_xe pixel_index, + out_param_xe(float4_xe, pixel_0), out_param_xe(float4_xe, pixel_1) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint first_sample = XeResolveFirstSampleIndex(resolve_info.sample_select); + XeResolveHostColorFullLoad2RGBAUnswappedSamples( + resolve_info, pixel_index, first_sample, pixel_0, pixel_1 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + float exp_bias = resolve_info.dest_exp_bias_factor; + dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { + exp_bias *= 0.5f; + float4_xe sample_pixel_0, sample_pixel_1; + XeResolveHostColorFullLoad2RGBAUnswappedSamples( + resolve_info, pixel_index, first_sample + 1u, sample_pixel_0, + sample_pixel_1 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + dont_flatten_xe if (resolve_info.sample_select >= + kXenosCopySampleSelect_0123) { + exp_bias *= 0.5f; + XeResolveHostColorFullLoad2RGBAUnswappedSamples( + resolve_info, pixel_index, first_sample + 2u, sample_pixel_0, + sample_pixel_1 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + XeResolveHostColorFullLoad2RGBAUnswappedSamples( + resolve_info, pixel_index, first_sample + 3u, sample_pixel_0, + sample_pixel_1 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + } + } + XeResolveHostColorApplyExpBiasAndSwap2(resolve_info, exp_bias, pixel_0, + pixel_1); +} + +void XeResolveHostColorFullLoad4RGBAColors( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index, + out_param_xe(float4_xe, pixel_0), out_param_xe(float4_xe, pixel_1), + out_param_xe(float4_xe, pixel_2), out_param_xe(float4_xe, pixel_3) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint first_sample = XeResolveFirstSampleIndex(resolve_info.sample_select); + XeResolveHostColorFullLoad4RGBAUnswappedSamples( + resolve_info, source_base, pixel_index, first_sample, pixel_0, pixel_1, + pixel_2, pixel_3 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + float exp_bias = resolve_info.dest_exp_bias_factor; + dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { + exp_bias *= 0.5f; + float4_xe sample_pixel_0, sample_pixel_1, sample_pixel_2, sample_pixel_3; + XeResolveHostColorFullLoad4RGBAUnswappedSamples( + resolve_info, source_base, pixel_index, first_sample + 1u, + sample_pixel_0, sample_pixel_1, sample_pixel_2, + sample_pixel_3 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + pixel_2 += sample_pixel_2; + pixel_3 += sample_pixel_3; + dont_flatten_xe if (resolve_info.sample_select >= + kXenosCopySampleSelect_0123) { + exp_bias *= 0.5f; + XeResolveHostColorFullLoad4RGBAUnswappedSamples( + resolve_info, source_base, pixel_index, first_sample + 2u, + sample_pixel_0, sample_pixel_1, sample_pixel_2, + sample_pixel_3 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + pixel_2 += sample_pixel_2; + pixel_3 += sample_pixel_3; + XeResolveHostColorFullLoad4RGBAUnswappedSamples( + resolve_info, source_base, pixel_index, first_sample + 3u, + sample_pixel_0, sample_pixel_1, sample_pixel_2, + sample_pixel_3 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixel_0 += sample_pixel_0; + pixel_1 += sample_pixel_1; + pixel_2 += sample_pixel_2; + pixel_3 += sample_pixel_3; + } + } + XeResolveHostColorApplyExpBiasAndSwap4(resolve_info, exp_bias, pixel_0, + pixel_1, pixel_2, pixel_3); +} + +void XeResolveHostColorFullLoad8RedColors( + XeResolveInfo resolve_info, XeResolveHostColorSourceBase source_base, + uint2_xe pixel_index, + out_param_xe(float4_xe, pixels_0123), + out_param_xe(float4_xe, pixels_4567) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + uint first_sample = XeResolveFirstSampleIndex(resolve_info.sample_select); + XeResolveHostColorFullLoad8RedSamples( + resolve_info, source_base, pixel_index, first_sample, pixels_0123, + pixels_4567 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + float exp_bias = resolve_info.dest_exp_bias_factor; + dont_flatten_xe if (resolve_info.sample_select >= kXenosCopySampleSelect_01) { + exp_bias *= 0.5f; + float4_xe sample_pixels_0123, sample_pixels_4567; + XeResolveHostColorFullLoad8RedSamples( + resolve_info, source_base, pixel_index, first_sample + 1u, + sample_pixels_0123, + sample_pixels_4567 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixels_0123 += sample_pixels_0123; + pixels_4567 += sample_pixels_4567; + dont_flatten_xe if (resolve_info.sample_select >= + kXenosCopySampleSelect_0123) { + exp_bias *= 0.5f; + XeResolveHostColorFullLoad8RedSamples( + resolve_info, source_base, pixel_index, first_sample + 2u, + sample_pixels_0123, + sample_pixels_4567 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixels_0123 += sample_pixels_0123; + pixels_4567 += sample_pixels_4567; + XeResolveHostColorFullLoad8RedSamples( + resolve_info, source_base, pixel_index, first_sample + 3u, + sample_pixels_0123, + sample_pixels_4567 pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + pixels_0123 += sample_pixels_0123; + pixels_4567 += sample_pixels_4567; + } + } + pixels_0123 *= exp_bias; + pixels_4567 *= exp_bias; +} + +void XeResolveHostColorFullMain( + uint2_xe thread_index + param_next_after_byte_buffer_xe + param_byte_buffer_wo_xe(xe_resolve_dest) + param_next_after_push_consts_xe + param_push_consts_xe + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + XeResolveInfo resolve_info = XeResolveGetInfo(pass_push_consts_xe); + uint2_xe pixel_index; + uint local_tile; + uint2_xe pixel_in_tile; + if (!XeResolveHostColorGetPixelIndex( + resolve_info, thread_index, pixel_index, local_tile, pixel_in_tile + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color))) { + return; + } + // The 128bpp path (the #else below) clamps pixel_index.x into the source read + // position itself, so it keeps the exact per-sample path; the descriptor is + // for the other formats, which apply the X half-pixel fill after fetching. + XeResolveHostColorSourceBase source_base = XeResolveHostColorComputeSourceBase( + resolve_info, pixel_index, local_tile, pixel_in_tile + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + + #if XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 8 + #if XE_RESOLVE_HOST_COLOR_MSAA_SAMPLES == 4 + float4_xe pixel_0, pixel_1, pixel_2, pixel_3; + XeResolveHostColorFullLoad4RGBAColors( + resolve_info, source_base, pixel_index, pixel_0, pixel_1, pixel_2, + pixel_3 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + float4_xe pixels_0123 = + float4_xe(pixel_0.r, pixel_1.r, pixel_2.r, pixel_3.r); + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + pixels_0123.z = pixels_0123.w; + } + pixels_0123.y = pixels_0123.z; + } + pixels_0123.x = pixels_0123.y; + } + byte_buffer_align4_store4_xe( + xe_resolve_dest, + XeResolveDestPixelAddress(resolve_info, pixel_index, 0u), + XePackR8G8B8A8UNorm(pixels_0123)); + #else + float4_xe pixels_0123, pixels_4567; + XeResolveHostColorFullLoad8RedColors( + resolve_info, source_base, pixel_index, pixels_0123, pixels_4567 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + pixels_0123.z = pixels_0123.w; + } + pixels_0123.y = pixels_0123.z; + } + pixels_0123.x = pixels_0123.y; + } + byte_buffer_align8_store8_xe( + xe_resolve_dest, + XeResolveDestPixelAddress(resolve_info, pixel_index, 0u), + uint2_xe(XePackR8G8B8A8UNorm(pixels_0123), + XePackR8G8B8A8UNorm(pixels_4567))); + #endif + #elif XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 16 + float4_xe pixel_0, pixel_1, pixel_2, pixel_3; + XeResolveHostColorFullLoad4RGBAColors( + resolve_info, source_base, pixel_index, pixel_0, pixel_1, pixel_2, + pixel_3 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint2_xe packed = XePack16bpp4PixelsInUInt2( + pixel_0, pixel_1, pixel_2, pixel_3, resolve_info.dest_format); + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + packed.y = (packed.y >> 16u) | (packed.y & 0xFFFF0000u); + } + packed.x = (packed.x & 0xFFFFu) | (packed.y << 16u); + } + packed.x = (packed.x >> 16u) | (packed.x & 0xFFFF0000u); + } + byte_buffer_align8_store8_xe( + xe_resolve_dest, + XeResolveDestPixelAddress(resolve_info, pixel_index, 1u), + XeEndianSwap16(packed, resolve_info.dest_endian_128)); + #elif XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 32 + float4_xe pixel_0, pixel_1, pixel_2, pixel_3; + XeResolveHostColorFullLoad4RGBAColors( + resolve_info, source_base, pixel_index, pixel_0, pixel_1, pixel_2, + pixel_3 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint4_xe packed = XePack32bpp4Pixels(pixel_0, pixel_1, pixel_2, + pixel_3, resolve_info.dest_format); + XeResolveHostColorApplyHalfPixelOffset32(resolve_info, pixel_index, packed); + byte_buffer_align16_store16_xe( + xe_resolve_dest, + XeResolveDestPixelAddress(resolve_info, pixel_index, 2u), + XeEndianSwap32(packed, resolve_info.dest_endian_128)); + #elif XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP == 64 + float4_xe pixel_0, pixel_1, pixel_2, pixel_3; + XeResolveHostColorFullLoad4RGBAColors( + resolve_info, source_base, pixel_index, pixel_0, pixel_1, pixel_2, + pixel_3 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + uint4_xe packed_01, packed_23; + XePack64bpp4Pixels(pixel_0, pixel_1, pixel_2, pixel_3, + resolve_info.dest_format, packed_01, packed_23); + XeResolveHostColorApplyHalfPixelOffset64(resolve_info, pixel_index, + packed_01, packed_23); + uint dest_address = XeResolveDestPixelAddress(resolve_info, pixel_index, + 3u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap64(packed_01, resolve_info.dest_endian_128)); + dest_address += XeResolveLocalXAddressXor(2u, 3u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap64(packed_23, resolve_info.dest_endian_128)); + #else + uint2_xe source_pixel_index = + uint2_xe(max(pixel_index.x, + resolve_info.half_pixel_offset_fill_source.x), + pixel_index.y); + float4_xe pixel_0, pixel_1; + XeResolveHostColorFullLoad2RGBAColors( + resolve_info, source_pixel_index, pixel_0, pixel_1 + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); + dont_flatten_xe + if (pixel_index.x < resolve_info.half_pixel_offset_fill_source.x) { + pixel_1 = pixel_0; + } + uint dest_address = XeResolveDestPixelAddress(resolve_info, pixel_index, 4u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap128(float_bits_to_uint_xe(pixel_0), + resolve_info.dest_endian_128)); + dest_address += XeResolveLocalXAddressXor(1u, 4u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap128(float_bits_to_uint_xe(pixel_1), + resolve_info.dest_endian_128)); + #endif +} + +#else + +void XeResolveHostColorMain( + uint2_xe thread_index + param_next_after_byte_buffer_xe + param_byte_buffer_wo_xe(xe_resolve_dest) + param_next_after_push_consts_xe + param_push_consts_xe + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_color) + param_next_after_texture_xe + param_texture_xe(xe_resolve_host_color_source)) { + XeResolveInfo resolve_info = XeResolveGetInfo(pass_push_consts_xe); + uint2_xe pixel_index; + uint local_tile; + uint2_xe pixel_in_tile; + if (!XeResolveHostColorGetPixelIndex( + resolve_info, thread_index, pixel_index, local_tile, pixel_in_tile + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color))) { + return; + } + + XeResolveHostColorSourceBase source_base = XeResolveHostColorComputeSourceBase( + resolve_info, pixel_index, local_tile, pixel_in_tile + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + uint2_xe base_texel = XeResolveHostColorBaseTexelForLane( + resolve_info, source_base, pixel_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color)); + + #if XE_RESOLVE_HOST_COLOR_BPP == 64 + uint2_xe packed_0, packed_1, packed_2, packed_3; + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + packed_0 = XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_1 = XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_2 = XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_3 = XeResolveHostColorPack64Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + packed_0 = XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_1 = XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_2 = XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + packed_3 = XeResolveHostColorPack64Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif + uint4_xe pixels_01 = + uint4_xe(packed_0.x, packed_0.y, packed_1.x, packed_1.y); + uint4_xe pixels_23 = + uint4_xe(packed_2.x, packed_2.y, packed_3.x, packed_3.y); + XeResolveHostColorApplyHalfPixelOffset64(resolve_info, pixel_index, + pixels_01, pixels_23); + XeResolveSwap4PixelsRedBlue64bpp(resolve_info, pixels_01, pixels_23); + uint dest_address = XeResolveDestPixelAddress(resolve_info, pixel_index, + 3u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap64(pixels_01, resolve_info.dest_endian_128)); + dest_address += XeResolveLocalXAddressXor(2u, 3u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap64(pixels_23, resolve_info.dest_endian_128)); + #else + uint4_xe pixels_0123; + uint4_xe pixels_4567; + #if XE_RESOLVE_HOST_COLOR_SOURCE_UINT + pixels_0123.x = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.y = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.z = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.w = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.x = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 4u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.y = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 5u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.z = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 6u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.w = XeResolveHostColorPack32Uint( + XeResolveHostColorFetchUintTexel( + XeResolveHostColorLaneTexel(base_texel, + 7u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #else + pixels_0123.x = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.y = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.z = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_0123.w = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.x = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 4u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.y = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 5u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.z = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 6u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + pixels_4567.w = XeResolveHostColorPack32Float( + XeResolveHostColorFetchFloatTexel( + XeResolveHostColorLaneTexel(base_texel, + 7u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)), + resolve_info.edram_format); + #endif + XeResolveHostColorApplyHalfPixelOffset32(resolve_info, pixel_index, + pixels_0123); + XeResolveSwap8PixelsRedBlue32bpp(resolve_info, pixels_0123, pixels_4567); + uint dest_address = XeResolveDestPixelAddress(resolve_info, pixel_index, + 2u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap32(pixels_0123, resolve_info.dest_endian_128)); + dest_address += XeResolveLocalXAddressXor(4u, 2u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap32(pixels_4567, resolve_info.dest_endian_128)); + #endif +} + +#endif // XE_RESOLVE_HOST_COLOR_FULL_DEST_BPP + +#endif // XENIA_GPU_SHADERS_RESOLVE_HOST_COLOR_XESLI_ diff --git a/src/xenia/gpu/shaders/resolve_host_color_entry.xesli b/src/xenia/gpu/shaders/resolve_host_color_entry.xesli new file mode 100644 index 000000000..d56f81088 --- /dev/null +++ b/src/xenia/gpu/shaders/resolve_host_color_entry.xesli @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * 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 "resolve_host_color.xesli" + +#define LOCAL_SIZE_X_XE 8 +#define LOCAL_SIZE_Y_XE 8 +#define LOCAL_SIZE_Z_XE 1 +entry_bindings_begin_compute_xe + XE_RESOLVE_PUSH_CONST_BINDING + entry_binding_next_xe + byte_buffer_wo_binding_xe(xe_resolve_dest, buffer(1)) + entry_binding_next_xe + XE_RESOLVE_HOST_COLOR_CONSTANTS_BINDING +#if SHADING_LANGUAGE_MSL_XE + entry_binding_next_xe + XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_BINDING +#endif +entry_bindings_end_inputs_begin_compute_xe + entry_in_global_thread_id_xe +entry_inputs_end_code_begin_compute_xe +{ + XeResolveHostColorMain( + in_global_thread_id_xe.xy + pass_next_after_byte_buffer_xe + pass_byte_buffer_xe(xe_resolve_dest) + pass_next_after_push_consts_xe + pass_push_consts_xe + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); +} +entry_code_end_compute_xe diff --git a/src/xenia/gpu/shaders/resolve_host_color_full_entry.xesli b/src/xenia/gpu/shaders/resolve_host_color_full_entry.xesli new file mode 100644 index 000000000..187c75b74 --- /dev/null +++ b/src/xenia/gpu/shaders/resolve_host_color_full_entry.xesli @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * 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 "resolve_host_color.xesli" + +#define LOCAL_SIZE_X_XE 8 +#define LOCAL_SIZE_Y_XE 8 +#define LOCAL_SIZE_Z_XE 1 +entry_bindings_begin_compute_xe + XE_RESOLVE_PUSH_CONST_BINDING + entry_binding_next_xe + byte_buffer_wo_binding_xe(xe_resolve_dest, buffer(1)) + entry_binding_next_xe + XE_RESOLVE_HOST_COLOR_CONSTANTS_BINDING +#if SHADING_LANGUAGE_MSL_XE + entry_binding_next_xe + XE_RESOLVE_HOST_COLOR_SOURCE_TEXTURE_BINDING +#endif +entry_bindings_end_inputs_begin_compute_xe + entry_in_global_thread_id_xe +entry_inputs_end_code_begin_compute_xe +{ + XeResolveHostColorFullMain( + in_global_thread_id_xe.xy + pass_next_after_byte_buffer_xe + pass_byte_buffer_xe(xe_resolve_dest) + pass_next_after_push_consts_xe + pass_push_consts_xe + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_color) + pass_next_after_texture_xe + pass_texture_xe(xe_resolve_host_color_source)); +} +entry_code_end_compute_xe diff --git a/src/xenia/gpu/shaders/resolve_host_depth.xesli b/src/xenia/gpu/shaders/resolve_host_depth.xesli new file mode 100644 index 000000000..c0e47df0e --- /dev/null +++ b/src/xenia/gpu/shaders/resolve_host_depth.xesli @@ -0,0 +1,547 @@ +/** + ****************************************************************************** + * 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_SHADERS_RESOLVE_HOST_DEPTH_XESLI_ +#define XENIA_GPU_SHADERS_RESOLVE_HOST_DEPTH_XESLI_ + +#include "endian.xesli" +#include "resolve.xesli" + +#if SHADING_LANGUAGE_GLSL_XE + #define texture_2d_host_depth_stencil_uint_xe utexture2D + #define texture_2d_ms_host_depth_stencil_uint_xe utexture2DMS +#elif SHADING_LANGUAGE_HLSL_XE + #define texture_2d_host_depth_stencil_uint_xe Texture2D + #define texture_2d_ms_host_depth_stencil_uint_xe Texture2DMS +#elif SHADING_LANGUAGE_MSL_XE + #define texture_2d_host_depth_stencil_uint_xe texture2d + #define texture_2d_ms_host_depth_stencil_uint_xe texture2d_ms +#else + #error Host depth stencil textures not defined for the target language. +#endif + +#define XE_RESOLVE_HOST_DEPTH_FLAG_HAS_STENCIL (1u << 0u) +#define XE_RESOLVE_HOST_DEPTH_FLAG_ROUND_DEPTH (1u << 1u) + +const_buffer_begin_xe(xe_resolve_host_depth, set=0, binding=1, b1, space0) + uint xe_resolve_host_depth_dispatch_offset; + uint xe_resolve_host_depth_dump_base; + uint xe_resolve_host_depth_dump_pitch_tiles; + uint xe_resolve_host_depth_source_base_tiles; + uint xe_resolve_host_depth_source_pitch_tiles; + uint xe_resolve_host_depth_thread_count_x; + uint xe_resolve_host_depth_thread_count_y; + uint xe_resolve_host_depth_height_scaled; + uint xe_resolve_host_depth_msaa_2x_sample_0; + uint xe_resolve_host_depth_msaa_2x_sample_1; + uint xe_resolve_host_depth_flags; +const_buffer_end_xe(xe_resolve_host_depth) + +#define XE_RESOLVE_HOST_DEPTH_CONSTANTS_BINDING \ + const_buffer_binding_xe(xe_resolve_host_depth, buffer(2)) + +byte_buffer_align16_wo_declare_xe(xe_resolve_dest, set=1, binding=0, u0, + space0) + +#if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + #define XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_xe, xe_resolve_host_depth_source, set=2, \ + binding=0, t0, space0, texture(0)) + #define XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_DECLARE \ + texture_xe(texture_2d_host_depth_stencil_uint_xe, \ + xe_resolve_host_depth_stencil, set=2, binding=1, t1, \ + space0, texture(1)) +#else + #define XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_DECLARE \ + texture_xe(texture_2d_ms_xe, xe_resolve_host_depth_source, set=2, \ + binding=0, t0, space0, texture(0)) + #define XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_DECLARE \ + texture_xe(texture_2d_ms_host_depth_stencil_uint_xe, \ + xe_resolve_host_depth_stencil, set=2, binding=1, t1, \ + space0, texture(1)) +#endif + +#if SHADING_LANGUAGE_MSL_XE + #define XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_BINDING \ + XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_DECLARE + #define XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_BINDING \ + XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_DECLARE +#else + XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_DECLARE + XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_DECLARE + #define XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_BINDING + #define XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_BINDING +#endif + +#if SHADING_LANGUAGE_MSL_XE + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + #define param_depth_texture_xe(name) texture2d name + #define param_stencil_texture_xe(name) texture2d name + #else + #define param_depth_texture_xe(name) texture2d_ms name + #define param_stencil_texture_xe(name) texture2d_ms name + #endif + #define param_next_after_depth_texture_xe , + #define pass_depth_texture_xe(name) name + #define pass_next_after_depth_texture_xe , + #define param_next_after_stencil_texture_xe , + #define pass_stencil_texture_xe(name) name + #define pass_next_after_stencil_texture_xe , +#endif +#ifndef param_depth_texture_xe + #define param_depth_texture_xe(name) +#endif +#ifndef param_next_after_depth_texture_xe + #define param_next_after_depth_texture_xe +#endif +#ifndef pass_depth_texture_xe + #define pass_depth_texture_xe(name) +#endif +#ifndef pass_next_after_depth_texture_xe + #define pass_next_after_depth_texture_xe +#endif +#ifndef param_stencil_texture_xe + #define param_stencil_texture_xe(name) +#endif +#ifndef param_next_after_stencil_texture_xe + #define param_next_after_stencil_texture_xe +#endif +#ifndef pass_stencil_texture_xe + #define pass_stencil_texture_xe(name) +#endif +#ifndef pass_next_after_stencil_texture_xe + #define pass_next_after_stencil_texture_xe +#endif + +uint XeResolveHostDepthTileSizeX(XeResolveInfo resolve_info) { + return 80u * resolve_info.resolution_scale.x; +} + +uint XeResolveHostDepthTileSizeY(XeResolveInfo resolve_info) { + return 16u * resolve_info.resolution_scale.y; +} + +uint XeResolveHostDepthTilePixelSizeX(XeResolveInfo resolve_info) { + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 4 + return XeResolveHostDepthTileSizeX(resolve_info) >> 1u; + #else + return XeResolveHostDepthTileSizeX(resolve_info); + #endif +} + +uint XeResolveHostDepthTilePixelSizeY(XeResolveInfo resolve_info) { + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES >= 2 + return XeResolveHostDepthTileSizeY(resolve_info) >> 1u; + #else + return XeResolveHostDepthTileSizeY(resolve_info); + #endif +} + +uint XeResolveHostDepthPixelsPerThread() { return 8u; } + +uint XeResolveHostDepthRoundToNearestEven(float value) { + float floor_value = floor(value); + float frac = value - floor_value; + uint result = uint(floor_value); + if (frac > 0.5f || (frac == 0.5f && ((result & 1u) != 0u))) { + result += 1u; + } + return result; +} + +uint2_xe XeResolveHostDepthSourceSampleToTexel( + XeResolveInfo resolve_info, uint2_xe source_sample + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth)) { + uint tile_size_x = XeResolveHostDepthTileSizeX(resolve_info); + uint tile_size_y = XeResolveHostDepthTileSizeY(resolve_info); + + uint source_tile_x = source_sample.x / tile_size_x; + uint source_tile_y = source_sample.y / tile_size_y; + uint sample_in_tile_x = source_sample.x - source_tile_x * tile_size_x; + uint sample_in_tile_y = source_sample.y - source_tile_y * tile_size_y; + uint source_local_tile = + source_tile_y * + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_dump_pitch_tiles) + + source_tile_x; + uint nonwrapped_tile = + constant_xe(xe_resolve_host_depth, xe_resolve_host_depth_dump_base) + + source_local_tile; + uint source_linear_tile = + nonwrapped_tile - + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_source_base_tiles); + uint source_pitch_tiles = + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_source_pitch_tiles); + uint source_rt_tile_y = source_linear_tile / source_pitch_tiles; + uint source_rt_tile_x = + source_linear_tile - source_rt_tile_y * source_pitch_tiles; + return uint2_xe(source_rt_tile_x * tile_size_x + sample_in_tile_x, + source_rt_tile_y * tile_size_y + sample_in_tile_y); +} + +uint2_xe XeResolveHostDepthSelectedSampleOffset( + XeResolveInfo resolve_info) { + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + return uint_x2_xe(0u); + #else + uint sample_index = XeResolveFirstSampleIndex(resolve_info.sample_select); + return (uint_x2_xe(sample_index) >> uint2_xe(1u, 0u)) & 1u; + #endif +} + +uint XeResolveHostDepthGet2xSampleId( + uint source_sample_y + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth)) { + return ((source_sample_y & 1u) != 0u) + ? constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_msaa_2x_sample_1) + : constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_msaa_2x_sample_0); +} + +// Fetches the source depth given an already-resolved host texel (the output of +// XeResolveHostDepthSourceSampleToTexel). Split out so the costly tile->texel +// mapping can be computed once per thread and reused across the thread's lanes +// (see XeResolveHostDepthLaneTexel). +float XeResolveHostDepthFetchDepthTexel( + uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth) + param_next_after_depth_texture_xe + param_depth_texture_xe(xe_resolve_host_depth_source)) { + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + return texel_fetch_2d_xe(xe_resolve_host_depth_source, + int2_xe(source_texel), 0).r; + #elif XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 2 + uint sample_id = XeResolveHostDepthGet2xSampleId( + source_texel.y + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth)); + return texel_fetch_2d_ms_xe( + xe_resolve_host_depth_source, + int2_xe(source_texel.x, source_texel.y >> 1u), int(sample_id)).r; + #else + uint sample_id = (source_texel.x & 1u) | ((source_texel.y & 1u) << 1u); + return texel_fetch_2d_ms_xe( + xe_resolve_host_depth_source, + int2_xe(source_texel.x >> 1u, source_texel.y >> 1u), + int(sample_id)).r; + #endif +} + +uint XeResolveHostDepthFetchStencilTexel( + uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth) + param_next_after_stencil_texture_xe + param_stencil_texture_xe(xe_resolve_host_depth_stencil)) { + if ((constant_xe(xe_resolve_host_depth, xe_resolve_host_depth_flags) & + XE_RESOLVE_HOST_DEPTH_FLAG_HAS_STENCIL) == 0u) { + return 0u; + } + + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + return texel_fetch_2d_xe(xe_resolve_host_depth_stencil, + int2_xe(source_texel), 0).r & 0xFFu; + #elif XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 2 + uint sample_id = XeResolveHostDepthGet2xSampleId( + source_texel.y + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth)); + return texel_fetch_2d_ms_xe( + xe_resolve_host_depth_stencil, + int2_xe(source_texel.x, source_texel.y >> 1u), int(sample_id)).r & + 0xFFu; + #else + uint sample_id = (source_texel.x & 1u) | ((source_texel.y & 1u) << 1u); + return texel_fetch_2d_ms_xe( + xe_resolve_host_depth_stencil, + int2_xe(source_texel.x >> 1u, source_texel.y >> 1u), + int(sample_id)).r & + 0xFFu; + #endif +} + +uint2_xe XeResolveHostDepthSourceSampleForLane(XeResolveInfo resolve_info, + uint2_xe pixel_index, + uint lane) { + uint2_xe source_pixel = + uint2_xe(pixel_index.x + lane, + max(pixel_index.y, + resolve_info.half_pixel_offset_fill_source.y)) + + resolve_info.edram_offset_scaled; + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 1 + return source_pixel; + #elif XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 2 + return (source_pixel << uint2_xe(0u, 1u)) + + XeResolveHostDepthSelectedSampleOffset(resolve_info); + #else + return (source_pixel << uint_x2_xe(1u)) + + XeResolveHostDepthSelectedSampleOffset(resolve_info); + #endif +} + +// The lanes of a thread cover source samples at consecutive X (same Y), so the +// source texel for lane N is the lane-0 texel offset by N * stride in X (1 for +// 1x/2x MSAA, 2 for 4x). The expensive tile->texel mapping (the integer +// divisions in XeResolveHostDepthSourceSampleToTexel) is therefore computed +// once per thread instead of per lane. The lane span stays within one source +// render-target tile row by the dispatch's tile alignment. +uint XeResolveHostDepthLaneTexelStrideX() { + #if XE_RESOLVE_HOST_DEPTH_MSAA_SAMPLES == 4 + return 2u; + #else + return 1u; + #endif +} + +uint2_xe XeResolveHostDepthLaneTexel(uint2_xe base_texel, uint lane) { + return uint2_xe(base_texel.x + lane * XeResolveHostDepthLaneTexelStrideX(), + base_texel.y); +} + +uint2_xe XeResolveHostDepthSourceTexelBaseForLane( + XeResolveInfo resolve_info, uint2_xe pixel_index + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth)) { + return XeResolveHostDepthSourceSampleToTexel( + resolve_info, + XeResolveHostDepthSourceSampleForLane(resolve_info, pixel_index, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth)); +} + +uint XeResolveHostDepthPackByTexel( + XeResolveInfo resolve_info, uint2_xe source_texel + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth) + param_next_after_depth_texture_xe + param_depth_texture_xe(xe_resolve_host_depth_source) + param_next_after_stencil_texture_xe + param_stencil_texture_xe(xe_resolve_host_depth_stencil)) { + float depth = XeResolveHostDepthFetchDepthTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source)); + uint stencil = XeResolveHostDepthFetchStencilTexel( + source_texel + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + + uint depth24; + if (resolve_info.edram_format == kXenosDepthRenderTargetFormat_D24FS8) { + bool round_depth = + (constant_xe(xe_resolve_host_depth, xe_resolve_host_depth_flags) & + XE_RESOLVE_HOST_DEPTH_FLAG_ROUND_DEPTH) != 0u; + depth24 = XeFloat32To20e4(float_bits_to_uint_xe(depth * 2.0f), + round_depth); + } else { + float depth_f = min(max(depth, 0.0f), 1.0f) * 16777215.0f; + depth24 = XeResolveHostDepthRoundToNearestEven(depth_f); + } + + return (depth24 << 8u) | (stencil & 0xFFu); +} + +bool XeResolveHostDepthGetPixelIndex( + XeResolveInfo resolve_info, uint2_xe thread_index, + out_param_xe(uint2_xe, pixel_index) + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth)) { + if (thread_index.x >= + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_thread_count_x) || + thread_index.y >= + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_thread_count_y)) { + return false; + } + + uint pixels_per_thread = XeResolveHostDepthPixelsPerThread(); + uint tile_pixel_size_x = XeResolveHostDepthTilePixelSizeX(resolve_info); + uint tile_pixel_size_y = XeResolveHostDepthTilePixelSizeY(resolve_info); + uint dispatch_pixel_x = thread_index.x * pixels_per_thread; + uint dispatch_pixel_y = thread_index.y; + uint dispatch_tile_x = dispatch_pixel_x / tile_pixel_size_x; + uint dispatch_tile_y = dispatch_pixel_y / tile_pixel_size_y; + uint pixel_in_tile_x = + dispatch_pixel_x - dispatch_tile_x * tile_pixel_size_x; + uint pixel_in_tile_y = + dispatch_pixel_y - dispatch_tile_y * tile_pixel_size_y; + + uint local_tile = + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_dispatch_offset) + + dispatch_tile_y * + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_dump_pitch_tiles) + + dispatch_tile_x; + uint dump_pitch_tiles = + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_dump_pitch_tiles); + uint dump_tile_y = local_tile / dump_pitch_tiles; + uint dump_tile_x = local_tile - dump_tile_y * dump_pitch_tiles; + uint2_xe dump_pixel = + uint2_xe(dump_tile_x * tile_pixel_size_x + pixel_in_tile_x, + dump_tile_y * tile_pixel_size_y + pixel_in_tile_y); + + if (dump_pixel.x < resolve_info.edram_offset_scaled.x || + dump_pixel.y < resolve_info.edram_offset_scaled.y) { + return false; + } + pixel_index = dump_pixel - resolve_info.edram_offset_scaled; + if (pixel_index.x >= (resolve_info.width_div_8_scaled << 3u) || + pixel_index.y >= + constant_xe(xe_resolve_host_depth, + xe_resolve_host_depth_height_scaled)) { + return false; + } + return true; +} + +void XeResolveHostDepthApplyHalfPixelOffset32( + XeResolveInfo resolve_info, uint2_xe pixel_index, + inout_param_xe(uint4_xe, pixels_0123), + inout_param_xe(uint4_xe, pixels_4567)) { + dont_flatten_xe + if (pixel_index.x == 0u && + resolve_info.half_pixel_offset_fill_source.x != 0u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 2u) { + if (resolve_info.half_pixel_offset_fill_source.x >= 3u) { + pixels_0123.z = pixels_0123.w; + } + pixels_0123.y = pixels_0123.z; + } + pixels_0123.x = pixels_0123.y; + } +} + +void XeResolveHostDepthMain( + uint2_xe thread_index + param_next_after_byte_buffer_xe + param_byte_buffer_wo_xe(xe_resolve_dest) + param_next_after_push_consts_xe + param_push_consts_xe + param_next_after_const_buffer_xe + param_const_buffer_xe(xe_resolve_host_depth) + param_next_after_depth_texture_xe + param_depth_texture_xe(xe_resolve_host_depth_source) + param_next_after_stencil_texture_xe + param_stencil_texture_xe(xe_resolve_host_depth_stencil)) { + XeResolveInfo resolve_info = XeResolveGetInfo(pass_push_consts_xe); + uint2_xe pixel_index; + if (!XeResolveHostDepthGetPixelIndex( + resolve_info, thread_index, pixel_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth))) { + return; + } + + uint2_xe base_texel = XeResolveHostDepthSourceTexelBaseForLane( + resolve_info, pixel_index + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth)); + + uint4_xe pixels_0123; + uint4_xe pixels_4567; + pixels_0123.x = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 0u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_0123.y = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 1u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_0123.z = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 2u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_0123.w = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 3u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_4567.x = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 4u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_4567.y = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 5u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_4567.z = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 6u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + pixels_4567.w = XeResolveHostDepthPackByTexel( + resolve_info, + XeResolveHostDepthLaneTexel(base_texel, 7u) + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); + + XeResolveHostDepthApplyHalfPixelOffset32(resolve_info, pixel_index, + pixels_0123, pixels_4567); + XeResolveSwap8PixelsRedBlue32bpp(resolve_info, pixels_0123, pixels_4567); + uint dest_address = XeResolveDestPixelAddress(resolve_info, pixel_index, 2u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap32(pixels_0123, resolve_info.dest_endian_128)); + dest_address += XeResolveLocalXAddressXor(4u, 2u); + byte_buffer_align16_store16_xe( + xe_resolve_dest, dest_address, + XeEndianSwap32(pixels_4567, resolve_info.dest_endian_128)); +} + +#endif // XENIA_GPU_SHADERS_RESOLVE_HOST_DEPTH_XESLI_ diff --git a/src/xenia/gpu/shaders/resolve_host_depth_entry.xesli b/src/xenia/gpu/shaders/resolve_host_depth_entry.xesli new file mode 100644 index 000000000..0043edf3b --- /dev/null +++ b/src/xenia/gpu/shaders/resolve_host_depth_entry.xesli @@ -0,0 +1,44 @@ +/** + ****************************************************************************** + * 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 "resolve_host_depth.xesli" + +#define LOCAL_SIZE_X_XE 8 +#define LOCAL_SIZE_Y_XE 8 +#define LOCAL_SIZE_Z_XE 1 +entry_bindings_begin_compute_xe + XE_RESOLVE_PUSH_CONST_BINDING + entry_binding_next_xe + byte_buffer_wo_binding_xe(xe_resolve_dest, buffer(1)) + entry_binding_next_xe + XE_RESOLVE_HOST_DEPTH_CONSTANTS_BINDING +#if SHADING_LANGUAGE_MSL_XE + entry_binding_next_xe + XE_RESOLVE_HOST_DEPTH_SOURCE_TEXTURE_BINDING + entry_binding_next_xe + XE_RESOLVE_HOST_DEPTH_STENCIL_TEXTURE_BINDING +#endif +entry_bindings_end_inputs_begin_compute_xe + entry_in_global_thread_id_xe +entry_inputs_end_code_begin_compute_xe +{ + XeResolveHostDepthMain( + in_global_thread_id_xe.xy + pass_next_after_byte_buffer_xe + pass_byte_buffer_xe(xe_resolve_dest) + pass_next_after_push_consts_xe + pass_push_consts_xe + pass_next_after_const_buffer_xe + pass_const_buffer_xe(xe_resolve_host_depth) + pass_next_after_depth_texture_xe + pass_depth_texture_xe(xe_resolve_host_depth_source) + pass_next_after_stencil_texture_xe + pass_stencil_texture_xe(xe_resolve_host_depth_stencil)); +} +entry_code_end_compute_xe diff --git a/src/xenia/gpu/shaders/texture_upload_repack.metal b/src/xenia/gpu/shaders/texture_upload_repack.metal new file mode 100644 index 000000000..cf7dcedbf --- /dev/null +++ b/src/xenia/gpu/shaders/texture_upload_repack.metal @@ -0,0 +1,47 @@ +#include + +using namespace metal; + +struct TextureUploadRepackConstants { + uint source_offset; + uint dest_offset; + uint source_row_pitch; + uint dest_row_pitch; + uint source_image_pitch; + uint dest_image_pitch; + uint row_bytes; + uint row_count; + uint depth; + uint padding0; + uint padding1; + uint padding2; +}; + +kernel void entry_xe(constant TextureUploadRepackConstants& constants + [[buffer(0)]], + const device uchar* source [[buffer(1)]], + device uchar* dest [[buffer(2)]], + uint3 gid [[thread_position_in_grid]]) { + uint row_offset = gid.x << 2u; + if (row_offset >= constants.row_bytes || gid.y >= constants.row_count || + gid.z >= constants.depth) { + return; + } + + uint source_offset = constants.source_offset + + gid.z * constants.source_image_pitch + + gid.y * constants.source_row_pitch + row_offset; + uint dest_offset = constants.dest_offset + gid.z * constants.dest_image_pitch + + gid.y * constants.dest_row_pitch + row_offset; + + dest[dest_offset] = source[source_offset]; + if (row_offset + 1u < constants.row_bytes) { + dest[dest_offset + 1u] = source[source_offset + 1u]; + } + if (row_offset + 2u < constants.row_bytes) { + dest[dest_offset + 2u] = source[source_offset + 2u]; + } + if (row_offset + 3u < constants.row_bytes) { + dest[dest_offset + 3u] = source[source_offset + 3u]; + } +} diff --git a/src/xenia/gpu/shared_memory.cc b/src/xenia/gpu/shared_memory.cc index 94fc5fe78..862206257 100644 --- a/src/xenia/gpu/shared_memory.cc +++ b/src/xenia/gpu/shared_memory.cc @@ -9,6 +9,8 @@ #include "xenia/gpu/shared_memory.h" +#include + #include "xenia/base/assert.h" #include "xenia/base/bit_range.h" #include "xenia/base/logging.h" @@ -140,6 +142,7 @@ void SharedMemory::InvalidateAllPages() { // Mark all blocks as dirty and set dirty flag dirty_blocks_.store(0xFFFFFFFF, std::memory_order_relaxed); gpu_written_data_dirty_.store(true, std::memory_order_relaxed); + invalidation_epoch_.fetch_add(1, std::memory_order_relaxed); } void SharedMemory::ClearCache() { @@ -462,53 +465,111 @@ void SharedMemory::UnlinkWatchRange(WatchRange* range) { watch_range_first_free_ = range; } -// todo: optimize, an enormous amount of cpu time (1.34%) is spent here. -bool SharedMemory::RequestRange(uint32_t start, uint32_t length) { +bool SharedMemory::IsRangeValid(uint32_t start, uint32_t length) const { if (!length) { - // Some texture or buffer is empty, for example - safe to draw in this case. return true; } if (start > kBufferSize || (kBufferSize - start) < length) { return false; } - SCOPE_profile_cpu_f("gpu"); - - if (!EnsureHostGpuMemoryAllocated(start, length)) { - return false; - } - - unsigned int current_upload_range = 0; uint32_t page_first = start >> page_size_log2_; uint32_t page_last = (start + length - 1) >> page_size_log2_; uint32_t block_first = page_first >> 6; uint32_t block_last = page_last >> 6; - // Lockless fast-path: check if all pages are already valid. - // This avoids lock acquisition for the common case where pages are resident. uint64_t* valid_flags = active_valid_flags_.load(std::memory_order_acquire); - if (valid_flags) { - bool all_valid = true; - for (uint32_t i = block_first; i <= block_last && all_valid; ++i) { - uint64_t block_valid = valid_flags[i]; - if (i == block_first) { - // Mask out pages before page_first - uint64_t block_before = (uint64_t(1) << (page_first & 63)) - 1; - block_valid |= block_before; - } - if (i == block_last && (page_last & 63) != 63) { - // Mask out pages after page_last - uint64_t block_after = ~((uint64_t(1) << ((page_last & 63) + 1)) - 1); - block_valid |= block_after; - } - if (block_valid != ~uint64_t(0)) { - all_valid = false; - } + if (!valid_flags) { + return false; + } + for (uint32_t i = block_first; i <= block_last; ++i) { + uint64_t block_valid = valid_flags[i]; + if (i == block_first) { + uint64_t block_before = (uint64_t(1) << (page_first & 63)) - 1; + block_valid |= block_before; } - if (all_valid) { - // All pages already valid, nothing to upload - return true; + if (i == block_last && (page_last & 63) != 63) { + uint64_t block_after = ~((uint64_t(1) << ((page_last & 63) + 1)) - 1); + block_valid |= block_after; + } + if (block_valid != ~uint64_t(0)) { + return false; + } + } + return true; +} + +bool SharedMemory::IsRangeInvalid(uint32_t start, uint32_t length) const { + if (!length) { + return true; + } + if (start > kBufferSize || (kBufferSize - start) < length) { + return false; + } + + uint32_t page_first = start >> page_size_log2_; + uint32_t page_last = (start + length - 1) >> page_size_log2_; + + uint32_t block_first = page_first >> 6; + uint32_t block_last = page_last >> 6; + + uint64_t* valid_flags = active_valid_flags_.load(std::memory_order_acquire); + if (!valid_flags) { + return false; + } + for (uint32_t i = block_first; i <= block_last; ++i) { + uint64_t range_mask = ~uint64_t(0); + if (i == block_first) { + range_mask &= ~((uint64_t(1) << (page_first & 63)) - 1); + } + if (i == block_last && (page_last & 63) != 63) { + range_mask &= (uint64_t(1) << ((page_last & 63) + 1)) - 1; + } + if (valid_flags[i] & range_mask) { + return false; + } + } + return true; +} + +void SharedMemory::WatchRangeForCpuWrites(uint32_t start, uint32_t length) { + if (!length || start >= kBufferSize || + !memory_invalidation_callback_handle_) { + return; + } + length = std::min(length, kBufferSize - start); + memory().EnablePhysicalMemoryAccessCallbacks(start, length, true, false); +} + +bool SharedMemory::RequestRange(uint32_t start, uint32_t length) { + Range range = {start, length}; + return RequestRanges(&range, 1); +} + +bool SharedMemory::RequestRanges(const Range* ranges, uint32_t range_count, + RequestRangeStats* stats) { + RequestRangeStats local_stats; + RequestRangeStats& request_stats = stats ? *stats : local_stats; + request_stats = {}; + request_stats.input_ranges = range_count; + if (!range_count) { + return true; + } + + SCOPE_profile_cpu_f("gpu"); + + for (uint32_t i = 0; i < range_count; ++i) { + const Range& range = ranges[i]; + if (!range.length) { + continue; + } + if (range.start > kBufferSize || + (kBufferSize - range.start) < range.length) { + return false; + } + if (!EnsureHostGpuMemoryAllocated(range.start, range.length)) { + return false; } } @@ -517,23 +578,86 @@ bool SharedMemory::RequestRange(uint32_t start, uint32_t length) { std::pair* uploads = reinterpret_cast*>(upload_ranges_.data()); - // swcache::PrefetchL1(&system_page_flags_[block_first]); - uint32_t range_start = UINT32_MAX; + unsigned int current_upload_range = 0; { auto global_lock = global_critical_region_.Acquire(); - TryFindUploadRange(block_first, block_last, page_first, page_last, - range_start, current_upload_range, uploads); - } - if (range_start != UINT32_MAX) { - uploads[current_upload_range++] = - (std::make_pair(range_start, page_last + 1 - range_start)); + for (uint32_t i = 0; i < range_count; ++i) { + const Range& range = ranges[i]; + if (!range.length) { + continue; + } + + uint32_t page_first = range.start >> page_size_log2_; + uint32_t page_last = (range.start + range.length - 1) >> page_size_log2_; + uint32_t block_first = page_first >> 6; + uint32_t block_last = page_last >> 6; + + if (IsRangeValid(range.start, range.length)) { + continue; + } + ++request_stats.invalid_input_ranges; + + uint32_t range_start = UINT32_MAX; + TryFindUploadRange(block_first, block_last, page_first, page_last, + range_start, current_upload_range, uploads); + if (range_start != UINT32_MAX) { + if (current_upload_range >= MAX_UPLOAD_RANGES) { + xe::FatalError( + "Hit max upload ranges in shared_memory.cc, tell a dev to " + "raise the limit!"); + } + uploads[current_upload_range++] = + std::make_pair(range_start, page_last + 1 - range_start); + } + } } + if (!current_upload_range) { return true; } - return UploadRanges(uploads, current_upload_range); + request_stats.upload_page_ranges_before_coalesce = current_upload_range; + + std::sort( + uploads, uploads + current_upload_range, + [](const std::pair& a, + const std::pair& b) { return a.first < b.first; }); + + uint32_t coalesced_upload_range_count = 0; + for (uint32_t i = 0; i < current_upload_range; ++i) { + const auto& upload_range = uploads[i]; + if (!upload_range.second) { + continue; + } + uint32_t start = upload_range.first; + uint32_t end = upload_range.first + upload_range.second; + if (!coalesced_upload_range_count) { + uploads[coalesced_upload_range_count++] = + std::make_pair(start, end - start); + continue; + } + + auto& previous = uploads[coalesced_upload_range_count - 1]; + uint32_t previous_end = previous.first + previous.second; + if (start <= previous_end) { + if (end > previous_end) { + previous.second = end - previous.first; + } + } else { + uploads[coalesced_upload_range_count++] = + std::make_pair(start, end - start); + } + } + + request_stats.upload_page_ranges_after_coalesce = + coalesced_upload_range_count; + for (uint32_t i = 0; i < coalesced_upload_range_count; ++i) { + request_stats.upload_bytes += uint64_t(uploads[i].second) + << page_size_log2_; + } + + return UploadRanges(uploads, coalesced_upload_range_count); } template @@ -694,6 +818,7 @@ std::pair SharedMemory::MemoryInvalidationCallback( // Mark as dirty since GPU-written flags changed due to CPU invalidation. gpu_written_data_dirty_.store(true, std::memory_order_relaxed); dirty_blocks_.fetch_or(dirty_blocks_mask, std::memory_order_relaxed); + invalidation_epoch_.fetch_add(1, std::memory_order_relaxed); FireWatches(page_first, page_last, false); diff --git a/src/xenia/gpu/shared_memory.h b/src/xenia/gpu/shared_memory.h index 340d60abf..838151c2e 100644 --- a/src/xenia/gpu/shared_memory.h +++ b/src/xenia/gpu/shared_memory.h @@ -75,7 +75,33 @@ class SharedMemory { // Checks if the range has been updated, uploads new data if needed and // ensures the host GPU memory backing the range are resident. Returns true if // the range has been fully updated and is usable. + struct Range { + uint32_t start; + uint32_t length; + }; + struct RequestRangeStats { + uint32_t input_ranges = 0; + uint32_t invalid_input_ranges = 0; + uint32_t upload_page_ranges_before_coalesce = 0; + uint32_t upload_page_ranges_after_coalesce = 0; + uint64_t upload_bytes = 0; + }; bool RequestRange(uint32_t start, uint32_t length); + bool RequestRanges(const Range* ranges, uint32_t range_count, + RequestRangeStats* stats = nullptr); + // Non-mutating residency check for paths that must not open transfer + // encoders. Returns true only if RequestRange would fast-path without upload. + bool IsRangeValid(uint32_t start, uint32_t length) const; + // Non-mutating check for CPU-backed upload paths. Returns true only if no + // page in the range is currently valid in the host GPU shared-memory copy. + bool IsRangeInvalid(uint32_t start, uint32_t length) const; + // Enables CPU write invalidation callbacks for a range without changing + // shared-memory residency. Used by paths that upload directly from CPU memory + // to a derived resource rather than to the shared-memory buffer. + void WatchRangeForCpuWrites(uint32_t start, uint32_t length); + uint64_t GetInvalidationEpoch() const { + return invalidation_epoch_.load(std::memory_order_relaxed); + } void TryFindUploadRange(const uint32_t& block_first, const uint32_t& block_last, @@ -230,6 +256,7 @@ class SharedMemory { // Total: 32 chunks requiring 32 bits. When GPU writes are localized, // this reduces copy overhead by 80-95%. std::atomic dirty_blocks_{0}; + std::atomic invalidation_epoch_{0}; uint64_t* system_page_flags_valid_and_gpu_written_ = nullptr; unsigned num_system_page_flags_ = 0; diff --git a/src/xenia/gpu/texture_cache.cc b/src/xenia/gpu/texture_cache.cc index c26864088..a1a80c923 100644 --- a/src/xenia/gpu/texture_cache.cc +++ b/src/xenia/gpu/texture_cache.cc @@ -317,6 +317,10 @@ uint32_t TextureCache::GuestToHostSwizzle(uint32_t guest_swizzle, } void TextureCache::RequestTextures(uint32_t used_texture_mask) { + RequestTextures(used_texture_mask, true); +} + +void TextureCache::RequestTextures(uint32_t used_texture_mask, bool load_data) { const auto& regs = register_file(); // Clear the aggregate flag, but invalidate only actually used outdated @@ -422,7 +426,9 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { } } - LoadTexturesData(textures_to_load, num_textures_to_load); + if (load_data) { + LoadTexturesData(textures_to_load, num_textures_to_load); + } if (bindings_changed) { UpdateTextureBindingsImpl(bindings_changed); @@ -431,24 +437,177 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { bool TextureCache::AnyUsedTextureRequestWorkPending( uint32_t used_texture_mask) const { - if (!used_texture_mask) { + return GetUsedTextureRequestWorkMask(used_texture_mask) != 0; +} + +bool TextureCache::MayRequestTexturesLoadData( + uint32_t used_texture_mask) const { + uint32_t work_mask = GetUsedTextureRequestWorkMask(used_texture_mask); + if (!work_mask) { return false; } - // Any used slot that is out of sync needs work. - if (used_texture_mask & ~texture_bindings_in_sync_) { - return true; + + const auto& regs = register_file(); + auto is_texture_outdated = [](const Texture* texture) { + return texture && (texture->base_outdated_lockless() || + texture->mips_outdated_lockless()); + }; + auto key_may_need_load = [this, &is_texture_outdated](TextureKey key) { + key = GetHostTextureKey(key); + auto texture_it = textures_.find(key); + if (texture_it == textures_.end()) { + return true; + } + return is_texture_outdated(texture_it->second.get()); + }; + + uint32_t remaining_bits = work_mask; + uint32_t index = 0; + while (xe::bit_scan_forward(remaining_bits, &index)) { + const uint32_t index_bit = UINT32_C(1) << index; + remaining_bits = xe::clear_lowest_bit(remaining_bits); + + const TextureBinding& binding = texture_bindings_[index]; + const TextureKey old_key = binding.key; + const uint8_t old_swizzled_signs = binding.swizzled_signs; + + TextureKey new_key; + uint8_t new_swizzled_signs = kSwizzledSignsUnsigned; + if (texture_bindings_in_sync_ & index_bit) { + new_key = old_key; + new_swizzled_signs = old_swizzled_signs; + } else { + BindingInfoFromFetchConstant(regs.GetTextureFetch(index), new_key, + &new_swizzled_signs); + } + if (!new_key.is_valid) { + continue; + } + + const bool key_changed = new_key != old_key; + const bool any_sign_was_not_signed = + texture_util::IsAnySignNotSigned(old_swizzled_signs); + const bool any_sign_was_signed = + texture_util::IsAnySignSigned(old_swizzled_signs); + const bool any_sign_is_not_signed = + texture_util::IsAnySignNotSigned(new_swizzled_signs); + const bool any_sign_is_signed = + texture_util::IsAnySignSigned(new_swizzled_signs); + + if (IsSignedVersionSeparateForFormat(new_key)) { + if (any_sign_is_not_signed) { + if (key_changed || !any_sign_was_not_signed) { + if (key_may_need_load(new_key)) { + return true; + } + } else if (is_texture_outdated(binding.texture)) { + return true; + } + } + if (any_sign_is_signed) { + TextureKey signed_key = new_key; + signed_key.signed_separate = 1; + if (key_changed || !any_sign_was_signed) { + if (key_may_need_load(signed_key)) { + return true; + } + } else if (is_texture_outdated(binding.texture_signed)) { + return true; + } + } + } else if (key_changed ? key_may_need_load(new_key) + : is_texture_outdated(binding.texture)) { + return true; + } } + + return false; +} + +uint32_t TextureCache::GetUsedTextureRequestWorkMask( + uint32_t used_texture_mask) const { + if (!used_texture_mask) { + return 0; + } + // Any used slot that is out of sync needs work. + uint32_t work_mask = used_texture_mask & ~texture_bindings_in_sync_; // Any in-sync slot whose backing texture data is outdated also needs work. uint32_t used_in_sync = used_texture_mask & texture_bindings_in_sync_; uint32_t index = 0; while (xe::bit_scan_forward(used_in_sync, &index)) { + uint32_t index_bit = UINT32_C(1) << index; used_in_sync = xe::clear_lowest_bit(used_in_sync); const TextureBinding& binding = texture_bindings_[index]; if (binding.key.is_valid && IsBindingOutdatedForUse(binding)) { - return true; + work_mask |= index_bit; } } - return false; + return work_mask; +} + +uint32_t TextureCache::GetUsedTextureRangeOverlapMask( + uint32_t used_texture_mask, uint32_t start, uint32_t length) const { + if (!used_texture_mask || !length) { + return 0; + } + start &= 0x1FFFFFFF; + length = std::min(length, 0x20000000 - start); + if (!length) { + return 0; + } + const uint64_t range_start = start; + const uint64_t range_end = range_start + length; + auto overlaps = [&](uint32_t texture_start, uint32_t texture_length) { + if (!texture_length) { + return false; + } + const uint64_t texture_end = uint64_t(texture_start) + texture_length; + return uint64_t(texture_start) < range_end && texture_end > range_start; + }; + const auto& regs = register_file(); + uint32_t overlap_mask = 0; + uint32_t remaining_bits = used_texture_mask; + uint32_t index = 0; + while (xe::bit_scan_forward(remaining_bits, &index)) { + const uint32_t index_bit = UINT32_C(1) << index; + remaining_bits = xe::clear_lowest_bit(remaining_bits); + TextureKey key; + texture_util::TextureGuestLayout computed_layout; + const texture_util::TextureGuestLayout* layout = nullptr; + const TextureBinding& binding = texture_bindings_[index]; + if ((texture_bindings_in_sync_ & index_bit) && binding.key.is_valid) { + key = binding.key; + const Texture* texture = + binding.texture ? binding.texture : binding.texture_signed; + if (texture) { + layout = &texture->guest_layout(); + } + } else { + uint8_t swizzled_signs = 0; + BindingInfoFromFetchConstant(regs.GetTextureFetch(index), key, + &swizzled_signs); + } + if (!key.is_valid) { + continue; + } + if (!layout) { + computed_layout = key.GetGuestLayout(); + layout = &computed_layout; + } + if (key.base_page && + overlaps( + key.base_page << 12, + xe::align(layout->base.level_data_extent_bytes, UINT32_C(16)))) { + overlap_mask |= index_bit; + continue; + } + if (key.mip_page && + overlaps(key.mip_page << 12, + xe::align(layout->mips_total_extent_bytes, UINT32_C(16)))) { + overlap_mask |= index_bit; + } + } + return overlap_mask; } bool TextureCache::IsBindingOutdatedForUse( @@ -576,20 +735,30 @@ TextureCache::Texture::~Texture() { void TextureCache::Texture::MakeUpToDateAndWatch( const global_unique_lock_type& global_lock) { + MakeLoadedDataUpToDateAndWatch(global_lock, true, true); +} + +void TextureCache::Texture::MakeLoadedDataUpToDateAndWatch( + const global_unique_lock_type& global_lock, bool loaded_base, + bool loaded_mips) { SharedMemory& shared_memory = texture_cache().shared_memory(); - if (base_outdated_) { + if (loaded_base && base_outdated_) { assert_not_zero(GetGuestBaseSize()); base_outdated_ = false; base_watch_handle_ = shared_memory.WatchMemoryRange( key().base_page << 12, GetGuestBaseSize(), TextureCache::WatchCallback, this, nullptr, 0); + shared_memory.WatchRangeForCpuWrites(key().base_page << 12, + GetGuestBaseSize()); } - if (mips_outdated_) { + if (loaded_mips && mips_outdated_) { assert_not_zero(GetGuestMipsSize()); mips_outdated_ = false; mips_watch_handle_ = shared_memory.WatchMemoryRange( key().mip_page << 12, GetGuestMipsSize(), TextureCache::WatchCallback, this, nullptr, 1); + shared_memory.WatchRangeForCpuWrites(key().mip_page << 12, + GetGuestMipsSize()); } } @@ -651,7 +820,26 @@ void TextureCache::DestroyAllTextures(bool from_destructor) { COUNT_profile_set("gpu/texture_cache/textures", 0); } -TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { +bool TextureCache::DestroyOldestTextureIfUnused( + uint64_t completed_submission_index) { + Texture* texture = texture_used_first_; + if (!texture || + texture->last_usage_submission_index() > completed_submission_index) { + return false; + } + ResetTextureBindings(); + auto found_texture_it = textures_.find(texture->key()); + assert_true(found_texture_it != textures_.end()); + if (found_texture_it == textures_.end()) { + return false; + } + assert_true(found_texture_it->second.get() == texture); + textures_.erase(found_texture_it); + COUNT_profile_set("gpu/texture_cache/textures", textures_.size()); + return true; +} + +TextureCache::TextureKey TextureCache::GetHostTextureKey(TextureKey key) const { // Check if the texture is a scaled resolve texture. if (IsDrawResolutionScaled() && key.tiled && IsScaledResolveSupportedForFormat(key)) { @@ -668,6 +856,11 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { key.scaled_resolve = 1; } } + return key; +} + +TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { + key = GetHostTextureKey(key); uint32_t host_width = key.GetWidth(); uint32_t host_height = key.GetHeight(); @@ -763,6 +956,10 @@ void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { if (nkept == 0) { return; } + if (!PrepareTextureDataLoadRanges(textures, n_textures, index_base_outdated, + index_mips_outdated)) { + return; + } for (uint32_t i = 0; i < n_textures; ++i) { Texture* p_texture = textures[i]; @@ -788,14 +985,16 @@ void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { // from the shared memory to load the unscaled parts. // TODO(Triang3l): Load unscaled parts. if (index_base_outdated & (1ULL << i)) { - if (!shared_memory().RequestRange( + if (!RequestTextureDataRange( + texture, TextureDataRangeSource::kBase, texture_key.base_page << 12, xe::align(texture.GetGuestBaseSize(), UINT32_C(16)))) { continue; } } if (index_mips_outdated & (1ULL << i)) { - if (!shared_memory().RequestRange( + if (!RequestTextureDataRange( + texture, TextureDataRangeSource::kMips, texture_key.mip_page << 12, xe::align(texture.GetGuestMipsSize(), UINT32_C(16)))) { continue; @@ -864,6 +1063,12 @@ bool TextureCache::LoadTextureData(Texture& texture) { } TextureKey texture_key = texture.key(); + Texture* texture_to_load = &texture; + if (!PrepareTextureDataLoadRanges(&texture_to_load, 1, + base_outdated ? UINT64_C(1) : 0, + mips_outdated ? UINT64_C(1) : 0)) { + return false; + } // Implementation may load multiple blocks at once via accesses of up to 128 // bits (R32G32B32A32_UINT), so aligning the size to this value to make sure @@ -880,15 +1085,15 @@ bool TextureCache::LoadTextureData(Texture& texture) { // shared memory to load the unscaled parts. // TODO(Triang3l): Load unscaled parts. if (base_outdated) { - if (!shared_memory().RequestRange( - texture_key.base_page << 12, + if (!RequestTextureDataRange( + texture, TextureDataRangeSource::kBase, texture_key.base_page << 12, xe::align(texture.GetGuestBaseSize(), UINT32_C(16)))) { return false; } } if (mips_outdated) { - if (!shared_memory().RequestRange( - texture_key.mip_page << 12, + if (!RequestTextureDataRange( + texture, TextureDataRangeSource::kMips, texture_key.mip_page << 12, xe::align(texture.GetGuestMipsSize(), UINT32_C(16)))) { return false; } @@ -1056,7 +1261,7 @@ void TextureCache::UpdateTexturesTotalHostMemoryUsage(uint64_t add, } bool TextureCache::IsRangeScaledResolved(uint32_t start_unscaled, - uint32_t length_unscaled) { + uint32_t length_unscaled) const { if (!IsDrawResolutionScaled()) { return false; } diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index b22e9cc59..0dea96a3e 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -128,6 +128,14 @@ class TextureCache { // bindings or reload texture data from guest memory. Used as a cheap // pre-check to skip the full RequestTextures call when nothing changed. bool AnyUsedTextureRequestWorkPending(uint32_t used_texture_mask) const; + // Conservative non-mutating check for whether RequestTextures may call + // LoadTextureDataFromResidentMemoryImpl for any used texture. + bool MayRequestTexturesLoadData(uint32_t used_texture_mask) const; + uint32_t GetUsedTextureRequestWorkMask(uint32_t used_texture_mask) const; + uint32_t GetUsedTextureRangeOverlapMask(uint32_t used_texture_mask, + uint32_t start, + uint32_t length) const; + size_t GetTotalTextureCount() const { return textures_.size(); } // "ActiveTexture" means as of the latest RequestTextures call. @@ -165,6 +173,8 @@ class TextureCache { } protected: + void RequestTextures(uint32_t used_texture_mask, bool load_data); + struct TextureKey { // Dimensions minus 1 are stored similarly to how they're stored in fetch // constants so fewer bits can be used, while the maximum size (8192 for 2D) @@ -293,6 +303,9 @@ class TextureCache { bool base_outdated_lockless() const { return base_outdated_; } bool mips_outdated_lockless() const { return mips_outdated_; } void MakeUpToDateAndWatch(const global_unique_lock_type& global_lock); + void MakeLoadedDataUpToDateAndWatch( + const global_unique_lock_type& global_lock, bool loaded_base, + bool loaded_mips); void WatchCallback(const global_unique_lock_type& global_lock, bool is_mip); @@ -551,6 +564,7 @@ class TextureCache { // to the implementation that are used in their destructor, and will become // invalid if the implementation is destroyed before the texture. void DestroyAllTextures(bool from_destructor = false); + bool DestroyOldestTextureIfUnused(uint64_t completed_submission_index); // Whether the signed version of the texture has a different representation on // the host than its unsigned version (for example, if it's a fixed-point @@ -597,6 +611,23 @@ class TextureCache { } bool LoadTextureData(Texture& texture); void LoadTexturesData(Texture** textures, uint32_t n_textures); + virtual bool PrepareTextureDataLoadRanges(Texture** textures, + uint32_t texture_count, + uint64_t base_outdated_mask, + uint64_t mips_outdated_mask) { + return true; + } + enum class TextureDataRangeSource { + kBase, + kMips, + }; + virtual bool RequestTextureDataRange(Texture&, TextureDataRangeSource, + uint32_t start, uint32_t length) { + // TODO(xenios-jp): Backend texture caches with encoder-lifetime ownership + // must not use this default direct shared-memory request while an encoder + // is active. Metal routes texture residency through backend preflight. + return shared_memory().RequestRange(start, length); + } // Writes the texture data (for base, mips or both - but not neither) from the // shared memory or the scaled resolve memory. The shared memory management is // done outside this function, the implementation just needs to load the data @@ -604,6 +635,9 @@ class TextureCache { virtual bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, bool load_mips) = 0; + global_unique_lock_type AcquireGlobalLock() { + return global_critical_region_.Acquire(); + } // Converts a texture fetch constant to a texture key, normalizing and // validating the values, or creating an invalid key, and also gets the @@ -630,6 +664,7 @@ class TextureCache { private: void UpdateTexturesTotalHostMemoryUsage(uint64_t add, uint64_t subtract); + TextureKey GetHostTextureKey(TextureKey key) const; // Shared memory callback for texture data invalidation. static void WatchCallback(const global_unique_lock_type& global_lock, @@ -638,7 +673,8 @@ class TextureCache { // Checks if there are any pages that contain scaled resolve data within the // range. - bool IsRangeScaledResolved(uint32_t start_unscaled, uint32_t length_unscaled); + bool IsRangeScaledResolved(uint32_t start_unscaled, + uint32_t length_unscaled) const; // Global shared memory invalidation callback for invalidating scaled resolved // texture data. static void ScaledResolveGlobalWatchCallbackThunk( diff --git a/src/xenia/gpu/vulkan/CMakeLists.txt b/src/xenia/gpu/vulkan/CMakeLists.txt index a4753d1a8..6b29efffc 100644 --- a/src/xenia/gpu/vulkan/CMakeLists.txt +++ b/src/xenia/gpu/vulkan/CMakeLists.txt @@ -1,6 +1,148 @@ add_library(xenia-gpu-vulkan STATIC) xe_platform_sources(xenia-gpu-vulkan ${CMAKE_CURRENT_SOURCE_DIR}) xe_shader_rules_spirv(xenia-gpu-vulkan ${CMAKE_CURRENT_SOURCE_DIR}/../shaders) + +set(_vulkan_direct_host_resolve_generated_root "${PROJECT_BINARY_DIR}/generated") +set(_vulkan_direct_host_resolve_bytecode_dir + "${_vulkan_direct_host_resolve_generated_root}/xenia/gpu/shaders/bytecode/vulkan_spirv") +set(_vulkan_direct_host_resolve_wrapper_dir + "${_vulkan_direct_host_resolve_generated_root}/xenia/gpu/shaders/vulkan_direct_host_resolve") +set(_vulkan_direct_host_resolve_bytecode_header + "${_vulkan_direct_host_resolve_generated_root}/xenia/gpu/shaders/vulkan_direct_host_resolve_bytecode.h") +set(_vulkan_direct_host_resolve_outputs) +file(MAKE_DIRECTORY "${_vulkan_direct_host_resolve_bytecode_dir}") +file(MAKE_DIRECTORY "${_vulkan_direct_host_resolve_wrapper_dir}") +file(WRITE "${_vulkan_direct_host_resolve_bytecode_header}" + "// Generated by CMake. Do not edit.\n") + +macro(_xe_vulkan_direct_host_resolve_variant id input) + set(_variant_define_args) + foreach(_define ${ARGN}) + list(APPEND _variant_define_args --define "${_define}") + endforeach() + set(_variant_out "${_vulkan_direct_host_resolve_bytecode_dir}/${id}.h") + set(_variant_dep "${_variant_out}.d") + list(APPEND _vulkan_direct_host_resolve_outputs "${_variant_out}") + file(APPEND "${_vulkan_direct_host_resolve_bytecode_header}" + "#include \"xenia/gpu/shaders/bytecode/vulkan_spirv/${id}.h\"\n") + add_custom_command( + OUTPUT "${_variant_out}" + COMMAND $ + --identifier "${id}" + ${_variant_define_args} + --depfile "${_variant_dep}" + "${input}" + "${_variant_out}" + DEPENDS "${input}" xenia-shader-cc + DEPFILE "${_variant_dep}" + COMMENT "SPIR-V: ${id}" + VERBATIM + ) +endmacro() + +set(_resolve_host_color_entry_source + "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_color_entry.xesli") +set(_resolve_host_color_full_entry_source + "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_color_full_entry.xesli") +set(_resolve_host_depth_entry_source + "${CMAKE_CURRENT_SOURCE_DIR}/../shaders/resolve_host_depth_entry.xesli") +file(RELATIVE_PATH _resolve_host_color_entry_include + "${_vulkan_direct_host_resolve_wrapper_dir}" + "${_resolve_host_color_entry_source}") +file(RELATIVE_PATH _resolve_host_color_full_entry_include + "${_vulkan_direct_host_resolve_wrapper_dir}" + "${_resolve_host_color_full_entry_source}") +file(RELATIVE_PATH _resolve_host_depth_entry_include + "${_vulkan_direct_host_resolve_wrapper_dir}" + "${_resolve_host_depth_entry_source}") +string(REPLACE "\\" "/" _resolve_host_color_entry_include + "${_resolve_host_color_entry_include}") +string(REPLACE "\\" "/" _resolve_host_color_full_entry_include + "${_resolve_host_color_full_entry_include}") +string(REPLACE "\\" "/" _resolve_host_depth_entry_include + "${_resolve_host_depth_entry_include}") +set(_resolve_host_color_entry + "${_vulkan_direct_host_resolve_wrapper_dir}/resolve_host_color_entry.cs.xesl") +set(_resolve_host_color_full_entry + "${_vulkan_direct_host_resolve_wrapper_dir}/resolve_host_color_full_entry.cs.xesl") +set(_resolve_host_depth_entry + "${_vulkan_direct_host_resolve_wrapper_dir}/resolve_host_depth_entry.cs.xesl") +file(WRITE "${_resolve_host_color_entry}" + "#include \"${_resolve_host_color_entry_include}\"\n") +file(WRITE "${_resolve_host_color_full_entry}" + "#include \"${_resolve_host_color_full_entry_include}\"\n") +file(WRITE "${_resolve_host_depth_entry}" + "#include \"${_resolve_host_depth_entry_include}\"\n") + +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_vulkan_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_vulkan_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_vulkan_direct_host_resolve_variant( + "${_id}" "${_resolve_host_depth_entry}" ${_defines}) + endforeach() +endforeach() + +add_custom_target(xenia-gpu-vulkan-direct-host-resolve-shaders + DEPENDS ${_vulkan_direct_host_resolve_outputs}) +add_dependencies(xenia-gpu-vulkan + xenia-gpu-vulkan-direct-host-resolve-shaders) +target_include_directories(xenia-gpu-vulkan BEFORE PRIVATE + "${_vulkan_direct_host_resolve_generated_root}") + target_include_directories(xenia-gpu-vulkan PRIVATE ${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include ${PROJECT_SOURCE_DIR}/third_party/glslang diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index f5abd868d..8ead65cdb 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -354,6 +354,19 @@ bool VulkanCommandProcessor::SetupContext() { "bound to the compute shader"); return false; } + descriptor_set_layout_binding_transient.binding = 1; + descriptor_set_layout_binding_transient.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + if (dfn.vkCreateDescriptorSetLayout( + device, &descriptor_set_layout_create_info, nullptr, + &descriptor_set_layouts_single_transient_[size_t( + SingleTransientDescriptorLayout::kUniformBufferComputeB1)]) != + VK_SUCCESS) { + XELOGE( + "Failed to create a Vulkan descriptor set layout for a uniform buffer " + "bound to binding 1 in the compute shader"); + return false; + } shared_memory_ = std::make_unique( *this, *memory_, trace_writer_, guest_shader_pipeline_stages_); @@ -3306,51 +3319,55 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, : 0); texture_cache_->RequestTextures(used_texture_mask); + auto pipeline_layout = + static_cast(pipeline->pipeline_layout); + auto bind_guest_graphics_pipeline = [&]() { + // The pipeline may be not ready yet if created asynchronously. + // EndSubmission must be called before submitting the command buffer to + // await its creation. + if (current_guest_graphics_pipeline_ != current_pipeline) { + deferred_command_buffer_.CmdVkBindPipeline( + VK_PIPELINE_BIND_POINT_GRAPHICS, current_pipeline); + current_guest_graphics_pipeline_ = current_pipeline; + current_external_graphics_pipeline_ = VK_NULL_HANDLE; + } + if (current_guest_graphics_pipeline_layout_ != pipeline_layout) { + if (current_guest_graphics_pipeline_layout_) { + // Keep descriptor set layouts for which the new pipeline layout is + // compatible with the previous one (pipeline layouts are compatible for + // set N if set layouts 0 through N are compatible). + uint32_t descriptor_sets_kept = + uint32_t(SpirvShaderTranslator::kDescriptorSetCount); + if (current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_textures_vertex_ref() != + pipeline_layout->descriptor_set_layout_textures_vertex_ref()) { + descriptor_sets_kept = std::min( + descriptor_sets_kept, + uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesVertex)); + } + if (current_guest_graphics_pipeline_layout_ + ->descriptor_set_layout_textures_pixel_ref() != + pipeline_layout->descriptor_set_layout_textures_pixel_ref()) { + descriptor_sets_kept = std::min( + descriptor_sets_kept, + uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesPixel)); + } + // Invalidate descriptor set bindings for incompatible sets. + current_graphics_descriptor_sets_bound_up_to_date_ &= + (UINT32_C(1) << descriptor_sets_kept) - 1; + } else { + // No or unknown pipeline layout previously bound - all bindings are in + // an indeterminate state. + current_graphics_descriptor_sets_bound_up_to_date_ = 0; + } + current_guest_graphics_pipeline_layout_ = pipeline_layout; + } + }; + // Update the graphics pipeline, and if the new graphics pipeline has a // different layout, invalidate incompatible descriptor sets before updating // current_guest_graphics_pipeline_layout_. - // The pipeline may be not ready yet if created asynchronously. - // EndSubmission must be called before submitting the command buffer to - // await its creation. - if (current_guest_graphics_pipeline_ != current_pipeline) { - deferred_command_buffer_.CmdVkBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, - current_pipeline); - current_guest_graphics_pipeline_ = current_pipeline; - current_external_graphics_pipeline_ = VK_NULL_HANDLE; - } - auto pipeline_layout = - static_cast(pipeline->pipeline_layout); - if (current_guest_graphics_pipeline_layout_ != pipeline_layout) { - if (current_guest_graphics_pipeline_layout_) { - // Keep descriptor set layouts for which the new pipeline layout is - // compatible with the previous one (pipeline layouts are compatible for - // set N if set layouts 0 through N are compatible). - uint32_t descriptor_sets_kept = - uint32_t(SpirvShaderTranslator::kDescriptorSetCount); - if (current_guest_graphics_pipeline_layout_ - ->descriptor_set_layout_textures_vertex_ref() != - pipeline_layout->descriptor_set_layout_textures_vertex_ref()) { - descriptor_sets_kept = std::min( - descriptor_sets_kept, - uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesVertex)); - } - if (current_guest_graphics_pipeline_layout_ - ->descriptor_set_layout_textures_pixel_ref() != - pipeline_layout->descriptor_set_layout_textures_pixel_ref()) { - descriptor_sets_kept = std::min( - descriptor_sets_kept, - uint32_t(SpirvShaderTranslator::kDescriptorSetTexturesPixel)); - } - // Invalidate descriptor set bindings for incompatible sets. - current_graphics_descriptor_sets_bound_up_to_date_ &= - (UINT32_C(1) << descriptor_sets_kept) - 1; - } else { - // No or unknown pipeline layout previously bound - all bindings are in an - // indeterminate state. - current_graphics_descriptor_sets_bound_up_to_date_ = 0; - } - current_guest_graphics_pipeline_layout_ = pipeline_layout; - } + bind_guest_graphics_pipeline(); bool host_render_targets_used = render_target_cache_->GetPath() == RenderTargetCache::Path::kHostRenderTargets; @@ -3549,6 +3566,25 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, render_target_cache_->last_update_render_pass(), render_target_cache_->last_update_framebuffer()); + if (render_target_cache_->HasPendingDrawPassTransfers()) { + if (!render_target_cache_->EncodePendingDrawPassTransfers()) { + if (!render_target_cache_->FlushPendingDrawPassTransfers()) { + return false; + } + SubmitBarriersAndEnterRenderTargetCacheRenderPass( + render_target_cache_->last_update_render_pass(), + render_target_cache_->last_update_framebuffer()); + } + bind_guest_graphics_pipeline(); + UpdateDynamicState(viewport_info, primitive_polygonal, + normalized_depth_control, draw_resolution_scale_x, + draw_resolution_scale_y, + apply_host_depth_polygon_offset); + if (!UpdateBindings(vertex_shader, pixel_shader)) { + return false; + } + } + // Track for device-lost diagnostics. ++submission_in_progress_.draw_count; submission_in_progress_.last_vs_hash = vertex_shader->ucode_data_hash(); diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.h b/src/xenia/gpu/vulkan/vulkan_command_processor.h index 871a593e6..8e087362b 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.h +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.h @@ -59,6 +59,9 @@ class VulkanCommandProcessor final : public CommandProcessor { // Single-descriptor layouts for use within a single frame. enum class SingleTransientDescriptorLayout { kStorageBufferCompute, + // Uniform buffer at binding 1 for shaders that keep binding 0 as push + // constants for D3D-style root constants. + kUniformBufferComputeB1, kCount, }; diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc index 5472ea54f..c902812a4 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.cc @@ -9,6 +9,7 @@ #include "xenia/gpu/vulkan/vulkan_render_target_cache.h" +#include #include #include #include @@ -32,6 +33,17 @@ DECLARE_bool(vulkan_dynamic_rendering); +DEFINE_bool( + vulkan_transfer_in_draw_pass, true, + "Encode eligible render target ownership transfers inside the following " + "guest draw pass instead of opening a standalone transfer pass.", + "GPU"); +DEFINE_bool( + vulkan_direct_host_resolve, true, + "Resolve eligible host render targets directly to shared memory with " + "compute shaders, avoiding the intermediate EDRAM dump.", + "GPU"); + namespace xe { namespace gpu { namespace vulkan { @@ -64,6 +76,7 @@ namespace shaders { #include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_full_64bpp_scaled_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_full_8bpp_cs.h" #include "xenia/gpu/shaders/bytecode/vulkan_spirv/resolve_full_8bpp_scaled_cs.h" +#include "xenia/gpu/shaders/vulkan_direct_host_resolve_bytecode.h" } // namespace shaders const VulkanRenderTargetCache::ResolveCopyShaderCode @@ -103,6 +116,132 @@ const VulkanRenderTargetCache::ResolveCopyShaderCode sizeof(shaders::resolve_full_128bpp_scaled_cs)}, }; +// Each entry expands to {code, sizeof(code), "code"} for one generated SPIR-V +// resolve variant, keeping the variant name spelled out only once. +#define XE_DHR_SHADER(name) {shaders::name, sizeof(shaders::name), #name} + +const VulkanRenderTargetCache::DirectHostResolveShaderCode + VulkanRenderTargetCache::kDirectHostResolveColorShaders + [VulkanRenderTargetCache::kDirectHostResolveBppCount] + [VulkanRenderTargetCache::kDirectHostResolveMsaaCount] + [VulkanRenderTargetCache::kDirectHostResolveScaledCount] + [VulkanRenderTargetCache::kDirectHostResolveSourceUintCount] = { + { + // 32bpp + {{XE_DHR_SHADER(resolve_host_color_32bpp_1xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_32bpp_1xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_32bpp_1xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_32bpp_1xmsaa_scaled_cs)}}, + {{XE_DHR_SHADER(resolve_host_color_32bpp_2xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_32bpp_2xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_32bpp_2xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_32bpp_2xmsaa_scaled_cs)}}, + {{XE_DHR_SHADER(resolve_host_color_32bpp_4xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_32bpp_4xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_32bpp_4xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_32bpp_4xmsaa_scaled_cs)}}, + }, + { + // 64bpp + {{XE_DHR_SHADER(resolve_host_color_64bpp_1xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_64bpp_1xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_64bpp_1xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_64bpp_1xmsaa_scaled_cs)}}, + {{XE_DHR_SHADER(resolve_host_color_64bpp_2xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_64bpp_2xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_64bpp_2xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_64bpp_2xmsaa_scaled_cs)}}, + {{XE_DHR_SHADER(resolve_host_color_64bpp_4xmsaa_cs), + XE_DHR_SHADER(resolve_host_color_uint_64bpp_4xmsaa_cs)}, + {XE_DHR_SHADER(resolve_host_color_64bpp_4xmsaa_scaled_cs), + XE_DHR_SHADER( + resolve_host_color_uint_64bpp_4xmsaa_scaled_cs)}}, + }, +}; + +#define XE_DHR_FULL_DESTS_1X(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_1xmsaa_cs), \ + XE_DHR_SHADER(prefix##_16bpp_1xmsaa_cs), \ + XE_DHR_SHADER(prefix##_32bpp_1xmsaa_cs), \ + XE_DHR_SHADER(prefix##_64bpp_1xmsaa_cs), \ + XE_DHR_SHADER(prefix##_128bpp_1xmsaa_cs)} +#define XE_DHR_FULL_DESTS_1X_SCALED(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_1xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_16bpp_1xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_32bpp_1xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_64bpp_1xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_128bpp_1xmsaa_scaled_cs)} +#define XE_DHR_FULL_DESTS_2X(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_2xmsaa_cs), \ + XE_DHR_SHADER(prefix##_16bpp_2xmsaa_cs), \ + XE_DHR_SHADER(prefix##_32bpp_2xmsaa_cs), \ + XE_DHR_SHADER(prefix##_64bpp_2xmsaa_cs), \ + XE_DHR_SHADER(prefix##_128bpp_2xmsaa_cs)} +#define XE_DHR_FULL_DESTS_2X_SCALED(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_2xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_16bpp_2xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_32bpp_2xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_64bpp_2xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_128bpp_2xmsaa_scaled_cs)} +#define XE_DHR_FULL_DESTS_4X(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_4xmsaa_cs), \ + XE_DHR_SHADER(prefix##_16bpp_4xmsaa_cs), \ + XE_DHR_SHADER(prefix##_32bpp_4xmsaa_cs), \ + XE_DHR_SHADER(prefix##_64bpp_4xmsaa_cs), \ + XE_DHR_SHADER(prefix##_128bpp_4xmsaa_cs)} +#define XE_DHR_FULL_DESTS_4X_SCALED(prefix) \ + {XE_DHR_SHADER(prefix##_8bpp_4xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_16bpp_4xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_32bpp_4xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_64bpp_4xmsaa_scaled_cs), \ + XE_DHR_SHADER(prefix##_128bpp_4xmsaa_scaled_cs)} + +const VulkanRenderTargetCache::DirectHostResolveShaderCode + VulkanRenderTargetCache::kDirectHostResolveColorFullShaders + [VulkanRenderTargetCache::kDirectHostResolveMsaaCount] + [VulkanRenderTargetCache::kDirectHostResolveScaledCount] + [VulkanRenderTargetCache::kDirectHostResolveSourceUintCount] + [VulkanRenderTargetCache::kDirectHostResolveFullDestCount] = { + {{XE_DHR_FULL_DESTS_1X(resolve_host_color_full), + XE_DHR_FULL_DESTS_1X(resolve_host_color_full_uint)}, + {XE_DHR_FULL_DESTS_1X_SCALED(resolve_host_color_full), + XE_DHR_FULL_DESTS_1X_SCALED(resolve_host_color_full_uint)}}, + {{XE_DHR_FULL_DESTS_2X(resolve_host_color_full), + XE_DHR_FULL_DESTS_2X(resolve_host_color_full_uint)}, + {XE_DHR_FULL_DESTS_2X_SCALED(resolve_host_color_full), + XE_DHR_FULL_DESTS_2X_SCALED(resolve_host_color_full_uint)}}, + {{XE_DHR_FULL_DESTS_4X(resolve_host_color_full), + XE_DHR_FULL_DESTS_4X(resolve_host_color_full_uint)}, + {XE_DHR_FULL_DESTS_4X_SCALED(resolve_host_color_full), + XE_DHR_FULL_DESTS_4X_SCALED(resolve_host_color_full_uint)}}, +}; + +#undef XE_DHR_FULL_DESTS_4X_SCALED +#undef XE_DHR_FULL_DESTS_4X +#undef XE_DHR_FULL_DESTS_2X_SCALED +#undef XE_DHR_FULL_DESTS_2X +#undef XE_DHR_FULL_DESTS_1X_SCALED +#undef XE_DHR_FULL_DESTS_1X + +const VulkanRenderTargetCache::DirectHostResolveShaderCode + VulkanRenderTargetCache::kDirectHostResolveDepthShaders + [VulkanRenderTargetCache::kDirectHostResolveMsaaCount] + [VulkanRenderTargetCache::kDirectHostResolveScaledCount] = { + {XE_DHR_SHADER(resolve_host_depth_32bpp_1xmsaa_cs), + XE_DHR_SHADER(resolve_host_depth_32bpp_1xmsaa_scaled_cs)}, + {XE_DHR_SHADER(resolve_host_depth_32bpp_2xmsaa_cs), + XE_DHR_SHADER(resolve_host_depth_32bpp_2xmsaa_scaled_cs)}, + {XE_DHR_SHADER(resolve_host_depth_32bpp_4xmsaa_cs), + XE_DHR_SHADER(resolve_host_depth_32bpp_4xmsaa_scaled_cs)}, +}; + +#undef XE_DHR_SHADER + const VulkanRenderTargetCache::TransferPipelineLayoutInfo VulkanRenderTargetCache::kTransferPipelineLayoutInfos[size_t( TransferPipelineLayoutIndex::kCount)] = { @@ -502,6 +641,56 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) { resolve_copy_pipelines_[i] = resolve_copy_pipeline; } + VkDescriptorSetLayout direct_host_resolve_color_descriptor_set_layouts[] = { + command_processor_.GetSingleTransientDescriptorLayout( + VulkanCommandProcessor::SingleTransientDescriptorLayout:: + kUniformBufferComputeB1), + command_processor_.GetSingleTransientDescriptorLayout( + VulkanCommandProcessor::SingleTransientDescriptorLayout:: + kStorageBufferCompute), + descriptor_set_layout_sampled_image_, + }; + VkDescriptorSetLayout direct_host_resolve_depth_descriptor_set_layouts[] = { + direct_host_resolve_color_descriptor_set_layouts[0], + direct_host_resolve_color_descriptor_set_layouts[1], + descriptor_set_layout_sampled_image_x2_, + }; + VkPipelineLayoutCreateInfo direct_host_resolve_pipeline_layout_create_info; + direct_host_resolve_pipeline_layout_create_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + direct_host_resolve_pipeline_layout_create_info.pNext = nullptr; + direct_host_resolve_pipeline_layout_create_info.flags = 0; + direct_host_resolve_pipeline_layout_create_info.setLayoutCount = + uint32_t(xe::countof(direct_host_resolve_color_descriptor_set_layouts)); + direct_host_resolve_pipeline_layout_create_info.pSetLayouts = + direct_host_resolve_color_descriptor_set_layouts; + direct_host_resolve_pipeline_layout_create_info.pushConstantRangeCount = 1; + direct_host_resolve_pipeline_layout_create_info.pPushConstantRanges = + &resolve_copy_push_constant_range; + if (dfn.vkCreatePipelineLayout( + device, &direct_host_resolve_pipeline_layout_create_info, nullptr, + &direct_host_resolve_pipeline_layout_color_) != VK_SUCCESS) { + XELOGE( + "VulkanRenderTargetCache: Failed to create the direct host color " + "resolve pipeline layout"); + Shutdown(); + return false; + } + direct_host_resolve_pipeline_layout_create_info.pSetLayouts = + direct_host_resolve_depth_descriptor_set_layouts; + if (dfn.vkCreatePipelineLayout( + device, &direct_host_resolve_pipeline_layout_create_info, nullptr, + &direct_host_resolve_pipeline_layout_depth_) != VK_SUCCESS) { + XELOGE( + "VulkanRenderTargetCache: Failed to create the direct host depth " + "resolve pipeline layout"); + Shutdown(); + return false; + } + direct_host_resolve_constants_pool_ = + std::make_unique( + vulkan_device, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + // TODO(Triang3l): All paths (FSI). if (path_ == Path::kHostRenderTargets) { @@ -967,6 +1156,40 @@ void VulkanRenderTargetCache::Shutdown(bool from_destructor) { ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device, resolve_copy_pipeline_layout_); + for (auto& bpp_pipelines : direct_host_resolve_pipelines_) { + for (auto& msaa_pipelines : bpp_pipelines) { + for (auto& scaled_pipelines : msaa_pipelines) { + for (VkPipeline& pipeline : scaled_pipelines) { + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, + pipeline); + } + } + } + } + for (auto& msaa_pipelines : direct_host_color_full_resolve_pipelines_) { + for (auto& scaled_pipelines : msaa_pipelines) { + for (auto& source_pipelines : scaled_pipelines) { + for (VkPipeline& pipeline : source_pipelines) { + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, + pipeline); + } + } + } + } + for (auto& msaa_pipelines : direct_host_depth_resolve_pipelines_) { + for (VkPipeline& pipeline : msaa_pipelines) { + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, + pipeline); + } + } + direct_host_resolve_constants_pool_.reset(); + ui::vulkan::util::DestroyAndNullHandle( + dfn.vkDestroyPipelineLayout, device, + direct_host_resolve_pipeline_layout_depth_); + ui::vulkan::util::DestroyAndNullHandle( + dfn.vkDestroyPipelineLayout, device, + direct_host_resolve_pipeline_layout_color_); + ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device, edram_storage_buffer_descriptor_pool_); ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device, @@ -1021,12 +1244,623 @@ void VulkanRenderTargetCache::CompletedSubmissionUpdated() { transfer_vertex_buffer_pool_->Reclaim( command_processor_.GetCompletedSubmission()); } + if (direct_host_resolve_constants_pool_) { + direct_host_resolve_constants_pool_->Reclaim( + command_processor_.GetCompletedSubmission()); + } } void VulkanRenderTargetCache::EndSubmission() { if (transfer_vertex_buffer_pool_) { transfer_vertex_buffer_pool_->FlushWrites(); } + if (direct_host_resolve_constants_pool_) { + direct_host_resolve_constants_pool_->FlushWrites(); + } +} + +size_t DirectHostResolveMsaaIndex(xenos::MsaaSamples msaa_samples) { + switch (msaa_samples) { + case xenos::MsaaSamples::k1X: + return 0; + case xenos::MsaaSamples::k2X: + return 1; + case xenos::MsaaSamples::k4X: + return 2; + default: + return 3; + } +} + +bool IsResolveDirectHostRTFastCandidate( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast32bpp4xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp1x2xMSAA: + case draw_util::ResolveCopyShaderIndex::kFast64bpp4xMSAA: + return true; + default: + return false; + } +} + +size_t DirectHostResolveFullDestIndex( + draw_util::ResolveCopyShaderIndex shader) { + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return 0; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + return 1; + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + return 2; + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 3; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 4; + default: + return 5; + } +} + +bool IsResolveDirectHostRTFullColorCandidate( + draw_util::ResolveCopyShaderIndex shader) { + return DirectHostResolveFullDestIndex(shader) < 5; +} + +bool IsResolveDirectHostRTFullColorSourcePackable( + xenos::ColorRenderTargetFormat format) { + switch (format) { + case xenos::ColorRenderTargetFormat::k_8_8_8_8: + case xenos::ColorRenderTargetFormat::k_2_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: + case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: + case xenos::ColorRenderTargetFormat::k_32_FLOAT: + case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: + return true; + default: + return false; + } +} + +uint32_t DirectHostResolvePixelsPerThread( + draw_util::ResolveCopyShaderIndex shader, bool source_is_64bpp, + bool resolve_is_depth, xenos::MsaaSamples msaa_samples) { + if (resolve_is_depth) { + return 8u; + } + switch (shader) { + case draw_util::ResolveCopyShaderIndex::kFull8bpp: + return msaa_samples >= xenos::MsaaSamples::k4X ? 4u : 8u; + case draw_util::ResolveCopyShaderIndex::kFull128bpp: + return 2u; + case draw_util::ResolveCopyShaderIndex::kFull16bpp: + case draw_util::ResolveCopyShaderIndex::kFull32bpp: + case draw_util::ResolveCopyShaderIndex::kFull64bpp: + return 4u; + default: + return source_is_64bpp ? 4u : 8u; + } +} + +VkPipeline VulkanRenderTargetCache::GetDirectHostResolvePipeline( + bool is_64bpp, xenos::MsaaSamples msaa_samples, bool scaled, + bool source_is_uint) { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + return VK_NULL_HANDLE; + } + VkPipeline& pipeline = + direct_host_resolve_pipelines_[is_64bpp ? 1u : 0u][msaa_index] + [scaled ? 1u : 0u] + [source_is_uint ? 1u : 0u]; + if (pipeline != VK_NULL_HANDLE) { + return pipeline; + } + const DirectHostResolveShaderCode& shader = + kDirectHostResolveColorShaders[is_64bpp ? 1u : 0u][msaa_index] + [scaled ? 1u : 0u] + [source_is_uint ? 1u : 0u]; + pipeline = ui::vulkan::util::CreateComputePipeline( + command_processor_.GetVulkanDevice(), + direct_host_resolve_pipeline_layout_color_, shader.code, + shader.size_bytes, nullptr, "main", 64); + if (pipeline == VK_NULL_HANDLE) { + XELOGW( + "VulkanRenderTargetCache: Failed to create optional direct host " + "resolve pipeline {}", + shader.debug_name); + return VK_NULL_HANDLE; + } + command_processor_.GetVulkanDevice()->SetObjectName( + VK_OBJECT_TYPE_PIPELINE, pipeline, shader.debug_name); + return pipeline; +} + +VkPipeline VulkanRenderTargetCache::GetDirectHostColorFullResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled, bool source_is_uint, + draw_util::ResolveCopyShaderIndex copy_shader) { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + size_t dest_index = DirectHostResolveFullDestIndex(copy_shader); + if (msaa_index >= kDirectHostResolveMsaaCount || + dest_index >= kDirectHostResolveFullDestCount) { + return VK_NULL_HANDLE; + } + VkPipeline& pipeline = + direct_host_color_full_resolve_pipelines_[msaa_index][scaled ? 1u : 0u] + [source_is_uint ? 1u : 0u] + [dest_index]; + if (pipeline != VK_NULL_HANDLE) { + return pipeline; + } + const DirectHostResolveShaderCode& shader = + kDirectHostResolveColorFullShaders[msaa_index][scaled ? 1u : 0u] + [source_is_uint ? 1u : 0u][dest_index]; + pipeline = ui::vulkan::util::CreateComputePipeline( + command_processor_.GetVulkanDevice(), + direct_host_resolve_pipeline_layout_color_, shader.code, + shader.size_bytes, nullptr, "main", 64); + if (pipeline == VK_NULL_HANDLE) { + XELOGW( + "VulkanRenderTargetCache: Failed to create optional direct host full " + "color resolve pipeline {}", + shader.debug_name); + return VK_NULL_HANDLE; + } + command_processor_.GetVulkanDevice()->SetObjectName( + VK_OBJECT_TYPE_PIPELINE, pipeline, shader.debug_name); + return pipeline; +} + +VkPipeline VulkanRenderTargetCache::GetDirectHostDepthResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled) { + size_t msaa_index = DirectHostResolveMsaaIndex(msaa_samples); + if (msaa_index >= kDirectHostResolveMsaaCount) { + return VK_NULL_HANDLE; + } + VkPipeline& pipeline = + direct_host_depth_resolve_pipelines_[msaa_index][scaled ? 1u : 0u]; + if (pipeline != VK_NULL_HANDLE) { + return pipeline; + } + const DirectHostResolveShaderCode& shader = + kDirectHostResolveDepthShaders[msaa_index][scaled ? 1u : 0u]; + pipeline = ui::vulkan::util::CreateComputePipeline( + command_processor_.GetVulkanDevice(), + direct_host_resolve_pipeline_layout_depth_, shader.code, + shader.size_bytes, nullptr, "main", 64); + if (pipeline == VK_NULL_HANDLE) { + XELOGW( + "VulkanRenderTargetCache: Failed to create optional direct host depth " + "resolve pipeline {}", + shader.debug_name); + return VK_NULL_HANDLE; + } + command_processor_.GetVulkanDevice()->SetObjectName( + VK_OBJECT_TYPE_PIPELINE, pipeline, shader.debug_name); + return pipeline; +} + +bool VulkanRenderTargetCache::TryDirectHostResolveCopy( + const draw_util::ResolveInfo& resolve_info, + const draw_util::ResolveCopyShaderConstants& copy_shader_constants, + draw_util::ResolveCopyShaderIndex copy_shader, uint32_t dump_base, + uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch, + VulkanSharedMemory& shared_memory, VulkanTextureCache& texture_cache, + uint32_t& written_address_out, uint32_t& written_length_out) { + if (!cvars::vulkan_direct_host_resolve || + GetPath() != Path::kHostRenderTargets || + !direct_host_resolve_constants_pool_) { + return false; + } + + bool resolve_is_depth = resolve_info.IsCopyingDepth(); + bool copy_shader_is_fast = IsResolveDirectHostRTFastCandidate(copy_shader); + bool copy_shader_is_full_color = + !resolve_is_depth && IsResolveDirectHostRTFullColorCandidate(copy_shader); + + xenos::ColorRenderTargetFormat resolve_color_format = + xenos::ColorRenderTargetFormat::k_8_8_8_8; + xenos::DepthRenderTargetFormat resolve_depth_format = + xenos::DepthRenderTargetFormat::kD24S8; + if (resolve_is_depth) { + if (!xenos::IsSingleCopySampleSelected( + resolve_info.copy_dest_coordinate_info.copy_sample_select) || + !copy_shader_is_fast) { + return false; + } + resolve_depth_format = + xenos::DepthRenderTargetFormat(resolve_info.depth_edram_info.format); + } else { + resolve_color_format = + xenos::ColorRenderTargetFormat(resolve_info.color_edram_info.format); + if (resolve_color_format == + xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA) { + // TODO (xenios-jp): Add the missing gamma render target format. + return false; + } + if (copy_shader_is_fast) { + if (!xenos::IsSingleCopySampleSelected( + resolve_info.copy_dest_coordinate_info.copy_sample_select) || + resolve_info.copy_dest_info.copy_dest_exp_bias || + !xenos::IsColorResolveFormatBitwiseEquivalent( + resolve_color_format, + xenos::ColorFormat( + resolve_info.copy_dest_info.copy_dest_format))) { + return false; + } + } else if (!copy_shader_is_full_color) { + return false; + } + } + + struct DirectHostResolveConstants { + uint32_t dispatch_offset; + uint32_t dump_base; + uint32_t dump_pitch_tiles; + uint32_t source_base_tiles; + uint32_t source_pitch_tiles; + uint32_t thread_count_x; + uint32_t thread_count_y; + uint32_t height_scaled; + uint32_t msaa_2x_sample_0; + uint32_t msaa_2x_sample_1; + uint32_t flags; + }; + constexpr uint32_t kDirectHostResolveDepthFlagHasStencil = 1u << 0; + constexpr uint32_t kDirectHostResolveDepthFlagRoundDepth = 1u << 1; + + struct DirectHostResolveSource { + RenderTargetKey key; + VulkanRenderTarget* render_target = nullptr; + VkPipeline pipeline = VK_NULL_HANDLE; + ResolveCopyDumpRectangle::Dispatch + dispatches[ResolveCopyDumpRectangle::kMaxDispatches]; + uint32_t dispatch_count = 0; + uint32_t flags = 0; + uint32_t pixels_per_thread = 0; + bool is_64bpp = false; + bool is_depth = false; + }; + + GetResolveCopyRectanglesToDump(dump_base, dump_row_length_used, dump_rows, + dump_pitch, dump_rectangles_); + if (dump_rectangles_.empty()) { + return false; + } + + uint64_t covered_tiles = 0; + std::vector sources; + sources.reserve(dump_rectangles_.size()); + for (const ResolveCopyDumpRectangle& rect : dump_rectangles_) { + if (!rect.rows || rect.row_last_end <= rect.row_first_start) { + return false; + } + if (rect.rows == 1) { + covered_tiles += rect.row_last_end - rect.row_first_start; + } else { + covered_tiles += dump_row_length_used - rect.row_first_start; + covered_tiles += uint64_t(rect.rows - 2) * dump_row_length_used; + covered_tiles += rect.row_last_end; + } + + auto* rt = static_cast(rect.render_target); + if (!rt) { + return false; + } + RenderTargetKey key = rt->key(); + if (key.is_depth != resolve_is_depth) { + return false; + } + + bool source_is_uint = false; + VkPipeline pipeline = VK_NULL_HANDLE; + uint32_t source_flags = 0; + bool is_64bpp = false; + if (resolve_is_depth) { + if (key.GetDepthFormat() != resolve_depth_format || + key.msaa_samples != resolve_info.depth_edram_info.msaa_samples) { + return false; + } + if (!depth_float24_convert_in_pixel_shader_ && depth_float24_round_) { + source_flags |= kDirectHostResolveDepthFlagRoundDepth; + } + source_flags |= kDirectHostResolveDepthFlagHasStencil; + pipeline = GetDirectHostDepthResolvePipeline(key.msaa_samples, + IsDrawResolutionScaled()); + } else { + if (key.GetColorFormat() != resolve_color_format || + key.msaa_samples != resolve_info.color_edram_info.msaa_samples) { + return false; + } + if (copy_shader_is_full_color && + !IsResolveDirectHostRTFullColorSourcePackable(resolve_color_format)) { + return false; + } + GetColorOwnershipTransferVulkanFormat(key.GetColorFormat(), + &source_is_uint); + is_64bpp = key.Is64bpp(); + pipeline = copy_shader_is_full_color + ? GetDirectHostColorFullResolvePipeline( + key.msaa_samples, IsDrawResolutionScaled(), + source_is_uint, copy_shader) + : GetDirectHostResolvePipeline(is_64bpp, key.msaa_samples, + IsDrawResolutionScaled(), + source_is_uint); + } + if (pipeline == VK_NULL_HANDLE) { + return false; + } + + DirectHostResolveSource source; + source.key = key; + source.render_target = rt; + source.pipeline = pipeline; + source.dispatch_count = + rect.GetDispatches(dump_pitch, dump_row_length_used, source.dispatches); + source.flags = source_flags; + source.pixels_per_thread = DirectHostResolvePixelsPerThread( + copy_shader, is_64bpp, resolve_is_depth, key.msaa_samples); + source.is_64bpp = is_64bpp; + source.is_depth = resolve_is_depth; + if (!source.dispatch_count) { + return false; + } + const uint32_t tile_size_x = + (source.is_64bpp ? 40u : 80u) * draw_resolution_scale_x(); + const uint32_t tile_pixel_size_x = + tile_size_x >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k4X); + for (uint32_t i = 0; i < source.dispatch_count; ++i) { + uint32_t dispatch_pixel_width = + source.dispatches[i].width_tiles * tile_pixel_size_x; + if (dispatch_pixel_width % source.pixels_per_thread) { + return false; + } + } + sources.push_back(source); + } + + if (covered_tiles != uint64_t(dump_row_length_used) * uint64_t(dump_rows)) { + return false; + } + + const ui::vulkan::VulkanDevice* const vulkan_device = + command_processor_.GetVulkanDevice(); + const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions(); + const VkDevice device = vulkan_device->device(); + DeferredCommandBuffer& command_buffer = + command_processor_.deferred_command_buffer(); + + VkDescriptorBufferInfo write_descriptor_set_dest_buffer_info = {}; + bool scaled_buffer_ready = false; + if (IsDrawResolutionScaled()) { + uint32_t dest_address = resolve_info.copy_dest_base; + uint32_t dest_length = resolve_info.copy_dest_extent_start - + resolve_info.copy_dest_base + + resolve_info.copy_dest_extent_length; + scaled_buffer_ready = + texture_cache.EnsureScaledResolveMemoryCommittedPublic(dest_address, + dest_length) && + texture_cache.MakeScaledResolveRangeCurrent(dest_address, dest_length); + VkBuffer scaled_buffer = scaled_buffer_ready + ? texture_cache.GetCurrentScaledResolveBuffer() + : VK_NULL_HANDLE; + if (scaled_buffer_ready && scaled_buffer != VK_NULL_HANDLE) { + uint32_t draw_resolution_scale_area = + draw_resolution_scale_x() * draw_resolution_scale_y(); + uint64_t scaled_offset = + uint64_t(dest_address) * draw_resolution_scale_area; + write_descriptor_set_dest_buffer_info.buffer = scaled_buffer; + write_descriptor_set_dest_buffer_info.offset = + scaled_offset - + texture_cache.GetCurrentScaledResolveBufferBaseOffset(); + write_descriptor_set_dest_buffer_info.range = + dest_length * draw_resolution_scale_area; + } else { + scaled_buffer_ready = false; + } + } + + if (!scaled_buffer_ready) { + if (!shared_memory.RequestRange(resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length)) { + return false; + } + write_descriptor_set_dest_buffer_info.buffer = shared_memory.buffer(); + write_descriptor_set_dest_buffer_info.offset = resolve_info.copy_dest_base; + write_descriptor_set_dest_buffer_info.range = + resolve_info.copy_dest_extent_start - resolve_info.copy_dest_base + + resolve_info.copy_dest_extent_length; + } + + VkDescriptorSet descriptor_set_dest = + command_processor_.AllocateSingleTransientDescriptor( + VulkanCommandProcessor::SingleTransientDescriptorLayout:: + kStorageBufferCompute); + if (descriptor_set_dest == VK_NULL_HANDLE) { + return false; + } + VkWriteDescriptorSet write_descriptor_set_dest = {}; + write_descriptor_set_dest.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor_set_dest.dstSet = descriptor_set_dest; + write_descriptor_set_dest.dstBinding = 0; + write_descriptor_set_dest.descriptorCount = 1; + write_descriptor_set_dest.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write_descriptor_set_dest.pBufferInfo = + &write_descriptor_set_dest_buffer_info; + dfn.vkUpdateDescriptorSets(device, 1, &write_descriptor_set_dest, 0, nullptr); + + // The scaled resolve buffer is bound for both reading (as the source render + // target backing) and writing, so it needs shader-read/shader-write barriers + // around the dispatches; shared memory uses the usage tracker instead. + auto emit_scaled_dest_buffer_barrier = [&](VkAccessFlags src_access, + VkAccessFlags dst_access) { + VkBufferMemoryBarrier buffer_barrier = {}; + buffer_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + buffer_barrier.srcAccessMask = src_access; + buffer_barrier.dstAccessMask = dst_access; + buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + buffer_barrier.buffer = write_descriptor_set_dest_buffer_info.buffer; + buffer_barrier.offset = 0; + buffer_barrier.size = VK_WHOLE_SIZE; + command_buffer.CmdVkPipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, + 0, nullptr, 1, &buffer_barrier, 0, + nullptr); + }; + + if (scaled_buffer_ready) { + emit_scaled_dest_buffer_barrier(VK_ACCESS_SHADER_READ_BIT, + VK_ACCESS_SHADER_WRITE_BIT); + } else { + shared_memory.Use(VulkanSharedMemory::Usage::kComputeWrite, + std::make_pair(resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length)); + } + + constexpr VkPipelineStageFlags kSourceStageMask = + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + constexpr VkAccessFlags kSourceAccessMask = VK_ACCESS_SHADER_READ_BIT; + constexpr VkImageLayout kSourceLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + for (const DirectHostResolveSource& source : sources) { + VulkanRenderTarget& rt = *source.render_target; + command_processor_.PushImageMemoryBarrier( + rt.image(), + ui::vulkan::util::InitializeSubresourceRange( + source.is_depth + ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) + : VK_IMAGE_ASPECT_COLOR_BIT), + rt.current_stage_mask(), kSourceStageMask, rt.current_access_mask(), + kSourceAccessMask, rt.current_layout(), kSourceLayout); + rt.SetUsage(kSourceStageMask, kSourceAccessMask, kSourceLayout); + } + + draw_util::ResolveCopyShaderConstants push_constants = copy_shader_constants; + if (!IsDrawResolutionScaled()) { + push_constants.dest_base -= + uint32_t(write_descriptor_set_dest_buffer_info.offset); + } + VkPipelineLayout pipeline_layout = + resolve_is_depth ? direct_host_resolve_pipeline_layout_depth_ + : direct_host_resolve_pipeline_layout_color_; + if (IsDrawResolutionScaled()) { + command_buffer.CmdVkPushConstants( + pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(push_constants.dest_relative), &push_constants.dest_relative); + } else { + command_buffer.CmdVkPushConstants(pipeline_layout, + VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(push_constants), &push_constants); + } + + uint64_t current_submission = command_processor_.GetCurrentSubmission(); + size_t constants_alignment = std::max( + 1, size_t(vulkan_device->properties().minUniformBufferOffsetAlignment)); + const uint32_t scale_x = draw_resolution_scale_x(); + const uint32_t scale_y = draw_resolution_scale_y(); + const uint32_t height_scaled = + (resolve_info.height_div_8 << xenos::kResolveAlignmentPixelsLog2) * + scale_y; + const uint32_t msaa_2x_sample_0 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA( + 0, msaa_2x_attachments_supported_); + const uint32_t msaa_2x_sample_1 = + draw_util::GetD3D10SampleIndexForGuest2xMSAA( + 1, msaa_2x_attachments_supported_); + + for (const DirectHostResolveSource& source : sources) { + command_processor_.BindExternalComputePipeline(source.pipeline); + VkDescriptorSet source_descriptor_set = + source.render_target->GetDescriptorSetTransferSource(); + for (uint32_t i = 0; i < source.dispatch_count; ++i) { + const ResolveCopyDumpRectangle::Dispatch& dispatch = source.dispatches[i]; + DirectHostResolveConstants constants = {}; + constants.dispatch_offset = dispatch.offset; + constants.dump_base = dump_base; + constants.dump_pitch_tiles = dump_pitch; + constants.source_base_tiles = source.key.base_tiles; + constants.source_pitch_tiles = source.key.GetPitchTiles(); + const uint32_t tile_size_x = (source.is_64bpp ? 40u : 80u) * scale_x; + const uint32_t tile_size_y = 16u * scale_y; + const uint32_t tile_pixel_size_x = + tile_size_x >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k4X); + const uint32_t tile_pixel_size_y = + tile_size_y >> + uint32_t(source.key.msaa_samples >= xenos::MsaaSamples::k2X); + constants.thread_count_x = + dispatch.width_tiles * tile_pixel_size_x / source.pixels_per_thread; + constants.thread_count_y = dispatch.height_tiles * tile_pixel_size_y; + constants.height_scaled = height_scaled; + constants.msaa_2x_sample_0 = msaa_2x_sample_0; + constants.msaa_2x_sample_1 = msaa_2x_sample_1; + constants.flags = source.flags; + + VkBuffer constants_buffer; + VkDeviceSize constants_offset; + auto* constants_mapping = direct_host_resolve_constants_pool_->Request( + current_submission, sizeof(constants), constants_alignment, + constants_buffer, constants_offset); + if (!constants_mapping) { + return false; + } + std::memcpy(constants_mapping, &constants, sizeof(constants)); + + VkDescriptorSet descriptor_set_constants = + command_processor_.AllocateSingleTransientDescriptor( + VulkanCommandProcessor::SingleTransientDescriptorLayout:: + kUniformBufferComputeB1); + if (descriptor_set_constants == VK_NULL_HANDLE) { + return false; + } + VkDescriptorBufferInfo constants_buffer_info = {}; + constants_buffer_info.buffer = constants_buffer; + constants_buffer_info.offset = constants_offset; + constants_buffer_info.range = sizeof(constants); + VkWriteDescriptorSet write_descriptor_set_constants = {}; + write_descriptor_set_constants.sType = + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor_set_constants.dstSet = descriptor_set_constants; + write_descriptor_set_constants.dstBinding = 1; + write_descriptor_set_constants.descriptorCount = 1; + write_descriptor_set_constants.descriptorType = + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_descriptor_set_constants.pBufferInfo = &constants_buffer_info; + dfn.vkUpdateDescriptorSets(device, 1, &write_descriptor_set_constants, 0, + nullptr); + + VkDescriptorSet descriptor_sets[] = { + descriptor_set_constants, + descriptor_set_dest, + source_descriptor_set, + }; + command_buffer.CmdVkBindDescriptorSets( + VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, + uint32_t(xe::countof(descriptor_sets)), descriptor_sets, 0, nullptr); + command_processor_.SubmitBarriers(true); + command_buffer.CmdVkDispatch((constants.thread_count_x + 7u) >> 3, + (constants.thread_count_y + 7u) >> 3, 1); + } + } + + if (scaled_buffer_ready) { + emit_scaled_dest_buffer_barrier(VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT); + } + + texture_cache.MarkRangeAsResolved(resolve_info.copy_dest_extent_start, + resolve_info.copy_dest_extent_length); + written_address_out = resolve_info.copy_dest_extent_start; + written_length_out = resolve_info.copy_dest_extent_length; + return true; } bool VulkanRenderTargetCache::Resolve(const Memory& memory, @@ -1069,27 +1903,34 @@ bool VulkanRenderTargetCache::Resolve(const Memory& memory, resolve_info); command_processor_.PushDebugMarker("%s", label); } - if (GetPath() == Path::kHostRenderTargets) { - // Dump the current contents of the render targets owning the affected - // range to edram_buffer_. - // TODO(Triang3l): Direct host render target -> shared memory resolve - // shaders for non-converting cases. - uint32_t dump_base; - uint32_t dump_row_length_used; - uint32_t dump_rows; - uint32_t dump_pitch; - resolve_info.GetCopyEdramTileSpan(dump_base, dump_row_length_used, - dump_rows, dump_pitch); - DumpRenderTargets(dump_base, dump_row_length_used, dump_rows, dump_pitch); - } - draw_util::ResolveCopyShaderConstants copy_shader_constants; uint32_t copy_group_count_x, copy_group_count_y; draw_util::ResolveCopyShaderIndex copy_shader = resolve_info.GetCopyShader( draw_resolution_scale_x(), draw_resolution_scale_y(), copy_shader_constants, copy_group_count_x, copy_group_count_y); assert_true(copy_group_count_x && copy_group_count_y); - if (copy_shader != draw_util::ResolveCopyShaderIndex::kUnknown) { + if (GetPath() == Path::kHostRenderTargets) { + // Dump the current contents of the render targets owning the affected + // range to edram_buffer_. + uint32_t dump_base; + uint32_t dump_row_length_used; + uint32_t dump_rows; + uint32_t dump_pitch; + resolve_info.GetCopyEdramTileSpan(dump_base, dump_row_length_used, + dump_rows, dump_pitch); + if (copy_shader != draw_util::ResolveCopyShaderIndex::kUnknown) { + copied = TryDirectHostResolveCopy( + resolve_info, copy_shader_constants, copy_shader, dump_base, + dump_row_length_used, dump_rows, dump_pitch, shared_memory, + texture_cache, written_address_out, written_length_out); + } + if (!copied) { + DumpRenderTargets(dump_base, dump_row_length_used, dump_rows, + dump_pitch); + } + } + + if (!copied && copy_shader != draw_util::ResolveCopyShaderIndex::kUnknown) { const draw_util::ResolveCopyShaderInfo& copy_shader_info = draw_util::resolve_copy_shader_info[size_t(copy_shader)]; @@ -1409,6 +2250,10 @@ bool VulkanRenderTargetCache::Resolve(const Memory& memory, bool VulkanRenderTargetCache::Update( bool is_rasterization_done, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask, const Shader& vertex_shader) { + if (!FlushPendingDrawPassTransfers()) { + return false; + } + if (!RenderTargetCache::Update(is_rasterization_done, normalized_depth_control, normalized_color_mask, vertex_shader)) { @@ -1427,10 +2272,6 @@ bool VulkanRenderTargetCache::Update( RenderTarget* const* depth_and_color_render_targets = last_update_accumulated_render_targets(); - PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, - depth_and_color_render_targets, - last_update_transfers()); - if (depth_and_color_render_targets[0]) { render_pass_key.depth_and_color_used |= 1 << 0; render_pass_key.depth_format = @@ -1457,6 +2298,48 @@ bool VulkanRenderTargetCache::Update( depth_and_color_render_targets[4]->key().GetColorFormat(); } + const std::vector* update_transfers = last_update_transfers(); + std::array, 1 + xenos::kMaxColorRenderTargets> + fallback_transfers; + bool fallback_transfer_work = false; + if (cvars::vulkan_transfer_in_draw_pass) { + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + const std::vector& transfers = update_transfers[i]; + if (transfers.empty()) { + continue; + } + if (CanQueueDrawPassTransfers(i, depth_and_color_render_targets, + transfers)) { + pending_draw_pass_render_targets_[i] = + depth_and_color_render_targets[i]; + pending_draw_pass_transfers_[i] = transfers; + pending_draw_pass_transfer_mask_ |= uint32_t(1) << i; + if (PendingDrawPassTransfersFullyOverwriteTarget( + i, depth_and_color_render_targets[i], transfers)) { + pending_draw_pass_full_overwrite_mask_ |= uint32_t(1) << i; + } + } else { + fallback_transfers[i] = transfers; + fallback_transfer_work = true; + } + } + if (fallback_transfer_work) { + PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, + depth_and_color_render_targets, + fallback_transfers.data()); + } + render_pass_key.depth_and_color_load_dont_care = + pending_draw_pass_full_overwrite_mask_ & + render_pass_key.depth_and_color_used; + if (HasPendingDrawPassTransfers() && + !PreflightPendingDrawPassTransfers(render_pass_key)) { + if (!FlushPendingDrawPassTransfers()) { + return false; + } + render_pass_key.depth_and_color_load_dont_care = 0; + } + } + const Framebuffer* framebuffer = last_update_framebuffer_; VkRenderPass render_pass = last_update_render_pass_key_ == render_pass_key ? last_update_render_pass_ @@ -1502,6 +2385,12 @@ bool VulkanRenderTargetCache::Update( sizeof(last_update_framebuffer_attachments_)); last_update_framebuffer_ = framebuffer; + if (!cvars::vulkan_transfer_in_draw_pass) { + PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, + depth_and_color_render_targets, + update_transfers); + } + // Transition the used render targets. for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { RenderTarget* rt = depth_and_color_render_targets[i]; @@ -1525,6 +2414,7 @@ bool VulkanRenderTargetCache::Update( vulkan_rt.SetUsage(rt_dst_stage_mask, rt_dst_access_mask, rt_new_layout); } + PreparePendingDrawPassTransferBarriers(); } break; case Path::kPixelShaderInterlock: { @@ -1552,6 +2442,316 @@ bool VulkanRenderTargetCache::Update( return true; } +void VulkanRenderTargetCache::ClearPendingDrawPassTransfers() { + for (auto& transfers : pending_draw_pass_transfers_) { + transfers.clear(); + } + pending_draw_pass_render_targets_.fill(nullptr); + pending_draw_pass_transfer_mask_ = 0; + pending_draw_pass_full_overwrite_mask_ = 0; +} + +bool VulkanRenderTargetCache::BuildTransferRectanglePlans( + RenderTargetKey dest_key, const std::vector& transfers, + std::vector& transfer_rectangles_out) const { + transfer_rectangles_out.clear(); + transfer_rectangles_out.reserve(transfers.size()); + for (const Transfer& transfer : transfers) { + TransferRectanglePlan plan; + plan.rectangle_count = transfer.GetRectangles( + dest_key.base_tiles, dest_key.GetPitchTiles(), dest_key.msaa_samples, + dest_key.Is64bpp(), plan.rectangles.data(), nullptr); + if (!plan.rectangle_count) { + transfer_rectangles_out.clear(); + return false; + } + transfer_rectangles_out.push_back(plan); + } + return true; +} + +bool VulkanRenderTargetCache::CanQueueDrawPassTransfers( + uint32_t render_target_index, RenderTarget* const* render_targets, + const std::vector& transfers) const { + if (!render_targets || transfers.empty() || + render_target_index > xenos::kMaxColorRenderTargets) { + return false; + } + auto* dest_vulkan_rt = + static_cast(render_targets[render_target_index]); + if (!dest_vulkan_rt) { + return false; + } + RenderTargetKey dest_key = dest_vulkan_rt->key(); + if (dest_key.is_depth != (render_target_index == 0)) { + return false; + } + + if (dest_key.is_depth) { + if (!dest_vulkan_rt->view_depth_stencil()) { + return false; + } + } else { + bool dest_is_integer = false; + VkFormat transfer_format = GetColorOwnershipTransferVulkanFormat( + dest_key.GetColorFormat(), &dest_is_integer); + if (dest_is_integer || + transfer_format != GetColorVulkanFormat(dest_key.GetColorFormat()) || + dest_vulkan_rt->view_color_transfer() != + dest_vulkan_rt->view_depth_color()) { + return false; + } + } + + auto is_active_draw_pass_rt = [&](const RenderTarget* rt) -> bool { + if (!rt) { + return false; + } + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + if (render_targets[i] == rt) { + return true; + } + } + return false; + }; + + for (const Transfer& transfer : transfers) { + if (!transfer.source || transfer.source == dest_vulkan_rt || + is_active_draw_pass_rt(transfer.source)) { + return false; + } + auto* source_vulkan_rt = static_cast(transfer.source); + if (source_vulkan_rt->key().is_depth && + !source_vulkan_rt->view_depth_stencil()) { + return false; + } + + if (transfer.host_depth_source) { + if (!dest_key.is_depth || transfer.host_depth_source == dest_vulkan_rt || + is_active_draw_pass_rt(transfer.host_depth_source)) { + return false; + } + auto* host_depth_vulkan_rt = + static_cast(transfer.host_depth_source); + if (!host_depth_vulkan_rt->key().is_depth || + !host_depth_vulkan_rt->view_depth_stencil()) { + return false; + } + } + } + + std::vector transfer_rectangle_plans; + return BuildTransferRectanglePlans(dest_key, transfers, + transfer_rectangle_plans); +} + +bool VulkanRenderTargetCache::PendingDrawPassTransfersFullyOverwriteTarget( + uint32_t render_target_index, RenderTarget* render_target, + const std::vector& transfers) const { + if (!render_target || transfers.empty() || + render_target_index > xenos::kMaxColorRenderTargets) { + return false; + } + + auto* dest_vulkan_rt = static_cast(render_target); + RenderTargetKey dest_key = dest_vulkan_rt->key(); + if (dest_key.is_depth != (render_target_index == 0) || + !last_update_framebuffer_) { + return false; + } + + const VkExtent2D& dest_extent = last_update_framebuffer_->host_extent; + if (!dest_extent.width || !dest_extent.height) { + return false; + } + + auto is_full_target_rectangle = [&](const Transfer::Rectangle& rect) { + uint32_t scaled_x = rect.x_pixels * draw_resolution_scale_x(); + uint32_t scaled_y = rect.y_pixels * draw_resolution_scale_y(); + uint32_t scaled_width = rect.width_pixels * draw_resolution_scale_x(); + uint32_t scaled_height = rect.height_pixels * draw_resolution_scale_y(); + return !scaled_x && !scaled_y && scaled_width == dest_extent.width && + scaled_height == dest_extent.height; + }; + + std::vector transfer_rectangle_plans; + if (!BuildTransferRectanglePlans(dest_key, transfers, + transfer_rectangle_plans)) { + return false; + } + for (const TransferRectanglePlan& plan : transfer_rectangle_plans) { + if (plan.rectangle_count != 1 || + !is_full_target_rectangle(plan.rectangles[0])) { + return false; + } + } + return true; +} + +VulkanRenderTargetCache::TransferMode VulkanRenderTargetCache::GetTransferMode( + bool is_stencil_bit_pass, bool dest_is_depth, bool source_is_depth, + bool has_host_depth_source, bool host_depth_source_is_copy) { + if (is_stencil_bit_pass) { + return source_is_depth ? TransferMode::kDepthToStencilBit + : TransferMode::kColorToStencilBit; + } + if (dest_is_depth) { + if (has_host_depth_source) { + return source_is_depth + ? (host_depth_source_is_copy + ? TransferMode::kDepthAndHostDepthCopyToDepth + : TransferMode::kDepthAndHostDepthToDepth) + : (host_depth_source_is_copy + ? TransferMode::kColorAndHostDepthCopyToDepth + : TransferMode::kColorAndHostDepthToDepth); + } + return source_is_depth ? TransferMode::kDepthToDepth + : TransferMode::kColorToDepth; + } + return source_is_depth ? TransferMode::kDepthToColor + : TransferMode::kColorToColor; +} + +bool VulkanRenderTargetCache::PreflightPendingDrawPassTransfers( + RenderPassKey render_pass_key) { + if (!HasPendingDrawPassTransfers()) { + return true; + } + + const ui::vulkan::VulkanDevice* const vulkan_device = + command_processor_.GetVulkanDevice(); + std::vector transfer_rectangle_plans; + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + if (!(pending_draw_pass_transfer_mask_ & (uint32_t(1) << i))) { + continue; + } + auto* dest_vulkan_rt = + static_cast(pending_draw_pass_render_targets_[i]); + if (!dest_vulkan_rt || pending_draw_pass_transfers_[i].empty()) { + return false; + } + RenderTargetKey dest_key = dest_vulkan_rt->key(); + if (dest_key.is_depth != (i == 0) || + !BuildTransferRectanglePlans(dest_key, pending_draw_pass_transfers_[i], + transfer_rectangle_plans)) { + return false; + } + + bool need_stencil_bit_draws = + dest_key.is_depth && + !vulkan_device->extensions().ext_EXT_shader_stencil_export; + for (uint32_t stencil_pass = 0; stencil_pass <= need_stencil_bit_draws; + ++stencil_pass) { + for (const Transfer& transfer : pending_draw_pass_transfers_[i]) { + auto* source_vulkan_rt = + static_cast(transfer.source); + if (!source_vulkan_rt) { + return false; + } + auto* host_depth_source_vulkan_rt = + stencil_pass + ? nullptr + : static_cast(transfer.host_depth_source); + TransferShaderKey shader_key; + shader_key.dest_msaa_samples = dest_key.msaa_samples; + shader_key.dest_color_rt_index = i ? i - 1 : 0; + shader_key.dest_resource_format = dest_key.resource_format; + shader_key.source_msaa_samples = source_vulkan_rt->key().msaa_samples; + shader_key.source_resource_format = + source_vulkan_rt->key().resource_format; + bool host_depth_source_is_copy = + host_depth_source_vulkan_rt == dest_vulkan_rt; + shader_key.host_depth_source_msaa_samples = + (host_depth_source_vulkan_rt && !host_depth_source_is_copy) + ? host_depth_source_vulkan_rt->key().msaa_samples + : xenos::MsaaSamples::k1X; + shader_key.mode = GetTransferMode( + stencil_pass != 0, dest_key.is_depth, + source_vulkan_rt->key().is_depth, + host_depth_source_vulkan_rt != nullptr, host_depth_source_is_copy); + if (!GetTransferPipelines( + TransferPipelineKey(render_pass_key, shader_key))) { + return false; + } + } + } + } + return true; +} + +void VulkanRenderTargetCache::PreparePendingDrawPassTransferBarriers() { + if (!HasPendingDrawPassTransfers()) { + return; + } + + constexpr VkPipelineStageFlags kSourceStageMask = + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + constexpr VkAccessFlags kSourceAccessMask = VK_ACCESS_SHADER_READ_BIT; + constexpr VkImageLayout kSourceLayout = + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + std::vector source_rts; + for (uint32_t i = 0; i < 1 + xenos::kMaxColorRenderTargets; ++i) { + if (!(pending_draw_pass_transfer_mask_ & (uint32_t(1) << i))) { + continue; + } + for (const Transfer& transfer : pending_draw_pass_transfers_[i]) { + auto add_source = [&](RenderTarget* rt) { + if (!rt) { + return; + } + auto* vulkan_rt = static_cast(rt); + if (std::find(source_rts.begin(), source_rts.end(), vulkan_rt) == + source_rts.end()) { + source_rts.push_back(vulkan_rt); + } + }; + add_source(transfer.source); + add_source(transfer.host_depth_source); + } + } + + for (VulkanRenderTarget* source_vulkan_rt : source_rts) { + command_processor_.PushImageMemoryBarrier( + source_vulkan_rt->image(), + ui::vulkan::util::InitializeSubresourceRange( + source_vulkan_rt->key().is_depth + ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) + : VK_IMAGE_ASPECT_COLOR_BIT), + source_vulkan_rt->current_stage_mask(), kSourceStageMask, + source_vulkan_rt->current_access_mask(), kSourceAccessMask, + source_vulkan_rt->current_layout(), kSourceLayout); + source_vulkan_rt->SetUsage(kSourceStageMask, kSourceAccessMask, + kSourceLayout); + } +} + +bool VulkanRenderTargetCache::EncodePendingDrawPassTransfers() { + if (!HasPendingDrawPassTransfers()) { + return true; + } + if (!PreflightPendingDrawPassTransfers(last_update_render_pass_key_)) { + return false; + } + PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, + pending_draw_pass_render_targets_.data(), + pending_draw_pass_transfers_.data(), nullptr, + nullptr, true); + ClearPendingDrawPassTransfers(); + return true; +} + +bool VulkanRenderTargetCache::FlushPendingDrawPassTransfers() { + if (!HasPendingDrawPassTransfers()) { + return true; + } + PerformTransfersAndResolveClears(1 + xenos::kMaxColorRenderTargets, + pending_draw_pass_render_targets_.data(), + pending_draw_pass_transfers_.data()); + ClearPendingDrawPassTransfers(); + return true; +} + void VulkanRenderTargetCache::GetLastUpdateRenderingAttachments( VkRenderingAttachmentInfo* color_attachments, uint32_t* color_attachment_count_out, @@ -1572,7 +2772,9 @@ void VulkanRenderTargetCache::GetLastUpdateRenderingAttachments( const auto* vulkan_rt = static_cast(rts[0]); depth_attachment->imageView = vulkan_rt->view_depth_stencil(); depth_attachment->imageLayout = VulkanRenderTarget::kDepthDrawLayout; - depth_attachment->loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + depth_attachment->loadOp = (key.depth_and_color_load_dont_care & 0b1) + ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; depth_attachment->storeOp = VK_ATTACHMENT_STORE_OP_STORE; // Stencil uses the same view for depth-stencil formats. *stencil_attachment = *depth_attachment; @@ -1600,7 +2802,10 @@ void VulkanRenderTargetCache::GetLastUpdateRenderingAttachments( const auto* vulkan_rt = static_cast(rts[1 + i]); color_attachment.imageView = vulkan_rt->view_depth_color(); color_attachment.imageLayout = VulkanRenderTarget::kColorDrawLayout; - color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + color_attachment.loadOp = + (key.depth_and_color_load_dont_care & (uint32_t(1) << (1 + i))) + ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; } *color_attachment_count_out = color_attachment_count; @@ -1638,9 +2843,11 @@ VkRenderPass VulkanRenderTargetCache::GetHostRenderTargetsRenderPass( attachment.flags = 0; attachment.format = GetDepthVulkanFormat(key.depth_format); attachment.samples = samples; - attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.loadOp = (key.depth_and_color_load_dont_care & 0b1) + ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.stencilLoadOp = attachment.loadOp; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.initialLayout = VulkanRenderTarget::kDepthDrawLayout; attachment.finalLayout = VulkanRenderTarget::kDepthDrawLayout; @@ -1671,7 +2878,9 @@ VkRenderPass VulkanRenderTargetCache::GetHostRenderTargetsRenderPass( ? GetColorOwnershipTransferVulkanFormat(color_format) : GetColorVulkanFormat(color_format); attachment.samples = samples; - attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.loadOp = (key.depth_and_color_load_dont_care & attachment_bit) + ? VK_ATTACHMENT_LOAD_OP_DONT_CARE + : VK_ATTACHMENT_LOAD_OP_LOAD; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -4589,7 +5798,7 @@ VkPipeline const* VulkanRenderTargetCache::GetTransferPipelines( // For VK_KHR_dynamic_rendering: set up VkPipelineRenderingCreateInfo. VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {}; - VkFormat color_attachment_format = VK_FORMAT_UNDEFINED; + VkFormat color_attachment_formats[xenos::kMaxColorRenderTargets] = {}; VkFormat depth_attachment_format = VK_FORMAT_UNDEFINED; VkFormat stencil_attachment_format = VK_FORMAT_UNDEFINED; if (use_dynamic_rendering) { @@ -4598,22 +5807,32 @@ VkPipeline const* VulkanRenderTargetCache::GetTransferPipelines( pipeline_rendering_create_info.pNext = nullptr; pipeline_rendering_create_info.viewMask = 0; - // Transfers target a single attachment - either depth or color. if (key.render_pass_key.depth_and_color_used & 0b1) { - // Depth attachment. depth_attachment_format = GetDepthVulkanFormat(key.render_pass_key.depth_format); stencil_attachment_format = depth_attachment_format; - pipeline_rendering_create_info.colorAttachmentCount = 0; - pipeline_rendering_create_info.pColorAttachmentFormats = nullptr; - } else { - // Color attachment (transfers use transfer formats). - color_attachment_format = GetColorOwnershipTransferVulkanFormat( - key.render_pass_key.color_0_view_format); - pipeline_rendering_create_info.colorAttachmentCount = 1; - pipeline_rendering_create_info.pColorAttachmentFormats = - &color_attachment_format; } + xenos::ColorRenderTargetFormat color_formats[] = { + key.render_pass_key.color_0_view_format, + key.render_pass_key.color_1_view_format, + key.render_pass_key.color_2_view_format, + key.render_pass_key.color_3_view_format, + }; + uint32_t color_attachment_count = 0; + for (uint32_t i = 0; i < xenos::kMaxColorRenderTargets; ++i) { + if (!(key.render_pass_key.depth_and_color_used & (1 << (1 + i)))) { + continue; + } + color_attachment_formats[i] = + key.render_pass_key.color_rts_use_transfer_formats + ? GetColorOwnershipTransferVulkanFormat(color_formats[i]) + : GetColorVulkanFormat(color_formats[i]); + color_attachment_count = i + 1; + } + pipeline_rendering_create_info.colorAttachmentCount = + color_attachment_count; + pipeline_rendering_create_info.pColorAttachmentFormats = + color_attachment_count ? color_attachment_formats : nullptr; pipeline_rendering_create_info.depthAttachmentFormat = depth_attachment_format; pipeline_rendering_create_info.stencilAttachmentFormat = @@ -4694,11 +5913,13 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( uint32_t render_target_count, RenderTarget* const* render_targets, const std::vector* render_target_transfers, const uint64_t* render_target_resolve_clear_values, - const Transfer::Rectangle* resolve_clear_rectangle) { + const Transfer::Rectangle* resolve_clear_rectangle, + bool in_current_render_pass) { assert_true(GetPath() == Path::kHostRenderTargets); bool resolve_clear_needed = render_target_resolve_clear_values && resolve_clear_rectangle; + assert_false(in_current_render_pass && resolve_clear_needed); // Check if there's any actual work to do before pushing debug marker. bool has_transfers = false; @@ -4738,7 +5959,8 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( // Do host depth storing for the depth destination (assuming there can be only // one depth destination) where depth destination == host depth source. bool host_depth_store_set_up = false; - for (uint32_t i = 0; i < render_target_count; ++i) { + for (uint32_t i = 0; !in_current_render_pass && i < render_target_count; + ++i) { RenderTarget* dest_rt = render_targets[i]; if (!dest_rt) { continue; @@ -4838,7 +6060,8 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( // use, choose the destination state, otherwise the source state - to match // the order in which transfers will actually happen (otherwise there will be // just a useless switch back and forth). - for (uint32_t i = 0; i < render_target_count; ++i) { + for (uint32_t i = 0; !in_current_render_pass && i < render_target_count; + ++i) { RenderTarget* dest_rt = render_targets[i]; if (!dest_rt) { continue; @@ -4963,7 +6186,7 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( // Late barriers in case there was cross-copying that prevented merging of // barriers. - { + if (!in_current_render_pass) { VkPipelineStageFlags dest_dst_stage_mask; VkAccessFlags dest_dst_access_mask; VkImageLayout dest_new_layout; @@ -4992,38 +6215,47 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( // overall perform all non-cross-copying transfers for the current // framebuffer configuration in a single pass, to load / store only once. RenderPassKey transfer_render_pass_key; - transfer_render_pass_key.msaa_samples = dest_rt_key.msaa_samples; - if (dest_rt_key.is_depth) { - transfer_render_pass_key.depth_and_color_used = 0b1; - transfer_render_pass_key.depth_format = dest_rt_key.GetDepthFormat(); + VkRenderPass transfer_render_pass = VK_NULL_HANDLE; + const Framebuffer* transfer_framebuffer = nullptr; + VkImageView transfer_dest_view = VK_NULL_HANDLE; + if (in_current_render_pass) { + transfer_render_pass_key = last_update_render_pass_key_; + transfer_render_pass = last_update_render_pass_; + transfer_framebuffer = last_update_framebuffer_; } else { - transfer_render_pass_key.depth_and_color_used = 0b1 << 1; - transfer_render_pass_key.color_0_view_format = - dest_rt_key.GetColorFormat(); - transfer_render_pass_key.color_rts_use_transfer_formats = 1; + transfer_render_pass_key.msaa_samples = dest_rt_key.msaa_samples; + if (dest_rt_key.is_depth) { + transfer_render_pass_key.depth_and_color_used = 0b1; + transfer_render_pass_key.depth_format = dest_rt_key.GetDepthFormat(); + } else { + transfer_render_pass_key.depth_and_color_used = 0b1 << 1; + transfer_render_pass_key.color_0_view_format = + dest_rt_key.GetColorFormat(); + transfer_render_pass_key.color_rts_use_transfer_formats = 1; + } + transfer_render_pass = + GetHostRenderTargetsRenderPass(transfer_render_pass_key); + if (transfer_render_pass == VK_NULL_HANDLE) { + continue; + } + const RenderTarget* transfer_framebuffer_render_targets + [1 + xenos::kMaxColorRenderTargets] = {}; + transfer_framebuffer_render_targets[dest_rt_key.is_depth ? 0 : 1] = + dest_rt; + transfer_framebuffer = GetHostRenderTargetsFramebuffer( + transfer_render_pass_key, dest_rt_key.pitch_tiles_at_32bpp, + transfer_framebuffer_render_targets); + if (!transfer_framebuffer) { + continue; + } + // Get the view for dynamic rendering (used for both transfers and + // clears). + transfer_dest_view = dest_rt_key.is_depth + ? dest_vulkan_rt.view_depth_stencil() + : dest_vulkan_rt.view_color_transfer(); + // Don't enter the render pass immediately - may still insert source + // barriers later. } - VkRenderPass transfer_render_pass = - GetHostRenderTargetsRenderPass(transfer_render_pass_key); - if (transfer_render_pass == VK_NULL_HANDLE) { - continue; - } - const RenderTarget* - transfer_framebuffer_render_targets[1 + xenos::kMaxColorRenderTargets] = - {}; - transfer_framebuffer_render_targets[dest_rt_key.is_depth ? 0 : 1] = dest_rt; - const Framebuffer* transfer_framebuffer = GetHostRenderTargetsFramebuffer( - transfer_render_pass_key, dest_rt_key.pitch_tiles_at_32bpp, - transfer_framebuffer_render_targets); - if (!transfer_framebuffer) { - continue; - } - // Don't enter the render pass immediately - may still insert source - // barriers later. - - // Get the view for dynamic rendering (used for both transfers and clears). - VkImageView transfer_dest_view = dest_rt_key.is_depth - ? dest_vulkan_rt.view_depth_stencil() - : dest_vulkan_rt.view_color_transfer(); if (!current_transfers.empty()) { uint32_t dest_pitch_tiles = dest_rt_key.GetPitchTiles(); @@ -5040,6 +6272,8 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( uint32_t rt_sort_index = 0; TransferShaderKey new_transfer_shader_key; new_transfer_shader_key.dest_msaa_samples = dest_rt_key.msaa_samples; + new_transfer_shader_key.dest_color_rt_index = + dest_rt_key.is_depth || !in_current_render_pass ? 0 : i - 1; new_transfer_shader_key.dest_resource_format = dest_rt_key.resource_format; uint32_t stencil_clear_rectangle_count = 0; @@ -5087,38 +6321,15 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( (host_depth_source_vulkan_rt && !host_depth_source_is_copy) ? host_depth_source_vulkan_rt->key().msaa_samples : xenos::MsaaSamples::k1X; + new_transfer_shader_key.mode = GetTransferMode( + j != 0, dest_rt_key.is_depth, source_rt_key.is_depth, + host_depth_source_vulkan_rt != nullptr, + host_depth_source_is_copy); if (j) { - new_transfer_shader_key.mode = - source_rt_key.is_depth ? TransferMode::kDepthToStencilBit - : TransferMode::kColorToStencilBit; stencil_clear_rectangle_count += transfer.GetRectangles(dest_rt_key.base_tiles, dest_pitch_tiles, dest_rt_key.msaa_samples, dest_is_64bpp, nullptr, resolve_clear_rectangle); - } else { - if (dest_rt_key.is_depth) { - if (host_depth_source_vulkan_rt) { - if (host_depth_source_is_copy) { - new_transfer_shader_key.mode = - source_rt_key.is_depth - ? TransferMode::kDepthAndHostDepthCopyToDepth - : TransferMode::kColorAndHostDepthCopyToDepth; - } else { - new_transfer_shader_key.mode = - source_rt_key.is_depth - ? TransferMode::kDepthAndHostDepthToDepth - : TransferMode::kColorAndHostDepthToDepth; - } - } else { - new_transfer_shader_key.mode = - source_rt_key.is_depth ? TransferMode::kDepthToDepth - : TransferMode::kColorToDepth; - } - } else { - new_transfer_shader_key.mode = source_rt_key.is_depth - ? TransferMode::kDepthToColor - : TransferMode::kColorToColor; - } } current_transfer_invocations_.emplace_back(transfer, new_transfer_shader_key); @@ -5131,54 +6342,59 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( std::sort(current_transfer_invocations_.begin(), current_transfer_invocations_.end()); - for (auto it = current_transfer_invocations_.cbegin(); - it != current_transfer_invocations_.cend(); ++it) { - assert_not_null(it->transfer.source); - auto& source_vulkan_rt = - *static_cast(it->transfer.source); - command_processor_.PushImageMemoryBarrier( - source_vulkan_rt.image(), - ui::vulkan::util::InitializeSubresourceRange( - source_vulkan_rt.key().is_depth - ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) - : VK_IMAGE_ASPECT_COLOR_BIT), - source_vulkan_rt.current_stage_mask(), kSourceStageMask, - source_vulkan_rt.current_access_mask(), kSourceAccessMask, - source_vulkan_rt.current_layout(), kSourceLayout); - source_vulkan_rt.SetUsage(kSourceStageMask, kSourceAccessMask, - kSourceLayout); - auto host_depth_source_vulkan_rt = - static_cast(it->transfer.host_depth_source); - if (host_depth_source_vulkan_rt) { - TransferShaderKey transfer_shader_key = it->shader_key; - if (transfer_shader_key.mode == - TransferMode::kDepthAndHostDepthCopyToDepth || - transfer_shader_key.mode == - TransferMode::kColorAndHostDepthCopyToDepth) { - // Reading copied host depth from the EDRAM buffer. - UseEdramBuffer(EdramBufferUsage::kFragmentRead); - } else { - // Reading host depth from the texture. - command_processor_.PushImageMemoryBarrier( - host_depth_source_vulkan_rt->image(), - ui::vulkan::util::InitializeSubresourceRange( - VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT), - host_depth_source_vulkan_rt->current_stage_mask(), - kSourceStageMask, - host_depth_source_vulkan_rt->current_access_mask(), - kSourceAccessMask, - host_depth_source_vulkan_rt->current_layout(), kSourceLayout); - host_depth_source_vulkan_rt->SetUsage( - kSourceStageMask, kSourceAccessMask, kSourceLayout); + if (!in_current_render_pass) { + for (auto it = current_transfer_invocations_.cbegin(); + it != current_transfer_invocations_.cend(); ++it) { + assert_not_null(it->transfer.source); + auto& source_vulkan_rt = + *static_cast(it->transfer.source); + command_processor_.PushImageMemoryBarrier( + source_vulkan_rt.image(), + ui::vulkan::util::InitializeSubresourceRange( + source_vulkan_rt.key().is_depth + ? (VK_IMAGE_ASPECT_DEPTH_BIT | + VK_IMAGE_ASPECT_STENCIL_BIT) + : VK_IMAGE_ASPECT_COLOR_BIT), + source_vulkan_rt.current_stage_mask(), kSourceStageMask, + source_vulkan_rt.current_access_mask(), kSourceAccessMask, + source_vulkan_rt.current_layout(), kSourceLayout); + source_vulkan_rt.SetUsage(kSourceStageMask, kSourceAccessMask, + kSourceLayout); + auto host_depth_source_vulkan_rt = + static_cast(it->transfer.host_depth_source); + if (host_depth_source_vulkan_rt) { + TransferShaderKey transfer_shader_key = it->shader_key; + if (transfer_shader_key.mode == + TransferMode::kDepthAndHostDepthCopyToDepth || + transfer_shader_key.mode == + TransferMode::kColorAndHostDepthCopyToDepth) { + // Reading copied host depth from the EDRAM buffer. + UseEdramBuffer(EdramBufferUsage::kFragmentRead); + } else { + // Reading host depth from the texture. + command_processor_.PushImageMemoryBarrier( + host_depth_source_vulkan_rt->image(), + ui::vulkan::util::InitializeSubresourceRange( + VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT), + host_depth_source_vulkan_rt->current_stage_mask(), + kSourceStageMask, + host_depth_source_vulkan_rt->current_access_mask(), + kSourceAccessMask, + host_depth_source_vulkan_rt->current_layout(), kSourceLayout); + host_depth_source_vulkan_rt->SetUsage( + kSourceStageMask, kSourceAccessMask, kSourceLayout); + } } } } // Perform the transfers for the render target. - command_processor_.SubmitBarriersAndEnterRenderTargetCacheRenderPass( - transfer_render_pass, transfer_framebuffer, transfer_dest_view, - dest_rt_key.is_depth); + if (!in_current_render_pass) { + command_processor_.SubmitBarriersAndEnterRenderTargetCacheRenderPass( + transfer_render_pass, transfer_framebuffer, transfer_dest_view, + dest_rt_key.is_depth); + } if (stencil_clear_rectangle_count) { VkClearAttachment* stencil_clear_attachment; @@ -5548,9 +6764,11 @@ void VulkanRenderTargetCache::PerformTransfersAndResolveClears( // Perform the clear. if (resolve_clear_needed) { - command_processor_.SubmitBarriersAndEnterRenderTargetCacheRenderPass( - transfer_render_pass, transfer_framebuffer, transfer_dest_view, - dest_rt_key.is_depth); + if (!in_current_render_pass) { + command_processor_.SubmitBarriersAndEnterRenderTargetCacheRenderPass( + transfer_render_pass, transfer_framebuffer, transfer_dest_view, + dest_rt_key.is_depth); + } VkClearAttachment resolve_clear_attachment; resolve_clear_attachment.colorAttachment = 0; std::memset(&resolve_clear_attachment.clearValue, 0, diff --git a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h index 3061504e3..37a4c0415 100644 --- a/src/xenia/gpu/vulkan/vulkan_render_target_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_render_target_cache.h @@ -67,6 +67,10 @@ class VulkanRenderTargetCache final : public RenderTargetCache { xenos::ColorRenderTargetFormat color_3_view_format : xenos::kColorRenderTargetFormatBits; // 24 uint32_t color_rts_use_transfer_formats : 1; // 25 + // << 0 is depth, << 1...4 is color. Indicates that the attachment is + // fully overwritten before guest drawing in the render pass. + uint32_t depth_and_color_load_dont_care : 1 + + xenos::kMaxColorRenderTargets; // 30 }; uint32_t key = 0; struct Hasher { @@ -135,6 +139,11 @@ class VulkanRenderTargetCache final : public RenderTargetCache { const Framebuffer* last_update_framebuffer() const { return last_update_framebuffer_; } + bool HasPendingDrawPassTransfers() const { + return pending_draw_pass_transfer_mask_ != 0; + } + bool EncodePendingDrawPassTransfers(); + bool FlushPendingDrawPassTransfers(); // For VK_KHR_dynamic_rendering: fills in attachment info structures. // Returns the number of color attachments (may be less than max if trailing @@ -269,6 +278,19 @@ class VulkanRenderTargetCache final : public RenderTargetCache { size_t scaled_size_bytes; }; + struct DirectHostResolveShaderCode { + const uint32_t* code; + size_t size_bytes; + const char* debug_name; + }; + + static constexpr size_t kDirectHostResolveBppCount = 2; // 32, 64 + static constexpr size_t kDirectHostResolveMsaaCount = 3; // 1, 2, 4 + static constexpr size_t kDirectHostResolveScaledCount = 2; // false, true + static constexpr size_t kDirectHostResolveSourceUintCount = 2; + static constexpr size_t kDirectHostResolveFullDestCount = + 5; // 8, 16, 32, 64, 128 + static void GetEdramBufferUsageMasks(EdramBufferUsage usage, VkPipelineStageFlags& stage_mask_out, VkAccessFlags& access_mask_out); @@ -313,6 +335,29 @@ class VulkanRenderTargetCache final : public RenderTargetCache { std::array resolve_copy_pipelines_{}; + VkPipelineLayout direct_host_resolve_pipeline_layout_color_ = VK_NULL_HANDLE; + VkPipelineLayout direct_host_resolve_pipeline_layout_depth_ = VK_NULL_HANDLE; + static const DirectHostResolveShaderCode kDirectHostResolveColorShaders + [kDirectHostResolveBppCount][kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount][kDirectHostResolveSourceUintCount]; + static const DirectHostResolveShaderCode kDirectHostResolveColorFullShaders + [kDirectHostResolveMsaaCount][kDirectHostResolveScaledCount] + [kDirectHostResolveSourceUintCount][kDirectHostResolveFullDestCount]; + static const DirectHostResolveShaderCode + kDirectHostResolveDepthShaders[kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount]; + VkPipeline direct_host_resolve_pipelines_ + [kDirectHostResolveBppCount][kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount][kDirectHostResolveSourceUintCount] = {}; + VkPipeline direct_host_color_full_resolve_pipelines_ + [kDirectHostResolveMsaaCount][kDirectHostResolveScaledCount] + [kDirectHostResolveSourceUintCount][kDirectHostResolveFullDestCount] = {}; + VkPipeline + direct_host_depth_resolve_pipelines_[kDirectHostResolveMsaaCount] + [kDirectHostResolveScaledCount] = {}; + std::unique_ptr + direct_host_resolve_constants_pool_; + // 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. @@ -719,6 +764,12 @@ class VulkanRenderTargetCache final : public RenderTargetCache { } }; + struct TransferRectanglePlan { + std::array + rectangles; + uint32_t rectangle_count = 0; + }; + union DumpPipelineKey { uint32_t key; struct { @@ -862,6 +913,14 @@ class VulkanRenderTargetCache final : public RenderTargetCache { // samples. If there was a failure to create a pipeline, returns nullptr. VkPipeline const* GetTransferPipelines(TransferPipelineKey key); + // Selects the transfer mode for one ownership transfer from the source/dest + // aspects. Shared by PerformTransfersAndResolveClears and the draw-pass + // transfer preflight so the two paths can't disagree on the mode. + static TransferMode GetTransferMode(bool is_stencil_bit_pass, + bool dest_is_depth, bool source_is_depth, + bool has_host_depth_source, + bool host_depth_source_is_copy); + // 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 @@ -871,10 +930,42 @@ class VulkanRenderTargetCache final : public RenderTargetCache { uint32_t render_target_count, RenderTarget* const* render_targets, const std::vector* render_target_transfers, const uint64_t* render_target_resolve_clear_values = nullptr, - const Transfer::Rectangle* resolve_clear_rectangle = nullptr); + const Transfer::Rectangle* resolve_clear_rectangle = nullptr, + bool in_current_render_pass = false); + + void ClearPendingDrawPassTransfers(); + bool CanQueueDrawPassTransfers(uint32_t render_target_index, + RenderTarget* const* render_targets, + const std::vector& transfers) const; + // Fills one plan per transfer; returns false (and clears output) if any + // transfer yields zero rectangles, so on success plans match transfers 1:1. + bool BuildTransferRectanglePlans( + RenderTargetKey dest_key, const std::vector& transfers, + std::vector& transfer_rectangles_out) const; + bool PendingDrawPassTransfersFullyOverwriteTarget( + uint32_t render_target_index, RenderTarget* render_target, + const std::vector& transfers) const; + bool PreflightPendingDrawPassTransfers(RenderPassKey render_pass_key); + void PreparePendingDrawPassTransferBarriers(); VkPipeline GetDumpPipeline(DumpPipelineKey key); + VkPipeline GetDirectHostResolvePipeline(bool is_64bpp, + xenos::MsaaSamples msaa_samples, + bool scaled, bool source_is_uint); + VkPipeline GetDirectHostColorFullResolvePipeline( + xenos::MsaaSamples msaa_samples, bool scaled, bool source_is_uint, + draw_util::ResolveCopyShaderIndex copy_shader); + VkPipeline GetDirectHostDepthResolvePipeline(xenos::MsaaSamples msaa_samples, + bool scaled); + bool TryDirectHostResolveCopy( + const draw_util::ResolveInfo& resolve_info, + const draw_util::ResolveCopyShaderConstants& copy_shader_constants, + draw_util::ResolveCopyShaderIndex copy_shader, uint32_t dump_base, + uint32_t dump_row_length_used, uint32_t dump_rows, uint32_t dump_pitch, + VulkanSharedMemory& shared_memory, VulkanTextureCache& texture_cache, + uint32_t& written_address_out, uint32_t& written_length_out); + // Writes contents of host render targets within rectangles from // ResolveInfo::GetCopyEdramTileSpan to edram_buffer_. void DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used, @@ -931,6 +1022,12 @@ class VulkanRenderTargetCache final : public RenderTargetCache { // Temporary storage for PerformTransfersAndResolveClears. std::vector current_transfer_invocations_; + std::array + pending_draw_pass_render_targets_ = {}; + std::array, 1 + xenos::kMaxColorRenderTargets> + pending_draw_pass_transfers_; + uint32_t pending_draw_pass_transfer_mask_ = 0; + uint32_t pending_draw_pass_full_overwrite_mask_ = 0; // Temporary storage for DumpRenderTargets. std::vector dump_rectangles_; diff --git a/src/xenia/ui/metal/CMakeLists.txt b/src/xenia/ui/metal/CMakeLists.txt index 67c89cbf3..040d8c5e6 100644 --- a/src/xenia/ui/metal/CMakeLists.txt +++ b/src/xenia/ui/metal/CMakeLists.txt @@ -21,4 +21,7 @@ target_link_libraries(xenia-ui-metal PUBLIC "-framework AppKit" ) xe_shader_rules_metal(xenia-ui-metal ${CMAKE_CURRENT_SOURCE_DIR}/../shaders) +if(TARGET xenia-gpu-metal-metal-shaders) + add_dependencies(xenia-ui-metal xenia-gpu-metal-metal-shaders) +endif() xe_target_defaults(xenia-ui-metal) diff --git a/src/xenia/ui/metal/metal_api.h b/src/xenia/ui/metal/metal_api.h index 45d09f4a0..870cdaec4 100644 --- a/src/xenia/ui/metal/metal_api.h +++ b/src/xenia/ui/metal/metal_api.h @@ -14,4 +14,11 @@ #include "third_party/metal-cpp/Metal/MTLDevice.hpp" #include "third_party/metal-cpp/Metal/Metal.hpp" +#ifdef METAL_SHADER_CONVERTER_AVAILABLE +#ifndef IR_RUNTIME_METALCPP +#define IR_RUNTIME_METALCPP +#endif +#include "third_party/metal-shader-converter/include/metal_irconverter/metal_irconverter.h" +#endif // METAL_SHADER_CONVERTER_AVAILABLE + #endif diff --git a/src/xenia/ui/metal/metal_immediate_drawer.h b/src/xenia/ui/metal/metal_immediate_drawer.h index 09fe0042d..9c77fe352 100644 --- a/src/xenia/ui/metal/metal_immediate_drawer.h +++ b/src/xenia/ui/metal/metal_immediate_drawer.h @@ -73,8 +73,9 @@ class MetalImmediateDrawer : public ImmediateDrawer { MTL::RenderCommandEncoder* current_render_encoder_ = nullptr; bool batch_open_ = false; - // void* holds an Obj-C id — the .mm retains on store and + // void* holds Obj-C id values - the .mm retains on store and // CFReleases here, so the header stays ObjC-free. + void* current_vertex_buffer_ = nullptr; void* current_index_buffer_ = nullptr; }; diff --git a/src/xenia/ui/metal/metal_immediate_drawer.mm b/src/xenia/ui/metal/metal_immediate_drawer.mm index 31a18fc76..ac04ed42c 100644 --- a/src/xenia/ui/metal/metal_immediate_drawer.mm +++ b/src/xenia/ui/metal/metal_immediate_drawer.mm @@ -206,7 +206,7 @@ std::unique_ptr MetalImmediateDrawer::CreateTexture(uint32_t w texture_desc->setArrayLength(1); texture_desc->setSampleCount(1); texture_desc->setUsage(MTL::TextureUsageShaderRead); - texture_desc->setStorageMode(MTL::StorageModeManaged); + texture_desc->setStorageMode(MTL::StorageModeShared); MTL::Texture* metal_texture = device_->newTexture(texture_desc); texture_desc->release(); if (!metal_texture) { @@ -334,6 +334,9 @@ void MetalImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) { // XeSL MSL backend places push constants at buffer(0), so vertex attributes // bind at buffer(1) (matches the VertexDescriptor's bufferIndex=1). [encoder setVertexBuffer:vertex_buffer offset:0 atIndex:1]; + // newBufferWithBytes returns +1; keep the batch vertex data alive until all + // draws using the encoder state have been encoded. + current_vertex_buffer_ = (__bridge void*)vertex_buffer; if (batch.indices && batch.index_count > 0) { size_t index_buffer_size = batch.index_count * sizeof(uint16_t); @@ -402,6 +405,10 @@ void MetalImmediateDrawer::Draw(const ImmediateDraw& draw) { void MetalImmediateDrawer::EndDrawBatch() { assert_true(batch_open_); batch_open_ = false; + if (current_vertex_buffer_) { + CFRelease(current_vertex_buffer_); + current_vertex_buffer_ = nullptr; + } if (current_index_buffer_) { CFRelease(current_index_buffer_); current_index_buffer_ = nullptr; diff --git a/src/xenia/ui/metal/metal_presenter.h b/src/xenia/ui/metal/metal_presenter.h index f8bfc09ce..7b7679625 100644 --- a/src/xenia/ui/metal/metal_presenter.h +++ b/src/xenia/ui/metal/metal_presenter.h @@ -116,6 +116,30 @@ class MetalPresenter : public Presenter { bool EnsureApplyGammaPipelines(); bool EnsureGuestOutputPaintResources(uint32_t pixel_format); + struct PresenterTextureViewCacheEntry { + MTL::Texture* parent = nullptr; + MTL::Texture* view = nullptr; + bool full_descriptor = false; + MTL::PixelFormat pixel_format = MTL::PixelFormatInvalid; + MTL::TextureType texture_type = MTL::TextureType2D; + uint64_t level_location = 0; + uint64_t level_length = 0; + uint64_t slice_location = 0; + uint64_t slice_length = 0; + MTL::TextureSwizzleChannels swizzle = {}; + }; + + MTL::Texture* GetCachedPresenterPixelFormatView( + PresenterTextureViewCacheEntry& entry, MTL::Texture* parent, + MTL::PixelFormat pixel_format); + MTL::Texture* GetCachedPresenterTextureView( + PresenterTextureViewCacheEntry& entry, MTL::Texture* parent, + MTL::PixelFormat pixel_format, MTL::TextureType texture_type, + NS::Range level_range, NS::Range slice_range, + MTL::TextureSwizzleChannels swizzle); + void ReleaseCachedPresenterTextureView(PresenterTextureViewCacheEntry& entry); + void ReleaseCachedPresenterTextureViews(); + MetalProvider* provider_; MTL::Device* device_ = nullptr; @@ -140,6 +164,9 @@ class MetalPresenter : public Presenter { uint32_t gamma_ramp_buffer_size_ = 0; bool gamma_ramp_table_valid_ = false; bool gamma_ramp_pwl_valid_ = false; + PresenterTextureViewCacheEntry linear_presenter_view_; + PresenterTextureViewCacheEntry array_presenter_view_; + PresenterTextureViewCacheEntry swizzle_presenter_view_; id guest_output_pipeline_bilinear_ = nullptr; // id id guest_output_pipeline_bilinear_dither_ = nullptr; // id diff --git a/src/xenia/ui/metal/metal_presenter.mm b/src/xenia/ui/metal/metal_presenter.mm index 6f77ef53c..b9203c2a0 100644 --- a/src/xenia/ui/metal/metal_presenter.mm +++ b/src/xenia/ui/metal/metal_presenter.mm @@ -16,6 +16,7 @@ #include "third_party/metal-cpp/Metal/Metal.hpp" +#include "xenia/base/autorelease_pool_mac.h" #include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/gpu/shaders/bytecode/metal/apply_gamma_pwl_cs.h" @@ -74,7 +75,7 @@ MetalPresenter::MetalPresenter(MetalProvider* provider, HostGpuLossCallback host guest_output_waited_submission_ = 0; } -MetalPresenter::~MetalPresenter() = default; +MetalPresenter::~MetalPresenter() { Shutdown(); } bool MetalPresenter::Initialize() { // Use the shared MetalProvider command queue so presenter work is serialized @@ -105,6 +106,7 @@ void MetalPresenter::Shutdown() { if (shared_event_ && last_submission) { [(id)shared_event_ waitUntilSignaledValue:last_submission timeoutMS:UINT64_MAX]; } + ReleaseCachedPresenterTextureViews(); if (command_queue_) { command_queue_ = nullptr; } @@ -193,6 +195,81 @@ void MetalPresenter::Shutdown() { XELOGD("Metal presenter shut down"); } +void MetalPresenter::ReleaseCachedPresenterTextureView( + PresenterTextureViewCacheEntry& entry) { + if (entry.view) { + entry.view->release(); + } + entry = {}; +} + +void MetalPresenter::ReleaseCachedPresenterTextureViews() { + ReleaseCachedPresenterTextureView(linear_presenter_view_); + ReleaseCachedPresenterTextureView(array_presenter_view_); + ReleaseCachedPresenterTextureView(swizzle_presenter_view_); +} + +MTL::Texture* MetalPresenter::GetCachedPresenterPixelFormatView( + PresenterTextureViewCacheEntry& entry, MTL::Texture* parent, + MTL::PixelFormat pixel_format) { + if (!parent) { + return nullptr; + } + if (entry.view && entry.parent == parent && !entry.full_descriptor && + entry.pixel_format == pixel_format) { + return entry.view; + } + ReleaseCachedPresenterTextureView(entry); + MTL::Texture* view = parent->newTextureView(pixel_format); + if (!view) { + return nullptr; + } + entry.parent = parent; + entry.view = view; + entry.full_descriptor = false; + entry.pixel_format = pixel_format; + return view; +} + +MTL::Texture* MetalPresenter::GetCachedPresenterTextureView( + PresenterTextureViewCacheEntry& entry, MTL::Texture* parent, + MTL::PixelFormat pixel_format, MTL::TextureType texture_type, + NS::Range level_range, NS::Range slice_range, + MTL::TextureSwizzleChannels swizzle) { + if (!parent) { + return nullptr; + } + if (entry.view && entry.parent == parent && entry.full_descriptor && + entry.pixel_format == pixel_format && entry.texture_type == texture_type && + entry.level_location == level_range.location && + entry.level_length == level_range.length && + entry.slice_location == slice_range.location && + entry.slice_length == slice_range.length && + entry.swizzle.red == swizzle.red && entry.swizzle.green == swizzle.green && + entry.swizzle.blue == swizzle.blue && + entry.swizzle.alpha == swizzle.alpha) { + return entry.view; + } + ReleaseCachedPresenterTextureView(entry); + MTL::Texture* view = + parent->newTextureView(pixel_format, texture_type, level_range, + slice_range, swizzle); + if (!view) { + return nullptr; + } + entry.parent = parent; + entry.view = view; + entry.full_descriptor = true; + entry.pixel_format = pixel_format; + entry.texture_type = texture_type; + entry.level_location = level_range.location; + entry.level_length = level_range.length; + entry.slice_location = slice_range.location; + entry.slice_length = slice_range.length; + entry.swizzle = swizzle; + return view; +} + Surface::TypeFlags MetalPresenter::GetSupportedSurfaceTypes() const { return Surface::kTypeFlag_MacNSView; } @@ -353,6 +430,8 @@ Presenter::PaintResult MetalPresenter::PaintAndPresentImpl(bool execute_ui_drawe return PaintResult::kNotPresented; } + XE_SCOPED_AUTORELEASE_POOL("MetalPresenter::PaintAndPresentImpl"); + if (surface_width_in_points_ && surface_height_in_points_) { CGFloat drawable_width = CGFloat(surface_width_in_points_) * surface_scale_; CGFloat drawable_height = CGFloat(surface_height_in_points_) * surface_scale_; @@ -708,16 +787,15 @@ MetalPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(Surface& new_sur metal_layer.contentsScale = surface_scale; metal_layer.drawableSize = CGSizeMake(new_surface_width * surface_scale, new_surface_height * surface_scale); + metal_layer.minificationFilter = kCAFilterNearest; + metal_layer.magnificationFilter = kCAFilterNearest; const bool tearing_allowed = cvars::metal_allow_tearing; metal_layer.displaySyncEnabled = tearing_allowed ? NO : YES; + is_vsync_implicit_out = !tearing_allowed; metal_layer_ = metal_layer; - // When displaySyncEnabled is YES, CAMetalLayer blocks nextDrawable on the - // compositor vsync; when NO, the framerate_limit throttle is authoritative. - is_vsync_implicit_out = !tearing_allowed; - XELOGI("Metal surface connected successfully: {}x{} (scale={}, drawable={}x{})", new_surface_width, new_surface_height, surface_scale, uint32_t(metal_layer.drawableSize.width), uint32_t(metal_layer.drawableSize.height)); @@ -1230,14 +1308,20 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d copy_command_buffer.label = @"XeniaGuestOutputCopy"; } - uint64_t submission_id = - guest_output_submission_counter_.fetch_add(1, std::memory_order_relaxed) + 1; - if (submission_out) { - *submission_out = submission_id; - } - if (shared_event_) { - [copy_command_buffer encodeSignalEvent:(id)shared_event_ value:submission_id]; - } + auto commit_copy_command_buffer = [&]() { + uint64_t submission_id = + guest_output_submission_counter_.fetch_add(1, + std::memory_order_relaxed) + + 1; + if (submission_out) { + *submission_out = submission_id; + } + if (shared_event_) { + [copy_command_buffer encodeSignalEvent:(id)shared_event_ + value:submission_id]; + } + [copy_command_buffer commit]; + }; // Cast dest_texture to proper Metal texture type id dest_metal_texture = (id)dest_texture; @@ -1350,13 +1434,13 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d bool decode_srgb = false; bool encode_srgb = false; MTL::Texture* sample_texture = source_texture; - MTL::Texture* linear_view = nullptr; - MTL::Texture* swizzle_view = nullptr; - MTL::Texture* present_view = nullptr; + bool swizzle_view_used = false; if (is_srgb_format(src_format)) { MTLPixelFormat linear_format = linear_format_for_srgb(src_format); if (linear_format != src_format) { - linear_view = source_texture->newTextureView(static_cast(linear_format)); + MTL::Texture* linear_view = GetCachedPresenterPixelFormatView( + linear_presenter_view_, source_texture, + static_cast(linear_format)); if (linear_view) { sample_texture = linear_view; src_format = linear_format; @@ -1375,8 +1459,9 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d NS::Range level_range = NS::Range::Make(0, sample_texture->mipmapLevelCount()); NS::Range slice_range = NS::Range::Make(0, 1); MTL::TextureSwizzleChannels swizzle = sample_texture->swizzle(); - present_view = sample_texture->newTextureView(sample_texture->pixelFormat(), MTL::TextureType2D, - level_range, slice_range, swizzle); + MTL::Texture* present_view = GetCachedPresenterTextureView( + array_presenter_view_, sample_texture, sample_texture->pixelFormat(), + MTL::TextureType2D, level_range, slice_range, swizzle); if (present_view) { sample_texture = present_view; } else { @@ -1391,30 +1476,16 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d NS::Range slice_range = NS::Range::Make(0, sample_texture->arrayLength()); MTL::TextureSwizzleChannels swizzle = {MTL::TextureSwizzleBlue, MTL::TextureSwizzleGreen, MTL::TextureSwizzleRed, MTL::TextureSwizzleAlpha}; - swizzle_view = - sample_texture->newTextureView(sample_texture->pixelFormat(), sample_texture->textureType(), - level_range, slice_range, swizzle); + MTL::Texture* swizzle_view = GetCachedPresenterTextureView( + swizzle_presenter_view_, sample_texture, sample_texture->pixelFormat(), + sample_texture->textureType(), level_range, slice_range, swizzle); if (swizzle_view) { sample_texture = swizzle_view; swap_rb_in_shader = false; + swizzle_view_used = true; } } - auto release_views = [&]() { - if (swizzle_view) { - swizzle_view->release(); - swizzle_view = nullptr; - } - if (present_view) { - present_view->release(); - present_view = nullptr; - } - if (linear_view) { - linear_view->release(); - linear_view = nullptr; - } - }; - if (is_srgb_format(dst_format) && !is_srgb_format(src_format)) { encode_srgb = true; } @@ -1444,7 +1515,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d XELOGE("MetalPresenter::CopyTextureToGuestOutput: Failed to create " "gamma output texture {}x{}", copy_width, copy_height); - release_views(); return false; } gamma_output_width_ = copy_width; @@ -1458,7 +1528,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d if (!compute_encoder) { XELOGE("MetalPresenter::CopyTextureToGuestOutput: Failed to create " "compute encoder for gamma"); - release_views(); return false; } if (cvars::metal_presenter_debug_markers) { @@ -1471,7 +1540,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d use_pwl_gamma_ramp ? gamma_ramp_pwl_texture_ : gamma_ramp_table_texture_; if (!pipeline || !ramp_texture) { XELOGE("MetalPresenter::CopyTextureToGuestOutput: missing gamma pipeline"); - release_views(); return false; } @@ -1505,17 +1573,15 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d last_used_shader = true; } - bool swap_rb_after_gamma = force_swap_rb && (swizzle_view == nullptr); + bool swap_rb_after_gamma = force_swap_rb && !swizzle_view_used; if (needs_gamma_convert || swap_rb_after_gamma) { if (!EnsureCopyTextureConvertPipelines()) { - release_views(); return false; } id convert_encoder = [copy_command_buffer computeCommandEncoder]; if (!convert_encoder) { XELOGE("MetalPresenter::CopyTextureToGuestOutput: Failed to create " "compute encoder for gamma conversion"); - release_views(); return false; } if (cvars::metal_presenter_debug_markers) { @@ -1549,8 +1615,7 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d [convert_encoder endEncoding]; } - [copy_command_buffer commit]; - release_views(); + commit_copy_command_buffer(); return true; } @@ -1565,7 +1630,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d int(dst_format)); if (!EnsureCopyTextureConvertPipelines()) { - release_views(); return false; } @@ -1575,7 +1639,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d XELOGE("MetalPresenter::CopyTextureToGuestOutput: Unsupported source " "texture type {} for conversion", int(src_type)); - release_views(); return false; } @@ -1583,7 +1646,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d if (!compute_encoder) { XELOGE("MetalPresenter::CopyTextureToGuestOutput: Failed to create " "compute encoder"); - release_views(); return false; } if (cvars::metal_presenter_debug_markers) { @@ -1633,9 +1695,8 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d threadsPerThreadgroup:threads_per_threadgroup]; [compute_encoder endEncoding]; - [copy_command_buffer commit]; + commit_copy_command_buffer(); XELOGD("MetalPresenter::CopyTextureToGuestOutput: Shader copy completed successfully"); - release_views(); return true; } @@ -1653,7 +1714,6 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d id blit_encoder = [copy_command_buffer blitCommandEncoder]; if (!blit_encoder) { XELOGE("MetalPresenter::CopyTextureToGuestOutput: Failed to create blit encoder"); - release_views(); return false; } @@ -1674,10 +1734,9 @@ bool MetalPresenter::CopyTextureToGuestOutput(MTL::Texture* source_texture, id d [blit_encoder endEncoding]; - [copy_command_buffer commit]; + commit_copy_command_buffer(); XELOGD("MetalPresenter::CopyTextureToGuestOutput: Copy completed successfully"); - release_views(); return true; } diff --git a/src/xenia/ui/metal/metal_provider.cc b/src/xenia/ui/metal/metal_provider.cc index 9876bc8f6..632f58942 100644 --- a/src/xenia/ui/metal/metal_provider.cc +++ b/src/xenia/ui/metal/metal_provider.cc @@ -19,7 +19,21 @@ namespace xe { namespace ui { namespace metal { +namespace { + +void ConfigureMetalValidationEnvironment() { + // Enable the Metal validation layer in Debug and Checked builds before any + // Metal device is created. +#if !defined(NDEBUG) || defined(MTL_DEBUG_LAYER) + setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 1); + setenv("METAL_DEBUG_ERROR_MODE", "assert", 1); +#endif +} + +} // namespace + bool MetalProvider::IsMetalAPIAvailable() { + ConfigureMetalValidationEnvironment(); MTL::Device* device = MTL::CreateSystemDefaultDevice(); bool available = (device != nullptr); if (device) { @@ -52,6 +66,7 @@ MetalProvider::~MetalProvider() { } bool MetalProvider::Initialize() { + ConfigureMetalValidationEnvironment(); device_ = MTL::CreateSystemDefaultDevice(); if (!device_) { XELOGE("Failed to create Metal device"); @@ -64,10 +79,7 @@ bool MetalProvider::Initialize() { return false; } - // Enable the Metal validation layer in Debug and Checked builds. #if !defined(NDEBUG) || defined(MTL_DEBUG_LAYER) - setenv("METAL_DEVICE_WRAPPER_TYPE", "1", 1); - setenv("METAL_DEBUG_ERROR_MODE", "assert", 1); XELOGI("Metal validation layer enabled"); #endif return true; diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index b23e8a7fb..cd71bf515 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -406,6 +406,159 @@ if(APPLE) "LINKER:-force_load,$") endif() +# ============================================================================== +# Metal Shader Converter and dxilconv (Apple only) +# ============================================================================== +if(APPLE) + set(_xe_metal_shader_converter_root + "${CMAKE_CURRENT_SOURCE_DIR}/metal-shader-converter") + if(XE_PLATFORM_IOS) + set(_xe_metal_shader_converter_dylib + "${_xe_metal_shader_converter_root}/lib_iOS/libmetalirconverter.dylib") + else() + set(_xe_metal_shader_converter_dylib + "${_xe_metal_shader_converter_root}/lib/libmetalirconverter.dylib") + endif() + if(NOT EXISTS "${_xe_metal_shader_converter_dylib}") + message(FATAL_ERROR + "Metal Shader Converter dylib not found at " + "${_xe_metal_shader_converter_dylib}.") + endif() + + add_library(xenia-third-party-metal-shader-converter SHARED IMPORTED GLOBAL) + set_target_properties(xenia-third-party-metal-shader-converter PROPERTIES + IMPORTED_CONFIGURATIONS "Release" + IMPORTED_LOCATION "${_xe_metal_shader_converter_dylib}" + IMPORTED_LOCATION_RELEASE "${_xe_metal_shader_converter_dylib}" + INTERFACE_INCLUDE_DIRECTORIES + "${_xe_metal_shader_converter_root}/include;${_xe_metal_shader_converter_root}/include/metal_irconverter;${_xe_metal_shader_converter_root}/include/metal_irconverter_runtime" + INTERFACE_COMPILE_DEFINITIONS + "METAL_SHADER_CONVERTER_AVAILABLE;IR_RUNTIME_METALCPP") + + set(_xe_dxilconv_root + "${CMAKE_CURRENT_SOURCE_DIR}/DirectXShaderCompiler") + set(_xe_dxilconv_headers_root + "${CMAKE_CURRENT_SOURCE_DIR}/DirectX-Headers/include") + # DXC's cross-compile TableGen helper expects Makefile-based native builds. + # Do not inherit Xcode from the outer app build. + set(_xe_dxilconv_cmake_generator_args -G "Unix Makefiles") + set(_xe_dxilconv_cmake_platform_args) + if(XE_PLATFORM_IOS) + set(_xe_dxilconv_build_dir "${_xe_dxilconv_root}/build_dxilconv_ios") + set(_xe_dxilconv_arch "arm64") + set(_xe_dxilconv_triple "arm64-apple-ios") + list(APPEND _xe_dxilconv_cmake_platform_args + -DCMAKE_SYSTEM_NAME=iOS + "-DCMAKE_SYSTEM_PROCESSOR=${_xe_dxilconv_arch}" + -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY + "-DCMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}") + elseif(XE_TARGET_X86_64) + set(_xe_dxilconv_build_dir + "${_xe_dxilconv_root}/build_dxilconv_macos_x86_64") + set(_xe_dxilconv_arch "x86_64") + set(_xe_dxilconv_triple "x86_64-apple-darwin") + else() + set(_xe_dxilconv_build_dir "${_xe_dxilconv_root}/build_dxilconv_macos") + set(_xe_dxilconv_arch "arm64") + set(_xe_dxilconv_triple "arm64-apple-darwin") + endif() + set(_xe_dxilconv_macos_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") + if(XE_PLATFORM_IOS) + set(_xe_dxilconv_macos_deployment_target "${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + set(_xe_dxilconv_dylib "${_xe_dxilconv_build_dir}/lib/libdxilconv.dylib") + set(_xe_dxbc2dxil_bin "${_xe_dxilconv_build_dir}/bin/dxbc2dxil") + set(_xe_dxilconv_outputs "${_xe_dxilconv_dylib}") + if(NOT XE_PLATFORM_IOS) + list(APPEND _xe_dxilconv_outputs "${_xe_dxbc2dxil_bin}") + endif() + # CMake's bare `--parallel` becomes an unbounded `make -j` with Unix + # Makefiles on some hosts. DXC's cross build spawns large native TableGen + # tools inside the app build, so keep this nested build deterministic. + set(_xe_dxilconv_build_jobs 2) + + add_custom_command( + OUTPUT ${_xe_dxilconv_outputs} + COMMAND "${CMAKE_COMMAND}" -E make_directory "${_xe_dxilconv_build_dir}" + COMMAND "${CMAKE_COMMAND}" + ${_xe_dxilconv_cmake_generator_args} + -S "${_xe_dxilconv_root}" + -B "${_xe_dxilconv_build_dir}" + ${_xe_dxilconv_cmake_platform_args} + "-DD3D12_INCLUDE_DIR=${_xe_dxilconv_headers_root}/directx" + "-DDXGI_INCLUDE_DIR=${_xe_dxilconv_headers_root}/directx" + "-DWSL_INCLUDE_DIR=${_xe_dxilconv_headers_root}/wsl" + -DCMAKE_BUILD_TYPE=Release + "-DCMAKE_OSX_ARCHITECTURES=${_xe_dxilconv_arch}" + "-DCMAKE_OSX_DEPLOYMENT_TARGET=${_xe_dxilconv_macos_deployment_target}" + -DLLVM_TARGETS_TO_BUILD=None + -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD= + "-DLLVM_DEFAULT_TARGET_TRIPLE=${_xe_dxilconv_triple}" + -DLLVM_INSTALL_TOOLCHAIN_ONLY=ON + -DLLVM_INCLUDE_TOOLS=OFF + -DLLVM_BUILD_TOOLS=OFF + -DLLVM_ENABLE_THREADS=ON + -DLLVM_ENABLE_PIC=ON + -DLLVM_BUILD_32_BITS=OFF + -DBUILD_SHARED_LIBS=OFF + -DLLVM_OPTIMIZED_TABLEGEN=ON + -DLLVM_USE_INTEL_JITEVENTS=OFF + -DLLVM_ENABLE_ZLIB=ON + -DLLVM_ENABLE_TERMINFO=OFF + -DLLVM_ENABLE_LIBXML2=OFF + -DCLANG_BUILD_EXAMPLES=OFF + -DLLVM_INCLUDE_TESTS=OFF + -DLLVM_INCLUDE_EXAMPLES=OFF + -DLLVM_INCLUDE_DOCS=OFF + -DCLANG_INCLUDE_TESTS=OFF + -DHLSL_INCLUDE_TESTS=OFF + -DENABLE_SPIRV_CODEGEN=OFF + -DSPIRV_BUILD_TESTS=OFF + -DCLANG_ENABLE_STATIC_ANALYZER=OFF + -DCLANG_ENABLE_ARCMT=OFF + -DLLVM_ENABLE_BINDINGS=OFF + -DLLVM_ENABLE_EH=ON + -DLLVM_ENABLE_RTTI=ON + -DLLVM_REQUIRES_EH=ON + -DLLVM_REQUIRES_RTTI=ON + -DCMAKE_C_COMPILER=/usr/bin/clang + -DCMAKE_CXX_COMPILER=/usr/bin/clang++ + -DCMAKE_CXX_STANDARD=17 + -DCMAKE_CXX_STANDARD_REQUIRED=ON + -DCMAKE_CXX_EXTENSIONS=OFF + "-DCMAKE_C_FLAGS=" + "-DCMAKE_CXX_FLAGS=-stdlib=libc++ -Wno-unknown-warning-option -Wno-deprecated-declarations -Wno-deprecated -Wno-deprecated-literal-operator -Wno-invalid-specialization -Wno-nontrivial-memcall" + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + COMMAND "${CMAKE_COMMAND}" --build "${_xe_dxilconv_build_dir}" + --target dxilconv --config Release --parallel ${_xe_dxilconv_build_jobs} + DEPENDS + "${_xe_dxilconv_root}/projects/dxilconv/include/DxbcConverter.h" + "${_xe_dxilconv_root}/projects/dxilconv/tools/dxilconv/dxilconv_unix.cpp" + "${_xe_dxilconv_root}/projects/dxilconv/tools/dxbc2dxil/dxbc2dxil_unix.cpp" + COMMENT "Building dxilconv for Metal shader conversion" + VERBATIM) + if(NOT XE_PLATFORM_IOS) + add_custom_command( + OUTPUT "${_xe_dxbc2dxil_bin}" + APPEND + COMMAND "${CMAKE_COMMAND}" --build "${_xe_dxilconv_build_dir}" + --target dxbc2dxil --config Release --parallel ${_xe_dxilconv_build_jobs} + ) + endif() + add_custom_target(xenia-third-party-dxilconv-build + DEPENDS ${_xe_dxilconv_outputs}) + + add_library(xenia-third-party-dxilconv SHARED IMPORTED GLOBAL) + add_dependencies(xenia-third-party-dxilconv xenia-third-party-dxilconv-build) + set_target_properties(xenia-third-party-dxilconv PROPERTIES + IMPORTED_CONFIGURATIONS "Release" + IMPORTED_LOCATION "${_xe_dxilconv_dylib}" + IMPORTED_LOCATION_RELEASE "${_xe_dxilconv_dylib}" + INTERFACE_INCLUDE_DIRECTORIES + "${_xe_dxilconv_root}/include;${_xe_dxilconv_root}/projects/dxilconv/include") +endif() + # ============================================================================== # xenia-shader-cc (build-time host tool: GLSL/XeSL -> SPIR-V -> .h, and on # Apple, XeSL -> MSL metallib -> .h via xcrun metal) diff --git a/third_party/DirectXShaderCompiler b/third_party/DirectXShaderCompiler index 69e54e290..55db9bc0b 160000 --- a/third_party/DirectXShaderCompiler +++ b/third_party/DirectXShaderCompiler @@ -1 +1 @@ -Subproject commit 69e54e29086b7035acffb304cec57a350225f8b0 +Subproject commit 55db9bc0bc574816d60073872b6a209f7c3d7f0a diff --git a/third_party/metal-shader-converter b/third_party/metal-shader-converter new file mode 160000 index 000000000..2bbd3665b --- /dev/null +++ b/third_party/metal-shader-converter @@ -0,0 +1 @@ +Subproject commit 2bbd3665b0143c1f18043ddd9a20de77ba65e834 diff --git a/tools/build/shader_cc.cc b/tools/build/shader_cc.cc index 3f205ff5b..66ac7bca8 100644 --- a/tools/build/shader_cc.cc +++ b/tools/build/shader_cc.cc @@ -1,6 +1,9 @@ // Build-time shader compiler for Xenia's built-in shaders. // // Usage: xenia-shader-cc [--msl | --dxbc] [--depfile ] +// [--define ] [--identifier ] +// [--metal-sdk ] [--metal-std ] +// [--metal-min-version-flag ] // // // Default: GLSL/XeSL -> SPIR-V, linked in-process via glslang and @@ -60,6 +63,13 @@ struct StageInfo { EShLanguage language; }; +struct ShaderDefine { + std::string name; + std::string value; + + std::string argument() const { return name + "=" + value; } +}; + constexpr StageInfo kStages[] = { {"vs", EShLangVertex}, {"hs", EShLangTessControl}, {"ds", EShLangTessEvaluation}, {"gs", EShLangGeometry}, @@ -98,6 +108,69 @@ std::string IdentifierFromFilename(const std::string& filename) { return stem; } +bool IsCIdentifier(std::string_view value) { + if (value.empty()) { + return false; + } + auto is_alpha_or_underscore = [](unsigned char c) { + return std::isalpha(c) || c == '_'; + }; + auto is_alnum_or_underscore = [](unsigned char c) { + return std::isalnum(c) || c == '_'; + }; + if (!is_alpha_or_underscore(static_cast(value.front()))) { + return false; + } + for (char c : value.substr(1)) { + if (!is_alnum_or_underscore(static_cast(c))) { + return false; + } + } + return true; +} + +bool ParseDefine(std::string_view text, ShaderDefine* out) { + size_t equals = text.find('='); + if (equals == std::string_view::npos || equals == 0 || + equals + 1 == text.size()) { + return false; + } + std::string_view name = text.substr(0, equals); + std::string_view value = text.substr(equals + 1); + if (!IsCIdentifier(name)) { + return false; + } + for (char c : value) { + if (c == '\n' || c == '\r') { + return false; + } + } + out->name.assign(name); + out->value.assign(value); + return true; +} + +void AppendDefines(std::vector* args, + const std::vector& defines, + const char* flag) { + for (const auto& define : defines) { + args->push_back(flag); + args->push_back(define.argument()); + } +} + +std::string SpirvPreamble(const std::vector& defines) { + std::string preamble = "#define SHADING_LANGUAGE_GLSL_XE 1\n"; + for (const auto& define : defines) { + preamble += "#define "; + preamble += define.name; + preamble += " "; + preamble += define.value; + preamble += "\n"; + } + return preamble; +} + bool ReadFile(const std::filesystem::path& path, std::string* out) { std::ifstream in(path, std::ios::binary); if (!in) { @@ -342,7 +415,16 @@ std::string FindFxc() { int main(int argc, char** argv) { bool msl_mode = false; bool dxbc_mode = false; + bool metal_debug = false; +#ifdef XE_SHADER_CC_METAL + std::string metal_sdk = "macosx"; + std::string metal_std = "macos-metal2.4"; + std::string metal_min_version_flag = + std::string("-mmacosx-version-min=") + XE_METAL_MIN_OS; +#endif std::string depfile_path; + std::vector defines; + std::string identifier_override; int arg_idx = 1; while (arg_idx < argc && argv[arg_idx][0] == '-') { if (std::strcmp(argv[arg_idx], "--msl") == 0) { @@ -353,6 +435,39 @@ int main(int argc, char** argv) { #endif msl_mode = true; ++arg_idx; + } else if (std::strcmp(argv[arg_idx], "--metal-debug") == 0) { + // Embed MSL source + line tables in the .metallib so the shader is + // viewable with per-line cost in the Xcode GPU trace. Increases size. + metal_debug = true; + ++arg_idx; + } else if (std::strcmp(argv[arg_idx], "--metal-sdk") == 0) { + if (arg_idx + 1 >= argc) { + std::fprintf(stderr, "--metal-sdk requires an SDK name\n"); + return 1; + } +#ifdef XE_SHADER_CC_METAL + metal_sdk = argv[arg_idx + 1]; +#endif + arg_idx += 2; + } else if (std::strcmp(argv[arg_idx], "--metal-std") == 0) { + if (arg_idx + 1 >= argc) { + std::fprintf(stderr, "--metal-std requires a standard name\n"); + return 1; + } +#ifdef XE_SHADER_CC_METAL + metal_std = argv[arg_idx + 1]; +#endif + arg_idx += 2; + } else if (std::strcmp(argv[arg_idx], "--metal-min-version-flag") == 0) { + if (arg_idx + 1 >= argc) { + std::fprintf(stderr, + "--metal-min-version-flag requires a compiler flag\n"); + return 1; + } +#ifdef XE_SHADER_CC_METAL + metal_min_version_flag = argv[arg_idx + 1]; +#endif + arg_idx += 2; } else if (std::strcmp(argv[arg_idx], "--dxbc") == 0) { dxbc_mode = true; ++arg_idx; @@ -363,16 +478,44 @@ int main(int argc, char** argv) { } depfile_path = argv[arg_idx + 1]; arg_idx += 2; + } else if (std::strcmp(argv[arg_idx], "--define") == 0) { + if (arg_idx + 1 >= argc) { + std::fprintf(stderr, "--define requires NAME=VALUE\n"); + return 1; + } + ShaderDefine define; + if (!ParseDefine(argv[arg_idx + 1], &define)) { + std::fprintf(stderr, "invalid --define value: %s\n", + argv[arg_idx + 1]); + return 1; + } + defines.push_back(std::move(define)); + arg_idx += 2; + } else if (std::strcmp(argv[arg_idx], "--identifier") == 0) { + if (arg_idx + 1 >= argc) { + std::fprintf(stderr, "--identifier requires a name\n"); + return 1; + } + if (!IsCIdentifier(argv[arg_idx + 1])) { + std::fprintf(stderr, "invalid --identifier value: %s\n", + argv[arg_idx + 1]); + return 1; + } + identifier_override = argv[arg_idx + 1]; + arg_idx += 2; } else { std::fprintf(stderr, "unknown flag: %s\n", argv[arg_idx]); return 1; } } if (argc - arg_idx != 2) { - std::fprintf( - stderr, - "Usage: %s [--msl | --dxbc] [--depfile ] \n", - argv[0]); + std::fprintf(stderr, + "Usage: %s [--msl | --dxbc] [--depfile ] " + "[--define ] [--identifier ] " + "[--metal-sdk ] [--metal-std ] " + "[--metal-min-version-flag ] " + " \n", + argv[0]); return 1; } if (msl_mode && dxbc_mode) { @@ -383,7 +526,9 @@ int main(int argc, char** argv) { std::filesystem::path input_path = argv[arg_idx]; std::filesystem::path output_path = argv[arg_idx + 1]; std::string input_filename = input_path.filename().string(); - std::string identifier = IdentifierFromFilename(input_filename); + std::string identifier = identifier_override.empty() + ? IdentifierFromFilename(input_filename) + : identifier_override; if (msl_mode) { #ifdef XE_SHADER_CC_METAL @@ -405,12 +550,20 @@ int main(int argc, char** argv) { "metal", "-x", "metal", - "-std=macos-metal2.3", - "-mmacosx-version-min=" XE_METAL_MIN_OS, + "-std=" + metal_std, + metal_min_version_flag, "-D", "SHADING_LANGUAGE_MSL_XE=1", "-w", }; + if (metal_debug) { + // Record preprocessed MSL source and line tables into the AIR/metallib + // so Xcode's GPU trace can show source + per-line cost for these + // offline-compiled compute/resolve shaders. + metal_cmd.push_back("-frecord-sources"); + metal_cmd.push_back("-gline-tables-only"); + } + AppendDefines(&metal_cmd, defines, "-D"); std::string input_dir = input_path.parent_path().string(); if (!input_dir.empty()) { metal_cmd.push_back("-I"); @@ -507,8 +660,9 @@ int main(int argc, char** argv) { "-Fh", output_path.string(), "-Vn", identifier, "-nologo", - input_path.string(), }); + AppendDefines(&cmd, defines, "-D"); + cmd.push_back(input_path.string()); } else { cmd.insert(cmd.end(), { "/D", "SHADING_LANGUAGE_HLSL_XE=1", @@ -522,8 +676,9 @@ int main(int argc, char** argv) { "/Qstrip_priv", "/Gfp", "/nologo", - input_path.string(), }); + AppendDefines(&cmd, defines, "/D"); + cmd.push_back(input_path.string()); } if (RunCommand(cmd, /*silent_stdout=*/true) != 0) { std::fprintf(stderr, "fxc failed for %s\n", @@ -560,8 +715,9 @@ int main(int argc, char** argv) { "-I", src_dir, "-nologo", - input_path.string(), }); + AppendDefines(&pp_cmd, defines, "-D"); + pp_cmd.push_back(input_path.string()); } else { pp_cmd.insert(pp_cmd.end(), { "/P", @@ -571,8 +727,9 @@ int main(int argc, char** argv) { "/I", src_dir, "/nologo", - input_path.string(), }); + AppendDefines(&pp_cmd, defines, "/D"); + pp_cmd.push_back(input_path.string()); } if (RunCommand(pp_cmd, /*silent_stdout=*/true) != 0) { std::fprintf(stderr, @@ -668,7 +825,8 @@ int main(int argc, char** argv) { } shader.setStringsWithLengthsAndNames(source_strings, source_lengths, source_names, 1); - shader.setPreamble("#define SHADING_LANGUAGE_GLSL_XE 1\n"); + std::string preamble = SpirvPreamble(defines); + shader.setPreamble(preamble.c_str()); // Match the old `glslangValidator -V` default: Vulkan 1.0 / SPV 1.0, which // stays compatible with the Vulkan 1.0 devices the runtime still supports. shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan,