diff --git a/.gitmodules b/.gitmodules index 1e63c6906..ed4fda155 100644 --- a/.gitmodules +++ b/.gitmodules @@ -102,10 +102,8 @@ url = https://github.com/microsoft/DirectX-Headers.git [submodule "third_party/metal-cpp"] path = third_party/metal-cpp - url = https://github.com/wmarti/metal-cpp.git -[submodule "third_party/metal-shader-converter"] - path = third_party/metal-shader-converter - url = https://github.com/wmarti/metal-shader-converter.git + url = https://github.com/bkaradzic/metal-cpp.git + branch = metal-cpp_26 [submodule "third_party/asio"] path = third_party/asio url = https://github.com/chriskohlhoff/asio.git diff --git a/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc b/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc deleted file mode 100644 index 0bc3e921e..000000000 --- a/src/xenia/gpu/metal/dxbc_to_dxil_converter.cc +++ /dev/null @@ -1,234 +0,0 @@ -/** - ****************************************************************************** - * 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 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 "xenia/base/logging.h" - -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; - -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(); - - dxilconv_path_ = "linked"; - is_available_ = true; - if (extra_options && *extra_options) { - XELOGD("DxbcToDxilConverter: Using extra options: {}", extra_options); - } else if (extra_options && !*extra_options) { - XELOGD("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 deleted file mode 100644 index 24c08c446..000000000 --- a/src/xenia/gpu/metal/dxbc_to_dxil_converter.h +++ /dev/null @@ -1,65 +0,0 @@ -/** - ****************************************************************************** - * 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: - 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_; } - - // Get the dxilconv library path (if resolved). - const std::string& GetDxbc2DxilPath() const { return dxilconv_path_; } - - private: - bool is_available_ = false; - std::string dxilconv_path_; - 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 deleted file mode 100644 index c83cb1b32..000000000 --- a/src/xenia/gpu/metal/ir_runtime_impl.mm +++ /dev/null @@ -1,17 +0,0 @@ -/** - ****************************************************************************** - * 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" - -#define IR_RUNTIME_METALCPP -#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_command_processor.cc b/src/xenia/gpu/metal/metal_command_processor.cc deleted file mode 100644 index d161a971f..000000000 --- a/src/xenia/gpu/metal/metal_command_processor.cc +++ /dev/null @@ -1,6528 +0,0 @@ -/** - ****************************************************************************** - * 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_command_processor.h" -#include "xenia/gpu/gpu_flags.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 "third_party/metal-cpp/Foundation/NSURL.hpp" -#include "third_party/metal-cpp/Metal/MTLEvent.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_graphics_system.h" -#include "xenia/gpu/metal/metal_shader_cache.h" -#include "xenia/gpu/metal/metal_shader_converter.h" -#include "xenia/gpu/packet_disassembler.h" -#include "xenia/gpu/registers.h" -#include "xenia/gpu/texture_info.h" -#include "xenia/gpu/xenos.h" -#include "xenia/kernel/kernel_state.h" -#include "xenia/kernel/user_module.h" -#include "xenia/ui/metal/metal_gpu_completion_timeline.h" -using BYTE = uint8_t; -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/adaptive_quad_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/adaptive_triangle_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_quad_1cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_quad_4cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_triangle_1cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/continuous_triangle_3cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_quad_1cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_quad_4cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_triangle_1cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/discrete_triangle_3cp_hs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/tessellation_adaptive_vs.h" -#include "xenia/gpu/shaders/bytecode/d3d12_5_1/tessellation_indexed_vs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_downscale_cs.h" -#include "xenia/ui/metal/metal_presenter.h" - -#ifndef DISPATCH_DATA_DESTRUCTOR_NONE -#define DISPATCH_DATA_DESTRUCTOR_NONE DISPATCH_DATA_DESTRUCTOR_DEFAULT -#endif - -// Metal IR Converter Runtime - defines IRDescriptorTableEntry and bind points -#define IR_RUNTIME_METALCPP -#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" - -// IR Converter bind points (from metal_irconverter_runtime.h) -// kIRDescriptorHeapBindPoint = 0 - -DECLARE_bool(clear_memory_page_state); -DECLARE_bool(submit_on_primary_buffer_end); -// kIRSamplerHeapBindPoint = 1 -// kIRArgumentBufferBindPoint = 2 -// kIRArgumentBufferDrawArgumentsBindPoint = 4 -// kIRArgumentBufferUniformsBindPoint = 5 -// kIRVertexBufferBindPoint = 6 - -namespace xe { -namespace gpu { -namespace metal { - -namespace { -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() : ""); - } -} - -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; - } - 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) { - // 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]; -} - -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]; -} - -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 DownscaleResolveTileData(const uint8_t* source, uint8_t* dest, - uint32_t tile_count, uint32_t pixel_size_log2, - uint32_t scale_x, uint32_t scale_y, - bool half_pixel_offset) { - if (!source || !dest || tile_count == 0) { - return; - } - const uint32_t pixel_size = 1u << pixel_size_log2; - const uint32_t scale_xy = scale_x * scale_y; - const uint32_t tile_size_1x = 32u * 32u * pixel_size; - const uint32_t tile_size_scaled = tile_size_1x * scale_xy; - const uint32_t block_sample_offset = - (half_pixel_offset && scale_xy > 1u) - ? ((scale_x >> 1u) + (scale_y >> 1u) * scale_x) - : 0u; - const uint32_t block_sample_offset_bytes = block_sample_offset * pixel_size; - const uint32_t src_pixel_stride = pixel_size * scale_xy; - - for (uint32_t tile_index = 0; tile_index < tile_count; ++tile_index) { - const uint8_t* src_tile = - source + tile_index * tile_size_scaled + block_sample_offset_bytes; - uint8_t* dst_tile = dest + tile_index * tile_size_1x; - for (uint32_t pixel_index = 0; pixel_index < 32u * 32u; ++pixel_index) { - const uint32_t src_offset = pixel_index * src_pixel_stride; - const uint32_t dst_offset = pixel_index * pixel_size; - switch (pixel_size_log2) { - case 0: { - dst_tile[dst_offset] = src_tile[src_offset]; - break; - } - case 1: { - uint16_t value; - std::memcpy(&value, src_tile + src_offset, sizeof(value)); - std::memcpy(dst_tile + dst_offset, &value, sizeof(value)); - break; - } - case 2: { - uint32_t value; - std::memcpy(&value, src_tile + src_offset, sizeof(value)); - std::memcpy(dst_tile + dst_offset, &value, sizeof(value)); - break; - } - case 3: { - uint64_t value; - std::memcpy(&value, src_tile + src_offset, sizeof(value)); - std::memcpy(dst_tile + dst_offset, &value, sizeof(value)); - break; - } - default: - break; - } - } - } -} - -} // namespace - -MetalCommandProcessor::MetalCommandProcessor( - MetalGraphicsSystem* graphics_system, kernel::KernelState* kernel_state) - : CommandProcessor(graphics_system, kernel_state) {} - -MetalCommandProcessor::~MetalCommandProcessor() { - // 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) - if (current_render_encoder_) { - // The encoder may already be ended if the command buffer was committed - // In that case, just release it - current_render_encoder_->release(); - current_render_encoder_ = nullptr; - } - if (current_command_buffer_) { - current_command_buffer_->release(); - current_command_buffer_ = nullptr; - } - 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 - for (auto& pair : pipeline_cache_) { - if (pair.second) { - pair.second->release(); - } - } - pipeline_cache_.clear(); - - for (auto& pair : geometry_pipeline_cache_) { - if (pair.second.pipeline) { - pair.second.pipeline->release(); - } - } - geometry_pipeline_cache_.clear(); - - 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(); - } - } - geometry_vertex_stage_cache_.clear(); - - for (auto& pair : geometry_shader_stage_cache_) { - if (pair.second.library) { - pair.second.library->release(); - } - } - geometry_shader_stage_cache_.clear(); - - for (auto& pair : depth_stencil_state_cache_) { - if (pair.second) { - pair.second->release(); - } - } - depth_stencil_state_cache_.clear(); - - // Release IR Converter runtime buffers and resources - if (null_buffer_) { - null_buffer_->release(); - null_buffer_ = nullptr; - } - if (null_texture_) { - null_texture_->release(); - null_texture_ = nullptr; - } - if (null_sampler_) { - null_sampler_->release(); - null_sampler_ = nullptr; - } - { - std::lock_guard lock(draw_ring_mutex_); - active_draw_ring_.reset(); - draw_ring_pool_.clear(); - command_buffer_draw_rings_.clear(); - } - res_heap_ab_ = nullptr; - smp_heap_ab_ = nullptr; - cbv_heap_ab_ = nullptr; - uniforms_buffer_ = nullptr; - top_level_ab_ = nullptr; - draw_args_buffer_ = nullptr; - - ShutdownShaderStorage(); -} - -MetalCommandProcessor::DrawRingBuffers::~DrawRingBuffers() { - if (res_heap_ab) { - res_heap_ab->release(); - res_heap_ab = nullptr; - } - if (smp_heap_ab) { - smp_heap_ab->release(); - smp_heap_ab = nullptr; - } - if (cbv_heap_ab) { - cbv_heap_ab->release(); - cbv_heap_ab = nullptr; - } - if (uniforms_buffer) { - uniforms_buffer->release(); - uniforms_buffer = nullptr; - } - if (top_level_ab) { - top_level_ab->release(); - top_level_ab = nullptr; - } - if (draw_args_buffer) { - draw_args_buffer->release(); - draw_args_buffer = nullptr; - } -} - -void MetalCommandProcessor::UpdateDebugMarkersEnabled() { - // Enable debug markers if the CVAR is set (RenderDoc auto-detect disabled on - // macOS). - debug_markers_enabled_ = IsGpuDebugMarkersEnabled(); -} - -void MetalCommandProcessor::PushDebugMarker(const char* format, ...) { - if (!debug_markers_enabled_) { - return; - } - char label[256]; - va_list args; - va_start(args, format); - vsnprintf(label, sizeof(label), format, args); - va_end(args); - auto* ns_label = NS::String::string(label, NS::UTF8StringEncoding); - if (current_render_encoder_) { - current_render_encoder_->pushDebugGroup(ns_label); - debug_marker_stack_.push_back(DebugMarkerTarget::kRenderEncoder); - } else if (current_command_buffer_) { - current_command_buffer_->pushDebugGroup(ns_label); - debug_marker_stack_.push_back(DebugMarkerTarget::kCommandBuffer); - } -} - -void MetalCommandProcessor::PopDebugMarker() { - if (!debug_markers_enabled_ || debug_marker_stack_.empty()) { - return; - } - DebugMarkerTarget target = debug_marker_stack_.back(); - debug_marker_stack_.pop_back(); - if (target == DebugMarkerTarget::kRenderEncoder) { - if (current_render_encoder_) { - current_render_encoder_->popDebugGroup(); - } - } else { - if (current_command_buffer_) { - current_command_buffer_->popDebugGroup(); - } - } -} - -void MetalCommandProcessor::InsertDebugMarker(const char* format, ...) { - if (!debug_markers_enabled_) { - return; - } - char label[256]; - va_list args; - va_start(args, format); - vsnprintf(label, sizeof(label), format, args); - va_end(args); - auto* ns_label = NS::String::string(label, NS::UTF8StringEncoding); - if (current_render_encoder_) { - current_render_encoder_->insertDebugSignpost(ns_label); - } else if (current_command_buffer_) { - current_command_buffer_->pushDebugGroup(ns_label); - current_command_buffer_->popDebugGroup(); - } -} - -void MetalCommandProcessor::RequestCapture() { - capture_requested_.store(true, std::memory_order_release); -} - -void MetalCommandProcessor::MaybeStartCapture() { - if (!capture_requested_.exchange(false, std::memory_order_acq_rel)) { - return; - } - if (!command_queue_) { - XELOGW("Metal capture requested but command queue is not ready"); - return; - } - capture_manager_ = MTL::CaptureManager::sharedCaptureManager(); - if (!capture_manager_) { - XELOGW("Metal capture manager not available"); - return; - } - auto* descriptor = MTL::CaptureDescriptor::alloc()->init(); - descriptor->setCaptureObject(command_queue_); - descriptor->setDestination(MTL::CaptureDestinationGPUTraceDocument); - - const char* capture_dir = std::getenv("XENIA_GPU_CAPTURE_DIR"); - std::string filename = - capture_dir ? (std::string(capture_dir) + "/metal_capture.gputrace") - : std::string("./metal_capture.gputrace"); - auto* url = NS::URL::fileURLWithPath( - NS::String::string(filename.c_str(), NS::UTF8StringEncoding)); - descriptor->setOutputURL(url); - - NS::Error* error = nullptr; - if (capture_manager_->startCapture(descriptor, &error)) { - capture_active_ = true; - XELOGI("Metal capture started: {}", filename); - } else { - XELOGE("Metal capture start failed: {} (set MTL_CAPTURE_ENABLED=1)", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - descriptor->release(); -} - -void MetalCommandProcessor::StopCaptureIfActive() { - if (!capture_active_ || !capture_manager_) { - return; - } - capture_manager_->stopCapture(); - capture_active_ = false; - XELOGI("Metal capture completed"); -} - -void MetalCommandProcessor::TracePlaybackWroteMemory(uint32_t base_ptr, - uint32_t length) { - if (shared_memory_) { - shared_memory_->MemoryInvalidationCallback(base_ptr, length, true); - } - if (primitive_processor_) { - primitive_processor_->MemoryInvalidationCallback(base_ptr, length, true); - } -} - -void MetalCommandProcessor::RestoreEdramSnapshot(const void* snapshot) { - // Restore the guest EDRAM snapshot captured in the trace into the Metal - // render-target cache so that subsequent host render targets created from - // EDRAM (via LoadTiledData) see the same initial contents as other - // backends like D3D12. - if (!snapshot) { - XELOGW( - "MetalCommandProcessor::RestoreEdramSnapshot called with null " - "snapshot"); - return; - } - if (!render_target_cache_) { - XELOGW( - "MetalCommandProcessor::RestoreEdramSnapshot called before render " - "target " - "cache initialization"); - return; - } - render_target_cache_->RestoreEdramSnapshot(snapshot); -} - -void MetalCommandProcessor::ClearCaches() { - CommandProcessor::ClearCaches(); - // TODO(wmarti): Add cache_clear_requested_ flag like D3D12 for deferred - // clearing of pipeline caches, texture caches, etc. -} - -void MetalCommandProcessor::InvalidateGpuMemory() { - if (shared_memory_) { - shared_memory_->InvalidateAllPages(); - } -} - -void MetalCommandProcessor::ClearReadbackBuffers() { - for (auto& entry : readback_buffers_) { - ReadbackBuffer& rb = entry.second; - for (size_t i = 0; i < 2; ++i) { - if (rb.buffers[i]) { - rb.buffers[i]->release(); - rb.buffers[i] = nullptr; - } - rb.sizes[i] = 0; - rb.submission_ids[i] = 0; - } - rb.current_index = 0; - rb.last_used_frame = 0; - } - readback_buffers_.clear(); -} - -void MetalCommandProcessor::EvictOldReadbackBuffers( - std::unordered_map& buffer_map) { - if (frame_current_ <= kReadbackBufferEvictionAgeFrames) { - return; - } - - for (auto it = buffer_map.begin(); it != buffer_map.end();) { - if (it->second.last_used_frame < - frame_current_ - kReadbackBufferEvictionAgeFrames) { - for (int i = 0; i < 2; ++i) { - if (it->second.buffers[i]) { - it->second.buffers[i]->release(); - } - } - it = buffer_map.erase(it); - } else { - ++it; - } - } -} - -ui::metal::MetalProvider& MetalCommandProcessor::GetMetalProvider() const { - return *static_cast(graphics_system_->provider()); -} - -uint64_t MetalCommandProcessor::GetCurrentSubmission() const { - return completion_timeline_ ? completion_timeline_->GetUpcomingSubmission() - : 1; -} - -uint64_t MetalCommandProcessor::GetCompletedSubmission() const { - return completion_timeline_ - ? completion_timeline_->GetCompletedSubmissionFromLastUpdate() - : 0; -} - -void MetalCommandProcessor::MarkResolvedMemory(uint32_t base_ptr, - uint32_t length) { - if (length == 0) return; - resolved_memory_ranges_.push_back({base_ptr, length}); -} - -bool MetalCommandProcessor::IsResolvedMemory(uint32_t base_ptr, - uint32_t length) const { - uint32_t end_ptr = base_ptr + length; - for (const auto& range : resolved_memory_ranges_) { - uint32_t range_end = range.base + range.length; - // Check if ranges overlap - if (base_ptr < range_end && end_ptr > 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_); -} - -void MetalCommandProcessor::SetSwapDestSwap(uint32_t dest_base, bool swap) { - if (!dest_base) { - return; - } - if (swap_dest_swaps_by_base_.size() > 256) { - swap_dest_swaps_by_base_.clear(); - } - swap_dest_swaps_by_base_[dest_base] = swap; -} - -bool MetalCommandProcessor::ConsumeSwapDestSwap(uint32_t dest_base, - bool* swap_out) { - if (!swap_out || !dest_base) { - return false; - } - auto it = swap_dest_swaps_by_base_.find(dest_base); - if (it == swap_dest_swaps_by_base_.end()) { - return false; - } - *swap_out = it->second; - swap_dest_swaps_by_base_.erase(it); - return true; -} - -bool MetalCommandProcessor::SetupContext() { - saw_swap_ = false; - last_swap_ptr_ = 0; - last_swap_width_ = 0; - last_swap_height_ = 0; - swap_dest_swaps_by_base_.clear(); - gamma_ramp_256_entry_table_up_to_date_ = false; - gamma_ramp_pwl_up_to_date_ = false; - if (!CommandProcessor::SetupContext()) { - XELOGE("Failed to initialize base command processor context"); - return false; - } - - // Check if debug markers should be enabled (CVAR). - UpdateDebugMarkersEnabled(); - if (debug_markers_enabled_) { - XELOGI("GPU debug markers enabled for Metal debug tools"); - } - - const ui::metal::MetalProvider& provider = GetMetalProvider(); - device_ = provider.GetDevice(); - command_queue_ = provider.GetCommandQueue(); - - if (!device_ || !command_queue_) { - XELOGE("MetalCommandProcessor: No Metal device or command queue available"); - return false; - } - - wait_shared_event_ = device_->newSharedEvent(); - if (wait_shared_event_) { - wait_shared_event_->setLabel( - NS::String::string("XeniaWaitEvent", NS::UTF8StringEncoding)); - wait_shared_event_value_ = 0; - } else { - XELOGW( - "MetalCommandProcessor: SharedEvent unavailable; falling back to " - "waitUntilCompleted"); - } - - completion_timeline_ = ui::metal::MetalGPUCompletionTimeline::Create(device_); - if (!completion_timeline_) { - XELOGE("MetalCommandProcessor: Failed to create completion timeline"); - return false; - } - submission_open_ = false; - submission_completed_processed_ = 0; - frame_open_ = false; - frame_current_ = 1; - frame_completed_ = 0; - std::fill_n(closed_frame_submissions_, kQueueFrames, 0); - - 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); - - // Initialize shared memory - shared_memory_ = std::make_unique(*this, *memory_); - if (!shared_memory_->Initialize()) { - XELOGE("Failed to initialize shared memory"); - return false; - } - - // Initialize primitive processor (index/primitive conversion like D3D12). - primitive_processor_ = std::make_unique( - *this, *register_file_, *memory_, trace_writer_, *shared_memory_); - if (!primitive_processor_->Initialize()) { - XELOGE("Failed to initialize Metal primitive processor"); - return false; - } - - // Get the draw resolution scale for the render target cache and the texture - // cache (match D3D12/Vulkan behavior). - uint32_t draw_resolution_scale_x = 1; - uint32_t draw_resolution_scale_y = 1; - bool draw_resolution_scale_not_clamped = - TextureCache::GetConfigDrawResolutionScale(draw_resolution_scale_x, - draw_resolution_scale_y); - if (!draw_resolution_scale_not_clamped) { - XELOGW( - "The requested draw resolution scale is not supported by the emulator " - "or config, reducing to {}x{}", - draw_resolution_scale_x, draw_resolution_scale_y); - } - XELOGI("Metal: draw resolution scale {}x{} (supported={})", - draw_resolution_scale_x, draw_resolution_scale_y, - draw_resolution_scale_not_clamped); - - texture_cache_ = std::make_unique( - this, *register_file_, *shared_memory_, draw_resolution_scale_x, - draw_resolution_scale_y); - if (!texture_cache_->Initialize()) { - XELOGE("Failed to initialize Metal texture cache"); - return false; - } - - // Initialize render target cache - render_target_cache_ = std::make_unique( - *register_file_, *memory_, &trace_writer_, draw_resolution_scale_x, - draw_resolution_scale_y, *this); - if (!render_target_cache_->Initialize()) { - XELOGE("Failed to initialize Metal render target cache"); - return false; - } - - resolve_downscale_pipeline_ = CreateComputePipelineFromEmbeddedLibrary( - device_, resolve_downscale_cs_metallib, - sizeof(resolve_downscale_cs_metallib), "resolve_downscale"); - if (!resolve_downscale_pipeline_) { - XELOGW("MetalCommandProcessor: resolve downscale pipeline unavailable"); - } - - // Initialize shader translation pipeline - if (!InitializeShaderTranslation()) { - XELOGE("Failed to initialize shader translation"); - return false; - } - 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); - - render_target_texture_ = device_->newTexture(color_desc); - color_desc->release(); - - 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_); - depth_desc->setStorageMode(MTL::StorageModePrivate); - 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); - - // Create a null buffer for unused descriptor entries - // This prevents shader validation errors when accessing unpopulated - // descriptors - null_buffer_ = - device_->newBuffer(kNullBufferSize, MTL::ResourceStorageModeShared); - if (!null_buffer_) { - XELOGE("Failed to create null buffer"); - return false; - } - null_buffer_->setLabel( - NS::String::string("NullBuffer", NS::UTF8StringEncoding)); - std::memset(null_buffer_->contents(), 0, kNullBufferSize); - - // Create a 1x1x1 placeholder 2D array texture for unbound texture slots - // Xbox 360 textures are typically 2D arrays (for texture atlases, cubemaps) - // Using 2DArray prevents "Invalid texture type" validation errors - MTL::TextureDescriptor* null_tex_desc = - MTL::TextureDescriptor::alloc()->init(); - null_tex_desc->setTextureType(MTL::TextureType2DArray); - null_tex_desc->setPixelFormat(MTL::PixelFormatRGBA8Unorm); - null_tex_desc->setWidth(1); - null_tex_desc->setHeight(1); - null_tex_desc->setArrayLength(1); // Single slice in the array - null_tex_desc->setStorageMode(MTL::StorageModeShared); - null_tex_desc->setUsage(MTL::TextureUsageShaderRead); - - null_texture_ = device_->newTexture(null_tex_desc); - null_tex_desc->release(); - - if (!null_texture_) { - XELOGE("Failed to create null texture"); - return false; - } - null_texture_->setLabel( - NS::String::string("NullTexture2DArray", NS::UTF8StringEncoding)); - - // Fill the 1x1x1 texture with opaque white (helps debug if sampled) - uint32_t white_pixel = 0xFFFFFFFF; - MTL::Region region = - MTL::Region(0, 0, 0, 1, 1, 1); // x,y,z origin, w,h,d size - null_texture_->replaceRegion(region, 0, 0, &white_pixel, 4, 0); // slice 0 - - // Create a default sampler for unbound sampler slots - // Must set supportsArgumentBuffers=YES for use in argument buffers - MTL::SamplerDescriptor* null_smp_desc = - MTL::SamplerDescriptor::alloc()->init(); - null_smp_desc->setMinFilter(MTL::SamplerMinMagFilterLinear); - null_smp_desc->setMagFilter(MTL::SamplerMinMagFilterLinear); - null_smp_desc->setMipFilter(MTL::SamplerMipFilterLinear); - null_smp_desc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); - null_smp_desc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); - null_smp_desc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); - null_smp_desc->setSupportArgumentBuffers(true); - - null_sampler_ = device_->newSamplerState(null_smp_desc); - null_smp_desc->release(); - - if (!null_sampler_) { - XELOGE("Failed to create null sampler"); - return false; - } - - auto ring = CreateDrawRingBuffers(); - if (!ring) { - return false; - } - SetActiveDrawRing(ring); - - 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()); - - shader_translator_ = std::make_unique( - ui::GraphicsProvider::GpuVendorID::kApple, - false, // bindless_resources_used - not using bindless for now - edram_rov_used, 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(), - false); // force_emit_source_map - - // Initialize DXBC to DXIL converter - dxbc_to_dxil_converter_ = std::make_unique(); - if (!dxbc_to_dxil_converter_->Initialize()) { - XELOGE("Failed to initialize DXBC to DXIL converter"); - return false; - } - - // Initialize Metal Shader Converter - metal_shader_converter_ = std::make_unique(); - if (!metal_shader_converter_->Initialize()) { - XELOGE("Failed to initialize Metal Shader Converter"); - return false; - } - - // Configure MSC minimum targets to avoid materialization failures on older - // GPUs/OS versions. - 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()); - } - - 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. - - EndRenderEncoder(); - - if (submission_open_ || current_command_buffer_) { - uint64_t submission_to_wait = - current_command_buffer_ ? GetCurrentSubmission() : 0; - if (!submission_open_) { - XELOGW( - "MetalCommandProcessor::PrepareForWait: command buffer without " - "open submission"); - submission_open_ = true; - } - EndSubmission(false); - if (submission_to_wait) { - CheckSubmissionCompletion(submission_to_wait); - } - } - 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. - 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; - 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(); - } - // 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 - CommandProcessor::PrepareForWait(); -} - -void MetalCommandProcessor::ShutdownContext() { - EndRenderEncoder(); - - if (submission_open_ || current_command_buffer_) { - uint64_t submission_to_wait = - current_command_buffer_ ? GetCurrentSubmission() : 0; - if (!submission_open_) { - XELOGW( - "MetalCommandProcessor::ShutdownContext: command buffer without " - "open submission"); - submission_open_ = true; - } - EndSubmission(false); - if (submission_to_wait) { - CheckSubmissionCompletion(submission_to_wait); - } - } - - // 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(); - } - - // Now safe to release encoder and command buffer - if (current_render_encoder_) { - current_render_encoder_->release(); - current_render_encoder_ = nullptr; - } - if (current_command_buffer_) { - current_command_buffer_->release(); - current_command_buffer_ = nullptr; - } - DrainCommandBufferAutoreleasePool(); - - ClearReadbackBuffers(); - if (resolve_downscale_buffer_) { - resolve_downscale_buffer_->release(); - resolve_downscale_buffer_ = nullptr; - resolve_downscale_buffer_size_ = 0; - } - if (resolve_downscale_pipeline_) { - resolve_downscale_pipeline_->release(); - resolve_downscale_pipeline_ = nullptr; - } - - { - std::lock_guard lock(draw_ring_mutex_); - active_draw_ring_.reset(); - draw_ring_pool_.clear(); - command_buffer_draw_rings_.clear(); - } - - if (texture_cache_) { - texture_cache_->Shutdown(); - texture_cache_.reset(); - } - - if (primitive_processor_) { - primitive_processor_->Shutdown(); - primitive_processor_.reset(); - } - if (tessellator_tables_buffer_) { - tessellator_tables_buffer_->release(); - tessellator_tables_buffer_ = nullptr; - } - if (depth_only_pixel_library_) { - depth_only_pixel_library_->release(); - depth_only_pixel_library_ = nullptr; - } - depth_only_pixel_function_name_.clear(); - frame_open_ = false; - frame_current_ = 1; - frame_completed_ = 0; - std::fill_n(closed_frame_submissions_, kQueueFrames, 0); - submission_open_ = false; - submission_completed_processed_ = 0; - completion_timeline_.reset(); - - shader_cache_.clear(); - shared_memory_.reset(); - shader_translator_.reset(); - dxbc_to_dxil_converter_.reset(); - metal_shader_converter_.reset(); - if (wait_shared_event_) { - wait_shared_event_->release(); - wait_shared_event_ = nullptr; - } - - ShutdownShaderStorage(); - - CommandProcessor::ShutdownContext(); -} - -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); - InitializeShaderStorageInternal(cache_root, title_id, blocking); - if (completion_callback) { - completion_callback(); - } -} - -bool MetalCommandProcessor::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_root_ = cache_root / "shaders" / "metal"; - shader_storage_local_root_ = - shader_storage_root_ / "local" / GetShaderStorageDeviceTag(); - 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; - } - - metallib_cache_dir_ = shader_storage_title_root_ / "metallib"; - if (::cvars::metal_shader_disk_cache && g_metal_shader_cache) { - g_metal_shader_cache->Initialize(metallib_cache_dir_); - } - - const char* path_suffix = "rtv"; - - pipeline_disk_cache_path_ = - shader_storage_title_root_ / - fmt::format("{:08X}.{}.metal.pipelines", title_id, path_suffix); - pipeline_binary_archive_path_ = - shader_storage_title_root_ / - fmt::format("{:08X}.{}.metal.binarchive", title_id, path_suffix); - - if (::cvars::metal_pipeline_disk_cache) { - LoadPipelineDiskCache(pipeline_disk_cache_path_, - &pipeline_disk_cache_entries_); - } - - if (::cvars::metal_pipeline_binary_archive) { - InitializePipelineBinaryArchive(pipeline_binary_archive_path_); - } - - if (blocking && pipeline_binary_archive_ && - !pipeline_disk_cache_entries_.empty()) { - PrewarmPipelineBinaryArchive(pipeline_disk_cache_entries_); - } - - return true; -} - -void MetalCommandProcessor::ShutdownShaderStorage() { - if (pipeline_binary_archive_) { - SerializePipelineBinaryArchive(); - pipeline_binary_archive_->release(); - pipeline_binary_archive_ = nullptr; - } - if (pipeline_disk_cache_file_) { - std::fclose(pipeline_disk_cache_file_); - pipeline_disk_cache_file_ = nullptr; - } - pipeline_disk_cache_keys_.clear(); - pipeline_disk_cache_entries_.clear(); - - if (g_metal_shader_cache) { - g_metal_shader_cache->Shutdown(); - } -} - -std::string MetalCommandProcessor::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; -} - -bool MetalCommandProcessor::LoadPipelineDiskCache( - const std::filesystem::path& path, - std::vector* entries) { - if (!entries) { - return false; - } - entries->clear(); - pipeline_disk_cache_keys_.clear(); - - pipeline_disk_cache_file_ = xe::filesystem::OpenFile(path, "a+b"); - if (!pipeline_disk_cache_file_) { - XELOGW("Metal pipeline disk cache: Failed to open {}", path.string()); - return false; - } - - PipelineDiskCacheHeader header = {}; - if (std::fread(&header, sizeof(header), 1, pipeline_disk_cache_file_) != 1 || - header.magic != kPipelineDiskCacheMagic || - header.version != kPipelineDiskCacheVersion) { - header.magic = kPipelineDiskCacheMagic; - header.version = kPipelineDiskCacheVersion; - header.reserved[0] = 0; - header.reserved[1] = 0; - xe::filesystem::Seek(pipeline_disk_cache_file_, 0, SEEK_SET); - std::fwrite(&header, sizeof(header), 1, pipeline_disk_cache_file_); - std::fflush(pipeline_disk_cache_file_); - xe::filesystem::Seek(pipeline_disk_cache_file_, 0, SEEK_END); - return true; - } - - while (true) { - PipelineDiskCacheEntryHeader entry_header = {}; - if (std::fread(&entry_header, sizeof(entry_header), 1, - pipeline_disk_cache_file_) != 1) { - break; - } - - if (entry_header.entry_size < sizeof(PipelineDiskCacheEntryBase) || - entry_header.entry_size > kPipelineDiskCacheMaxEntrySize) { - break; - } - - PipelineDiskCacheEntryBase base = {}; - if (std::fread(&base, sizeof(base), 1, pipeline_disk_cache_file_) != 1) { - break; - } - - size_t expected_size = sizeof(PipelineDiskCacheEntryBase) + - size_t(base.vertex_attribute_count) * - sizeof(PipelineDiskCacheVertexAttribute) + - size_t(base.vertex_layout_count) * - sizeof(PipelineDiskCacheVertexLayout); - if (entry_header.entry_size != expected_size) { - xe::filesystem::Seek( - pipeline_disk_cache_file_, - entry_header.entry_size - sizeof(PipelineDiskCacheEntryBase), - SEEK_CUR); - continue; - } - - PipelineDiskCacheEntry entry = {}; - entry.pipeline_key = base.pipeline_key; - entry.vertex_shader_cache_key = base.vertex_shader_cache_key; - entry.pixel_shader_cache_key = base.pixel_shader_cache_key; - entry.sample_count = base.sample_count; - entry.depth_format = base.depth_format; - entry.stencil_format = base.stencil_format; - std::memcpy(entry.color_formats, base.color_formats, - sizeof(base.color_formats)); - entry.normalized_color_mask = base.normalized_color_mask; - entry.alpha_to_mask_enable = base.alpha_to_mask_enable; - std::memcpy(entry.blendcontrol, base.blendcontrol, - sizeof(base.blendcontrol)); - - entry.vertex_attributes.resize(base.vertex_attribute_count); - if (base.vertex_attribute_count) { - if (std::fread(entry.vertex_attributes.data(), - sizeof(PipelineDiskCacheVertexAttribute), - base.vertex_attribute_count, pipeline_disk_cache_file_) != - base.vertex_attribute_count) { - break; - } - } - entry.vertex_layouts.resize(base.vertex_layout_count); - if (base.vertex_layout_count) { - if (std::fread(entry.vertex_layouts.data(), - sizeof(PipelineDiskCacheVertexLayout), - base.vertex_layout_count, - pipeline_disk_cache_file_) != base.vertex_layout_count) { - break; - } - } - - entries->push_back(std::move(entry)); - pipeline_disk_cache_keys_.insert(base.pipeline_key); - } - - xe::filesystem::Seek(pipeline_disk_cache_file_, 0, SEEK_END); - return true; -} - -bool MetalCommandProcessor::AppendPipelineDiskCacheEntry( - const PipelineDiskCacheEntry& entry) { - if (!pipeline_disk_cache_file_) { - return false; - } - if (!pipeline_disk_cache_keys_.insert(entry.pipeline_key).second) { - return false; - } - - PipelineDiskCacheEntryBase base = {}; - base.pipeline_key = entry.pipeline_key; - base.vertex_shader_cache_key = entry.vertex_shader_cache_key; - base.pixel_shader_cache_key = entry.pixel_shader_cache_key; - base.sample_count = entry.sample_count; - base.depth_format = entry.depth_format; - base.stencil_format = entry.stencil_format; - std::memcpy(base.color_formats, entry.color_formats, - sizeof(base.color_formats)); - base.normalized_color_mask = entry.normalized_color_mask; - base.alpha_to_mask_enable = entry.alpha_to_mask_enable; - std::memcpy(base.blendcontrol, entry.blendcontrol, sizeof(base.blendcontrol)); - base.vertex_attribute_count = - static_cast(entry.vertex_attributes.size()); - base.vertex_layout_count = static_cast(entry.vertex_layouts.size()); - - size_t entry_size = - sizeof(PipelineDiskCacheEntryBase) + - entry.vertex_attributes.size() * - sizeof(PipelineDiskCacheVertexAttribute) + - entry.vertex_layouts.size() * sizeof(PipelineDiskCacheVertexLayout); - if (entry_size > kPipelineDiskCacheMaxEntrySize) { - return false; - } - - PipelineDiskCacheEntryHeader entry_header = {}; - entry_header.entry_size = static_cast(entry_size); - - std::fwrite(&entry_header, sizeof(entry_header), 1, - pipeline_disk_cache_file_); - std::fwrite(&base, sizeof(base), 1, pipeline_disk_cache_file_); - if (!entry.vertex_attributes.empty()) { - std::fwrite(entry.vertex_attributes.data(), - sizeof(PipelineDiskCacheVertexAttribute), - entry.vertex_attributes.size(), pipeline_disk_cache_file_); - } - if (!entry.vertex_layouts.empty()) { - std::fwrite(entry.vertex_layouts.data(), - sizeof(PipelineDiskCacheVertexLayout), - entry.vertex_layouts.size(), pipeline_disk_cache_file_); - } - std::fflush(pipeline_disk_cache_file_); - pipeline_disk_cache_entries_.push_back(entry); - return true; -} - -bool MetalCommandProcessor::InitializePipelineBinaryArchive( - const std::filesystem::path& archive_path) { - if (!device_) { - return false; - } - if (pipeline_binary_archive_) { - pipeline_binary_archive_->release(); - pipeline_binary_archive_ = nullptr; - } - - MTL::BinaryArchiveDescriptor* desc = - MTL::BinaryArchiveDescriptor::alloc()->init(); - 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); - 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 MetalCommandProcessor::SerializePipelineBinaryArchive() { - 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 MetalCommandProcessor::PrewarmPipelineBinaryArchive( - const std::vector& entries) { - if (!pipeline_binary_archive_ || entries.empty()) { - return; - } - if (!g_metal_shader_cache || !g_metal_shader_cache->IsInitialized()) { - return; - } - - size_t prewarmed = 0; - for (const auto& entry : entries) { - MetalShaderCache::CachedMetallib vs_cached; - if (!g_metal_shader_cache->Load(entry.vertex_shader_cache_key, - &vs_cached)) { - continue; - } - - NS::Error* error = nullptr; - dispatch_data_t vs_data = dispatch_data_create( - vs_cached.metallib_data.data(), vs_cached.metallib_data.size(), nullptr, - DISPATCH_DATA_DESTRUCTOR_NONE); - MTL::Library* vs_library = device_->newLibrary(vs_data, &error); - dispatch_release(vs_data); - if (!vs_library) { - continue; - } - NS::String* vs_name = NS::String::string(vs_cached.function_name.c_str(), - NS::UTF8StringEncoding); - MTL::Function* vs_function = vs_library->newFunction(vs_name); - if (!vs_function) { - vs_library->release(); - continue; - } - - MTL::Library* ps_library = nullptr; - MTL::Function* ps_function = nullptr; - if (entry.pixel_shader_cache_key) { - MetalShaderCache::CachedMetallib ps_cached; - if (g_metal_shader_cache->Load(entry.pixel_shader_cache_key, - &ps_cached)) { - dispatch_data_t ps_data = dispatch_data_create( - ps_cached.metallib_data.data(), ps_cached.metallib_data.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_NONE); - ps_library = device_->newLibrary(ps_data, &error); - dispatch_release(ps_data); - if (ps_library) { - NS::String* ps_name = NS::String::string( - ps_cached.function_name.c_str(), NS::UTF8StringEncoding); - ps_function = ps_library->newFunction(ps_name); - } - } - } - - MTL::RenderPipelineDescriptor* desc = - MTL::RenderPipelineDescriptor::alloc()->init(); - desc->setVertexFunction(vs_function); - if (ps_function) { - desc->setFragmentFunction(ps_function); - } - - for (uint32_t i = 0; i < 4; ++i) { - desc->colorAttachments()->object(i)->setPixelFormat( - static_cast(entry.color_formats[i])); - } - desc->setDepthAttachmentPixelFormat( - static_cast(entry.depth_format)); - desc->setStencilAttachmentPixelFormat( - static_cast(entry.stencil_format)); - desc->setSampleCount(entry.sample_count); - desc->setAlphaToCoverageEnabled(entry.alpha_to_mask_enable != 0); - - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - if (entry.color_formats[i] == - static_cast(MTL::PixelFormatInvalid)) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - uint32_t rt_write_mask = (entry.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 = entry.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); - } - } - - if (!entry.vertex_attributes.empty() || !entry.vertex_layouts.empty()) { - MTL::VertexDescriptor* vertex_desc = - MTL::VertexDescriptor::alloc()->init(); - for (const auto& attr : entry.vertex_attributes) { - auto* attr_desc = - vertex_desc->attributes()->object(attr.attribute_index); - attr_desc->setFormat(static_cast(attr.format)); - attr_desc->setOffset(attr.offset); - attr_desc->setBufferIndex(attr.buffer_index); - } - for (const auto& layout : entry.vertex_layouts) { - auto* layout_desc = vertex_desc->layouts()->object(layout.buffer_index); - layout_desc->setStride(layout.stride); - layout_desc->setStepFunction( - static_cast(layout.step_function)); - layout_desc->setStepRate(layout.step_rate); - } - desc->setVertexDescriptor(vertex_desc); - vertex_desc->release(); - } - - NS::Array* archives = NS::Array::array(pipeline_binary_archive_); - desc->setBinaryArchives(archives); - if (pipeline_binary_archive_->addRenderPipelineFunctions(desc, &error)) { - pipeline_binary_archive_dirty_ = true; - ++prewarmed; - } - desc->release(); - vs_function->release(); - vs_library->release(); - if (ps_function) { - ps_function->release(); - } - if (ps_library) { - ps_library->release(); - } - } -} - -void MetalCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, - uint32_t frontbuffer_width, - uint32_t frontbuffer_height) { - ProcessCompletedSubmissions(); - saw_swap_ = true; - last_swap_ptr_ = frontbuffer_ptr; - last_swap_width_ = frontbuffer_width; - last_swap_height_ = frontbuffer_height; - EndSubmission(true); - - // 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_. - 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_; - - MTL::Texture* source_texture = nullptr; - bool use_pwl_gamma_ramp = false; - if (texture_cache_) { - uint32_t swap_width = 0; - uint32_t swap_height = 0; - xenos::TextureFormat swap_format = xenos::TextureFormat::k_8_8_8_8; - source_texture = texture_cache_->RequestSwapTexture( - swap_width, swap_height, swap_format); - if (source_texture) { - output_width = swap_width; - output_height = swap_height; - 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_) { - constexpr size_t kGammaRampTableBytes = - sizeof(reg::DC_LUT_30_COLOR) * 256; - constexpr size_t kGammaRampPwlBytes = - sizeof(reg::DC_LUT_PWL_DATA) * 128 * 3; - if (presenter->UpdateGammaRamp( - gamma_ramp_256_entry_table(), kGammaRampTableBytes, - gamma_ramp_pwl_rgb(), kGammaRampPwlBytes)) { - gamma_ramp_256_entry_table_up_to_date_ = true; - gamma_ramp_pwl_up_to_date_ = true; - } else { - XELOGW("Metal IssueSwap: gamma ramp upload failed"); - } - } - } - } - } - - 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) { - static bool missing_swap_logged = false; - if (!missing_swap_logged) { - missing_swap_logged = true; - XELOGW( - "MetalCommandProcessor::IssueSwap: swap texture unavailable; " - "presenting inactive (black) output"); - } - presenter->RefreshGuestOutput( - 0, 0, 0, 0, [](ui::Presenter::GuestOutputRefreshContext&) -> bool { - return false; - }); - return; - } - - if (source_texture) { - ui::metal::MetalPresenter* metal_presenter = presenter; - uint32_t source_width = output_width; - uint32_t source_height = output_height; - bool force_swap_rb_copy = force_swap_rb; - bool use_pwl_gamma_ramp_copy = use_pwl_gamma_ramp; - auto aspect = graphics_system_->GetScaledAspectRatio(); - presenter->RefreshGuestOutput( - output_width, output_height, aspect.first, aspect.second, - [source_texture, metal_presenter, source_width, source_height, - force_swap_rb_copy, use_pwl_gamma_ramp_copy]( - ui::Presenter::GuestOutputRefreshContext& context) -> bool { - auto& metal_context = - static_cast( - context); - context.SetIs8bpc(!use_pwl_gamma_ramp_copy); - uint64_t submission_id = 0; - bool copy_ok = metal_presenter->CopyTextureToGuestOutput( - source_texture, metal_context.resource_uav_capable(), - source_width, source_height, force_swap_rb_copy, - use_pwl_gamma_ramp_copy, &submission_id); - if (submission_id) { - metal_context.SetSubmissionId(submission_id); - } - return copy_ok; - }); - } - } - - StopCaptureIfActive(); -} - -void MetalCommandProcessor::OnPrimaryBufferEnd() { - if (cvars::submit_on_primary_buffer_end && submission_open_ && - CanEndSubmissionImmediately()) { - EndSubmission(false); - } -} - -Shader* MetalCommandProcessor::LoadShader(xenos::ShaderType shader_type, - uint32_t guest_address, - const uint32_t* host_address, - uint32_t dword_count) { - // Create hash for caching using XXH3 (same as D3D12) - uint64_t hash = XXH3_64bits(host_address, dword_count * sizeof(uint32_t)); - - // Check cache - auto it = shader_cache_.find(hash); - if (it != shader_cache_.end()) { - return it->second.get(); - } - - // Create new shader - analysis and translation happen later when the shader - // is actually used in a draw call (matching D3D12 pattern) - 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 at {:08X} ({} dwords, hash {:016X})", - shader_type == xenos::ShaderType::kVertex ? "vertex" : "pixel", - guest_address, dword_count, hash); - - return result; -} - -bool MetalCommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, - uint32_t index_count, - IndexBufferInfo* index_buffer_info, - bool major_mode_explicit) { - const RegisterFile& regs = *register_file_; - uint32_t normalized_color_mask = 0; - - if (!BeginSubmission(true)) { - return false; - } - - // Check for copy mode - xenos::EdramMode edram_mode = regs.Get().edram_mode; - if (edram_mode == xenos::EdramMode::kCopy) { - return IssueCopy(); - } - - // Vertex shader analysis. - auto vertex_shader = static_cast(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_); - } - bool memexport_used_vertex = vertex_shader->memexport_eM_written() != 0; - - // Pixel shader analysis. - bool primitive_polygonal = draw_util::IsPrimitivePolygonal(regs); - bool is_rasterization_done = - draw_util::IsRasterizationPotentiallyDone(regs, primitive_polygonal); - MetalShader* pixel_shader = nullptr; - if (is_rasterization_done) { - if (edram_mode == xenos::EdramMode::kColorDepth) { - pixel_shader = static_cast(active_pixel_shader()); - if (pixel_shader) { - if (!pixel_shader->is_ucode_analyzed()) { - pixel_shader->AnalyzeUcode(ucode_disasm_buffer_); - } - if (!draw_util::IsPixelShaderNeededWithRasterization(*pixel_shader, - regs)) { - pixel_shader = nullptr; - } - } - } - } else { - if (!memexport_used_vertex) { - return true; - } - } - 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_); - } - if (memexport_used_pixel) { - draw_util::AddMemExportRanges(regs, *pixel_shader, memexport_ranges_); - } - // Primitive/index processing (like D3D12/Vulkan). - PrimitiveProcessor::ProcessingResult primitive_processing_result; - if (!primitive_processor_) { - XELOGE("IssueDraw: primitive processor is not initialized"); - return false; - } - if (!primitive_processor_->Process(primitive_processing_result)) { - XELOGE("IssueDraw: primitive processing failed"); - return false; - } - if (!primitive_processing_result.host_draw_vertex_count) { - return true; - } - if (primitive_processing_result.host_vertex_shader_type == - Shader::HostVertexShaderType::kMemExportCompute) { - primitive_processing_result.host_vertex_shader_type = - Shader::HostVertexShaderType::kVertex; - } - - bool use_tessellation_emulation = false; - if (primitive_processing_result.IsTessellated()) { - if (!mesh_shader_supported_) { - static bool tess_mesh_logged = false; - if (!tess_mesh_logged) { - tess_mesh_logged = true; - XELOGW( - "Metal: Tessellation emulation requested but mesh shaders are not " - "supported on this device"); - } - return true; - } - if (!pixel_shader) { - static bool tess_no_ps_logged = false; - if (!tess_no_ps_logged) { - tess_no_ps_logged = true; - XELOGW( - "Metal: Tessellation emulation requested without a pixel shader; " - "using depth-only PS fallback"); - } - } - use_tessellation_emulation = true; - } - - // Configure render targets via MetalRenderTargetCache, similar to D3D12. - 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; - if (!render_target_cache_->Update(is_rasterization_done, - normalized_depth_control, - normalized_color_mask, *vertex_shader)) { - XELOGE( - "MetalCommandProcessor::IssueDraw - RenderTargetCache::Update " - "failed"); - return false; - } - } - - // Begin command buffer if needed (will use cache-provided render targets). - BeginCommandBuffer(); - EnsureDrawRingCapacity(); - - 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 (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); - 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 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 = - GetCurrentVertexShaderModification( - *vertex_shader, host_vertex_shader_type_for_translation, - interpolator_mask); - DxbcShaderTranslator::Modification pixel_shader_modification = - pixel_shader ? 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; - } - } - - 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)); - if (!vertex_translation->is_translated()) { - if (!shader_translator_->TranslateAnalyzedShader(*vertex_translation)) { - XELOGE("Failed to translate vertex shader to DXBC"); - return false; - } - } - if (!use_tessellation_emulation && !vertex_translation->is_valid()) { - if (!vertex_translation->TranslateToMetal(device_, *dxbc_to_dxil_converter_, - *metal_shader_converter_)) { - XELOGE("Failed to translate vertex shader to Metal"); - return false; - } - } - - MetalShader::MetalTranslation* pixel_translation = nullptr; - if (pixel_shader) { - pixel_translation = static_cast( - pixel_shader->GetOrCreateTranslation(pixel_shader_modification.value)); - if (!pixel_translation->is_translated()) { - if (!shader_translator_->TranslateAnalyzedShader(*pixel_translation)) { - XELOGE("Failed to translate pixel shader to DXBC"); - return false; - } - } - if (!pixel_translation->is_valid()) { - if (!pixel_translation->TranslateToMetal( - device_, *dxbc_to_dxil_converter_, *metal_shader_converter_)) { - XELOGE("Failed to translate pixel shader to Metal"); - return false; - } - } - } - - TessellationPipelineState* tessellation_pipeline_state = nullptr; - GeometryPipelineState* geometry_pipeline_state = nullptr; - if (use_tessellation_emulation) { - tessellation_pipeline_state = GetOrCreateTessellationPipelineState( - vertex_translation, pixel_translation, primitive_processing_result, - regs); - pipeline = tessellation_pipeline_state - ? tessellation_pipeline_state->pipeline - : nullptr; - } else if (use_geometry_emulation) { - geometry_pipeline_state = GetOrCreateGeometryPipelineState( - vertex_translation, pixel_translation, geometry_shader_key, regs); - pipeline = - geometry_pipeline_state ? geometry_pipeline_state->pipeline : nullptr; - } else { - pipeline = - GetOrCreatePipelineState(vertex_translation, pixel_translation, regs); - } - - if (!pipeline) { - XELOGE("Failed to create pipeline state"); - return false; - } - - uint32_t used_texture_mask = - vertex_shader->GetUsedTextureMaskAfterTranslation(); - if (pixel_shader) { - used_texture_mask |= pixel_shader->GetUsedTextureMaskAfterTranslation(); - } - if (texture_cache_ && used_texture_mask) { - texture_cache_->RequestTextures(used_texture_mask); - } - - struct VertexBindingRange { - uint32_t binding_index = 0; - uint32_t offset = 0; - uint32_t length = 0; - uint32_t stride = 0; - }; - 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); - - // 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 = - 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( - "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("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( - "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( - "Failed to request vertex buffer at 0x{:08X} (size {}) in shared " - "memory", - buffer_offset, buffer_length); - return false; - } - } - } - - 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( - "Failed to request memexport stream at 0x{:08X} (size {}) in " - "shared " - "memory", - base_bytes, memexport_range.size_bytes); - 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; - } - } - - // Set pipeline state on encoder - current_render_encoder_->setRenderPipelineState(pipeline); - if (use_tessellation_emulation) { - if (!tessellator_tables_buffer_) { - XELOGE("Tessellation emulation requires tessellator tables buffer"); - return false; - } - current_render_encoder_->setObjectBuffer( - tessellator_tables_buffer_, 0, kIRRuntimeTessellatorTablesBindPoint); - current_render_encoder_->setMeshBuffer( - tessellator_tables_buffer_, 0, kIRRuntimeTessellatorTablesBindPoint); - UseRenderEncoderResource(tessellator_tables_buffer_, - MTL::ResourceUsageRead); - } - - // Determine if shared memory should be UAV (for memexport). - bool shared_memory_is_uav = memexport_used_vertex || memexport_used_pixel; - MTL::ResourceUsage shared_memory_usage = - shared_memory_is_uav ? (MTL::ResourceUsageRead | MTL::ResourceUsageWrite) - : MTL::ResourceUsageRead; - // Bind IR Converter runtime resources. - // The Metal Shader Converter expects resources at specific bind points. - if (res_heap_ab_ && smp_heap_ab_ && uniforms_buffer_ && shared_memory_) { - // 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 = render_target_width_; - uint32_t vp_height = render_target_height_; - if (render_target_cache_) { - MTL::Texture* rt0_tex = render_target_cache_->GetColorTarget(0); - if (rt0_tex) { - vp_width = rt0_tex->width(); - vp_height = rt0_tex->height(); - } else if (MTL::Texture* depth_tex = - render_target_cache_->GetDepthTarget()) { - vp_width = depth_tex->width(); - vp_height = depth_tex->height(); - } else if (MTL::Texture* dummy = - render_target_cache_->GetDummyColorTarget()) { - vp_width = dummy->width(); - vp_height = dummy->height(); - } - } - draw_util::ViewportInfo viewport_info; - auto depth_control = draw_util::GetNormalizedDepthControl(regs); - constexpr uint32_t kViewportBoundsMax = 32767; - 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 = - texture_cache_ ? texture_cache_->draw_resolution_scale_y() : 1; - draw_util::GetViewportInfoArgs gviargs{}; - gviargs.Setup( - draw_resolution_scale_x, draw_resolution_scale_y, - texture_cache_ ? texture_cache_->draw_resolution_scale_x_divisor() - : divisors::MagicDiv(1), - texture_cache_ ? texture_cache_->draw_resolution_scale_y_divisor() - : divisors::MagicDiv(1), - 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 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). - if (scissor.offset[0] + scissor.extent[0] > vp_width) { - scissor.extent[0] = - (scissor.offset[0] < vp_width) ? (vp_width - scissor.offset[0]) : 0; - } - if (scissor.offset[1] + scissor.extent[1] > vp_height) { - scissor.extent[1] = - (scissor.offset[1] < vp_height) ? (vp_height - scissor.offset[1]) : 0; - } - - 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::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]; - current_render_encoder_->setScissorRect(mtl_scissor); - - ApplyRasterizerState(primitive_polygonal); - - // Fixed-function depth/stencil state is not part of the pipeline state in - // Metal, so update it per draw. - ApplyDepthStencilState(primitive_polygonal, depth_control); - - // Update full system constants from GPU registers - // This populates flags, NDC transform, alpha test, blend constants, etc. - uint32_t normalized_color_mask = - pixel_shader ? draw_util::GetNormalizedColorMask( - regs, pixel_shader->writes_color_targets()) - : 0; - 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, depth_control, normalized_color_mask); - - float blend_constants[] = { - 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), - }; - bool blend_factor_update_needed = - !ff_blend_factor_valid_ || - std::memcmp(ff_blend_factor_, blend_constants, sizeof(float) * 4) != 0; - if (blend_factor_update_needed) { - 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]); - } - - constexpr size_t kStageVertex = 0; - constexpr size_t kStagePixel = 1; - uint32_t ring_index = current_draw_index_ % uint32_t(draw_ring_count_); - size_t table_index_vertex = size_t(ring_index) * kStageCount + kStageVertex; - size_t table_index_pixel = size_t(ring_index) * kStageCount + kStagePixel; - - // Uniforms buffer layout (4KB per CBV for alignment): - // b0 (offset 0): System constants (~512 bytes) - // b1 (offset 4096): Float constants (256 float4s = 4KB) - // b2 (offset 8192): Bool/loop constants (~256 bytes) - // b3 (offset 12288): Fetch constants (768 bytes) - // b4 (offset 16384): Descriptor indices (unused in bindful mode) - const size_t kCBVSize = kCbvSizeBytes; - 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; - - // b0: System constants. - std::memcpy(uniforms_vertex, &system_constants_, - sizeof(DxbcShaderTranslator::SystemConstants)); - std::memcpy(uniforms_pixel, &system_constants_, - sizeof(DxbcShaderTranslator::SystemConstants)); - - // b1: Float constants at offset 4096 (1 * kCBVSize). - const size_t kFloatConstantOffset = 1 * kCBVSize; - // DxbcShaderTranslator uses packed float constants, mirroring the D3D12 - // backend behavior: only the constants actually used by the shader are - // written sequentially based on Shader::ConstantRegisterMap. - auto write_packed_float_constants = [&](uint8_t* dst, const Shader& shader, - uint32_t regs_base) { - std::memset(dst, 0, kCBVSize); - const Shader::ConstantRegisterMap& map = shader.constant_register_map(); - if (!map.float_count) { - return; - } - uint8_t* out = dst; - for (uint32_t i = 0; i < 4; ++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 + kCBVSize) { - return; - } - std::memcpy( - out, ®s.values[regs_base + (i << 8) + (constant_index << 2)], - 4 * sizeof(uint32_t)); - out += 4 * sizeof(uint32_t); - } - } - }; - - // Vertex shader uses c0-c255, pixel shader uses c256-c511. - write_packed_float_constants(uniforms_vertex + kFloatConstantOffset, - *vertex_shader, - XE_GPU_REG_SHADER_CONSTANT_000_X); - if (pixel_shader) { - write_packed_float_constants(uniforms_pixel + kFloatConstantOffset, - *pixel_shader, - XE_GPU_REG_SHADER_CONSTANT_256_X); - } else { - std::memset(uniforms_pixel + kFloatConstantOffset, 0, kCBVSize); - } - - // b2: Bool/Loop constants at offset 8192 (2 * kCBVSize). - const size_t kBoolLoopConstantOffset = 2 * kCBVSize; - constexpr size_t kBoolLoopConstantsSize = (8 + 32) * sizeof(uint32_t); - std::memcpy(uniforms_vertex + kBoolLoopConstantOffset, - ®s.values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031], - kBoolLoopConstantsSize); - std::memcpy(uniforms_pixel + kBoolLoopConstantOffset, - ®s.values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031], - kBoolLoopConstantsSize); - - // b3: Fetch constants at offset 12288 (3 * kCBVSize). - // 32 fetch groups * 6 DWORDs = 192 DWORDs (same data as 96 vertex fetches). - const size_t kFetchConstantOffset = 3 * kCBVSize; - const size_t kFetchConstantCount = - xenos::kTextureFetchConstantCount * 6; // 192 DWORDs = 768 bytes - std::memcpy(uniforms_vertex + kFetchConstantOffset, - ®s.values[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0], - kFetchConstantCount * sizeof(uint32_t)); - std::memcpy(uniforms_pixel + kFetchConstantOffset, - ®s.values[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0], - kFetchConstantCount * sizeof(uint32_t)); - - auto* res_entries_all = - reinterpret_cast(res_heap_ab_->contents()); - const size_t kDescriptorTableCount = kStageCount * draw_ring_count_; - const size_t uav_table_base_index = - kResourceHeapSlotsPerTable * kDescriptorTableCount; - auto* uav_entries_all = res_entries_all + uav_table_base_index; - auto* smp_entries_all = - reinterpret_cast(smp_heap_ab_->contents()); - auto* cbv_entries_all = - reinterpret_cast(cbv_heap_ab_->contents()); - - IRDescriptorTableEntry* res_entries_vertex = - res_entries_all + table_index_vertex * kResourceHeapSlotsPerTable; - IRDescriptorTableEntry* res_entries_pixel = - res_entries_all + table_index_pixel * kResourceHeapSlotsPerTable; - IRDescriptorTableEntry* uav_entries_vertex = - uav_entries_all + table_index_vertex * kResourceHeapSlotsPerTable; - IRDescriptorTableEntry* uav_entries_pixel = - uav_entries_all + table_index_pixel * kResourceHeapSlotsPerTable; - IRDescriptorTableEntry* smp_entries_vertex = - smp_entries_all + table_index_vertex * kSamplerHeapSlotsPerTable; - IRDescriptorTableEntry* smp_entries_pixel = - smp_entries_all + table_index_pixel * kSamplerHeapSlotsPerTable; - IRDescriptorTableEntry* cbv_entries_vertex = - cbv_entries_all + table_index_vertex * kCbvHeapSlotsPerTable; - IRDescriptorTableEntry* cbv_entries_pixel = - cbv_entries_all + table_index_pixel * kCbvHeapSlotsPerTable; - - uint64_t res_heap_gpu_base_vertex = - res_heap_ab_->gpuAddress() + table_index_vertex * - kResourceHeapSlotsPerTable * - sizeof(IRDescriptorTableEntry); - uint64_t res_heap_gpu_base_pixel = - res_heap_ab_->gpuAddress() + table_index_pixel * - kResourceHeapSlotsPerTable * - sizeof(IRDescriptorTableEntry); - uint64_t uav_heap_gpu_base_vertex = - res_heap_ab_->gpuAddress() + - (uav_table_base_index + - table_index_vertex * kResourceHeapSlotsPerTable) * - sizeof(IRDescriptorTableEntry); - uint64_t uav_heap_gpu_base_pixel = - res_heap_ab_->gpuAddress() + - (uav_table_base_index + - table_index_pixel * kResourceHeapSlotsPerTable) * - sizeof(IRDescriptorTableEntry); - uint64_t smp_heap_gpu_base_vertex = - smp_heap_ab_->gpuAddress() + table_index_vertex * - kSamplerHeapSlotsPerTable * - sizeof(IRDescriptorTableEntry); - uint64_t smp_heap_gpu_base_pixel = - smp_heap_ab_->gpuAddress() + table_index_pixel * - kSamplerHeapSlotsPerTable * - sizeof(IRDescriptorTableEntry); - - uint64_t uniforms_gpu_base_vertex = - uniforms_buffer_->gpuAddress() + - table_index_vertex * kUniformsBytesPerTable; - uint64_t uniforms_gpu_base_pixel = - uniforms_buffer_->gpuAddress() + - table_index_pixel * kUniformsBytesPerTable; - - MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer(); - if (shared_mem_buffer) { - IRDescriptorTableSetBuffer(&res_entries_vertex[0], - shared_mem_buffer->gpuAddress(), - shared_mem_buffer->length()); - IRDescriptorTableSetBuffer(&res_entries_pixel[0], - shared_mem_buffer->gpuAddress(), - shared_mem_buffer->length()); - IRDescriptorTableSetBuffer(&uav_entries_vertex[0], - shared_mem_buffer->gpuAddress(), - shared_mem_buffer->length()); - IRDescriptorTableSetBuffer(&uav_entries_pixel[0], - shared_mem_buffer->gpuAddress(), - shared_mem_buffer->length()); - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - } - if (render_target_cache_) { - if (MTL::Buffer* edram_buffer = render_target_cache_->GetEdramBuffer()) { - IRDescriptorTableSetBuffer(&uav_entries_vertex[1], - edram_buffer->gpuAddress(), - edram_buffer->length()); - IRDescriptorTableSetBuffer(&uav_entries_pixel[1], - edram_buffer->gpuAddress(), - edram_buffer->length()); - UseRenderEncoderResource( - edram_buffer, MTL::ResourceUsageRead | MTL::ResourceUsageWrite); - } - } - - std::array textures_for_encoder; - uint32_t textures_for_encoder_count = 0; - auto track_texture_usage = [&](MTL::Texture* texture) { - if (!texture) { - return; - } - for (uint32_t i = 0; i < textures_for_encoder_count; ++i) { - if (textures_for_encoder[i] == texture) { - return; - } - } - assert_true(textures_for_encoder_count < textures_for_encoder.size()); - textures_for_encoder[textures_for_encoder_count++] = texture; - }; - - auto bind_shader_textures = [&](const char* stage, MetalShader* shader, - IRDescriptorTableEntry* stage_res_entries) { - if (!shader || !texture_cache_) { - return; - } - const auto& shader_texture_bindings = - shader->GetTextureBindingsAfterTranslation(); - MetalTextureCache* metal_texture_cache = texture_cache_.get(); - for (size_t binding_index = 0; - binding_index < shader_texture_bindings.size(); ++binding_index) { - uint32_t srv_slot = 1 + static_cast(binding_index); - if (srv_slot >= kResourceHeapSlotsPerTable) { - break; - } - const auto& binding = shader_texture_bindings[binding_index]; - MTL::Texture* texture = texture_cache_->GetTextureForBinding( - binding.fetch_constant, binding.dimension, binding.is_signed); - if (!texture) { - // Use a dimension-compatible null texture to avoid Metal validation - // errors (for example, cube-array expectations from converted - // shaders). - switch (binding.dimension) { - case xenos::FetchOpDimension::k1D: - case xenos::FetchOpDimension::k2D: - texture = metal_texture_cache->GetNullTexture2D(); - break; - 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; - } - if (!logged_missing_texture_warning_) { - XELOGW( - "Metal: Missing texture for fetch constant {} (dimension {} " - "signed {})", - binding.fetch_constant, static_cast(binding.dimension), - binding.is_signed); - logged_missing_texture_warning_ = true; - } - } - if (texture) { - IRDescriptorTableSetTexture(&stage_res_entries[srv_slot], texture, - 0.0f, 0); - track_texture_usage(texture); - } - } - }; - - auto bind_shader_samplers = [&](const char* stage, MetalShader* shader, - IRDescriptorTableEntry* stage_smp_entries) { - if (!shader || !texture_cache_) { - return; - } - const auto& sampler_bindings = - shader->GetSamplerBindingsAfterTranslation(); - for (size_t sampler_index = 0; sampler_index < sampler_bindings.size(); - ++sampler_index) { - if (sampler_index >= kSamplerHeapSlotsPerTable) { - break; - } - auto parameters = texture_cache_->GetSamplerParameters( - sampler_bindings[sampler_index]); - MTL::SamplerState* sampler_state = - texture_cache_->GetOrCreateSampler(parameters); - if (!sampler_state) { - sampler_state = null_sampler_; - } - if (sampler_state) { - IRDescriptorTableSetSampler(&stage_smp_entries[sampler_index], - sampler_state, 0.0f); - } - } - }; - - bind_shader_textures("VS", vertex_shader, res_entries_vertex); - bind_shader_textures("PS", pixel_shader, res_entries_pixel); - bind_shader_samplers("VS", vertex_shader, smp_entries_vertex); - bind_shader_samplers("PS", pixel_shader, smp_entries_pixel); - - for (uint32_t i = 0; i < textures_for_encoder_count; ++i) { - UseRenderEncoderResource(textures_for_encoder[i], MTL::ResourceUsageRead); - } - - UseRenderEncoderResource(null_buffer_, MTL::ResourceUsageRead); - UseRenderEncoderResource(null_texture_, MTL::ResourceUsageRead); - UseRenderEncoderResource(res_heap_ab_, MTL::ResourceUsageRead); - UseRenderEncoderResource(smp_heap_ab_, MTL::ResourceUsageRead); - UseRenderEncoderResource(top_level_ab_, MTL::ResourceUsageRead); - UseRenderEncoderResource(cbv_heap_ab_, MTL::ResourceUsageRead); - UseRenderEncoderResource(uniforms_buffer_, MTL::ResourceUsageRead); - - auto write_top_level_and_cbvs = [&](size_t table_index, - uint64_t res_table_gpu_base, - uint64_t uav_table_gpu_base, - uint64_t smp_table_gpu_base, - IRDescriptorTableEntry* cbv_entries, - uint64_t uniforms_gpu_base) { - size_t top_level_offset = table_index * kTopLevelABBytesPerTable; - auto* top_level_ptrs = reinterpret_cast( - static_cast(top_level_ab_->contents()) + top_level_offset); - std::memset(top_level_ptrs, 0, kTopLevelABBytesPerTable); - - for (int i = 0; i < 4; ++i) { - top_level_ptrs[i] = res_table_gpu_base; - } - top_level_ptrs[4] = res_table_gpu_base; - for (int i = 5; i < 9; ++i) { - top_level_ptrs[i] = uav_table_gpu_base; - } - top_level_ptrs[9] = smp_table_gpu_base; - - IRDescriptorTableSetBuffer(&cbv_entries[0], - uniforms_gpu_base + 0 * kCbvSizeBytes, - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[1], - uniforms_gpu_base + 1 * kCbvSizeBytes, - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[2], - uniforms_gpu_base + 2 * kCbvSizeBytes, - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[3], - uniforms_gpu_base + 3 * kCbvSizeBytes, - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[4], - uniforms_gpu_base + 4 * kCbvSizeBytes, - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[5], null_buffer_->gpuAddress(), - kCbvSizeBytes); - IRDescriptorTableSetBuffer(&cbv_entries[6], null_buffer_->gpuAddress(), - kCbvSizeBytes); - - uint64_t cbv_table_gpu_base = - cbv_heap_ab_->gpuAddress() + - table_index * kCbvHeapSlotsPerTable * sizeof(IRDescriptorTableEntry); - top_level_ptrs[10] = cbv_table_gpu_base; - top_level_ptrs[11] = cbv_table_gpu_base; - top_level_ptrs[12] = cbv_table_gpu_base; - top_level_ptrs[13] = cbv_table_gpu_base; - }; - - write_top_level_and_cbvs(table_index_vertex, res_heap_gpu_base_vertex, - uav_heap_gpu_base_vertex, smp_heap_gpu_base_vertex, - cbv_entries_vertex, uniforms_gpu_base_vertex); - write_top_level_and_cbvs(table_index_pixel, res_heap_gpu_base_pixel, - uav_heap_gpu_base_pixel, smp_heap_gpu_base_pixel, - cbv_entries_pixel, uniforms_gpu_base_pixel); - - if (use_geometry_emulation || use_tessellation_emulation) { - current_render_encoder_->setObjectBuffer( - top_level_ab_, table_index_vertex * kTopLevelABBytesPerTable, - kIRArgumentBufferBindPoint); - current_render_encoder_->setMeshBuffer( - top_level_ab_, table_index_vertex * kTopLevelABBytesPerTable, - kIRArgumentBufferBindPoint); - current_render_encoder_->setFragmentBuffer( - top_level_ab_, table_index_pixel * kTopLevelABBytesPerTable, - kIRArgumentBufferBindPoint); - - if (use_tessellation_emulation) { - current_render_encoder_->setObjectBuffer( - top_level_ab_, table_index_vertex * kTopLevelABBytesPerTable, - kIRArgumentBufferHullDomainBindPoint); - current_render_encoder_->setMeshBuffer( - top_level_ab_, table_index_vertex * kTopLevelABBytesPerTable, - kIRArgumentBufferHullDomainBindPoint); - } - - current_render_encoder_->setObjectBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setMeshBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setFragmentBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setObjectBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - current_render_encoder_->setMeshBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - current_render_encoder_->setFragmentBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - } else { - current_render_encoder_->setVertexBuffer( - top_level_ab_, table_index_vertex * kTopLevelABBytesPerTable, - kIRArgumentBufferBindPoint); - current_render_encoder_->setFragmentBuffer( - top_level_ab_, table_index_pixel * kTopLevelABBytesPerTable, - kIRArgumentBufferBindPoint); - - current_render_encoder_->setVertexBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setFragmentBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setVertexBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - current_render_encoder_->setFragmentBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - } - } - - // 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) { - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - 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; - } - } - } - // 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); - } 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()) { - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - } - } - } else { - // 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 - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - - // 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); - current_render_encoder_->setVertexBuffer(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()) { - UseRenderEncoderResource(shared_mem_buffer, shared_memory_usage); - } - } - } - - 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) { - XELOGW( - "Index buffer range out of bounds (base=0x{:08X} size={} count={})", - static_cast(index_base), index_length, index_count); - return false; - } - return shared_memory_->RequestRange(static_cast(index_base), - static_cast(index_length)); - }; - - 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; - 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, - primitive_processing_result.host_draw_vertex_count, - index_type)) { - XELOGE( - "IssueDraw: failed to validate guest index buffer range for " - "tessellation"); - 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_) { - index_buffer = primitive_processor_->GetBuiltinIndexBuffer(); - index_offset = primitive_processing_result.host_index_buffer_handle; - } - break; - default: - XELOGE("Unsupported index buffer type {} for tessellation", - uint32_t(primitive_processing_result.index_buffer_type)); - return false; - } - if (!index_buffer) { - XELOGE("IssueDraw: index buffer is null for tessellation"); - return false; - } - UseRenderEncoderResource(index_buffer, MTL::ResourceUsageRead); - 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; - 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, - primitive_processing_result.host_draw_vertex_count, - index_type)) { - XELOGE("IssueDraw: 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_) { - index_buffer = primitive_processor_->GetBuiltinIndexBuffer(); - index_offset = 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) { - XELOGE("IssueDraw: index buffer is null for type {}", - uint32_t(primitive_processing_result.index_buffer_type)); - return false; - } - UseRenderEncoderResource(index_buffer, MTL::ResourceUsageRead); - 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: - mtl_primitive = MTL::PrimitiveTypePoint; - break; - case xenos::PrimitiveType::kLineList: - mtl_primitive = MTL::PrimitiveTypeLine; - break; - case xenos::PrimitiveType::kLineStrip: - mtl_primitive = MTL::PrimitiveTypeLineStrip; - break; - case xenos::PrimitiveType::kTriangleList: - case xenos::PrimitiveType::kRectangleList: - mtl_primitive = MTL::PrimitiveTypeTriangle; - break; - case xenos::PrimitiveType::kTriangleStrip: - mtl_primitive = MTL::PrimitiveTypeTriangleStrip; - break; - default: - 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; - } - - // Draw using primitive processor output. - if (primitive_processing_result.index_buffer_type == - PrimitiveProcessor::ProcessedIndexBufferType::kNone) { - IRRuntimeDrawPrimitives( - current_render_encoder_, mtl_primitive, NS::UInteger(0), - NS::UInteger(primitive_processing_result.host_draw_vertex_count)); - } 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; - 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, - primitive_processing_result.host_draw_vertex_count, - index_type)) { - XELOGE("IssueDraw: 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_) { - index_buffer = primitive_processor_->GetBuiltinIndexBuffer(); - index_offset = 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) { - XELOGE("IssueDraw: index buffer is null for type {}", - uint32_t(primitive_processing_result.index_buffer_type)); - return false; - } - 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); - } - } - - if (memexport_used && shared_memory_) { - for (const draw_util::MemExportRange& memexport_range : memexport_ranges_) { - shared_memory_->RangeWrittenByGpu( - memexport_range.base_address_dwords << 2, memexport_range.size_bytes); - } - } - - // Advance ring-buffer indices for descriptor and argument buffers. - ++current_draw_index_; - - return true; -} - -bool MetalCommandProcessor::IssueCopy() { - if (!BeginSubmission(true)) { - return false; - } - - // Finish any in-flight rendering so the render target contents are - // available to the render target cache, similar to D3D12's - // D3D12CommandProcessor::IssueCopy. - if (current_render_encoder_) { - current_render_encoder_->endEncoding(); - current_render_encoder_->release(); - current_render_encoder_ = nullptr; - } - - if (!current_command_buffer_) { - if (!EnsureCommandBuffer()) { - XELOGE("MetalCommandProcessor::IssueCopy: no command buffer"); - return false; - } - current_command_buffer_->setLabel( - NS::String::string("XeniaCopyCommandBuffer", NS::UTF8StringEncoding)); - } - - 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, - current_command_buffer_)) { - XELOGE("MetalCommandProcessor::IssueCopy - Resolve 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; - ReadbackBuffer* readback_buffer = nullptr; - 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; - - 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; - } - } - - if (!written_length) { - // Commit any in-flight work so ordering matches D3D12 submission behavior. - EndSubmission(false); - 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); - - if (do_readback) { - MTL::Buffer* source_buffer = nullptr; - size_t source_offset = 0; - size_t source_length_size_t = 0; - - if (texture_cache_ && texture_cache_->IsDrawResolutionScaled()) { - readback_scaled = true; - auto* metal_texture_cache = - static_cast(texture_cache_.get()); - if (!metal_texture_cache || - !metal_texture_cache->GetCurrentScaledResolveBuffer( - source_buffer, source_offset, source_length_size_t)) { - XELOGE("MetalResolveReadback: failed to get scaled resolve buffer"); - do_readback = false; - } else { - scale_x = texture_cache_->draw_resolution_scale_x(); - scale_y = texture_cache_->draw_resolution_scale_y(); - uint64_t scale_area = uint64_t(scale_x) * uint64_t(scale_y); - uint64_t range_start_scaled = - metal_texture_cache->GetCurrentScaledResolveRangeStartScaled(); - if (scale_area && (range_start_scaled % scale_area) == 0) { - uint64_t range_start_unscaled = range_start_scaled / scale_area; - if (written_address >= range_start_unscaled) { - scaled_range_offset_bytes = - (uint64_t(written_address) - range_start_unscaled) * scale_area; - } - } - if (scaled_range_offset_bytes > source_length_size_t) { - XELOGE("MetalResolveReadback: scaled range offset out of bounds"); - do_readback = false; - } else { - readback_base_offset_bytes = scaled_range_offset_bytes; - } - } - } else { - source_buffer = shared_memory_ ? shared_memory_->GetBuffer() : nullptr; - source_offset = written_address; - source_length_size_t = written_length; - source_offset_bytes_log = source_offset; - } - - if (do_readback) { - if (!source_buffer || source_length_size_t == 0) { - do_readback = false; - } else if (source_length_size_t > std::numeric_limits::max()) { - XELOGE("MetalResolveReadback: source length too large ({})", - source_length_size_t); - do_readback = false; - } else if (source_offset + source_length_size_t > - size_t(source_buffer->length())) { - XELOGE("MetalResolveReadback: source range out of bounds"); - do_readback = false; - } - } - - if (do_readback) { - source_length = uint32_t(source_length_size_t); - uint64_t scaled_available_bytes = source_length_size_t; - if (readback_scaled) { - if (scaled_range_offset_bytes >= source_length_size_t) { - XELOGE("MetalResolveReadback: scaled range offset exceeds length"); - do_readback = false; - } else { - scaled_available_bytes = - source_length_size_t - scaled_range_offset_bytes; - } - } - uint64_t resolve_key = - MakeReadbackResolveKey(written_address, written_length); - ReadbackBuffer& rb = readback_buffers_[resolve_key]; - rb.last_used_frame = frame_current_; - readback_buffer = &rb; - write_index = rb.current_index; - use_delayed_sync = (readback_mode == ReadbackResolveMode::kFast || - readback_mode == ReadbackResolveMode::kSome); - read_index = use_delayed_sync ? (1u - write_index) : write_index; - - readback_length = source_length; - if (readback_scaled) { - auto copy_dest_info = register_file_->Get(); - const FormatInfo* format_info = - FormatInfo::Get(uint32_t(copy_dest_info.copy_dest_format)); - uint32_t bits_per_pixel = format_info->bits_per_pixel; - xe::bit_scan_forward(bits_per_pixel >> 3, &pixel_size_log2); - uint32_t bytes_per_pixel = 1u << pixel_size_log2; - uint32_t tile_size_1x = 32u * 32u * bytes_per_pixel; - tile_count = written_length / tile_size_1x; - half_pixel_offset = cvars::readback_resolve_half_pixel_offset && - (scale_x > 1 || scale_y > 1); - uint64_t tile_size_scaled = - uint64_t(tile_size_1x) * uint64_t(scale_x) * uint64_t(scale_y); - uint64_t required_scaled = uint64_t(tile_count) * tile_size_scaled; - if (required_scaled > scaled_available_bytes) { - tile_count = uint32_t(scaled_available_bytes / tile_size_scaled); - required_scaled = uint64_t(tile_count) * tile_size_scaled; - } - if (tile_count == 0) { - do_readback = false; - } - - uint64_t source_offset_bytes_64 = - uint64_t(source_offset) + scaled_range_offset_bytes; - source_offset_bytes_log = source_offset_bytes_64; - if (do_readback && tile_count != 0 && resolve_downscale_pipeline_) { - uint32_t downscale_buffer_size = - AlignReadbackBufferSize(written_length); - if (downscale_buffer_size > resolve_downscale_buffer_size_) { - if (resolve_downscale_buffer_) { - resolve_downscale_buffer_->release(); - resolve_downscale_buffer_ = nullptr; - resolve_downscale_buffer_size_ = 0; - } - if (device_) { - resolve_downscale_buffer_ = device_->newBuffer( - downscale_buffer_size, MTL::ResourceStorageModePrivate); - if (resolve_downscale_buffer_) { - resolve_downscale_buffer_size_ = downscale_buffer_size; - } - } - } - if (resolve_downscale_buffer_) { - use_gpu_downscale = true; - readback_length = written_length; - source_buffer_binding_offset = - size_t(source_offset_bytes_64 & ~uint64_t(3)); - source_offset_bytes = - uint32_t(source_offset_bytes_64 - - uint64_t(source_buffer_binding_offset)); - } - } - if (do_readback && !use_gpu_downscale) { - scaled_copy_length = required_scaled; - if (scaled_copy_length > std::numeric_limits::max()) { - XELOGE("MetalResolveReadback: scaled copy length too large ({})", - scaled_copy_length); - do_readback = false; - } else { - readback_length = uint32_t(scaled_copy_length); - source_offset = size_t(source_offset_bytes_64); - readback_base_offset_bytes = 0; - } - } - } - - uint32_t aligned_size = AlignReadbackBufferSize(readback_length); - if (aligned_size > rb.sizes[write_index]) { - if (!device_) { - XELOGE("MetalResolveReadback: missing Metal device"); - do_readback = false; - } - } - if (do_readback && aligned_size > rb.sizes[write_index]) { - if (rb.buffers[write_index]) { - rb.buffers[write_index]->release(); - rb.buffers[write_index] = nullptr; - } - rb.buffers[write_index] = - device_->newBuffer(aligned_size, MTL::ResourceStorageModeShared); - rb.sizes[write_index] = aligned_size; - rb.submission_ids[write_index] = 0; - } - if (do_readback && !rb.buffers[write_index]) { - XELOGE("MetalResolveReadback: failed to allocate readback buffer"); - do_readback = false; - } else if (do_readback) { - if (readback_scaled && use_gpu_downscale) { - MTL::ComputeCommandEncoder* compute = - current_command_buffer_->computeCommandEncoder(); - if (!compute) { - XELOGE("MetalResolveReadback: failed to create compute encoder"); - do_readback = false; - } else { - struct ResolveDownscaleConstants { - uint32_t scale_x; - uint32_t scale_y; - uint32_t pixel_size_log2; - uint32_t tile_count; - uint32_t source_offset_bytes; - uint32_t half_pixel_offset; - } constants; - constants.scale_x = scale_x; - constants.scale_y = scale_y; - constants.pixel_size_log2 = pixel_size_log2; - constants.tile_count = tile_count; - constants.source_offset_bytes = source_offset_bytes; - constants.half_pixel_offset = half_pixel_offset ? 1u : 0u; - - compute->setComputePipelineState(resolve_downscale_pipeline_); - compute->setBytes(&constants, sizeof(constants), 0); - compute->setBuffer(source_buffer, source_buffer_binding_offset, 1); - compute->setBuffer(resolve_downscale_buffer_, 0, 2); - compute->useResource(source_buffer, MTL::ResourceUsageRead); - compute->useResource(resolve_downscale_buffer_, - MTL::ResourceUsageWrite); - compute->dispatchThreadgroups(MTL::Size::Make(tile_count, 1, 1), - MTL::Size::Make(32, 32, 1)); - compute->endEncoding(); - - MTL::BlitCommandEncoder* blit = - current_command_buffer_->blitCommandEncoder(); - if (!blit) { - XELOGE("MetalResolveReadback: failed to create blit encoder"); - do_readback = false; - } else { - blit->copyFromBuffer(resolve_downscale_buffer_, 0, - rb.buffers[write_index], 0, readback_length); - blit->endEncoding(); - rb.submission_ids[write_index] = GetCurrentSubmission(); - readback_scheduled = true; - readback_scaled_gpu = true; - } - } - } else { - MTL::BlitCommandEncoder* blit = - current_command_buffer_->blitCommandEncoder(); - if (!blit) { - XELOGE("MetalResolveReadback: failed to create blit encoder"); - do_readback = false; - } else { - blit->copyFromBuffer(source_buffer, source_offset, - rb.buffers[write_index], 0, readback_length); - blit->endEncoding(); - rb.submission_ids[write_index] = GetCurrentSubmission(); - readback_scheduled = true; - } - } - } - - ProcessCompletedSubmissions(); - if (readback_scheduled && use_delayed_sync) { - if (rb.buffers[read_index] == nullptr || - readback_length > rb.sizes[read_index] || - rb.submission_ids[read_index] == 0 || - rb.submission_ids[read_index] > submission_completed_processed_) { - is_cache_miss = true; - read_index = write_index; - } - } - - wait_for_completion = !use_delayed_sync || is_cache_miss; - should_copy = - (readback_mode == ReadbackResolveMode::kSome) ? is_cache_miss : true; - if (readback_scaled && tile_count == 0) { - should_copy = false; - wait_for_completion = false; - } - } - } - - // Submit the command buffer without waiting - the resolve writes are now - // ordered in the same submission as the preceding draws. - uint64_t submission_to_wait = 0; - bool defer_submission = - readback_scheduled && use_delayed_sync && !wait_for_completion; - if (readback_scheduled && wait_for_completion) { - submission_to_wait = GetCurrentSubmission(); - } - if (!defer_submission) { - EndSubmission(false); - if (submission_to_wait) { - CheckSubmissionCompletion(submission_to_wait); - } - } - - if (readback_scheduled && should_copy && readback_buffer && - readback_buffer->buffers[read_index]) { - static uint32_t readback_log_count = 0; - if (readback_log_count < 8) { - ++readback_log_count; - XELOGI( - "MetalResolveReadback: addr=0x{:08X} len={} scaled={} gpu={} " - "scale={}x{} pix_log2={} tiles={} src_off={} " - "scaled_off={} src_len={}", - written_address, written_length, readback_scaled ? 1 : 0, - readback_scaled_gpu ? 1 : 0, scale_x, scale_y, pixel_size_log2, - tile_count, source_offset_bytes_log, scaled_range_offset_bytes, - source_length); - } - const uint8_t* readback_bytes = static_cast( - readback_buffer->buffers[read_index]->contents()); - uint8_t* dest_ptr = memory_->TranslatePhysical(written_address); - if (readback_bytes && dest_ptr) { - if (readback_scaled) { - if (readback_scaled_gpu) { - memory::vastcpy(dest_ptr, const_cast(readback_bytes), - written_length); - } else { - const uint8_t* readback_base = readback_bytes; - if (readback_base_offset_bytes < readback_length) { - readback_base += readback_base_offset_bytes; - } - DownscaleResolveTileData(readback_base, dest_ptr, tile_count, - pixel_size_log2, scale_x, scale_y, - half_pixel_offset); - } - // Scaled resolve data isn't in shared memory; invalidate so CPU memory - // becomes authoritative and shared memory uploads on demand. - if (shared_memory_) { - shared_memory_->MemoryInvalidationCallback(written_address, - written_length, true); - } - if (primitive_processor_) { - primitive_processor_->MemoryInvalidationCallback( - written_address, written_length, true); - } - } else { - memory::vastcpy(dest_ptr, const_cast(readback_bytes), - written_length); - } - } - } - if (readback_scheduled && readback_buffer) { - readback_buffer->current_index = 1u - readback_buffer->current_index; - } - - return true; -} - -void MetalCommandProcessor::OnGammaRamp256EntryTableValueWritten() { - gamma_ramp_256_entry_table_up_to_date_ = false; -} - -void MetalCommandProcessor::OnGammaRampPWLValueWritten() { - gamma_ramp_pwl_up_to_date_ = false; -} - -void MetalCommandProcessor::WriteRegister(uint32_t index, uint32_t value) { - CommandProcessor::WriteRegister(index, value); - - if (index >= XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 && - index <= XE_GPU_REG_SHADER_CONSTANT_FETCH_31_5) { - if (texture_cache_) { - texture_cache_->TextureFetchConstantWritten( - (index - XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0) / 6); - } - } -} - -MTL::CommandBuffer* MetalCommandProcessor::EnsureCommandBuffer() { - if (current_command_buffer_) { - return current_command_buffer_; - } - if (!submission_open_) { - XELOGE("EnsureCommandBuffer: no open submission"); - return nullptr; - } - if (!command_queue_) { - XELOGE("EnsureCommandBuffer: no command queue"); - return nullptr; - } - - EnsureCommandBufferAutoreleasePool(); - - // Note: commandBuffer() returns an autoreleased object, we must retain it. - current_command_buffer_ = command_queue_->commandBuffer(); - if (!current_command_buffer_) { - XELOGE("EnsureCommandBuffer: failed to create command buffer"); - return nullptr; - } - current_command_buffer_->retain(); - current_command_buffer_->setLabel( - NS::String::string("XeniaCommandBuffer", NS::UTF8StringEncoding)); - - return current_command_buffer_; -} - -void MetalCommandProcessor::ProcessCompletedSubmissions() { - CheckSubmissionCompletion(0); -} - -void MetalCommandProcessor::CheckSubmissionCompletion( - uint64_t await_submission) { - if (!completion_timeline_) { - return; - } - if (await_submission) { - completion_timeline_->AwaitSubmissionAndUpdateCompleted(await_submission); - } else { - completion_timeline_->UpdateCompletedSubmission(); - } - const uint64_t completed = - completion_timeline_->GetCompletedSubmissionFromLastUpdate(); - if (completed <= submission_completed_processed_) { - return; - } - submission_completed_processed_ = completed; - if (primitive_processor_) { - primitive_processor_->CompletedSubmissionUpdated(); - } - if (texture_cache_) { - texture_cache_->CompletedSubmissionUpdated(completed); - } -} - -bool MetalCommandProcessor::BeginSubmission(bool is_guest_command) { - bool is_opening_frame = is_guest_command && !frame_open_; - if (submission_open_ && !is_opening_frame) { - return true; - } - - MaybeStartCapture(); - - uint64_t await_submission = 0; - if (is_opening_frame) { - await_submission = closed_frame_submissions_[frame_current_ % kQueueFrames]; - } - CheckSubmissionCompletion(await_submission); - - if (is_opening_frame) { - frame_completed_ = - std::max(frame_current_, uint64_t(kQueueFrames)) - kQueueFrames; - for (uint64_t frame = frame_completed_ + 1; frame < frame_current_; - ++frame) { - if (closed_frame_submissions_[frame % kQueueFrames] > - GetCompletedSubmission()) { - break; - } - frame_completed_ = frame; - } - } - - if (!submission_open_) { - submission_open_ = true; - if (!EnsureCommandBuffer()) { - submission_open_ = false; - return false; - } - if (primitive_processor_) { - primitive_processor_->BeginSubmission(); - } - if (texture_cache_) { - texture_cache_->BeginSubmission(GetCurrentSubmission()); - } - } - - if (is_opening_frame) { - frame_open_ = true; - if (primitive_processor_) { - primitive_processor_->BeginFrame(); - } - if (texture_cache_) { - texture_cache_->BeginFrame(); - } - if (render_target_cache_) { - render_target_cache_->BeginFrame(); - } - } - - return true; -} - -bool MetalCommandProcessor::EndSubmission(bool is_swap) { - bool is_closing_frame = is_swap && frame_open_; - - if (is_closing_frame) { - if (primitive_processor_) { - primitive_processor_->EndFrame(); - } - } - - if (submission_open_) { - EndRenderEncoder(); - - if (!current_command_buffer_) { - XELOGW("MetalCommandProcessor::EndSubmission: missing command buffer"); - } else { - if (completion_timeline_) { - completion_timeline_->SignalAndAdvance(current_command_buffer_); - } - ScheduleDrawRingRelease(current_command_buffer_); - current_command_buffer_->commit(); - current_command_buffer_->release(); - current_command_buffer_ = nullptr; - } - SetActiveDrawRing(nullptr); - current_draw_index_ = 0; - - submission_open_ = false; - DrainCommandBufferAutoreleasePool(); - } - - if (is_closing_frame) { - if (shared_memory_ && ::cvars::clear_memory_page_state) { - shared_memory_->SetSystemPageBlocksValidWithGpuDataWritten(); - } - frame_open_ = false; - closed_frame_submissions_[frame_current_++ % kQueueFrames] = - GetCurrentSubmission() - 1; - EvictOldReadbackBuffers(readback_buffers_); - } - - return true; -} - -bool MetalCommandProcessor::CanEndSubmissionImmediately() const { return true; } - -void MetalCommandProcessor::EnsureCommandBufferAutoreleasePool() { - if (command_buffer_autorelease_pool_) { - return; - } - command_buffer_autorelease_pool_ = NS::AutoreleasePool::alloc()->init(); -} - -void MetalCommandProcessor::DrainCommandBufferAutoreleasePool() { - if (!command_buffer_autorelease_pool_) { - return; - } - command_buffer_autorelease_pool_->release(); - command_buffer_autorelease_pool_ = nullptr; -} - -void MetalCommandProcessor::EndRenderEncoder() { - if (!current_render_encoder_) { - return; - } - current_render_encoder_->endEncoding(); - current_render_encoder_->release(); - current_render_encoder_ = nullptr; - current_render_pass_descriptor_ = nullptr; - ResetRenderEncoderResourceUsage(); -} - -void MetalCommandProcessor::ResetRenderEncoderResourceUsage() { - render_encoder_resource_usage_.clear(); - render_encoder_heap_usage_.clear(); -} - -void MetalCommandProcessor::UseRenderEncoderResource(MTL::Resource* resource, - MTL::ResourceUsage usage) { - 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) { - return; - } - it->second |= usage_bits; - } else { - render_encoder_resource_usage_.emplace(resource, usage_bits); - } - current_render_encoder_->useResource(resource, usage); -} - -void MetalCommandProcessor::UseRenderEncoderHeap(MTL::Heap* heap) { - if (!current_render_encoder_ || !heap) { - return; - } - if (!render_encoder_heap_usage_.insert(heap).second) { - return; - } - current_render_encoder_->useHeap(heap); -} - -void MetalCommandProcessor::UseRenderEncoderAttachmentHeaps( - MTL::RenderPassDescriptor* descriptor) { - if (!current_render_encoder_ || !descriptor) { - return; - } - auto* color_attachments = descriptor->colorAttachments(); - for (uint32_t i = 0; i < 8; ++i) { - auto* attachment = color_attachments->object(i); - if (!attachment) { - continue; - } - MTL::Texture* texture = attachment->texture(); - if (texture) { - UseRenderEncoderHeap(texture->heap()); - } - } - auto* depth_attachment = descriptor->depthAttachment(); - if (depth_attachment && depth_attachment->texture()) { - UseRenderEncoderHeap(depth_attachment->texture()->heap()); - } - auto* stencil_attachment = descriptor->stencilAttachment(); - if (stencil_attachment && stencil_attachment->texture()) { - UseRenderEncoderHeap(stencil_attachment->texture()->heap()); - } -} - -void MetalCommandProcessor::BeginCommandBuffer() { - if (!EnsureCommandBuffer()) { - return; - } - - if (!current_render_encoder_ && (!render_encoder_resource_usage_.empty() || - !render_encoder_heap_usage_.empty())) { - ResetRenderEncoderResourceUsage(); - } - - EnsureActiveDrawRing(); - - // 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; - } - - // 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); - if (auto* da = pass_descriptor->depthAttachment()) { - if (reverse_z) { - da->setClearDepth(0.0); - } else { - da->setClearDepth(1.0); - } - } - } - - bool render_pass_dirty = render_target_cache_ && - render_target_cache_->IsRenderPassDescriptorDirty(); - - // If the render pass configuration has changed since the current render - // encoder was created (e.g. dummy RT0 -> real RTs, depth/stencil binding), - // restart the render encoder with the updated descriptor. - if (current_render_encoder_ && - (current_render_pass_descriptor_ != pass_descriptor || - render_pass_dirty)) { - EndRenderEncoder(); - } - - if (!current_render_encoder_) { - // Note: renderCommandEncoder() returns an autoreleased object, we must - // retain it. - current_render_encoder_ = - current_command_buffer_->renderCommandEncoder(pass_descriptor); - if (!current_render_encoder_) { - XELOGE("Failed to create render command encoder"); - return; - } - current_render_encoder_->retain(); - current_render_encoder_->setLabel( - NS::String::string("XeniaRenderEncoder", NS::UTF8StringEncoding)); - ff_blend_factor_valid_ = false; - current_render_pass_descriptor_ = pass_descriptor; - UseRenderEncoderAttachmentHeaps(pass_descriptor); - } - - // Derive viewport/scissor from the actual bound render pass attachments. - uint32_t rt_width = render_target_width_; - uint32_t rt_height = render_target_height_; - MTL::Texture* pass_size_texture = nullptr; - if (pass_descriptor) { - if (auto* color_attachments = pass_descriptor->colorAttachments()) { - if (auto* attachment = color_attachments->object(0)) { - pass_size_texture = attachment->texture(); - } - } - if (!pass_size_texture) { - if (auto* depth_attachment = pass_descriptor->depthAttachment()) { - pass_size_texture = depth_attachment->texture(); - } - } - if (!pass_size_texture) { - if (auto* stencil_attachment = pass_descriptor->stencilAttachment()) { - pass_size_texture = stencil_attachment->texture(); - } - } - } - if (!pass_size_texture && render_target_cache_) { - pass_size_texture = render_target_cache_->GetColorTarget(0); - if (!pass_size_texture) { - pass_size_texture = render_target_cache_->GetDepthTarget(); - } - if (!pass_size_texture) { - pass_size_texture = render_target_cache_->GetDummyColorTarget(); - } - } - if (pass_size_texture) { - rt_width = static_cast(pass_size_texture->width()); - rt_height = static_cast(pass_size_texture->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); - - // Set scissor (must not exceed render pass dimensions) - MTL::ScissorRect scissor = {0, 0, rt_width, rt_height}; - current_render_encoder_->setScissorRect(scissor); -} - -void MetalCommandProcessor::EnsureDrawRingCapacity() { - if (current_draw_index_ < draw_ring_count_) { - return; - } - - auto ring = AcquireDrawRingBuffers(); - if (!ring) { - XELOGE("Metal draw ring exhausted but failed to allocate a new ring"); - return; - } - - SetActiveDrawRing(ring); - command_buffer_draw_rings_.push_back(ring); - current_draw_index_ = 0; -} - -void MetalCommandProcessor::EndCommandBuffer() { EndSubmission(false); } - -void MetalCommandProcessor::ApplyDepthStencilState( - bool primitive_polygonal, reg::RB_DEPTHCONTROL normalized_depth_control) { - 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); - - DepthStencilStateKey key; - key.depth_control = normalized_depth_control.value; - key.stencil_ref_mask_front = stencil_ref_mask_front.value; - key.stencil_ref_mask_back = stencil_ref_mask_back.value; - key.polygonal_and_backface = - (primitive_polygonal ? 1u : 0u) | - (normalized_depth_control.backface_enable ? 2u : 0u); - - MTL::DepthStencilState* state = nullptr; - auto it = depth_stencil_state_cache_.find(key); - if (it != depth_stencil_state_cache_.end()) { - state = it->second; - } else { - MTL::DepthStencilDescriptor* ds_desc = - MTL::DepthStencilDescriptor::alloc()->init(); - if (normalized_depth_control.z_enable) { - ds_desc->setDepthCompareFunction( - ToMetalCompareFunction(normalized_depth_control.zfunc)); - ds_desc->setDepthWriteEnabled(normalized_depth_control.z_write_enable != - 0); - } else { - ds_desc->setDepthCompareFunction(MTL::CompareFunctionAlways); - ds_desc->setDepthWriteEnabled(false); - } - - if (normalized_depth_control.stencil_enable) { - auto* front = MTL::StencilDescriptor::alloc()->init(); - front->setStencilCompareFunction( - ToMetalCompareFunction(normalized_depth_control.stencilfunc)); - front->setStencilFailureOperation( - ToMetalStencilOperation(normalized_depth_control.stencilfail)); - front->setDepthFailureOperation( - ToMetalStencilOperation(normalized_depth_control.stencilzfail)); - front->setDepthStencilPassOperation( - ToMetalStencilOperation(normalized_depth_control.stencilzpass)); - front->setReadMask(stencil_ref_mask_front.stencilmask); - front->setWriteMask(stencil_ref_mask_front.stencilwritemask); - - ds_desc->setFrontFaceStencil(front); - - if (primitive_polygonal && normalized_depth_control.backface_enable) { - auto* back = MTL::StencilDescriptor::alloc()->init(); - back->setStencilCompareFunction( - ToMetalCompareFunction(normalized_depth_control.stencilfunc_bf)); - back->setStencilFailureOperation( - ToMetalStencilOperation(normalized_depth_control.stencilfail_bf)); - back->setDepthFailureOperation( - ToMetalStencilOperation(normalized_depth_control.stencilzfail_bf)); - back->setDepthStencilPassOperation( - ToMetalStencilOperation(normalized_depth_control.stencilzpass_bf)); - back->setReadMask(stencil_ref_mask_back.stencilmask); - back->setWriteMask(stencil_ref_mask_back.stencilwritemask); - ds_desc->setBackFaceStencil(back); - back->release(); - } else { - ds_desc->setBackFaceStencil(front); - } - - front->release(); - } - - state = device_->newDepthStencilState(ds_desc); - ds_desc->release(); - - if (!state) { - XELOGE("Failed to create Metal depth/stencil state"); - return; - } - depth_stencil_state_cache_.emplace(key, state); - } - - current_render_encoder_->setDepthStencilState(state); - - if (normalized_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 && normalized_depth_control.backface_enable && - pa_su_sc_mode_cntl.cull_front && !pa_su_sc_mode_cntl.cull_back) { - ref = ref_back; - } else if (primitive_polygonal && - normalized_depth_control.backface_enable && - ref_front != ref_back) { - static bool mismatch_logged = false; - if (!mismatch_logged) { - mismatch_logged = true; - XELOGW( - "Metal: front/back stencil ref differ (front={}, back={}); using " - "front for both", - ref_front, ref_back); - } - } - current_render_encoder_->setStencilReferenceValue(ref); - } -} - -void MetalCommandProcessor::ApplyRasterizerState(bool primitive_polygonal) { - 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; - } - } - 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; - } - } - 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); - - current_render_encoder_->setDepthClipMode(pa_cl_clip_cntl.clip_disable - ? MTL::DepthClipModeClamp - : MTL::DepthClipModeClip); -} - -MTL::RenderPassDescriptor* -MetalCommandProcessor::GetCurrentRenderPassDescriptor() { - return render_pass_descriptor_; -} - -MTL::RenderPipelineState* MetalCommandProcessor::GetOrCreatePipelineState( - MetalShader::MetalTranslation* vertex_translation, - MetalShader::MetalTranslation* pixel_translation, - const RegisterFile& regs) { - if (!vertex_translation || !vertex_translation->metal_function()) { - XELOGE("No valid vertex shader function"); - return nullptr; - } - - // Determine attachment formats and sample count from the render target cache - // so the pipeline matches the actual render pass. If no real RT is bound, - // fall back to the dummy RT0 format used by the 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())); - } - } - } - - struct PipelineKey { - 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)); - - PipelineDiskCacheEntry disk_entry; - bool record_disk_entry = - ::cvars::metal_pipeline_disk_cache && pipeline_disk_cache_file_; - if (record_disk_entry) { - disk_entry.pipeline_key = key; - disk_entry.vertex_shader_cache_key = MetalShaderCache::GetCacheKey( - vertex_translation->shader().ucode_data_hash(), - vertex_translation->modification(), - static_cast(vertex_translation->shader().type())); - if (pixel_translation) { - disk_entry.pixel_shader_cache_key = MetalShaderCache::GetCacheKey( - pixel_translation->shader().ucode_data_hash(), - pixel_translation->modification(), - static_cast(pixel_translation->shader().type())); - } - disk_entry.sample_count = key_data.sample_count; - disk_entry.depth_format = key_data.depth_format; - disk_entry.stencil_format = key_data.stencil_format; - std::memcpy(disk_entry.color_formats, key_data.color_formats, - sizeof(key_data.color_formats)); - disk_entry.normalized_color_mask = key_data.normalized_color_mask; - disk_entry.alpha_to_mask_enable = key_data.alpha_to_mask_enable; - std::memcpy(disk_entry.blendcontrol, key_data.blendcontrol, - sizeof(key_data.blendcontrol)); - } - - // Check cache - auto it = pipeline_cache_.find(key); - if (it != pipeline_cache_.end()) { - return it->second; - } - - // 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()); - } - - // Set render target formats and sample count to match bound RTs. - 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->setSampleCount(sample_count); - desc->setAlphaToCoverageEnabled(key_data.alpha_to_mask_enable != 0); - - // Fixed-function blending and color write masks. - // These are part of the render pipeline state, so the cache key must include - // the relevant register-derived values (mask, RB_BLENDCONTROL, A2C). - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - if (color_formats[i] == MTL::PixelFormatInvalid) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - - uint32_t rt_write_mask = (key_data.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); - } - } - - // Configure vertex fetch layout for MSC stage-in. - // NOTE: The translated shaders use vfetch (buffer load) to read vertices - // directly from shared memory via SRV descriptors, NOT stage-in attributes. - // This vertex descriptor may be unnecessary. - 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: - // Metal only supports normalized variants of 10:10:10:2. - 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_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(); - if (record_disk_entry) { - disk_entry.vertex_attributes.clear(); - disk_entry.vertex_layouts.clear(); - } - - 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)); - if (record_disk_entry) { - PipelineDiskCacheVertexAttribute cached_attr = {}; - cached_attr.attribute_index = attr_index; - cached_attr.format = static_cast(fmt); - cached_attr.offset = attr.fetch_instr.attributes.offset * 4; - cached_attr.buffer_index = static_cast(buffer_index); - disk_entry.vertex_attributes.push_back(cached_attr); - } - 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); - if (record_disk_entry) { - PipelineDiskCacheVertexLayout cached_layout = {}; - cached_layout.buffer_index = static_cast(buffer_index); - cached_layout.stride = binding.stride_words * 4; - cached_layout.step_function = - static_cast(MTL::VertexStepFunctionPerVertex); - cached_layout.step_rate = 1; - disk_entry.vertex_layouts.push_back(cached_layout); - } - } - } - - desc->setVertexDescriptor(vertex_desc); - vertex_desc->release(); - } - - 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; - MTL::RenderPipelineState* pipeline = nullptr; - pipeline = device_->newRenderPipelineState(desc, &error); - 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; - } - - pipeline_cache_[key] = pipeline; - if (record_disk_entry) { - AppendPipelineDiskCacheEntry(disk_entry); - } - - return pipeline; -} - -MetalCommandProcessor::GeometryPipelineState* -MetalCommandProcessor::GetOrCreateGeometryPipelineState( - MetalShader::MetalTranslation* vertex_translation, - MetalShader::MetalTranslation* pixel_translation, - GeometryShaderKey geometry_shader_key, const RegisterFile& regs) { - 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 = 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())); - } - } - } - - 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]); - } - uint32_t pixel_shader_writes_color_targets = - use_fallback_pixel_shader - ? 0 - : (pixel_translation - ? pixel_translation->shader().writes_color_targets() - : 0); - key_data.normalized_color_mask = - pixel_shader_writes_color_targets - ? draw_util::GetNormalizedColorMask(regs, - pixel_shader_writes_color_targets) - : 0; - 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)); - - auto it = geometry_pipeline_cache_.find(key); - if (it != geometry_pipeline_cache_.end()) { - return &it->second; - } - - auto get_vertex_stage = [&]() -> GeometryVertexStageState* { - auto vertex_it = geometry_vertex_stage_cache_.find(vertex_translation); - if (vertex_it != geometry_vertex_stage_cache_.end()) { - return &vertex_it->second; - } - - std::vector dxil_data = vertex_translation->dxil_data(); - if (dxil_data.empty()) { - std::string dxil_error; - if (!dxbc_to_dxil_converter_->Convert( - vertex_translation->translated_binary(), dxil_data, - &dxil_error)) { - XELOGE("Geometry VS: DXBC to DXIL conversion failed: {}", dxil_error); - return nullptr; - } - } - - struct InputAttribute { - uint32_t input_slot = 0; - uint32_t offset = 0; - IRFormat format = IRFormatUnknown; - }; - std::vector attribute_map; - attribute_map.reserve(32); - - auto map_ir_format = - [](const ParsedVertexFetchInstruction::Attributes& attrs) -> IRFormat { - 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_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; - } - }; - - const Shader& vertex_shader_ref = vertex_translation->shader(); - const auto& vertex_bindings = vertex_shader_ref.vertex_bindings(); - uint32_t attr_index = 0; - for (const auto& binding : vertex_bindings) { - for (const auto& attr : binding.attributes) { - if (attr_index >= 31) { - break; - } - InputAttribute mapped = {}; - mapped.input_slot = static_cast(binding.binding_index); - mapped.offset = - static_cast(attr.fetch_instr.attributes.offset * 4); - mapped.format = map_ir_format(attr.fetch_instr.attributes); - attribute_map.push_back(mapped); - ++attr_index; - } - if (attr_index >= 31) { - break; - } - } - - IRInputTopology input_topology = IRInputTopologyUndefined; - switch (geometry_shader_key.type) { - case PipelineGeometryShader::kPointList: - input_topology = IRInputTopologyPoint; - break; - case PipelineGeometryShader::kRectangleList: - input_topology = IRInputTopologyTriangle; - break; - case PipelineGeometryShader::kQuadList: - // Quad lists use LineWithAdjacency in DXBC; MSC input topology doesn't - // model adjacency, so leave undefined to avoid mismatches. - input_topology = IRInputTopologyUndefined; - break; - default: - input_topology = IRInputTopologyUndefined; - break; - } - MetalShaderConversionResult vertex_result; - MetalShaderReflectionInfo vertex_reflection; - - // First pass: get reflection for vertex inputs. - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kVertex, dxil_data, vertex_result, - &vertex_reflection, nullptr, nullptr, true, - static_cast(input_topology))) { - XELOGE("Geometry VS: DXIL to Metal conversion failed: {}", - vertex_result.error_message); - return nullptr; - } - - IRVersionedInputLayoutDescriptor input_layout = {}; - input_layout.version = IRInputLayoutDescriptorVersion_1; - input_layout.desc_1_0.numElements = 0; - std::vector semantic_names_storage; - if (!vertex_reflection.vertex_inputs.empty()) { - 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("Geometry VS: vertex input {} out of range (max {})", - input.attribute_index, attribute_map.size()); - continue; - } - const InputAttribute& mapped = attribute_map[input.attribute_index]; - if (mapped.format == IRFormatUnknown) { - XELOGW("Geometry VS: unknown IRFormat for vertex input {}", - 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"; - } - semantic_names_storage.push_back(std::move(semantic_base)); - input_layout.desc_1_0.semanticNames[element_count] = - semantic_names_storage.back().c_str(); - IRInputElementDescriptor1& element = - input_layout.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.desc_1_0.numElements = element_count; - } - - // Second pass: synthesize stage-in using the input layout. - std::vector stage_in_metallib; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kVertex, dxil_data, vertex_result, - &vertex_reflection, &input_layout, &stage_in_metallib, true, - static_cast(input_topology))) { - XELOGE("Geometry VS: DXIL to Metal conversion failed: {}", - vertex_result.error_message); - return nullptr; - } - if (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; - } - - NS::Error* error = nullptr; - dispatch_data_t vertex_data = dispatch_data_create( - vertex_result.metallib_data.data(), vertex_result.metallib_data.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* vertex_library = device_->newLibrary(vertex_data, &error); - dispatch_release(vertex_data); - if (!vertex_library) { - XELOGE("Geometry VS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - NS::Error* stage_in_error = nullptr; - dispatch_data_t stage_in_data = - dispatch_data_create(stage_in_metallib.data(), stage_in_metallib.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* stage_in_library = - device_->newLibrary(stage_in_data, &stage_in_error); - dispatch_release(stage_in_data); - if (!stage_in_library) { - XELOGE("Geometry VS: Failed to create stage-in library: {}", - stage_in_error - ? stage_in_error->localizedDescription()->utf8String() - : "unknown error"); - 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; - }; - - auto get_geometry_stage = [&]() -> GeometryShaderStageState* { - 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 (!dxbc_to_dxil_converter_->Convert(dxbc_bytes, dxil_data, &dxil_error)) { - XELOGE("Geometry GS: DXBC to DXIL conversion failed: {}", dxil_error); - return nullptr; - } - - IRInputTopology input_topology = IRInputTopologyUndefined; - switch (geometry_shader_key.type) { - case PipelineGeometryShader::kPointList: - input_topology = IRInputTopologyPoint; - break; - case PipelineGeometryShader::kRectangleList: - input_topology = IRInputTopologyTriangle; - break; - case PipelineGeometryShader::kQuadList: - // Quad lists use LineWithAdjacency in DXBC; MSC input topology doesn't - // model adjacency, so leave undefined to avoid mismatches. - input_topology = IRInputTopologyUndefined; - break; - default: - input_topology = IRInputTopologyUndefined; - break; - } - MetalShaderConversionResult geometry_result; - MetalShaderReflectionInfo geometry_reflection; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kGeometry, dxil_data, geometry_result, - &geometry_reflection, nullptr, nullptr, true, - static_cast(input_topology))) { - XELOGE("Geometry GS: DXIL to Metal conversion failed: {}", - geometry_result.error_message); - return nullptr; - } - 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"); - } - } - - NS::Error* error = nullptr; - dispatch_data_t geometry_data = - dispatch_data_create(geometry_result.metallib_data.data(), - geometry_result.metallib_data.size(), nullptr, - DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* geometry_library = device_->newLibrary(geometry_data, &error); - dispatch_release(geometry_data); - if (!geometry_library) { - XELOGE("Geometry GS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() - : "unknown error"); - 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; - }; - - GeometryVertexStageState* vertex_stage = get_vertex_stage(); - if (!vertex_stage || !vertex_stage->library || - !vertex_stage->stage_in_library) { - return nullptr; - } - GeometryShaderStageState* geometry_stage = get_geometry_stage(); - 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(key_data.alpha_to_mask_enable != 0); - - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - if (color_formats[i] == MTL::PixelFormatInvalid) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - - uint32_t rt_write_mask = (key_data.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); - } - } - 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; - MTL::RenderPipelineState* pipeline = - IRRuntimeNewGeometryEmulationPipeline(device_, &ir_desc, &error); - desc->release(); - - if (!pipeline) { - XELOGE( - "Failed to create geometry pipeline state: {}", - error ? error->localizedDescription()->utf8String() : "unknown error"); - 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)); - return &inserted_it->second; -} - -MetalCommandProcessor::TessellationPipelineState* -MetalCommandProcessor::GetOrCreateTessellationPipelineState( - MetalShader::MetalTranslation* domain_translation, - MetalShader::MetalTranslation* pixel_translation, - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - const RegisterFile& regs) { - 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 = 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())); - } - } - } - - 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]); - } - uint32_t pixel_shader_writes_color_targets = - use_fallback_pixel_shader - ? 0 - : (pixel_translation - ? pixel_translation->shader().writes_color_targets() - : 0); - key_data.normalized_color_mask = - pixel_shader_writes_color_targets - ? draw_util::GetNormalizedColorMask(regs, - pixel_shader_writes_color_targets) - : 0; - 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)); - - 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; - - auto get_vertex_stage = [&]() -> TessellationVertexStageState* { - struct VertexStageKey { - const void* shader; - uint32_t tessellation_mode; - } vertex_key = {domain_translation, uint32_t(tessellation_mode)}; - uint32_t vertex_key_hash = - uint32_t(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 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 (!dxbc_to_dxil_converter_->Convert(dxbc_bytes, dxil_data, &dxil_error)) { - XELOGE("Tessellation VS: DXBC to DXIL conversion failed: {}", dxil_error); - return nullptr; - } - - struct InputAttribute { - uint32_t input_slot = 0; - uint32_t offset = 0; - IRFormat format = IRFormatUnknown; - }; - std::vector attribute_map; - attribute_map.reserve(32); - - auto map_ir_format = - [](const ParsedVertexFetchInstruction::Attributes& attrs) -> IRFormat { - 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_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; - } - }; - - const Shader& vertex_shader_ref = domain_translation->shader(); - const auto& vertex_bindings = vertex_shader_ref.vertex_bindings(); - uint32_t attr_index = 0; - for (const auto& binding : vertex_bindings) { - for (const auto& attr : binding.attributes) { - if (attr_index >= 31) { - break; - } - InputAttribute mapped = {}; - mapped.input_slot = static_cast(binding.binding_index); - mapped.offset = - static_cast(attr.fetch_instr.attributes.offset * 4); - mapped.format = map_ir_format(attr.fetch_instr.attributes); - attribute_map.push_back(mapped); - ++attr_index; - } - if (attr_index >= 31) { - break; - } - } - - IRInputTopology input_topology = IRInputTopologyUndefined; - - MetalShaderConversionResult vertex_result; - MetalShaderReflectionInfo vertex_reflection; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kVertex, dxil_data, vertex_result, - &vertex_reflection, nullptr, nullptr, true, - static_cast(input_topology))) { - XELOGE("Tessellation VS: DXIL to Metal conversion failed: {}", - vertex_result.error_message); - return nullptr; - } - - IRVersionedInputLayoutDescriptor input_layout = {}; - input_layout.version = IRInputLayoutDescriptorVersion_1; - input_layout.desc_1_0.numElements = 0; - std::vector semantic_names_storage; - if (!vertex_reflection.vertex_inputs.empty()) { - 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("Tessellation VS: vertex input {} out of range (max {})", - input.attribute_index, attribute_map.size()); - continue; - } - const InputAttribute& mapped = attribute_map[input.attribute_index]; - if (mapped.format == IRFormatUnknown) { - XELOGW("Tessellation VS: unknown IRFormat for vertex input {}", - 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"; - } - semantic_names_storage.push_back(std::move(semantic_base)); - input_layout.desc_1_0.semanticNames[element_count] = - semantic_names_storage.back().c_str(); - IRInputElementDescriptor1& element = - input_layout.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.desc_1_0.numElements = element_count; - } - - std::vector stage_in_metallib; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kVertex, dxil_data, vertex_result, - &vertex_reflection, &input_layout, &stage_in_metallib, true, - static_cast(input_topology))) { - XELOGE("Tessellation VS: DXIL to Metal conversion failed: {}", - vertex_result.error_message); - return nullptr; - } - if (stage_in_metallib.empty()) { - XELOGE("Tessellation VS: Failed to synthesize stage-in function"); - return nullptr; - } - - NS::Error* error = nullptr; - dispatch_data_t vertex_data = dispatch_data_create( - vertex_result.metallib_data.data(), vertex_result.metallib_data.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* vertex_library = device_->newLibrary(vertex_data, &error); - dispatch_release(vertex_data); - if (!vertex_library) { - XELOGE("Tessellation VS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - NS::Error* stage_in_error = nullptr; - dispatch_data_t stage_in_data = - dispatch_data_create(stage_in_metallib.data(), stage_in_metallib.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* stage_in_library = - device_->newLibrary(stage_in_data, &stage_in_error); - dispatch_release(stage_in_data); - if (!stage_in_library) { - XELOGE("Tessellation VS: Failed to create stage-in library: {}", - stage_in_error - ? stage_in_error->localizedDescription()->utf8String() - : "unknown error"); - 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; - }; - - auto get_hull_stage = [&]() -> TessellationHullStageState* { - 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 (!dxbc_to_dxil_converter_->Convert(dxbc_bytes, dxil_data, &dxil_error)) { - XELOGE("Tessellation HS: DXBC to DXIL conversion failed: {}", dxil_error); - return nullptr; - } - - MetalShaderConversionResult hull_result; - MetalShaderReflectionInfo hull_reflection; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kHull, dxil_data, hull_result, &hull_reflection, - nullptr, nullptr, true, - static_cast(IRInputTopologyUndefined))) { - XELOGE("Tessellation HS: DXIL to Metal conversion failed: {}", - hull_result.error_message); - return nullptr; - } - - NS::Error* error = nullptr; - dispatch_data_t hull_data = dispatch_data_create( - hull_result.metallib_data.data(), hull_result.metallib_data.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* hull_library = device_->newLibrary(hull_data, &error); - dispatch_release(hull_data); - if (!hull_library) { - XELOGE("Tessellation HS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - TessellationHullStageState state; - state.library = hull_library; - state.function_name = hull_result.function_name; - state.reflection = hull_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; - }; - - auto get_domain_stage = [&]() -> TessellationDomainStageState* { - 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; - } - - std::vector dxil_data = domain_translation->dxil_data(); - if (dxil_data.empty()) { - std::string dxil_error; - if (!dxbc_to_dxil_converter_->Convert( - domain_translation->translated_binary(), dxil_data, - &dxil_error)) { - XELOGE("Tessellation DS: DXBC to DXIL conversion failed: {}", - dxil_error); - return nullptr; - } - } - - MetalShaderConversionResult domain_result; - MetalShaderReflectionInfo domain_reflection; - if (!metal_shader_converter_->ConvertWithStageEx( - MetalShaderStage::kDomain, dxil_data, domain_result, - &domain_reflection, nullptr, nullptr, true, - static_cast(IRInputTopologyUndefined))) { - XELOGE("Tessellation DS: DXIL to Metal conversion failed: {}", - domain_result.error_message); - return nullptr; - } - - NS::Error* error = nullptr; - dispatch_data_t domain_data = dispatch_data_create( - domain_result.metallib_data.data(), domain_result.metallib_data.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* domain_library = device_->newLibrary(domain_data, &error); - dispatch_release(domain_data); - if (!domain_library) { - XELOGE("Tessellation DS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - TessellationDomainStageState state; - state.library = domain_library; - state.function_name = domain_result.function_name; - state.reflection = domain_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; - }; - - TessellationVertexStageState* vertex_stage = get_vertex_stage(); - if (!vertex_stage || !vertex_stage->library || - !vertex_stage->stage_in_library) { - return nullptr; - } - TessellationHullStageState* hull_stage = get_hull_stage(); - if (!hull_stage || !hull_stage->library) { - return nullptr; - } - TessellationDomainStageState* domain_stage = get_domain_stage(); - 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(key_data.alpha_to_mask_enable != 0); - - for (uint32_t i = 0; i < 4; ++i) { - auto* color_attachment = desc->colorAttachments()->object(i); - if (color_formats[i] == MTL::PixelFormatInvalid) { - color_attachment->setWriteMask(MTL::ColorWriteMaskNone); - color_attachment->setBlendingEnabled(false); - continue; - } - uint32_t rt_write_mask = (key_data.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); - } - } - 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; - MTL::RenderPipelineState* pipeline = - IRRuntimeNewGeometryTessellationEmulationPipeline(device_, &ir_desc, - &error); - 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)); - return &inserted_it->second; -} - -bool MetalCommandProcessor::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 (!dxbc_to_dxil_converter_->Convert(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; - } - - NS::Error* error = nullptr; - dispatch_data_t lib_data = dispatch_data_create( - result.metallib_data.data(), result.metallib_data.size(), nullptr, - DISPATCH_DATA_DESTRUCTOR_DEFAULT); - depth_only_pixel_library_ = device_->newLibrary(lib_data, &error); - dispatch_release(lib_data); - if (!depth_only_pixel_library_) { - XELOGE("Depth-only PS: Failed to create Metal library: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - 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; -} - -bool MetalCommandProcessor::CreateIRConverterBuffers() { - // Buffer creation is now done inline in SetupContext - // This function exists for header compatibility - return res_heap_ab_ && smp_heap_ab_ && uniforms_buffer_; -} - -std::shared_ptr -MetalCommandProcessor::CreateDrawRingBuffers() { - if (!device_) { - XELOGE("CreateDrawRingBuffers: Metal device is null"); - return nullptr; - } - if (!null_buffer_ || !null_texture_ || !null_sampler_) { - XELOGE("CreateDrawRingBuffers: Null resources not initialized"); - return nullptr; - } - - static uint32_t ring_id = 0; - auto ring = std::make_shared(); - - const size_t kDescriptorTableCount = kStageCount * draw_ring_count_; - const size_t kResourceHeapSlots = - kResourceHeapSlotsPerTable * kDescriptorTableCount; - const size_t kUavTableBaseIndex = kResourceHeapSlots; - const size_t kResourceHeapSlotsTotal = kResourceHeapSlots * 2; - const size_t kResourceHeapBytes = - kResourceHeapSlotsTotal * sizeof(IRDescriptorTableEntry); - const size_t kSamplerHeapSlots = - kSamplerHeapSlotsPerTable * kDescriptorTableCount; - const size_t kSamplerHeapBytes = - kSamplerHeapSlots * sizeof(IRDescriptorTableEntry); - const size_t kUniformsBufferSize = - kUniformsBytesPerTable * kDescriptorTableCount; - const size_t kTopLevelABTotalBytes = - kTopLevelABBytesPerTable * kDescriptorTableCount; - const size_t kDrawArgsSize = 64; // Enough for draw arguments struct - const size_t kCBVHeapSlots = kCbvHeapSlotsPerTable * kDescriptorTableCount; - const size_t kCBVHeapBytes = kCBVHeapSlots * sizeof(IRDescriptorTableEntry); - - ring->res_heap_ab = - device_->newBuffer(kResourceHeapBytes, MTL::ResourceStorageModeShared); - if (!ring->res_heap_ab) { - XELOGE("Failed to create resource descriptor heap buffer"); - return nullptr; - } - std::string ring_label_suffix = std::to_string(ring_id); - ring->res_heap_ab->setLabel(NS::String::string( - ("ResourceDescriptorHeap_" + ring_label_suffix).c_str(), - NS::UTF8StringEncoding)); - - // Initialize all tables: - // - Slot 0: null buffer (will be replaced with shared memory per draw). - // - Slots 1+: null texture (safe default for any accidental access). - auto* res_entries = - reinterpret_cast(ring->res_heap_ab->contents()); - auto* uav_entries = res_entries + kUavTableBaseIndex; - for (size_t table = 0; table < kDescriptorTableCount; ++table) { - IRDescriptorTableEntry* table_entries = - res_entries + table * kResourceHeapSlotsPerTable; - IRDescriptorTableSetBuffer(&table_entries[0], null_buffer_->gpuAddress(), - kNullBufferSize); - for (size_t i = 1; i < kResourceHeapSlotsPerTable; ++i) { - IRDescriptorTableSetTexture(&table_entries[i], null_texture_, 0.0f, 0); - } - IRDescriptorTableEntry* uav_table_entries = - uav_entries + table * kResourceHeapSlotsPerTable; - for (size_t i = 0; i < kResourceHeapSlotsPerTable; ++i) { - IRDescriptorTableSetBuffer(&uav_table_entries[i], - null_buffer_->gpuAddress(), kNullBufferSize); - } - } - - ring->smp_heap_ab = - device_->newBuffer(kSamplerHeapBytes, MTL::ResourceStorageModeShared); - if (!ring->smp_heap_ab) { - XELOGE("Failed to create sampler descriptor heap buffer"); - return nullptr; - } - ring->smp_heap_ab->setLabel( - NS::String::string(("SamplerDescriptorHeap_" + ring_label_suffix).c_str(), - NS::UTF8StringEncoding)); - auto* smp_entries = - reinterpret_cast(ring->smp_heap_ab->contents()); - for (size_t i = 0; i < kSamplerHeapSlots; ++i) { - IRDescriptorTableSetSampler(&smp_entries[i], null_sampler_, 0.0f); - } - - ring->uniforms_buffer = - device_->newBuffer(kUniformsBufferSize, MTL::ResourceStorageModeShared); - if (!ring->uniforms_buffer) { - XELOGE("Failed to create uniforms buffer"); - return nullptr; - } - ring->uniforms_buffer->setLabel(NS::String::string( - ("UniformsBuffer_" + ring_label_suffix).c_str(), NS::UTF8StringEncoding)); - std::memset(ring->uniforms_buffer->contents(), 0, kUniformsBufferSize); - - ring->top_level_ab = - device_->newBuffer(kTopLevelABTotalBytes, MTL::ResourceStorageModeShared); - if (!ring->top_level_ab) { - XELOGE("Failed to create top-level argument buffer"); - return nullptr; - } - ring->top_level_ab->setLabel(NS::String::string( - ("TopLevelArgumentBuffer_" + ring_label_suffix).c_str(), - NS::UTF8StringEncoding)); - std::memset(ring->top_level_ab->contents(), 0, kTopLevelABTotalBytes); - - ring->draw_args_buffer = - device_->newBuffer(kDrawArgsSize, MTL::ResourceStorageModeShared); - if (!ring->draw_args_buffer) { - XELOGE("Failed to create draw arguments buffer"); - return nullptr; - } - ring->draw_args_buffer->setLabel( - NS::String::string(("DrawArgumentsBuffer_" + ring_label_suffix).c_str(), - NS::UTF8StringEncoding)); - std::memset(ring->draw_args_buffer->contents(), 0, kDrawArgsSize); - - ring->cbv_heap_ab = - device_->newBuffer(kCBVHeapBytes, MTL::ResourceStorageModeShared); - if (!ring->cbv_heap_ab) { - XELOGE("Failed to create CBV descriptor heap buffer"); - return nullptr; - } - ring->cbv_heap_ab->setLabel( - NS::String::string(("CBVDescriptorHeap_" + ring_label_suffix).c_str(), - NS::UTF8StringEncoding)); - std::memset(ring->cbv_heap_ab->contents(), 0, kCBVHeapBytes); - - ++ring_id; - - return ring; -} - -std::shared_ptr -MetalCommandProcessor::AcquireDrawRingBuffers() { - std::lock_guard lock(draw_ring_mutex_); - if (!draw_ring_pool_.empty()) { - auto ring = draw_ring_pool_.back(); - draw_ring_pool_.pop_back(); - return ring; - } - return CreateDrawRingBuffers(); -} - -void MetalCommandProcessor::SetActiveDrawRing( - const std::shared_ptr& ring) { - active_draw_ring_ = ring; - res_heap_ab_ = ring ? ring->res_heap_ab : nullptr; - smp_heap_ab_ = ring ? ring->smp_heap_ab : nullptr; - cbv_heap_ab_ = ring ? ring->cbv_heap_ab : nullptr; - uniforms_buffer_ = ring ? ring->uniforms_buffer : nullptr; - top_level_ab_ = ring ? ring->top_level_ab : nullptr; - draw_args_buffer_ = ring ? ring->draw_args_buffer : nullptr; -} - -void MetalCommandProcessor::EnsureActiveDrawRing() { - if (!active_draw_ring_) { - auto ring = AcquireDrawRingBuffers(); - if (!ring) { - return; - } - SetActiveDrawRing(ring); - } - if (command_buffer_draw_rings_.empty()) { - command_buffer_draw_rings_.push_back(active_draw_ring_); - current_draw_index_ = 0; - } -} - -void MetalCommandProcessor::ScheduleDrawRingRelease( - MTL::CommandBuffer* command_buffer) { - if (!command_buffer || command_buffer_draw_rings_.empty()) { - return; - } - auto rings = std::move(command_buffer_draw_rings_); - command_buffer->addCompletedHandler( - [this, rings](MTL::CommandBuffer*) mutable { - std::lock_guard lock(draw_ring_mutex_); - for (auto& ring : rings) { - draw_ring_pool_.push_back(ring); - } - }); -} - -void MetalCommandProcessor::PopulateIRConverterBuffers() { - if (!res_heap_ab_ || !smp_heap_ab_ || !uniforms_buffer_ || !shared_memory_) { - return; - } - - // Get shared memory buffer for vertex data fetching - MTL::Buffer* shared_mem_buffer = shared_memory_->GetBuffer(); - if (!shared_mem_buffer) { - XELOGW("PopulateIRConverterBuffers: No shared memory buffer available"); - return; - } - - // Populate resource descriptor heap slot 0 with shared memory buffer - // Xbox 360 shaders use vfetch instructions that read from this buffer - IRDescriptorTableEntry* res_heap = - static_cast(res_heap_ab_->contents()); - - // Set slot 0 (t0) to shared memory for vertex buffer fetching - // The metadata encodes buffer size for bounds checking - uint64_t shared_mem_gpu_addr = shared_mem_buffer->gpuAddress(); - uint64_t shared_mem_size = shared_mem_buffer->length(); - - // Use IRDescriptorTableSetBuffer to properly encode the descriptor - // metadata = buffer size in low 32 bits for bounds checking - IRDescriptorTableSetBuffer(&res_heap[0], shared_mem_gpu_addr, - shared_mem_size); - - // Populate uniforms buffer with system constants and fetch constants - // Layout matches DxbcShaderTranslator::SystemConstants (b0) - uint8_t* uniforms = static_cast(uniforms_buffer_->contents()); - - // For now, populate minimal system constants for passthrough rendering - // This structure needs to match what the translated shaders expect - struct MinimalSystemConstants { - uint32_t flags; // 0x00 - float tessellation_factor_range[2]; // 0x04 - uint32_t line_loop_closing_index; // 0x0C - - uint32_t vertex_index_endian; // 0x10 - uint32_t vertex_index_offset; // 0x14 - uint32_t vertex_index_min; // 0x18 - uint32_t vertex_index_max; // 0x1C - - float user_clip_planes[6][4]; // 0x20 - 6 clip planes * 4 floats = 96 bytes - - float ndc_scale[3]; // 0x80 - float point_vertex_diameter_min; // 0x8C - - float ndc_offset[3]; // 0x90 - float point_vertex_diameter_max; // 0x9C - }; - - MinimalSystemConstants* sys_const = - reinterpret_cast(uniforms); - - // Initialize to zero - std::memset(sys_const, 0, sizeof(MinimalSystemConstants)); - - // Set passthrough NDC transform (identity) - sys_const->ndc_scale[0] = 1.0f; - sys_const->ndc_scale[1] = 1.0f; - sys_const->ndc_scale[2] = 1.0f; - sys_const->ndc_offset[0] = 0.0f; - sys_const->ndc_offset[1] = 0.0f; - sys_const->ndc_offset[2] = 0.0f; - - // Set reasonable vertex index bounds - sys_const->vertex_index_min = 0; - sys_const->vertex_index_max = 0xFFFFFFFF; - - // Copy fetch constants from register file (b3 in DXBC) - // Fetch constants start at register XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 - // There are 32 fetch constants, each 6 DWORDs = 192 DWORDs total = 768 bytes - const uint32_t* regs = register_file_->values; - const size_t kFetchConstantOffset = 512; // After system constants (b0) - const size_t kFetchConstantCount = 32 * 6; // 32 fetch constants * 6 DWORDs - - std::memcpy(uniforms + kFetchConstantOffset, - ®s[XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0], - kFetchConstantCount * sizeof(uint32_t)); - - // Bind IR Converter runtime buffers to render encoder - if (current_render_encoder_) { - // Bind resource descriptor heap at index 0 (kIRDescriptorHeapBindPoint) - current_render_encoder_->setVertexBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - current_render_encoder_->setFragmentBuffer(res_heap_ab_, 0, - kIRDescriptorHeapBindPoint); - - // Bind sampler heap at index 1 (kIRSamplerHeapBindPoint) - current_render_encoder_->setVertexBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - current_render_encoder_->setFragmentBuffer(smp_heap_ab_, 0, - kIRSamplerHeapBindPoint); - - // Bind uniforms at index 5 (kIRArgumentBufferUniformsBindPoint) - current_render_encoder_->setVertexBuffer( - uniforms_buffer_, 0, kIRArgumentBufferUniformsBindPoint); - current_render_encoder_->setFragmentBuffer( - uniforms_buffer_, 0, kIRArgumentBufferUniformsBindPoint); - - // Make shared memory resident for GPU access - UseRenderEncoderResource(shared_mem_buffer, MTL::ResourceUsageRead); - } -} - -DxbcShaderTranslator::Modification -MetalCommandProcessor::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 -MetalCommandProcessor::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 (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). - // These must be explicitly set, as zero-initialized bits would be kZero - // which causes the shader to multiply output color by zero (black). - modification.pixel.rt0_blend_rgb_factor_for_premult = - xenos::BlendFactor::kOne; - modification.pixel.rt0_blend_a_factor_for_premult = xenos::BlendFactor::kOne; - - return modification; -} - -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 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; - - // 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]); - } - - // 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; - } - - // 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; - } - - // Primitive type flags - if (primitive_polygonal) { - flags |= DxbcShaderTranslator::kSysFlag_PrimitivePolygonal; - } - if (draw_util::IsPrimitiveLine(regs)) { - flags |= DxbcShaderTranslator::kSysFlag_PrimitiveLine; - } - - // Depth format - if (rb_depth_info.depth_format == xenos::DepthRenderTargetFormat::kD24FS8) { - flags |= DxbcShaderTranslator::kSysFlag_DepthFloat24; - } - - // 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; - - // 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; - } - } - } - - 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; - system_constants_.tessellation_factor_range_min = tessellation_factor_min; - system_constants_.tessellation_factor_range_max = tessellation_factor_max; - - // Line loop closing index - system_constants_.line_loop_closing_index = line_loop_closing_index; - - // Vertex index configuration - 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]); - 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) { - 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(); - system_constants_.point_vertex_diameter_min = - float(pa_su_point_minmax.min_size) * (2.0f / 16.0f); - system_constants_.point_vertex_diameter_max = - float(pa_su_point_minmax.max_size) * (2.0f / 16.0f); - system_constants_.point_constant_diameter[0] = - float(pa_su_point_size.width) * (2.0f / 16.0f); - system_constants_.point_constant_diameter[1] = - float(pa_su_point_size.height) * (2.0f / 16.0f); - // 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; - system_constants_.point_screen_diameter_to_ndc_radius[0] = - (/* 0.5f * 2.0f * */ float(point_draw_resolution_scale_x)) / - std::max(viewport_info.xy_extent[0], uint32_t(1)); - system_constants_.point_screen_diameter_to_ndc_radius[1] = - (/* 0.5f * 2.0f * */ float(point_draw_resolution_scale_y)) / - std::max(viewport_info.xy_extent[1], uint32_t(1)); - } - - // 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; - texture_signs_uint = - (texture_signs_uint & ~texture_signs_mask) | texture_signs_shifted; - textures_resolution_scaled |= - uint32_t( - texture_cache_->IsActiveTextureResolutionScaled(texture_index)) - << texture_index; - } - } - system_constants_.textures_resolution_scaled = textures_resolution_scaled; - - // 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; - system_constants_.sample_count_log2[0] = sample_count_log2_x; - system_constants_.sample_count_log2[1] = sample_count_log2_y; - - // Alpha test reference - system_constants_.alpha_test_reference = rb_alpha_ref; - - // Alpha to mask - uint32_t alpha_to_mask = rb_colorcontrol.alpha_to_mask_enable - ? (rb_colorcontrol.value >> 24) | (1 << 8) - : 0; - 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; - // 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; - } - } - auto color_exp_bias_scale = xe::memory::Reinterpret( - int32_t(0x3F800000 + (color_exp_bias << 23))); - system_constants_.color_exp_bias[i] = color_exp_bias_scale; - } - - // Blend constants (used by EDRAM and for host blending) - system_constants_.edram_blend_constant[0] = - regs.Get(XE_GPU_REG_RB_BLEND_RED); - system_constants_.edram_blend_constant[1] = - regs.Get(XE_GPU_REG_RB_BLEND_GREEN); - system_constants_.edram_blend_constant[2] = - regs.Get(XE_GPU_REG_RB_BLEND_BLUE); - system_constants_.edram_blend_constant[3] = - regs.Get(XE_GPU_REG_RB_BLEND_ALPHA); - - system_constants_dirty_ = true; -} - -#define COMMAND_PROCESSOR MetalCommandProcessor -#include "../pm4_command_processor_implement.h" -#undef COMMAND_PROCESSOR - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_command_processor.h b/src/xenia/gpu/metal/metal_command_processor.h deleted file mode 100644 index a41f1a4c8..000000000 --- a/src/xenia/gpu/metal/metal_command_processor.h +++ /dev/null @@ -1,571 +0,0 @@ -/** - ****************************************************************************** - * 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_COMMAND_PROCESSOR_H_ -#define XENIA_GPU_METAL_METAL_COMMAND_PROCESSOR_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "third_party/metal-shader-converter/include/metal_irconverter_runtime.h" -#include "xenia/base/platform.h" -#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_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/ui/metal/metal_api.h" -#include "xenia/ui/metal/metal_provider.h" - -namespace MTL { -class Heap; -class SharedEvent; -} // namespace MTL - -namespace xe { -namespace ui { -namespace metal { -class MetalGPUCompletionTimeline; -} // namespace metal -} // namespace ui -} // namespace xe - -namespace xe { -namespace gpu { -namespace metal { - -class MetalGraphicsSystem; - -class MetalCommandProcessor : public CommandProcessor { - protected: -#define OVERRIDING_BASE_CMDPROCESSOR -#include "../pm4_command_processor_declare.h" -#undef OVERRIDING_BASE_CMDPROCESSOR - - public: - explicit MetalCommandProcessor(MetalGraphicsSystem* graphics_system, - kernel::KernelState* kernel_state); - ~MetalCommandProcessor(); - - void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override; - void RestoreEdramSnapshot(const void* snapshot) override; - void ClearCaches() override; - void InvalidateGpuMemory() override; - void ClearReadbackBuffers() 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 - MTL::Device* GetMetalDevice() const { return device_; } - MTL::CommandQueue* GetMetalCommandQueue() const { return command_queue_; } - MTL::CommandBuffer* GetCurrentCommandBuffer() const { - return current_command_buffer_; - } - - // Debug marker methods - public so subsystems can annotate their operations. - void UpdateDebugMarkersEnabled(); - void PushDebugMarker(const char* format, ...); - void PopDebugMarker(); - void InsertDebugMarker(const char* format, ...); - bool debug_markers_enabled() const { return debug_markers_enabled_; } - void RequestCapture(); - uint32_t current_draw_index() const { return current_draw_index_; } - uint64_t GetCurrentSubmission() const; - uint64_t GetCompletedSubmission() const; - uint64_t GetCurrentFrame() const { return frame_current_; } - uint64_t GetCompletedFrame() const { return frame_completed_; } - MTL::CommandBuffer* EnsureCommandBuffer(); - void EndRenderEncoder(); - void ResetRenderEncoderResourceUsage(); - void UseRenderEncoderResource(MTL::Resource* resource, - MTL::ResourceUsage usage); - 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); - - protected: - bool SetupContext() override; - void ShutdownContext() override; - void InitializeShaderStorage( - const std::filesystem::path& cache_root, uint32_t title_id, bool blocking, - std::function completion_callback = nullptr) override; - - // Flush pending GPU work before entering wait state. - // This ensures Metal command buffers are submitted and completed before - // the autorelease pool is drained, preventing hangs from deferred - // deallocation. - void PrepareForWait() override; - - // Use base class WriteRegister - don't override with empty implementation! - // The base class stores values in register_file_->values[] which we need. - void OnGammaRamp256EntryTableValueWritten() override; - void OnGammaRampPWLValueWritten() override; - - void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width, - uint32_t frontbuffer_height) override; - void OnPrimaryBufferEnd() override; - - Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address, - const uint32_t* host_address, - uint32_t dword_count) override; - - bool IssueDraw(xenos::PrimitiveType primitive_type, uint32_t index_count, - IndexBufferInfo* index_buffer_info, - bool major_mode_explicit) override; - bool IssueCopy() override; - void WriteRegister(uint32_t index, uint32_t value) override; - - private: - // Initialize shader translation pipeline - bool InitializeShaderTranslation(); - - // Request shared-memory ranges needed for the current draw, mirroring - // D3D12/Vulkan behavior (vertex buffers, memexport streams). - bool RequestSharedMemoryRangesForCurrentDraw(MetalShader* vertex_shader, - MetalShader* pixel_shader, - bool memexport_used_vertex, - bool memexport_used_pixel, - bool* any_data_resolved_out); - - // Command buffer management - void BeginCommandBuffer(); - void EndCommandBuffer(); - void ProcessCompletedSubmissions(); - void CheckSubmissionCompletion(uint64_t await_submission); - bool BeginSubmission(bool is_guest_command); - bool EndSubmission(bool is_swap); - void MaybeStartCapture(); - void StopCaptureIfActive(); - bool CanEndSubmissionImmediately() const; - void EnsureDrawRingCapacity(); - void UseRenderEncoderAttachmentHeaps(MTL::RenderPassDescriptor* descriptor); - void UseRenderEncoderHeap(MTL::Heap* heap); - - // Pipeline state management - MTL::RenderPipelineState* GetOrCreatePipelineState( - MetalShader::MetalTranslation* vertex_translation, - MetalShader::MetalTranslation* pixel_translation, - const RegisterFile& regs); - - 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 GeometryPipelineState { - MTL::RenderPipelineState* pipeline = nullptr; - uint32_t gs_vertex_size_in_bytes = 0; - uint32_t gs_max_input_primitives_per_mesh_threadgroup = 0; - }; - - 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; - }; - - struct TessellationPipelineState { - MTL::RenderPipelineState* pipeline = nullptr; - IRRuntimeTessellationPipelineConfig config = {}; - IRRuntimePrimitiveType primitive = IRRuntimePrimitiveTypeTriangle; - }; - - struct DrawRingBuffers { - MTL::Buffer* res_heap_ab = nullptr; - MTL::Buffer* smp_heap_ab = nullptr; - MTL::Buffer* cbv_heap_ab = nullptr; - MTL::Buffer* uniforms_buffer = nullptr; - MTL::Buffer* top_level_ab = nullptr; - MTL::Buffer* draw_args_buffer = nullptr; - - ~DrawRingBuffers(); - }; - - GeometryPipelineState* GetOrCreateGeometryPipelineState( - MetalShader::MetalTranslation* vertex_translation, - MetalShader::MetalTranslation* pixel_translation, - GeometryShaderKey geometry_shader_key, const RegisterFile& regs); - - TessellationPipelineState* GetOrCreateTessellationPipelineState( - MetalShader::MetalTranslation* domain_translation, - MetalShader::MetalTranslation* pixel_translation, - const PrimitiveProcessor::ProcessingResult& primitive_processing_result, - const RegisterFile& regs); - - // 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); - - bool EnsureDepthOnlyPixelShader(); - - struct PipelineDiskCacheVertexAttribute { - uint32_t attribute_index; - uint32_t format; - uint32_t offset; - uint32_t buffer_index; - }; - - struct PipelineDiskCacheVertexLayout { - uint32_t buffer_index; - uint32_t stride; - uint32_t step_function; - uint32_t step_rate; - }; - - struct PipelineDiskCacheEntry { - uint64_t pipeline_key = 0; - uint64_t vertex_shader_cache_key = 0; - uint64_t pixel_shader_cache_key = 0; - uint32_t sample_count = 1; - uint32_t depth_format = 0; - uint32_t stencil_format = 0; - uint32_t color_formats[4] = {}; - uint32_t normalized_color_mask = 0; - uint32_t alpha_to_mask_enable = 0; - uint32_t blendcontrol[4] = {}; - std::vector vertex_attributes; - std::vector vertex_layouts; - }; - - bool InitializeShaderStorageInternal(const std::filesystem::path& cache_root, - uint32_t title_id, bool blocking); - void ShutdownShaderStorage(); - std::string GetShaderStorageDeviceTag() const; - bool LoadPipelineDiskCache(const std::filesystem::path& path, - std::vector* entries); - bool AppendPipelineDiskCacheEntry(const PipelineDiskCacheEntry& entry); - bool InitializePipelineBinaryArchive( - const std::filesystem::path& archive_path); - void SerializePipelineBinaryArchive(); - void PrewarmPipelineBinaryArchive( - const std::vector& entries); - - // Constants for descriptor heap sizes. - // MSC's IR runtime uses a D3D12-like "root signature" model. In D3D12, many - // root parameters are stage-visible, so VS and PS can both use registers like - // `t1` / `s0` without colliding because their descriptor tables are bound - // independently. - // - // To mirror that behavior on Metal, keep separate descriptor table slices for - // VS and PS (per draw), and ring-buffer them so CPU writes don't overwrite - // data still in flight on the GPU. - static constexpr size_t kStageCount = 2; // Vertex + pixel. - - // Root signature descriptor counts in MetalShaderConverter are intentionally - // oversized (bindless-style). Allocate extra padding because MSC IR shaders - // may read one entry past the declared count. - static constexpr size_t kResourceHeapSlotsPerTable = 1025 + 2; - static constexpr size_t kSamplerHeapSlotsPerTable = 257 + 2; - static constexpr size_t kCbvHeapSlotsPerTable = 5 + 2; // b0-b4 + padding. - static constexpr size_t kNullBufferSize = 4096; - - static constexpr size_t kCbvSizeBytes = 4096; - static constexpr size_t kUniformsBytesPerTable = 5 * kCbvSizeBytes; - - // Top-level argument buffer ring buffer constants - // Each draw needs its own copy of the top-level pointers to avoid race - // conditions where later draws overwrite earlier draws' descriptor table - // pointers before the GPU executes them. - static constexpr size_t kTopLevelABSlotsPerTable = 32; // 14 + padding. - static constexpr size_t kTopLevelABBytesPerTable = - kTopLevelABSlotsPerTable * sizeof(uint64_t); - - // IR Converter runtime resource binding - bool CreateIRConverterBuffers(); - void PopulateIRConverterBuffers(); - std::shared_ptr CreateDrawRingBuffers(); - std::shared_ptr AcquireDrawRingBuffers(); - void SetActiveDrawRing(const std::shared_ptr& ring); - void EnsureActiveDrawRing(); - void ScheduleDrawRingRelease(MTL::CommandBuffer* command_buffer); - - // System constants population (mirrors D3D12 implementation) - void 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); - - // Shader modification selection (mirrors D3D12 PipelineCache logic). - 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; - - // Metal device and command queue (from provider) - MTL::Device* device_ = nullptr; - MTL::CommandQueue* command_queue_ = nullptr; - 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; - - // Current command buffer and encoder - MTL::CommandBuffer* current_command_buffer_ = nullptr; - MTL::RenderCommandEncoder* current_render_encoder_ = nullptr; - MTL::RenderPassDescriptor* current_render_pass_descriptor_ = nullptr; - 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_; - - // Shared memory for Xbox 360 memory access - std::unique_ptr shared_memory_; - std::unique_ptr primitive_processor_; - - bool saw_swap_ = false; - uint32_t last_swap_ptr_ = 0; - uint32_t last_swap_width_ = 0; - 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 translation components - std::unique_ptr shader_translator_; - std::unique_ptr dxbc_to_dxil_converter_; - std::unique_ptr metal_shader_converter_; - StringBuffer ucode_disasm_buffer_; - - // Shader cache (keyed by ucode hash) - std::unordered_map> shader_cache_; - - // Pipeline cache (keyed by shader combination) - std::unordered_map pipeline_cache_; - std::unordered_map geometry_pipeline_cache_; - 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_; - std::unordered_map - tessellation_pipeline_cache_; - - struct DepthStencilStateKey { - uint32_t depth_control; - uint32_t stencil_ref_mask_front; - uint32_t stencil_ref_mask_back; - uint32_t polygonal_and_backface; - bool operator==(const DepthStencilStateKey& other) const { - return depth_control == other.depth_control && - stencil_ref_mask_front == other.stencil_ref_mask_front && - stencil_ref_mask_back == other.stencil_ref_mask_back && - polygonal_and_backface == other.polygonal_and_backface; - } - struct Hasher { - size_t operator()(const DepthStencilStateKey& key) const { - size_t h = size_t(key.depth_control); - h ^= size_t(key.stencil_ref_mask_front) << 1; - h ^= size_t(key.stencil_ref_mask_back) << 2; - h ^= size_t(key.polygonal_and_backface) << 3; - return h; - } - }; - }; - - std::unordered_map - depth_stencil_state_cache_; - - bool mesh_shader_supported_ = false; - - // Texture cache for guest texture uploads - std::unique_ptr texture_cache_; - - // Render target cache for framebuffer management - std::unique_ptr render_target_cache_; - - // IR Converter runtime buffers for shader resource binding - MTL::Buffer* null_buffer_ = nullptr; // Null buffer for unused descriptors - MTL::Texture* null_texture_ = - nullptr; // Placeholder texture for unbound slots - MTL::SamplerState* null_sampler_ = - nullptr; // Default sampler for unbound slots - MTL::Buffer* res_heap_ab_ = nullptr; // Resource descriptor heap (SRVs/UAVs) - MTL::Buffer* smp_heap_ab_ = nullptr; // Sampler descriptor heap - MTL::Buffer* cbv_heap_ab_ = nullptr; // CBV descriptor heap (b0-b3) - MTL::Buffer* uniforms_buffer_ = nullptr; // Raw constant buffer data - MTL::Buffer* top_level_ab_ = - nullptr; // Top-level argument buffer (bind point 2) - MTL::Buffer* draw_args_buffer_ = - nullptr; // Draw arguments buffer (bind point 4) - MTL::Buffer* tessellator_tables_buffer_ = nullptr; - std::shared_ptr active_draw_ring_; - std::vector> draw_ring_pool_; - std::vector> command_buffer_draw_rings_; - std::mutex draw_ring_mutex_; - size_t draw_ring_count_ = 0; - - MTL::Library* depth_only_pixel_library_ = nullptr; - std::string depth_only_pixel_function_name_; - - // System constants - matches DxbcShaderTranslator::SystemConstants layout - // Stored persistently to track dirty state - DxbcShaderTranslator::SystemConstants system_constants_; - bool system_constants_dirty_ = true; - bool logged_missing_texture_warning_ = false; - - // Fixed-function dynamic state cached per render encoder. - float ff_blend_factor_[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - bool ff_blend_factor_valid_ = false; - - std::filesystem::path shader_storage_root_; - std::filesystem::path shader_storage_local_root_; - std::filesystem::path shader_storage_title_root_; - std::filesystem::path metallib_cache_dir_; - std::filesystem::path pipeline_disk_cache_path_; - std::filesystem::path pipeline_binary_archive_path_; - std::unordered_set pipeline_disk_cache_keys_; - std::vector pipeline_disk_cache_entries_; - FILE* pipeline_disk_cache_file_ = nullptr; - MTL::BinaryArchive* pipeline_binary_archive_ = nullptr; - bool pipeline_binary_archive_dirty_ = false; - - static constexpr uint32_t kQueueFrames = 3; - - std::unique_ptr completion_timeline_; - bool submission_open_ = false; - uint64_t submission_completed_processed_ = 0; - - bool frame_open_ = false; - uint64_t frame_current_ = 1; - uint64_t frame_completed_ = 0; - uint64_t closed_frame_submissions_[kQueueFrames] = {}; - - enum class DebugMarkerTarget { - kCommandBuffer, - kRenderEncoder, - }; - bool debug_markers_enabled_ = false; - std::vector debug_marker_stack_; - - // Draw counter for ring-buffer descriptor heap allocation - // Each draw uses a different region of the descriptor heap to avoid - // overwriting previous draws' descriptors before GPU execution - uint32_t current_draw_index_ = 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; - - // Resolve downscale compute shader for scaled resolution readback. - MTL::ComputePipelineState* resolve_downscale_pipeline_ = nullptr; - MTL::Buffer* resolve_downscale_buffer_ = nullptr; - uint32_t resolve_downscale_buffer_size_ = 0; - - // Per-resolve double-buffered readback for delayed sync. - struct ReadbackBuffer { - MTL::Buffer* buffers[2] = {nullptr, nullptr}; - uint32_t sizes[2] = {0, 0}; - uint64_t submission_ids[2] = {0, 0}; - uint32_t current_index = 0; - uint64_t last_used_frame = 0; - }; - void EvictOldReadbackBuffers( - std::unordered_map& buffer_map); - std::unordered_map readback_buffers_; - - // 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_; - - std::atomic capture_requested_{false}; - MTL::CaptureManager* capture_manager_ = nullptr; - bool capture_active_ = false; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_COMMAND_PROCESSOR_H_ diff --git a/src/xenia/gpu/metal/metal_geometry_shader.cc b/src/xenia/gpu/metal/metal_geometry_shader.cc deleted file mode 100644 index 64e3699b2..000000000 --- a/src/xenia/gpu/metal/metal_geometry_shader.cc +++ /dev/null @@ -1,1146 +0,0 @@ -/** - ****************************************************************************** - * 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 -#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 { -namespace metal { - -static std::unordered_map, - GeometryShaderKey::Hasher> - geometry_shaders_; - -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)); - } -} - -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 deleted file mode 100644 index 6b34af90b..000000000 --- a/src/xenia/gpu/metal/metal_geometry_shader.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - ****************************************************************************** - * 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 -#include - -#include "xenia/base/assert.h" -#include "xenia/gpu/dxbc_shader_translator.h" - -namespace xe { -namespace gpu { -namespace metal { - -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); - } -}; - -bool GetGeometryShaderKey( - PipelineGeometryShader geometry_shader_type, - DxbcShaderTranslator::Modification vertex_shader_modification, - DxbcShaderTranslator::Modification pixel_shader_modification, - GeometryShaderKey& key_out); - -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_graphics_system.cc b/src/xenia/gpu/metal/metal_graphics_system.cc deleted file mode 100644 index 148d90036..000000000 --- a/src/xenia/gpu/metal/metal_graphics_system.cc +++ /dev/null @@ -1,52 +0,0 @@ -/** - ****************************************************************************** - * 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_graphics_system.h" - -#include - -#include "xenia/base/logging.h" -#include "xenia/base/math.h" -#include "xenia/gpu/draw_util.h" -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/ui/metal/metal_util.h" -#include "xenia/xbox.h" - -namespace xe { -namespace gpu { -namespace metal { - -MetalGraphicsSystem::MetalGraphicsSystem() {} - -MetalGraphicsSystem::~MetalGraphicsSystem() {} - -bool MetalGraphicsSystem::IsAvailable() { - return xe::ui::metal::MetalProvider::IsMetalAPIAvailable(); -} - -std::string MetalGraphicsSystem::name() const { return "MetalGraphicsSystem"; } - -X_STATUS MetalGraphicsSystem::Setup(cpu::Processor* processor, - kernel::KernelState* kernel_state, - ui::WindowedAppContext* app_context, - bool is_surface_required) { - provider_ = xe::ui::metal::MetalProvider::Create(); - return GraphicsSystem::Setup(processor, kernel_state, app_context, - is_surface_required); -} - -std::unique_ptr -MetalGraphicsSystem::CreateCommandProcessor() { - return std::unique_ptr( - new MetalCommandProcessor(this, kernel_state_)); -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_graphics_system.h b/src/xenia/gpu/metal/metal_graphics_system.h deleted file mode 100644 index d0c394dc2..000000000 --- a/src/xenia/gpu/metal/metal_graphics_system.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - ****************************************************************************** - * 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_GRAPHICS_SYSTEM_H -#define XENIA_GPU_METAL_GRAPHICS_SYSTEM_H - -#include - -#include "xenia/gpu/command_processor.h" -#include "xenia/gpu/graphics_system.h" - -#include "third_party/metal-cpp/Metal/Metal.hpp" - -namespace xe { -namespace gpu { -namespace metal { -class MetalGraphicsSystem : public GraphicsSystem { - public: - MetalGraphicsSystem(); - ~MetalGraphicsSystem(); - - static bool IsAvailable(); - - std::string name() const override; - - X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state, - ui::WindowedAppContext* app_context, - bool is_surface_required) override; - - protected: - std::unique_ptr CreateCommandProcessor() override; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_GRAPHICS_SYSTEM_H diff --git a/src/xenia/gpu/metal/metal_heap_pool.cc b/src/xenia/gpu/metal/metal_heap_pool.cc deleted file mode 100644 index 0adcbb98a..000000000 --- a/src/xenia/gpu/metal/metal_heap_pool.cc +++ /dev/null @@ -1,127 +0,0 @@ -/** - ****************************************************************************** - * 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_heap_pool.h" - -#include - -#include "xenia/base/logging.h" -#include "xenia/base/math.h" - -namespace xe { -namespace gpu { -namespace metal { - -namespace { - -constexpr size_t kDefaultMaxHeapBytes = 512ull * 1024ull * 1024ull; -constexpr size_t kMinMaxHeapBytes = 256ull * 1024ull * 1024ull; -constexpr size_t kMaxMaxHeapBytes = 1024ull * 1024ull * 1024ull; - -size_t GetMaxHeapBytes(MTL::Device* device) { - if (!device) { - return kDefaultMaxHeapBytes; - } - uint64_t recommended = device->recommendedMaxWorkingSetSize(); - if (!recommended) { - return kDefaultMaxHeapBytes; - } - uint64_t budget = recommended / 4; - budget = std::max(budget, kMinMaxHeapBytes); - budget = std::min(budget, kMaxMaxHeapBytes); - return static_cast(budget); -} - -} // namespace - -MetalHeapPool::MetalHeapPool(MTL::Device* device, MTL::StorageMode storage_mode, - size_t min_heap_size, const char* label_prefix) - : device_(device), - storage_mode_(storage_mode), - min_heap_size_(min_heap_size), - max_heap_bytes_(GetMaxHeapBytes(device)), - label_prefix_(label_prefix ? label_prefix : "") {} - -MetalHeapPool::~MetalHeapPool() { Shutdown(); } - -void MetalHeapPool::Shutdown() { - for (auto& entry : heaps_) { - if (entry.heap) { - entry.heap->release(); - entry.heap = nullptr; - } - } - heaps_.clear(); - total_heap_bytes_ = 0; -} - -MTL::Texture* MetalHeapPool::CreateTexture(MTL::TextureDescriptor* descriptor) { - if (!device_ || !descriptor) { - return nullptr; - } - MTL::SizeAndAlign size_align = device_->heapTextureSizeAndAlign(descriptor); - if (!size_align.size || !size_align.align) { - return nullptr; - } - - MTL::Heap* heap = - GetHeapForSize(size_t(size_align.size), size_t(size_align.align)); - if (!heap) { - return nullptr; - } - return heap->newTexture(descriptor); -} - -MTL::Heap* MetalHeapPool::GetHeapForSize(size_t size, size_t alignment) { - for (auto& entry : heaps_) { - if (!entry.heap) { - continue; - } - size_t available = size_t( - entry.heap->maxAvailableSize(static_cast(alignment))); - if (available >= size) { - return entry.heap; - } - } - - size_t heap_size = std::max(size, min_heap_size_); - heap_size = xe::next_pow2(heap_size); - heap_size = xe::round_up(heap_size, alignment); - if (max_heap_bytes_ && heap_size > max_heap_bytes_) { - return nullptr; - } - if (max_heap_bytes_ && total_heap_bytes_ > max_heap_bytes_ - heap_size) { - return nullptr; - } - - MTL::HeapDescriptor* desc = MTL::HeapDescriptor::alloc()->init(); - desc->setStorageMode(storage_mode_); - desc->setHazardTrackingMode(MTL::HazardTrackingModeTracked); - desc->setSize(heap_size); - - MTL::Heap* heap = device_->newHeap(desc); - desc->release(); - if (!heap) { - XELOGE("MetalHeapPool: failed to create heap ({} bytes)", heap_size); - return nullptr; - } - if (!label_prefix_.empty()) { - std::string label = - label_prefix_ + "_heap_" + std::to_string(heaps_.size()); - heap->setLabel(NS::String::string(label.c_str(), NS::UTF8StringEncoding)); - } - - heaps_.push_back({heap, heap_size}); - total_heap_bytes_ += heap_size; - return heap; -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_heap_pool.h b/src/xenia/gpu/metal/metal_heap_pool.h deleted file mode 100644 index f757bd90c..000000000 --- a/src/xenia/gpu/metal/metal_heap_pool.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - ****************************************************************************** - * 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_HEAP_POOL_H_ -#define XENIA_GPU_METAL_METAL_HEAP_POOL_H_ - -#include -#include -#include - -#include "third_party/metal-cpp/Metal/Metal.hpp" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalHeapPool { - public: - MetalHeapPool(MTL::Device* device, MTL::StorageMode storage_mode, - size_t min_heap_size, const char* label_prefix); - ~MetalHeapPool(); - - MTL::Texture* CreateTexture(MTL::TextureDescriptor* descriptor); - void Shutdown(); - - private: - struct HeapEntry { - MTL::Heap* heap = nullptr; - size_t size = 0; - }; - - MTL::Heap* GetHeapForSize(size_t size, size_t alignment); - - MTL::Device* device_ = nullptr; - MTL::StorageMode storage_mode_ = MTL::StorageModePrivate; - size_t min_heap_size_ = 0; - size_t max_heap_bytes_ = 0; - size_t total_heap_bytes_ = 0; - std::string label_prefix_; - std::vector heaps_; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_HEAP_POOL_H_ diff --git a/src/xenia/gpu/metal/metal_primitive_processor.cc b/src/xenia/gpu/metal/metal_primitive_processor.cc deleted file mode 100644 index 562eb182d..000000000 --- a/src/xenia/gpu/metal/metal_primitive_processor.cc +++ /dev/null @@ -1,217 +0,0 @@ -/** - ****************************************************************************** - * 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_primitive_processor.h" - -#include -#include -#include -#include - -#include "xenia/base/assert.h" -#include "xenia/base/logging.h" -#include "xenia/gpu/metal/metal_command_processor.h" - -namespace xe { -namespace gpu { -namespace metal { - -MetalPrimitiveProcessor::MetalPrimitiveProcessor( - MetalCommandProcessor& command_processor, const RegisterFile& register_file, - Memory& memory, TraceWriter& trace_writer, SharedMemory& shared_memory) - : PrimitiveProcessor(register_file, memory, trace_writer, shared_memory), - command_processor_(command_processor) {} - -MetalPrimitiveProcessor::~MetalPrimitiveProcessor() { Shutdown(true); } - -bool MetalPrimitiveProcessor::Initialize() { - // Initialize the base primitive processor - // Metal supports all primitive types through conversion - 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) - true, // point_sprites_supported_without_vs_expansion - true)) // rectangle_lists_supported_without_vs_expansion - { - Shutdown(); - return false; - } - - XELOGI("MetalPrimitiveProcessor initialized successfully"); - 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(); - - // 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 (!from_destructor) { - ShutdownCommon(); - } -} - -void MetalPrimitiveProcessor::CompletedSubmissionUpdated() { - // Nothing to do for Metal -} - -void MetalPrimitiveProcessor::BeginSubmission() { - // Nothing to do for Metal -} - -void MetalPrimitiveProcessor::BeginFrame() { - // 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()); -} - -void MetalPrimitiveProcessor::EndFrame() { ClearPerFrameCache(); } - -MTL::Buffer* MetalPrimitiveProcessor::GetConvertedIndexBuffer( - size_t handle, uint64_t& offset_bytes_out) const { - // The handle is actually a pointer to the MTL::Buffer - MTL::Buffer* buffer = reinterpret_cast(handle); - offset_bytes_out = 0; // We use the full buffer from the start - return buffer; -} - -bool MetalPrimitiveProcessor::InitializeBuiltinIndexBuffer( - size_t size_bytes, std::function fill_callback) { - assert_not_zero(size_bytes); - assert_null(builtin_index_buffer_); - - MTL::Device* device = command_processor_.GetMetalDevice(); - - // Create buffer with shared storage so we can write to it - builtin_index_buffer_ = - device->newBuffer(size_bytes, MTL::ResourceStorageModeShared); - if (!builtin_index_buffer_) { - XELOGE("Failed to create Metal built-in index buffer"); - return false; - } - builtin_index_buffer_size_ = size_bytes; - - builtin_index_buffer_->setLabel(NS::String::string( - "Xenia Built-in Index Buffer", NS::UTF8StringEncoding)); - - // Fill the buffer with built-in indices - 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; -} - -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; - - // 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 - backend_handle_out = reinterpret_cast(chosen_buffer->buffer); - 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; - } - - return cpu_buffer; -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_primitive_processor.h b/src/xenia/gpu/metal/metal_primitive_processor.h deleted file mode 100644 index 4c5d0899b..000000000 --- a/src/xenia/gpu/metal/metal_primitive_processor.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - ****************************************************************************** - * 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_PRIMITIVE_PROCESSOR_H_ -#define XENIA_GPU_METAL_METAL_PRIMITIVE_PROCESSOR_H_ - -#include -#include -#include "third_party/metal-cpp/Metal/Metal.hpp" - -#include "xenia/gpu/primitive_processor.h" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalCommandProcessor; - -class MetalPrimitiveProcessor : public PrimitiveProcessor { - public: - MetalPrimitiveProcessor(MetalCommandProcessor& command_processor, - const RegisterFile& register_file, Memory& memory, - TraceWriter& trace_writer, - SharedMemory& shared_memory); - ~MetalPrimitiveProcessor(); - - 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* GetConvertedIndexBuffer(size_t handle, - uint64_t& offset_bytes_out) const; - - protected: - bool InitializeBuiltinIndexBuffer( - size_t size_bytes, std::function fill_callback) override; - - void* RequestHostConvertedIndexBufferForCurrentFrame( - xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd, - uint32_t coalignment_original_address, - size_t& backend_handle_out) override; - - private: - MetalCommandProcessor& command_processor_; - - struct ConvertedIndexBufferBinding { - MTL::Buffer* buffer = nullptr; - uint64_t offset_bytes = 0; - }; - - 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; - - // 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_; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_PRIMITIVE_PROCESSOR_H_ diff --git a/src/xenia/gpu/metal/metal_render_target_cache.cc b/src/xenia/gpu/metal/metal_render_target_cache.cc deleted file mode 100644 index d8e1f863a..000000000 --- a/src/xenia/gpu/metal/metal_render_target_cache.cc +++ /dev/null @@ -1,8494 +0,0 @@ -/** - ****************************************************************************** - * 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 "xenia/gpu/gpu_flags.h" - -#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/byte_order.h" -#include "xenia/base/logging.h" -#include "xenia/base/math.h" -#include "xenia/gpu/draw_util.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/metal/metal_heap_pool.h" -#include "xenia/gpu/metal/metal_texture_cache.h" -#include "xenia/gpu/shaders/bytecode/metal/host_depth_store_1xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/host_depth_store_2xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/host_depth_store_4xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_32bpp_1x2xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_32bpp_1x2xmsaa_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_32bpp_4xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_32bpp_4xmsaa_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_64bpp_1x2xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_64bpp_1x2xmsaa_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_64bpp_4xmsaa_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_fast_64bpp_4xmsaa_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_128bpp_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_128bpp_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_16bpp_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_16bpp_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_32bpp_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_32bpp_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_64bpp_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_64bpp_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_8bpp_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/resolve_full_8bpp_scaled_cs.h" - -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/gpu/texture_info.h" -#include "xenia/gpu/texture_util.h" -#include "xenia/gpu/xenos.h" - -DEFINE_bool( - metal_allow_gamma_unorm16, false, - "Allow gamma_render_target_as_unorm16 on Metal despite known issues", - "GPU"); -DEFINE_bool(metal_transfer_fast_divmod, true, - "Use fast exact div/mod in Metal transfer shaders", "GPU"); -DEFINE_bool(metal_transfer_tile_instancing, true, - "Use per-tile instanced draws for Metal transfer shaders", "GPU"); -DEFINE_bool( - metal_transfer_msaa_sample_id, true, - "Use sample_id in Metal transfer shaders for MSAA (sample-rate shading)", - "GPU"); -DEFINE_int32(metal_memory_log_rate, 0, - "Log Metal render target/pipeline/instance buffer sizes every N " - "frames (0 to disable)", - "GPU"); - -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; - } - - // 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; -} - -// Packing formats for transferring host RT contents to the EDRAM buffer. -// Keep numeric values in sync with Metal dump shaders in -// InitializeEdramComputeShaders. -enum class MetalEdramDumpFormat : uint32_t { - kColorRGBA8 = 0, - kColorRGB10A2Unorm = 1, - kColorRGB10A2Float = 2, - kColorRG16Snorm = 3, - kColorRG16Float = 4, - kColorR32Float = 5, - kColorRGBA16Snorm = 6, - kColorRGBA16Float = 7, - kColorRGBA16Unorm = 8, - kColorRG32Float = 9, - kDepthD24S8 = 16, - kDepthD24FS8 = 17, -}; - -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: - return 0; - case xenos::MsaaSamples::k2X: - return 1; - case xenos::MsaaSamples::k4X: - return 2; - default: - return 0; - } -} - -uint32_t MsaaSamplesToCount(xenos::MsaaSamples samples) { - switch (samples) { - case xenos::MsaaSamples::k1X: - return 1; - case xenos::MsaaSamples::k2X: - return 2; - case xenos::MsaaSamples::k4X: - return 4; - default: - return 1; - } -} - -struct TransferAddressConstants { - uint32_t dest_pitch; - uint32_t source_pitch; - int32_t source_to_dest; -}; - -struct TransferShaderConstants { - TransferAddressConstants address; - TransferAddressConstants host_depth_address; - uint32_t source_format; - uint32_t dest_format; - uint32_t source_is_depth; - uint32_t dest_is_depth; - uint32_t source_is_uint; - uint32_t dest_is_uint; - uint32_t source_is_64bpp; - uint32_t dest_is_64bpp; - uint32_t source_msaa_samples; - uint32_t dest_msaa_samples; - uint32_t host_depth_source_msaa_samples; - uint32_t host_depth_source_is_copy; - uint32_t depth_round; - uint32_t msaa_2x_supported; - uint32_t tile_width_samples; - uint32_t tile_height_samples; - uint32_t dest_tile_width_pixels; - uint32_t dest_tile_height_pixels; - float dest_tile_width_pixels_inv; - float dest_tile_height_pixels_inv; - float source_pitch_tiles_inv; - float host_depth_source_pitch_tiles_inv; - float dest_pixel_to_ndc_x; - float dest_pixel_to_ndc_y; - uint32_t dest_sample_id; - uint32_t stencil_mask; - 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 TransferClearColorFloatConstants { - float color[4]; -}; - -struct TransferClearColorUintConstants { - uint32_t color[4]; -}; - -struct TransferClearDepthConstants { - float depth; - float padding[3]; -}; - -enum class TransferOutput { - kColor, - kDepth, - kStencilBit, -}; - -struct TransferModeInfo { - TransferOutput output; - bool source_is_color; - bool uses_host_depth; -}; - -constexpr TransferModeInfo kTransferModeInfos[] = { - {TransferOutput::kColor, true, false}, // kColorToColor - {TransferOutput::kDepth, true, false}, // kColorToDepth - {TransferOutput::kColor, false, false}, // kDepthToColor - {TransferOutput::kDepth, false, false}, // kDepthToDepth - {TransferOutput::kStencilBit, true, false}, // kColorToStencilBit - {TransferOutput::kStencilBit, false, false}, // kDepthToStencilBit - {TransferOutput::kDepth, true, true}, // kColorAndHostDepthToDepth - {TransferOutput::kDepth, false, true}, // kDepthAndHostDepthToDepth -}; - -} // 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. - return key.Is64bpp() || - (!key.is_depth && - key.GetColorFormat() == - xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && - gamma_render_target_as_unorm16_); -} - -uint32_t MetalRenderTargetCache::GetMetalEdramDumpFormat(RenderTargetKey key) { - if (key.is_depth) { - switch (key.GetDepthFormat()) { - case xenos::DepthRenderTargetFormat::kD24FS8: - return static_cast(MetalEdramDumpFormat::kDepthD24FS8); - case xenos::DepthRenderTargetFormat::kD24S8: - default: - return static_cast(MetalEdramDumpFormat::kDepthD24S8); - } - } - switch (key.GetColorFormat()) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - return static_cast(MetalEdramDumpFormat::kColorRGBA8); - case xenos::ColorRenderTargetFormat::k_2_10_10_10: - case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: - return static_cast(MetalEdramDumpFormat::kColorRGB10A2Unorm); - case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: - case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: - return static_cast(MetalEdramDumpFormat::kColorRGB10A2Float); - case xenos::ColorRenderTargetFormat::k_16_16: - return static_cast(MetalEdramDumpFormat::kColorRG16Snorm); - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: - return static_cast(MetalEdramDumpFormat::kColorRG16Float); - case xenos::ColorRenderTargetFormat::k_32_FLOAT: - return static_cast(MetalEdramDumpFormat::kColorR32Float); - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - return static_cast(MetalEdramDumpFormat::kColorRGBA16Snorm); - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - return static_cast(MetalEdramDumpFormat::kColorRGBA16Float); - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: - return static_cast(MetalEdramDumpFormat::kColorRG32Float); - default: - return static_cast(MetalEdramDumpFormat::kColorRGBA8); - } -} - -// MetalRenderTarget implementation -MetalRenderTargetCache::MetalRenderTarget::~MetalRenderTarget() { - if (stencil_view_) { - stencil_view_->release(); - stencil_view_ = nullptr; - } - if (draw_texture_ && draw_texture_ != texture_) { - draw_texture_->release(); - draw_texture_ = nullptr; - } - if (transfer_texture_ && transfer_texture_ != texture_) { - transfer_texture_->release(); - transfer_texture_ = nullptr; - } - if (msaa_draw_texture_ && msaa_draw_texture_ != msaa_texture_) { - msaa_draw_texture_->release(); - msaa_draw_texture_ = nullptr; - } - if (msaa_transfer_texture_ && msaa_transfer_texture_ != msaa_texture_) { - msaa_transfer_texture_->release(); - msaa_transfer_texture_ = nullptr; - } - if (texture_) { - texture_->release(); - texture_ = nullptr; - } - if (msaa_texture_) { - msaa_texture_->release(); - msaa_texture_ = nullptr; - } -} - -// MetalRenderTargetCache implementation -MetalRenderTargetCache::MetalRenderTargetCache( - const RegisterFile& register_file, const Memory& memory, - TraceWriter* trace_writer, uint32_t draw_resolution_scale_x, - uint32_t draw_resolution_scale_y, MetalCommandProcessor& command_processor) - : RenderTargetCache(register_file, memory, trace_writer, - draw_resolution_scale_x, draw_resolution_scale_y), - command_processor_(command_processor), - trace_writer_(trace_writer) {} - -MetalRenderTargetCache::~MetalRenderTargetCache() { Shutdown(true); } - -RenderTargetCache::Path MetalRenderTargetCache::GetPath() const { - return Path::kHostRenderTargets; -} - -bool MetalRenderTargetCache::Initialize() { - device_ = command_processor_.GetMetalDevice(); - if (!device_) { - XELOGE("MetalRenderTargetCache: No Metal device available"); - return false; - } - - // 2x msaa and unorm16 support virtually guarunteed as minimum OS target / - // Metal version currently is MacOS 15 / Metal 3 - msaa_2x_supported_ = device_->supportsTextureSampleCount(2); - - gamma_render_target_as_unorm16_ = ::cvars::gamma_render_target_as_unorm16 && - ::cvars::metal_allow_gamma_unorm16; - if (::cvars::gamma_render_target_as_unorm16 && - !::cvars::metal_allow_gamma_unorm16) { - XELOGW( - "Metal: gamma_render_target_as_unorm16 disabled due to known issues; " - "set --metal_allow_gamma_unorm16=true to force"); - } - - if (::cvars::metal_use_heaps) { - 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"); - } - - // Create the EDRAM buffer. - // - // The guest has 10 MiB of EDRAM for samples, but with host resolution - // scaling enabled the compute path addresses a scaled EDRAM layout (the - // shaders multiply the tile dimensions by resolution_scale_x/y). The buffer - // therefore must be scaled by the same factor to avoid out-of-bounds writes. - const uint32_t scale_x = std::max(1u, draw_resolution_scale_x()); - const uint32_t scale_y = std::max(1u, draw_resolution_scale_y()); - const size_t edram_dwords = size_t(xenos::kEdramTileCount) * - size_t(xenos::kEdramTileWidthSamples) * - size_t(xenos::kEdramTileHeightSamples) * - size_t(scale_x) * size_t(scale_y); - const size_t edram_size_bytes = edram_dwords * sizeof(uint32_t); - const bool edram_cpu_visible = false; - const MTL::ResourceOptions edram_storage_mode = - edram_cpu_visible ? MTL::ResourceStorageModeShared - : MTL::ResourceStorageModePrivate; - edram_buffer_ = device_->newBuffer(edram_size_bytes, edram_storage_mode); - if (!edram_buffer_) { - XELOGE("MetalRenderTargetCache: Failed to create EDRAM buffer"); - return false; - } - edram_buffer_->setLabel( - NS::String::string("EDRAM Buffer", NS::UTF8StringEncoding)); - if (edram_cpu_visible) { - void* edram_contents = edram_buffer_->contents(); - if (edram_contents) { - std::memset(edram_contents, 0, edram_size_bytes); - } - } 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(); - } - } - } - } - // Initialize EDRAM compute shaders - if (!InitializeEdramComputeShaders()) { - XELOGE( - "MetalRenderTargetCache: Failed to initialize EDRAM compute shaders"); - return false; - } - - // Initialize base class - InitializeCommon(); - - return true; -} - -void MetalRenderTargetCache::Shutdown(bool from_destructor) { - if (!from_destructor) { - ClearCache(); - } - - // Clean up dummy target - dummy_color_targets_.clear(); - dummy_color_target_ = nullptr; - if (cached_render_pass_descriptor_) { - cached_render_pass_descriptor_->release(); - cached_render_pass_descriptor_ = nullptr; - } - - for (auto& it : transfer_pipelines_) { - if (it.second) { - it.second->release(); - } - } - 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(); - } - } - edram_load_pipelines_.clear(); - for (auto& it : transfer_clear_pipelines_) { - if (it.second) { - it.second->release(); - } - } - transfer_clear_pipelines_.clear(); - if (transfer_library_) { - transfer_library_->release(); - transfer_library_ = nullptr; - } - if (edram_load_library_) { - edram_load_library_->release(); - edram_load_library_ = nullptr; - } - if (edram_load_library_msaa_) { - edram_load_library_msaa_->release(); - edram_load_library_msaa_ = nullptr; - } - if (transfer_depth_state_) { - transfer_depth_state_->release(); - transfer_depth_state_ = nullptr; - } - if (transfer_depth_state_none_) { - transfer_depth_state_none_->release(); - transfer_depth_state_none_ = nullptr; - } - if (transfer_depth_clear_state_) { - transfer_depth_clear_state_->release(); - transfer_depth_clear_state_ = nullptr; - } - if (transfer_stencil_clear_state_) { - transfer_stencil_clear_state_->release(); - transfer_stencil_clear_state_ = nullptr; - } - for (auto& state : transfer_stencil_bit_states_) { - if (state) { - state->release(); - state = nullptr; - } - } - if (transfer_dummy_buffer_) { - 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(); - transfer_dummy_color_float_[i] = nullptr; - } - if (transfer_dummy_color_uint_[i]) { - transfer_dummy_color_uint_[i]->release(); - transfer_dummy_color_uint_[i] = nullptr; - } - if (transfer_dummy_depth_[i]) { - transfer_dummy_depth_[i]->release(); - transfer_dummy_depth_[i] = nullptr; - } - if (transfer_dummy_stencil_[i]) { - transfer_dummy_stencil_[i]->release(); - transfer_dummy_stencil_[i] = nullptr; - } - } - - // Clean up EDRAM compute shaders - ShutdownEdramComputeShaders(); - - if (edram_buffer_) { - edram_buffer_->release(); - edram_buffer_ = nullptr; - } - - // Destroy all render targets - DestroyAllRenderTargets(!from_destructor); - render_target_map_.clear(); - - if (render_target_heap_pool_) { - render_target_heap_pool_->Shutdown(); - render_target_heap_pool_.reset(); - } - - // Shutdown base class - if (!from_destructor) { - ShutdownCommon(); - } -} - -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 (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; - } - - 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"); - return false; - } - } - - host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k1X)] = - CreateComputePipelineFromEmbeddedLibrary( - device_, host_depth_store_1xmsaa_cs_metallib, - sizeof(host_depth_store_1xmsaa_cs_metallib), - "host_depth_store_1xmsaa"); - host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k2X)] = - CreateComputePipelineFromEmbeddedLibrary( - device_, host_depth_store_2xmsaa_cs_metallib, - sizeof(host_depth_store_2xmsaa_cs_metallib), - "host_depth_store_2xmsaa"); - host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X)] = - CreateComputePipelineFromEmbeddedLibrary( - device_, host_depth_store_4xmsaa_cs_metallib, - sizeof(host_depth_store_4xmsaa_cs_metallib), - "host_depth_store_4xmsaa"); - - for (size_t i = 0; i < xe::countof(host_depth_store_pipelines_); ++i) { - if (!host_depth_store_pipelines_[i]) { - XELOGE("Metal: failed to initialize host depth store pipelines"); - return false; - } - } - - // EDRAM dump compute shader for 32-bpp color, 1x MSAA. - { - static const char kEdramDumpColor32bpp1xMsaaShader[] = 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) { - 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_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; - } -} - -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) { - 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) { - 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_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"); - } - } - } - } - - // 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; - } -} - -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_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; -} -)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"); - } - } - } - } - - // 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( - texture2d source [[texture(0)]], - texture2d 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_coord = uint2(source_tile_x * tile_size.x + sample_in_tile.x, - source_tile_y * tile_size.y + sample_in_tile.y); - - float depth = source.read(source_coord).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(source_coord).x & 0xFFu; - } - - uint packed = (depth24 << 8u) | stencil_value; - - edram[edram_index] = packed; -} -)METAL"; - - NS::String* source = NS::String::string(kEdramDumpDepth32bpp1xMsaaShader, - NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(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"); - } - } - } - } - - // 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); - } - 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"); - } - } - } - } - - return true; -} - -void MetalRenderTargetCache::ShutdownEdramComputeShaders() { - if (edram_load_pipeline_) { - edram_load_pipeline_->release(); - edram_load_pipeline_ = nullptr; - } - if (edram_store_pipeline_) { - edram_store_pipeline_->release(); - edram_store_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; - } - 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 (size_t i = 0; i < xe::countof(host_depth_store_pipelines_); ++i) { - if (host_depth_store_pipelines_[i]) { - host_depth_store_pipelines_[i]->release(); - host_depth_store_pipelines_[i] = nullptr; - } - } -} - -void MetalRenderTargetCache::ClearCache() { - // 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; - - // Clear the tracking of which render targets have been cleared - cleared_render_targets_this_frame_.clear(); - dummy_color_targets_.clear(); - dummy_color_target_ = nullptr; - render_target_map_.clear(); - - // Call base implementation - RenderTargetCache::ClearCache(); -} - -void MetalRenderTargetCache::BeginFrame() { - ++frame_id_; - - // Clear the tracking of which render targets have been cleared this frame - cleared_render_targets_this_frame_.clear(); - - // 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_targets().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]); - } -} - -bool MetalRenderTargetCache::Update( - bool is_rasterization_done, reg::RB_DEPTHCONTROL normalized_depth_control, - uint32_t normalized_color_mask, const Shader& vertex_shader) { - // Use the base class logic to update the current render target setup. - if (!RenderTargetCache::Update(is_rasterization_done, - normalized_depth_control, - normalized_color_mask, vertex_shader)) { - XELOGE("MetalRenderTargetCache::Update - Base class Update failed"); - 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_targets().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]) - : nullptr; - 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] - ? static_cast(accumulated_targets[i + 1]) - : nullptr; - 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); - } - } - } - - // 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); - - // Only mark render pass descriptor as dirty if targets actually changed - if (targets_changed) { - render_pass_descriptor_dirty_ = true; - } - - return true; -} - -uint32_t MetalRenderTargetCache::GetMaxRenderTargetWidth() const { - // Metal maximum texture dimension - return 16384; -} - -uint32_t MetalRenderTargetCache::GetMaxRenderTargetHeight() const { - // Metal maximum texture dimension - return 16384; -} - -bool MetalRenderTargetCache::IsGammaFormatHostStorageSeparate() const { - return gamma_render_target_as_unorm16_; -} - -RenderTargetCache::RenderTarget* MetalRenderTargetCache::CreateRenderTarget( - RenderTargetKey key) { - // Calculate dimensions - uint32_t width = key.GetWidth(); - uint32_t height = - GetRenderTargetHeight(key.pitch_tiles_at_32bpp, key.msaa_samples); - - // Apply resolution scaling - width *= draw_resolution_scale_x(); - height *= draw_resolution_scale_y(); - - // Create Metal render target - auto* render_target = new MetalRenderTarget(key); - - // Create the texture based on format - MTL::Texture* texture = nullptr; - uint32_t samples = 1 << uint32_t(key.msaa_samples); - - if (key.is_depth) { - texture = CreateDepthTexture(width, height, key.GetDepthFormat(), samples); - } else { - texture = CreateColorTexture(width, height, key.GetColorFormat(), samples); - } - - if (!texture) { - delete render_target; - return nullptr; - } - - render_target->SetTexture(texture); - if (!key.is_depth) { - MTL::PixelFormat resource_format = - GetColorResourcePixelFormat(key.GetColorFormat()); - MTL::PixelFormat draw_format = - GetColorDrawPixelFormat(key.GetColorFormat()); - MTL::PixelFormat transfer_format = - GetColorOwnershipTransferPixelFormat(key.GetColorFormat(), nullptr); - if (draw_format != resource_format) { - MTL::Texture* draw_view = texture->newTextureView(draw_format); - RecordRenderTargetViewCreated(); - render_target->SetDrawTexture(draw_view); - } - if (transfer_format != resource_format) { - MTL::Texture* transfer_view = texture->newTextureView(transfer_format); - RecordRenderTargetViewCreated(); - 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(); - 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(); - render_target->SetMsaaTransferTexture(msaa_transfer_view); - } - } - } - - // NOTE: Unlike the previous implementation, we do NOT load EDRAM data here. - // This matches D3D12's approach where: - // 1. CreateRenderTarget creates an empty texture - // 2. Data transfer happens via ownership transfers in - // PerformTransfersAndResolveClears - // 3. The EDRAM buffer is only used as scratch space for resolves - // - // The ownership transfer system (called from Update()) handles copying data - // between render target textures when EDRAM regions are aliased between - // different RT configurations. - - // Store in our map for later retrieval - render_target_map_[key.key] = render_target; - - return render_target; -} - -bool MetalRenderTargetCache::IsHostDepthEncodingDifferent( - xenos::DepthRenderTargetFormat format) const { - // Metal uses different depth encoding than Xbox 360 - // D24S8 on Xbox 360 vs D32Float_S8 on Metal - return format == xenos::DepthRenderTargetFormat::kD24S8 || - format == xenos::DepthRenderTargetFormat::kD24FS8; -} - -void MetalRenderTargetCache::RestoreEdramSnapshot(const void* snapshot) { - if (!snapshot) { - return; - } - - if (IsDrawResolutionScaled()) { - return; - } - - RenderTarget* full_edram_rt = - PrepareFullEdram1280xRenderTargetForSnapshotRestoration( - xenos::ColorRenderTargetFormat::k_32_FLOAT); - if (!full_edram_rt) { - return; - } - - MetalRenderTarget* metal_rt = static_cast(full_edram_rt); - MTL::Texture* texture = metal_rt->texture(); - if (!texture) { - return; - } - - constexpr uint32_t kPitchTilesAt32bpp = 16; - constexpr uint32_t kWidth = - kPitchTilesAt32bpp * xenos::kEdramTileWidthSamples; - constexpr uint32_t kTileRows = xenos::kEdramTileCount / kPitchTilesAt32bpp; - constexpr uint32_t kHeight = kTileRows * xenos::kEdramTileHeightSamples; - - size_t staging_size = size_t(kWidth) * size_t(kHeight) * sizeof(uint32_t); - MTL::Buffer* staging = - device_->newBuffer(staging_size, MTL::ResourceStorageModeShared); - if (!staging) { - return; - } - - auto* dst_base = static_cast(staging->contents()); - const uint8_t* src = static_cast(snapshot); - uint32_t bytes_per_row = kWidth * sizeof(uint32_t); - - for (uint32_t y_tile = 0; y_tile < kTileRows; ++y_tile) { - for (uint32_t x_tile = 0; x_tile < kPitchTilesAt32bpp; ++x_tile) { - uint32_t tile_index = y_tile * kPitchTilesAt32bpp + x_tile; - const uint8_t* tile_src = - src + tile_index * xenos::kEdramTileWidthSamples * - xenos::kEdramTileHeightSamples * sizeof(uint32_t); - - for (uint32_t sample_row = 0; sample_row < xenos::kEdramTileHeightSamples; - ++sample_row) { - uint32_t dst_y = y_tile * xenos::kEdramTileHeightSamples + sample_row; - uint32_t dst_x = x_tile * xenos::kEdramTileWidthSamples; - - uint8_t* dst_row = - dst_base + dst_y * bytes_per_row + dst_x * sizeof(uint32_t); - const uint8_t* src_row = tile_src + sample_row * - xenos::kEdramTileWidthSamples * - sizeof(uint32_t); - - std::memcpy(dst_row, src_row, - xenos::kEdramTileWidthSamples * sizeof(uint32_t)); - } - } - } - - ScopedAutoreleasePool autorelease_pool; - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - if (!queue) { - staging->release(); - return; - } - - MTL::CommandBuffer* cmd = queue->commandBuffer(); - if (!cmd) { - staging->release(); - return; - } - - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (!blit) { - // cmd is autoreleased from commandBuffer() - do not release - staging->release(); - return; - } - - blit->copyFromBuffer(staging, 0, bytes_per_row, 0, - 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 - staging->release(); - if (metal_rt->needs_initial_clear()) { - metal_rt->SetNeedsInitialClear(false); - render_pass_descriptor_dirty_ = true; - } - - // 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); -} - -MTL::Texture* MetalRenderTargetCache::CreateColorTexture( - uint32_t width, uint32_t height, xenos::ColorRenderTargetFormat format, - uint32_t samples) { - MTL::PixelFormat resource_format = GetColorResourcePixelFormat(format); - MTL::PixelFormat draw_format = GetColorDrawPixelFormat(format); - MTL::PixelFormat transfer_format = - GetColorOwnershipTransferPixelFormat(format, nullptr); - bool needs_pixel_format_view = - draw_format != resource_format || transfer_format != resource_format; - - MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); - desc->setWidth(width); - desc->setHeight(height ? height : 720); // Default height if not specified - desc->setPixelFormat(resource_format); - desc->setTextureType(samples > 1 ? MTL::TextureType2DMultisample - : MTL::TextureType2D); - desc->setSampleCount(samples); - MTL::TextureUsage usage = - MTL::TextureUsageRenderTarget | MTL::TextureUsageShaderRead; - if (needs_pixel_format_view) { - usage |= MTL::TextureUsagePixelFormatView; - } - desc->setUsage(usage); - desc->setStorageMode(MTL::StorageModePrivate); - - MTL::Texture* texture = nullptr; - if (render_target_heap_pool_) { - texture = render_target_heap_pool_->CreateTexture(desc); - } - if (!texture) { - texture = device_->newTexture(desc); - } - desc->release(); - // Initial clear is handled on first bind via load actions; avoid - // synchronous clears here to keep the host RT path fast. - return texture; -} - -MTL::Texture* MetalRenderTargetCache::CreateDepthTexture( - uint32_t width, uint32_t height, xenos::DepthRenderTargetFormat format, - uint32_t samples) { - MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); - desc->setWidth(width); - desc->setHeight(height ? height : 720); // Default height if not specified - MTL::PixelFormat pixel_format = GetDepthPixelFormat(format); - desc->setPixelFormat(pixel_format); - desc->setTextureType(samples > 1 ? MTL::TextureType2DMultisample - : MTL::TextureType2D); - desc->setSampleCount(samples); - MTL::TextureUsage usage = - MTL::TextureUsageRenderTarget | MTL::TextureUsageShaderRead; - if (pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || - pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8) { - usage |= MTL::TextureUsagePixelFormatView; - } - desc->setUsage(usage); - desc->setStorageMode(MTL::StorageModePrivate); - - MTL::Texture* texture = nullptr; - if (render_target_heap_pool_) { - texture = render_target_heap_pool_->CreateTexture(desc); - } - if (!texture) { - texture = device_->newTexture(desc); - } - desc->release(); - // Initial clear is handled on first bind via load actions; avoid - // synchronous clears here to keep the host RT path fast. - return texture; -} - -MTL::PixelFormat MetalRenderTargetCache::GetColorResourcePixelFormat( - xenos::ColorRenderTargetFormat format) const { - switch (format) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: - return MTL::PixelFormatRGBA8Unorm; - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: - // Updated to suport unorm16 for gamma render targets. - return gamma_render_target_as_unorm16_ ? MTL::PixelFormatRGBA16Unorm - : MTL::PixelFormatRGBA8Unorm; - case xenos::ColorRenderTargetFormat::k_2_10_10_10: - case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: - return MTL::PixelFormatRGB10A2Unorm; - case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: - case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16: - // Match D3D12 behavior: store as RGBA16F and pack to float10 on dump. - return MTL::PixelFormatRGBA16Float; - case xenos::ColorRenderTargetFormat::k_16_16: - return MTL::PixelFormatRG16Snorm; - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - return MTL::PixelFormatRGBA16Snorm; - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: - return MTL::PixelFormatRG16Float; - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - return MTL::PixelFormatRGBA16Float; - case xenos::ColorRenderTargetFormat::k_32_FLOAT: - return MTL::PixelFormatR32Float; - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: - return MTL::PixelFormatRG32Float; - default: - XELOGE("MetalRenderTargetCache: Unsupported color format {}", - static_cast(format)); - return MTL::PixelFormatRGBA8Unorm; - } -} - -MTL::PixelFormat MetalRenderTargetCache::GetColorDrawPixelFormat( - xenos::ColorRenderTargetFormat format) const { - switch (format) { - case xenos::ColorRenderTargetFormat::k_16_16: - return MTL::PixelFormatRG16Snorm; - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - return MTL::PixelFormatRGBA16Snorm; - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: - return MTL::PixelFormatRG16Float; - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - return MTL::PixelFormatRGBA16Float; - case xenos::ColorRenderTargetFormat::k_32_FLOAT: - return MTL::PixelFormatR32Float; - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: - return MTL::PixelFormatRG32Float; - default: - return GetColorResourcePixelFormat(format); - } -} - -MTL::PixelFormat MetalRenderTargetCache::GetColorOwnershipTransferPixelFormat( - xenos::ColorRenderTargetFormat format, bool* is_integer_out) const { - if (is_integer_out) { - *is_integer_out = true; - } - switch (format) { - case xenos::ColorRenderTargetFormat::k_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: - return MTL::PixelFormatRG16Uint; - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - return MTL::PixelFormatRGBA16Uint; - case xenos::ColorRenderTargetFormat::k_32_FLOAT: - return MTL::PixelFormatR32Uint; - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: - return MTL::PixelFormatRG32Uint; - default: - if (is_integer_out) { - *is_integer_out = false; - } - // Ownership transfers must use a linear resource view to avoid - // implicit sRGB conversion for gamma render targets. - return GetColorResourcePixelFormat(format); - } -} - -MTL::PixelFormat MetalRenderTargetCache::GetDepthPixelFormat( - xenos::DepthRenderTargetFormat format) const { - switch (format) { - case xenos::DepthRenderTargetFormat::kD24S8: - case xenos::DepthRenderTargetFormat::kD24FS8: - // Metal doesn't have D24S8, use D32Float_S8 - return MTL::PixelFormatDepth32Float_Stencil8; - default: - XELOGE("MetalRenderTargetCache: Unsupported depth format {}", - static_cast(format)); - return MTL::PixelFormatDepth32Float_Stencil8; - } -} - -MTL::Texture* MetalRenderTargetCache::GetStencilTextureView( - MetalRenderTarget* render_target) { - if (!render_target) { - return nullptr; - } - if (render_target->stencil_view()) { - return render_target->stencil_view(); - } - RenderTargetKey key = render_target->key(); - if (!key.is_depth) { - return nullptr; - } - MTL::Texture* depth_texture = render_target->texture(); - if (!depth_texture) { - return nullptr; - } - MTL::Texture* view = - depth_texture->newTextureView(MTL::PixelFormatX32_Stencil8); - if (view) { - render_target->SetStencilView(view); - } - return view; -} - -MTL::RenderPassDescriptor* MetalRenderTargetCache::GetRenderPassDescriptor( - uint32_t expected_sample_count) { - if (!render_pass_descriptor_dirty_ && cached_render_pass_descriptor_ && - cached_render_pass_descriptor_sample_count_ == expected_sample_count) { - return cached_render_pass_descriptor_; - } - if (cached_render_pass_descriptor_sample_count_ != expected_sample_count) { - render_pass_descriptor_dirty_ = true; - } - - // Release old descriptor - if (cached_render_pass_descriptor_) { - cached_render_pass_descriptor_->release(); - cached_render_pass_descriptor_ = nullptr; - } - - // Create new descriptor - cached_render_pass_descriptor_ = - MTL::RenderPassDescriptor::renderPassDescriptor(); - if (!cached_render_pass_descriptor_) { - XELOGE("MetalRenderTargetCache: Failed to create render pass descriptor"); - return nullptr; - } - cached_render_pass_descriptor_->retain(); - cached_render_pass_descriptor_sample_count_ = expected_sample_count; - - 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); - - // Bind the actual render targets retrieved from base class in Update() - - // Bind depth target if present - if (current_depth_target_ && current_depth_target_->texture()) { - auto* depth_attachment = cached_render_pass_descriptor_->depthAttachment(); - depth_attachment->setTexture(current_depth_target_->draw_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(); - 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->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(); - 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->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())); - } - } - } - - // Bind color targets - for (uint32_t i = 0; i < 4; ++i) { - if (current_color_targets_[i] && current_color_targets_[i]->texture()) { - auto* color_attachment = - cached_render_pass_descriptor_->colorAttachments()->object(i); - color_attachment->setTexture(current_color_targets_[i]->draw_texture()); - - // Clear on first bind to avoid synchronous clears at creation. - bool color_needs_clear = current_color_targets_[i]->needs_initial_clear(); - 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_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())); - } - } - } - } - - // If no color render targets are bound, attach a dummy color target so Metal - // has at least one color attachment. This mirrors the D3D12/Vulkan behavior - // where an RTV is always bound when drawing, and also keeps pipeline state - // validation happy for depth-only passes. - if (!has_any_color_target) { - xenos::ColorRenderTargetFormat fmt = - xenos::ColorRenderTargetFormat::k_8_8_8_8; - uint32_t samples = std::max(1u, expected_sample_count); - - uint32_t width = 1280; - uint32_t height = 720; - if (current_depth_target_ && current_depth_target_->texture()) { - width = static_cast(current_depth_target_->texture()->width()); - height = - static_cast(current_depth_target_->texture()->height()); - if (current_depth_target_->texture()->sampleCount() > 0) { - samples = std::max( - samples, static_cast( - current_depth_target_->texture()->sampleCount())); - } - } else if (last_real_color_targets_[0] && - last_real_color_targets_[0]->texture()) { - width = static_cast( - last_real_color_targets_[0]->texture()->width()); - height = static_cast( - last_real_color_targets_[0]->texture()->height()); - } else if (last_real_depth_target_ && last_real_depth_target_->texture()) { - width = - static_cast(last_real_depth_target_->texture()->width()); - height = - static_cast(last_real_depth_target_->texture()->height()); - if (last_real_depth_target_->texture()->sampleCount() > 0) { - samples = std::max( - samples, static_cast( - last_real_depth_target_->texture()->sampleCount())); - } - } - - uint64_t dummy_key = 0; - if (current_depth_target_) { - dummy_key = 0x100000000ull | uint64_t(current_depth_target_->key().key); - } else if (last_real_color_targets_[0]) { - dummy_key = - 0x200000000ull | uint64_t(last_real_color_targets_[0]->key().key); - } else if (last_real_depth_target_) { - dummy_key = 0x300000000ull | uint64_t(last_real_depth_target_->key().key); - } else { - dummy_key = uint64_t(width) | (uint64_t(height) << 20) | - (uint64_t(samples) << 40); - } - auto& entry = dummy_color_targets_[dummy_key]; - if (!entry.target || !entry.target->texture()) { - RenderTargetKey dummy_rt_key; - dummy_rt_key.key = 0; - dummy_rt_key.is_depth = 0; - dummy_rt_key.resource_format = uint32_t(fmt); - dummy_rt_key.msaa_samples = (samples >= 4u) ? xenos::MsaaSamples::k4X - : (samples == 2u) ? xenos::MsaaSamples::k2X - : xenos::MsaaSamples::k1X; - entry.target = std::make_unique(dummy_rt_key); - entry.last_cleared_frame = frame_id_ - 1; - MTL::Texture* tex = CreateColorTexture(width, height, fmt, samples); - entry.target->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(); - } - if (transfer_format != resource_format) { - entry.target->SetTransferTexture( - tex->newTextureView(transfer_format)); - RecordRenderTargetViewCreated(); - } - } - } - - entry.last_used_frame = frame_id_; - dummy_color_target_ = entry.target.get(); - - constexpr size_t kMaxDummyColorTargets = 64; - if (dummy_color_targets_.size() > kMaxDummyColorTargets) { - uint64_t oldest_key = 0; - uint64_t oldest_frame = frame_id_; - bool found = false; - for (const auto& it : dummy_color_targets_) { - if (it.first == dummy_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); - } - } - - 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); - - has_any_render_target = true; - if (!coverage_width && dummy_color_target_->draw_texture()) { - coverage_width = - static_cast(dummy_color_target_->draw_texture()->width()); - coverage_height = - static_cast(dummy_color_target_->draw_texture()->height()); - if (dummy_color_target_->draw_texture()->sampleCount() > 0) { - coverage_samples = std::max( - coverage_samples, - static_cast( - dummy_color_target_->draw_texture()->sampleCount())); - } - } - } - - render_pass_descriptor_dirty_ = needs_descriptor_refresh; - return cached_render_pass_descriptor_; -} - -MTL::Texture* MetalRenderTargetCache::GetColorTarget(uint32_t index) const { - if (index >= 4 || !current_color_targets_[index]) { - return nullptr; - } - return current_color_targets_[index]->texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetDepthTarget() const { - if (!current_depth_target_) { - return nullptr; - } - return current_depth_target_->texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetDummyColorTarget() const { - if (dummy_color_target_ && dummy_color_target_->texture()) { - return dummy_color_target_->texture(); - } - 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]) { - return nullptr; - } - return current_color_targets_[index]->draw_texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetDepthTargetForDraw() const { - if (!current_depth_target_) { - return nullptr; - } - return current_depth_target_->draw_texture(); -} - -MTL::Texture* MetalRenderTargetCache::GetDummyColorTargetForDraw() const { - if (dummy_color_target_ && dummy_color_target_->draw_texture()) { - return dummy_color_target_->draw_texture(); - } - return nullptr; -} - -MTL::Texture* MetalRenderTargetCache::GetLastRealColorTarget( - uint32_t index) const { - if (index >= 4 || !last_real_color_targets_[index]) { - return nullptr; - } - 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) { - // For storing depth/stencil data back to EDRAM, we would need to read - // from the depth texture This is complex because depth textures can't be - // directly read in compute shaders. For now, we'll skip storing depth - // data back to EDRAM since depth buffers are typically write-only during - // rendering and don't need to be preserved across frames. - 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(); -} - -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) { - XELOGGPU( - "MetalRenderTargetCache::DumpRenderTargets: base={} row_length_used={} " - "rows={} pitch={}", - dump_base, dump_row_length_used, dump_rows, dump_pitch); - - std::vector rectangles; - GetResolveCopyRectanglesToDump(dump_base, dump_row_length_used, dump_rows, - dump_pitch, rectangles); - - XELOGGPU("MetalRenderTargetCache::DumpRenderTargets: {} rectangles to dump", - rectangles.size()); - if (rectangles.empty()) { - XELOGW( - "MetalRenderTargetCache::DumpRenderTargets: no rectangles for base={} " - "row_length_used={} rows={} pitch={}", - dump_base, dump_row_length_used, dump_rows, dump_pitch); - return; - } - - if (!edram_buffer_) { - XELOGW( - "MetalRenderTargetCache::DumpRenderTargets: EDRAM buffer not " - "initialized, skipping GPU dump"); - return; - } - - struct EdramDumpConstants { - uint32_t dispatch_first_tile; - uint32_t source_base_tiles; - uint32_t dest_pitch_tiles; - uint32_t source_pitch_tiles; - uint32_t resolution_scale_x; - uint32_t resolution_scale_y; - uint32_t tile_size_x; - uint32_t tile_size_y; - float tile_size_inv_x; - float tile_size_inv_y; - float source_pitch_tiles_inv; - uint32_t format; - uint32_t flags; - 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; - MTL::CommandBuffer* cmd = command_buffer; - if (!cmd) { - cmd = queue->commandBuffer(); - if (!cmd) { - XELOGE("MetalRenderTargetCache::DumpRenderTargets: no command buffer"); - return; - } - owns_command_buffer = true; - } - - MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); - if (!encoder) { - XELOGE("MetalRenderTargetCache::DumpRenderTargets: no compute encoder"); - // cmd is autoreleased from commandBuffer() - do not release - return; - } - - encoder->setBuffer(edram_buffer_, 0, 0); - encoder->useResource(edram_buffer_, MTL::ResourceUsageWrite); - - uint32_t scale_x = draw_resolution_scale_x(); - uint32_t scale_y = draw_resolution_scale_y(); - - for (const ResolveCopyDumpRectangle& rect : rectangles) { - auto* rt = static_cast(rect.render_target); - if (!rt) { - continue; - } - - RenderTargetKey key = rt->key(); - MTL::Texture* tex = rt->texture(); - if (!tex) { - continue; - } - if (key.is_depth) { - MTL::PixelFormat expected_format = - GetDepthPixelFormat(key.GetDepthFormat()); - assert_true(tex->pixelFormat() == expected_format, - "Dump depth must bind resource pixel format"); - } else { - MTL::PixelFormat expected_format = - GetColorResourcePixelFormat(key.GetColorFormat()); - assert_true(tex->pixelFormat() == expected_format, - "Dump color must bind resource pixel format"); - } - - uint32_t dump_format = GetMetalEdramDumpFormat(key); - uint32_t dump_flags = 0; - MTL::Texture* stencil_tex = nullptr; - if (key.is_depth) { - if (!::cvars::depth_float24_convert_in_pixel_shader && - ::cvars::depth_float24_round) { - dump_flags |= kMetalEdramDumpFlagDepthRound; - } - stencil_tex = GetStencilTextureView(rt); - if (stencil_tex) { - dump_flags |= kMetalEdramDumpFlagHasStencil; - } - } - - // Choose the appropriate dump pipeline based on: - // - 32bpp vs 64bpp (key.Is64bpp()) - // - color vs depth (key.is_depth) - // - MSAA sample count (key.msaa_samples) - // This mirrors D3D12's dump pipeline selection: use key.Is64bpp() directly, - // NOT IsKey64bpp() which includes gamma-as-unorm16. The EDRAM buffer is - // always 32bpp for gamma formats; only the host texture storage is 64bpp. - MTL::ComputePipelineState* dump_pipeline = nullptr; - bool is_64bpp = key.Is64bpp(); - - // If this is a gamma RT stored as linear RGBA16Unorm, we need to encode - // to PWL gamma when dumping. Set the flag for the dump shader. - if (!key.is_depth && - key.GetColorFormat() == - xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA && - gamma_render_target_as_unorm16_) { - 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; - } - } 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; - } - } - - if (!dump_pipeline) { - XELOGGPU( - "MetalRenderTargetCache::DumpRenderTargets: no dump pipeline for " - "key=0x{:08X} (is_depth={}, is_64bpp={}, msaa={})", - key.key, key.is_depth ? 1 : 0, is_64bpp ? 1 : 0, - static_cast(key.msaa_samples)); - continue; - } - - XELOGGPU( - "MetalRenderTargetCache::DumpRenderTargets: dump RT key=0x{:08X} " - "(is_depth={}, is_64bpp={}, 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)); - - ResolveCopyDumpRectangle::Dispatch - dispatches[ResolveCopyDumpRectangle::kMaxDispatches]; - uint32_t dispatch_count = - rect.GetDispatches(dump_pitch, dump_row_length_used, dispatches); - if (!dispatch_count) { - continue; - } - - for (uint32_t i = 0; i < dispatch_count; ++i) { - const ResolveCopyDumpRectangle::Dispatch& dispatch = dispatches[i]; - - EdramDumpConstants constants; - constants.dispatch_first_tile = dump_base + dispatch.offset; - constants.source_base_tiles = key.base_tiles; - constants.dest_pitch_tiles = dump_pitch; - constants.source_pitch_tiles = key.GetPitchTiles(); - constants.resolution_scale_x = scale_x; - constants.resolution_scale_y = scale_y; - uint32_t tile_size_x = (is_64bpp ? 40u : 80u) * scale_x; - uint32_t tile_size_y = 16u * scale_y; - constants.tile_size_x = tile_size_x; - constants.tile_size_y = tile_size_y; - constants.tile_size_inv_x = - tile_size_x ? (1.0f / float(tile_size_x)) : 0.0f; - constants.tile_size_inv_y = - tile_size_y ? (1.0f / float(tile_size_y)) : 0.0f; - constants.source_pitch_tiles_inv = - constants.source_pitch_tiles - ? (1.0f / float(constants.source_pitch_tiles)) - : 0.0f; - constants.format = dump_format; - constants.flags = dump_flags; - constants.padding = 0; - - encoder->setComputePipelineState(dump_pipeline); - encoder->setTexture(tex, 0); - if (stencil_tex) { - encoder->setTexture(stencil_tex, 1); - } - encoder->useResource(tex, MTL::ResourceUsageRead); - if (stencil_tex) { - encoder->useResource(stencil_tex, MTL::ResourceUsageRead); - } - encoder->setBytes(&constants, sizeof(constants), 1); - - // Thread group dispatch: - // - 40x16 threads per group (same as D3D12/Vulkan) - // - For 32bpp: two groups per tile along X (80 samples / 40 threads) - // - For 64bpp: one group per tile along X (40 samples / 40 threads) - uint32_t groups_x = dispatch.width_tiles * scale_x; - if (!is_64bpp) { - groups_x <<= 1; // Double for 32bpp - } - uint32_t groups_y = dispatch.height_tiles * scale_y; - - MTL::Size threads_per_group = MTL::Size::Make(40, 16, 1); - MTL::Size threadgroups = MTL::Size::Make(groups_x, groups_y, 1); - encoder->dispatchThreadgroups(threadgroups, threads_per_group); - } - } - - encoder->endEncoding(); - if (owns_command_buffer) { - cmd->commit(); - cmd->waitUntilCompleted(); - } - // cmd is autoreleased from commandBuffer() - do not release -} - -MTL::Library* MetalRenderTargetCache::GetOrCreateEdramLoadLibrary(bool msaa) { - MTL::Library*& library = - msaa ? edram_load_library_msaa_ : edram_load_library_; - if (library) { - return library; - } - - static const char kEdramLoadShaderSource[] = R"METAL( -#include -using namespace metal; - -struct EdramLoadConstants { - uint base_tiles; - uint pitch_tiles; - uint format; - uint format_is_64bpp; - uint msaa_samples; - uint sample_id; - uint resolution_scale_x; - uint resolution_scale_y; -}; - -struct VSOut { - float4 position [[position]]; -}; - -vertex VSOut edram_load_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); - return out; -} - -constant uint kXenosMsaaSamples1X = 0u; -constant uint kXenosMsaaSamples2X = 1u; -constant uint kXenosMsaaSamples4X = 2u; -constant uint kEdramTileCount = 2048u; - -uint XeEdramOffsetInts(uint2 pixel_index, uint base_tiles, bool wrap, - uint pitch_tiles, uint msaa_samples, bool is_depth, - uint format_ints_log2, uint pixel_sample_index, - uint2 resolution_scale) { - uint msaa_samples_x_log2 = (msaa_samples >= kXenosMsaaSamples4X) ? 1u : 0u; - uint msaa_samples_y_log2 = (msaa_samples >= kXenosMsaaSamples2X) ? 1u : 0u; - uint2 rt_sample_index = - pixel_index << uint2(msaa_samples_x_log2, msaa_samples_y_log2); - rt_sample_index += - (uint2(pixel_sample_index) >> uint2(1u, 0u)) & 1u; - uint2 tile_size_at_32bpp = uint2(80u, 16u) * resolution_scale; - uint2 tile_size_samples = - tile_size_at_32bpp >> uint2(format_ints_log2, 0u); - uint2 tile_offset_xy = rt_sample_index / tile_size_samples; - base_tiles += tile_offset_xy.y * pitch_tiles + tile_offset_xy.x; - rt_sample_index -= tile_offset_xy * tile_size_samples; - if (is_depth) { - uint tile_width_half = tile_size_samples.x >> 1u; - rt_sample_index.x = - uint(int(rt_sample_index.x) + - ((rt_sample_index.x >= tile_width_half) - ? -int(tile_width_half) - : int(tile_width_half))); - } - uint address = - base_tiles * (tile_size_at_32bpp.x * tile_size_at_32bpp.y) + - ((rt_sample_index.y * tile_size_samples.x + rt_sample_index.x) << - format_ints_log2); - if (wrap) { - address %= tile_size_at_32bpp.x * tile_size_at_32bpp.y * kEdramTileCount; - } - return address; -} - -float XeFloat7e3To32(uint f10) { - f10 &= 0x3FFu; - if (f10 == 0u) { - return 0.0f; - } - uint mantissa = f10 & 0x7Fu; - uint exponent = f10 >> 7u; - if (exponent == 0u) { - uint mantissa_lzcnt = clz(mantissa) - 24u; - exponent = uint(int(1) - int(mantissa_lzcnt)); - mantissa = (mantissa << mantissa_lzcnt) & 0x7Fu; - } - uint f32 = ((exponent + 124u) << 23u) | (mantissa << 16u); - return as_type(f32); -} - -float4 XeUnpackR8G8B8A8UNorm(uint packed) { - float4 value = float4(packed & 0xFFu, (packed >> 8u) & 0xFFu, - (packed >> 16u) & 0xFFu, packed >> 24u); - return value * (1.0f / 255.0f); -} - -float4 XeUnpackR10G10B10A2UNorm(uint packed) { - float4 value = float4(packed & 0x3FFu, (packed >> 10u) & 0x3FFu, - (packed >> 20u) & 0x3FFu, (packed >> 30u) & 0x3u); - return value * float4(1.0f / 1023.0f, 1.0f / 1023.0f, 1.0f / 1023.0f, - 1.0f / 3.0f); -} - -float4 XeUnpackR10G10B10A2Float(uint packed) { - float r = XeFloat7e3To32(packed & 0x3FFu); - float g = XeFloat7e3To32((packed >> 10u) & 0x3FFu); - float b = XeFloat7e3To32((packed >> 20u) & 0x3FFu); - float a = float((packed >> 30u) & 0x3u) * (1.0f / 3.0f); - return float4(r, g, b, a); -} - -float2 XeUnpackR16G16Edram(uint packed) { - int r = int(packed << 16u) >> 16u; - int g = int(packed) >> 16u; - float2 value = float2(float(r), float(g)) * (32.0f / 32767.0f); - return max(value, float2(-1.0f)); -} - -float4 XeUnpackR16G16B16A16Edram(uint2 packed) { - int r = int(packed.x << 16u) >> 16u; - int g = int(packed.x) >> 16u; - int b = int(packed.y << 16u) >> 16u; - int a = int(packed.y) >> 16u; - float4 value = float4(float(r), float(g), float(b), float(a)) * - (32.0f / 32767.0f); - return max(value, float4(-1.0f)); -} - -float2 XeUnpackHalf2(uint packed) { - return float2(as_type(packed)); -} - -float4 XeUnpackColor32bpp(uint format, uint packed) { - switch (format) { - case 0u: // kXenosColorRenderTargetFormat_8_8_8_8 - case 1u: // kXenosColorRenderTargetFormat_8_8_8_8_GAMMA - return XeUnpackR8G8B8A8UNorm(packed); - case 2u: // kXenosColorRenderTargetFormat_2_10_10_10 - case 10u: // kXenosColorRenderTargetFormat_2_10_10_10_AS_10_10_10_10 - return XeUnpackR10G10B10A2UNorm(packed); - case 3u: // kXenosColorRenderTargetFormat_2_10_10_10_FLOAT - case 12u: // kXenosColorRenderTargetFormat_2_10_10_10_FLOAT_AS_16_16_16_16 - return XeUnpackR10G10B10A2Float(packed); - case 4u: { // kXenosColorRenderTargetFormat_16_16 - float2 rg = XeUnpackR16G16Edram(packed); - return float4(rg, 0.0f, 1.0f); - } - case 6u: { // kXenosColorRenderTargetFormat_16_16_FLOAT - float2 rg = XeUnpackHalf2(packed); - return float4(rg, 0.0f, 1.0f); - } - case 14u: // kXenosColorRenderTargetFormat_32_FLOAT - return float4(as_type(packed), 0.0f, 0.0f, 1.0f); - default: - return float4(0.0f); - } -} - -float4 XeUnpackColor64bpp(uint format, uint2 packed) { - switch (format) { - case 5u: // kXenosColorRenderTargetFormat_16_16_16_16 - return XeUnpackR16G16B16A16Edram(packed); - case 7u: { // kXenosColorRenderTargetFormat_16_16_16_16_FLOAT - float2 rg = XeUnpackHalf2(packed.x); - float2 ba = XeUnpackHalf2(packed.y); - return float4(rg, ba); - } - case 15u: // kXenosColorRenderTargetFormat_32_32_FLOAT - return float4(as_type(packed.x), as_type(packed.y), - 0.0f, 0.0f); - default: - return float4(0.0f); - } -} - -struct EdramLoadOut { - float4 color [[color(0)]]; -#if XE_EDRAM_LOAD_MSAA - uint sample_mask [[sample_mask]]; -#endif -}; - -fragment EdramLoadOut edram_load_ps( - VSOut in [[stage_in]], - constant EdramLoadConstants& constants [[buffer(0)]], - device const uint* edram [[buffer(1)]]) { - uint2 pixel = uint2(in.position.xy); - uint format_ints_log2 = constants.format_is_64bpp; - uint address = XeEdramOffsetInts( - pixel, constants.base_tiles, true, constants.pitch_tiles, - constants.msaa_samples, false, format_ints_log2, constants.sample_id, - uint2(constants.resolution_scale_x, constants.resolution_scale_y)); - float4 color; - if (constants.format_is_64bpp != 0u) { - uint2 packed = uint2(edram[address], edram[address + 1u]); - color = XeUnpackColor64bpp(constants.format, packed); - } else { - color = XeUnpackColor32bpp(constants.format, edram[address]); - } - EdramLoadOut out; - out.color = color; -#if XE_EDRAM_LOAD_MSAA - out.sample_mask = 1u << (constants.sample_id & 0x1Fu); -#endif - return out; -} -)METAL"; - - std::string source; - source.reserve(sizeof(kEdramLoadShaderSource) + 32); - source.append(msaa ? "#define XE_EDRAM_LOAD_MSAA 1\n" - : "#define XE_EDRAM_LOAD_MSAA 0\n"); - source.append(kEdramLoadShaderSource); - - NS::Error* error = nullptr; - auto source_str = NS::String::string(source.c_str(), NS::UTF8StringEncoding); - library = device_->newLibrary(source_str, nullptr, &error); - if (!library) { - XELOGE("Metal: failed to compile edram load shader: {}", - error && error->localizedDescription() - ? error->localizedDescription()->utf8String() - : "unknown error"); - } - return library; -} - -MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateEdramLoadPipeline( - MTL::PixelFormat dest_format, uint32_t sample_count) { - uint64_t key = uint64_t(dest_format) | (uint64_t(sample_count) << 32); - auto it = edram_load_pipelines_.find(key); - if (it != edram_load_pipelines_.end()) { - return it->second; - } - - bool msaa = sample_count > 1; - MTL::Library* lib = GetOrCreateEdramLoadLibrary(msaa); - if (!lib) { - return nullptr; - } - - NS::String* vs_name = - NS::String::string("edram_load_vs", NS::UTF8StringEncoding); - NS::String* ps_name = - NS::String::string("edram_load_ps", NS::UTF8StringEncoding); - MTL::Function* vs = lib->newFunction(vs_name); - MTL::Function* ps = lib->newFunction(ps_name); - if (!vs || !ps) { - if (vs) { - vs->release(); - } - if (ps) { - ps->release(); - } - XELOGE("Metal: edram load missing shader entrypoints"); - return nullptr; - } - - MTL::RenderPipelineDescriptor* desc = - MTL::RenderPipelineDescriptor::alloc()->init(); - desc->setVertexFunction(vs); - desc->setFragmentFunction(ps); - desc->colorAttachments()->object(0)->setPixelFormat(dest_format); - desc->setDepthAttachmentPixelFormat(MTL::PixelFormatInvalid); - desc->setSampleCount(sample_count); - - NS::Error* error = nullptr; - MTL::RenderPipelineState* pipeline = - device_->newRenderPipelineState(desc, &error); - desc->release(); - vs->release(); - ps->release(); - - if (!pipeline) { - XELOGE("Metal: failed to create edram load pipeline: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - return nullptr; - } - - edram_load_pipelines_.emplace(key, pipeline); - 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; - 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(); - bool fixed_rgba16_trunc = IsFixedRGBA16TruncatedToMinus1To1(); - - if (!trace_writer_) { - XELOGE("MetalRenderTargetCache::Resolve: trace_writer_ is null"); - return false; - } - - if (!draw_util::GetResolveInfo(regs, memory, *trace_writer_, - draw_resolution_scale_x(), - draw_resolution_scale_y(), fixed_rg16_trunc, - fixed_rgba16_trunc, resolve_info)) { - XELOGE("MetalRenderTargetCache::Resolve: GetResolveInfo failed"); - return false; - } - - // Nothing to do. - if (!resolve_info.coordinate_info.width_div_8 || !resolve_info.height_div_8) { - return true; - } - - bool is_depth = resolve_info.IsCopyingDepth(); - - if (!resolve_info.copy_dest_extent_length) { - return true; - } - - bool draw_resolution_scaled = IsDrawResolutionScaled(); - - MetalRenderTarget* src_rt = nullptr; - RenderTarget* const* accumulated_targets = - last_update_accumulated_render_targets(); - - if (is_depth) { - // For depth resolves, use the current depth render target as the source, - // matching D3D12/Vulkan behavior. - if (accumulated_targets && accumulated_targets[0]) { - src_rt = static_cast(accumulated_targets[0]); - } - } else { - // Color resolves select the source via copy_src_select. - uint32_t copy_src = resolve_info.rb_copy_control.copy_src_select; - if (copy_src < xenos::kMaxColorRenderTargets) { - if (accumulated_targets && accumulated_targets[1 + copy_src]) { - src_rt = - static_cast(accumulated_targets[1 + copy_src]); - } - } else { - } - } - - 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); - if (src_rt) { - const RenderTargetKey& src_key = src_rt->key(); - if (dump_pitch != src_key.GetPitchTiles()) { - XELOGW( - "MetalResolve: dump_pitch {} does not match src pitch_tiles {} " - "(rt_key=0x{:08X})", - dump_pitch, src_key.GetPitchTiles(), src_key.key); - } - } - // 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); - - // For now, only apply the 8888 restriction to color resolves; depth resolves - // may use different destination formats. - 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); - - // 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)); - } - } - - 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 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( - 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; - } - - shared_bytes = static_cast(dest_buffer->contents()); - } - if (draw_resolution_scaled) { - } - - MTL::CommandQueue* queue = command_processor_.GetMetalCommandQueue(); - - if (!queue) { - 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); - - // 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; - } - } - } - } - } - - XELOGE( - "MetalRenderTargetCache::Resolve: no valid GPU resolve shader / pipeline " - "for this configuration"); - return false; -} - -void 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) { - if (!render_targets || !render_target_transfers) { - return; - } - - bool resolve_clear_needed = - render_target_resolve_clear_values && resolve_clear_rectangle; - bool any_work = false; - bool host_depth_store_needed = false; - for (uint32_t i = 0; i < render_target_count; ++i) { - RenderTarget* dest_rt = render_targets[i]; - if (!dest_rt) { - continue; - } - if (resolve_clear_needed) { - any_work = true; - } - const std::vector& transfers = render_target_transfers[i]; - if (transfers.empty()) { - continue; - } - any_work = true; - if (!dest_rt->key().is_depth) { - continue; - } - for (const Transfer& transfer : transfers) { - if (transfer.host_depth_source == dest_rt) { - host_depth_store_needed = true; - break; - } - } - } - if (!any_work) { - return; - } - - MTL::CommandBuffer* cmd = command_buffer; - if (!cmd) { - cmd = command_processor_.EnsureCommandBuffer(); - } - if (!cmd) { - XELOGE( - "MetalRenderTargetCache::PerformTransfersAndResolveClears: no command " - "buffer"); - return; - } - - command_processor_.EndRenderEncoder(); - - uint32_t scale_x = draw_resolution_scale_x(); - uint32_t scale_y = draw_resolution_scale_y(); - uint32_t tile_width_samples = - xenos::kEdramTileWidthSamples * draw_resolution_scale_x(); - uint32_t tile_height_samples = - xenos::kEdramTileHeightSamples * draw_resolution_scale_y(); - uint32_t depth_round = (!::cvars::depth_float24_convert_in_pixel_shader && - ::cvars::depth_float24_round) - ? 1u - : 0u; - - // Host depth store pass (dest depth where host depth source == dest). - bool host_depth_store_dispatched = false; - if (host_depth_store_needed) { - for (uint32_t i = 0; i < render_target_count; ++i) { - RenderTarget* dest_rt = render_targets[i]; - if (!dest_rt) { - continue; - } - RenderTargetKey dest_key = dest_rt->key(); - if (!dest_key.is_depth) { - continue; - } - const std::vector& depth_transfers = render_target_transfers[i]; - for (const Transfer& transfer : depth_transfers) { - if (transfer.host_depth_source != dest_rt) { - continue; - } - auto* dest_metal_rt = static_cast(dest_rt); - MTL::Texture* depth_texture = dest_metal_rt->texture(); - if (!depth_texture || !edram_buffer_) { - continue; - } - size_t pipeline_index = size_t(dest_key.msaa_samples); - if (pipeline_index >= xe::countof(host_depth_store_pipelines_) || - !host_depth_store_pipelines_[pipeline_index]) { - XELOGE( - "MetalRenderTargetCache::PerformTransfersAndResolveClears: " - "missing host depth store pipeline for msaa={}", - 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) { - 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; - } - 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) { - 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); - 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( - MTL::Size::Make(group_count_x, group_count_y, 1), - MTL::Size::Make(8, 8, 1)); - host_depth_store_dispatched = true; - } - encoder->endEncoding(); - } - break; - } - } - - bool any_transfers_done = false; - - for (uint32_t i = 0; i < render_target_count; ++i) { - RenderTarget* dest_rt = render_targets[i]; - if (!dest_rt) { - continue; - } - - const std::vector& transfers = render_target_transfers[i]; - if (transfers.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; - } - RenderTargetKey dest_key = dest_metal_rt->key(); - bool dest_is_depth = dest_key.is_depth; - - bool dest_is_uint = false; - MTL::PixelFormat dest_pixel_format = - dest_is_depth ? GetDepthPixelFormat(dest_key.GetDepthFormat()) - : GetColorOwnershipTransferPixelFormat( - dest_key.GetColorFormat(), &dest_is_uint); - - MTL::Texture* dest_texture = dest_is_depth - ? dest_metal_rt->texture() - : dest_metal_rt->transfer_texture(); - if (!dest_texture) { - XELOGW( - "MetalRenderTargetCache::PerformTransfersAndResolveClears: " - "Destination RT {} has no texture", - i); - continue; - } - if (dest_is_depth) { - assert_true(dest_texture->pixelFormat() == dest_pixel_format, - "Transfer depth must use resource pixel format"); - } else { - assert_true(dest_texture->pixelFormat() == dest_pixel_format, - "Transfer color must use ownership pixel format"); - } - - uint32_t dest_sample_count = MsaaSamplesToCount(dest_key.msaa_samples); - bool transfer_use_sample_id_default = - dest_sample_count > 1 && ::cvars::metal_transfer_msaa_sample_id; - uint32_t dest_width = uint32_t(dest_texture->width()); - uint32_t dest_height = uint32_t(dest_texture->height()); - - auto get_scaled_rect = [&](const Transfer::Rectangle& rect, - uint32_t& scaled_x, uint32_t& scaled_y, - uint32_t& scaled_width, - uint32_t& scaled_height) -> bool { - uint32_t rect_x = rect.x_pixels * scale_x; - uint32_t rect_y = rect.y_pixels * scale_y; - uint32_t rect_width = rect.width_pixels * scale_x; - uint32_t rect_height = rect.height_pixels * scale_y; - if (rect_x >= dest_width || rect_y >= dest_height) { - return false; - } - rect_width = std::min(rect_width, dest_width - rect_x); - rect_height = std::min(rect_height, dest_height - rect_y); - if (!rect_width || !rect_height) { - return false; - } - scaled_x = rect_x; - scaled_y = rect_y; - scaled_width = rect_width; - scaled_height = rect_height; - return true; - }; - - auto set_rect_viewport = [&](MTL::RenderCommandEncoder* encoder, - const Transfer::Rectangle& rect) -> bool { - 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)) { - return false; - } - MTL::Viewport vp; - vp.originX = double(scaled_x); - vp.originY = double(scaled_y); - vp.width = double(scaled_width); - vp.height = double(scaled_height); - vp.znear = 0.0; - vp.zfar = 1.0; - encoder->setViewport(vp); - MTL::ScissorRect scissor; - scissor.x = scaled_x; - scissor.y = scaled_y; - scissor.width = scaled_width; - scissor.height = scaled_height; - encoder->setScissorRect(scissor); - 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; - }; - - auto allocate_instance_buffer = [&](size_t size, MTL::Buffer*& buffer, - size_t& offset) -> bool { - if (!device_) { - return false; - } - 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)); - size_t required = aligned_offset + size; - 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 (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; - 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_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; - } - - 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; - }; - - std::vector filtered_transfers; - bool used_blit = false; - MTL::BlitCommandEncoder* blit_encoder = nullptr; - auto ensure_blit_encoder = [&]() -> MTL::BlitCommandEncoder* { - if (!blit_encoder) { - blit_encoder = cmd->blitCommandEncoder(); - } - 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 { - auto* source_rt = static_cast(transfer.source); - if (!source_rt || transfer.host_depth_source) { - return false; - } - - RenderTargetKey source_key = source_rt->key(); - if (dest_is_depth != source_key.is_depth) { - return false; - } - if (source_key.resource_format != dest_key.resource_format || - source_key.msaa_samples != dest_key.msaa_samples || - source_key.pitch_tiles_at_32bpp != dest_key.pitch_tiles_at_32bpp) { - return false; - } - - bool base_tiles_match = source_key.base_tiles == dest_key.base_tiles; - if (dest_is_depth && !base_tiles_match) { - return false; - } - - MTL::Texture* source_texture = dest_is_depth - ? source_rt->texture() - : source_rt->transfer_texture(); - if (!source_texture) { - return false; - } - if (!dest_is_depth) { - MTL::PixelFormat expected_format = - GetColorOwnershipTransferPixelFormat(source_key.GetColorFormat(), - nullptr); - assert_true(source_texture->pixelFormat() == expected_format, - "Transfer source must use ownership pixel format"); - } - 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) { - return false; - } - - MTL::BlitCommandEncoder* blit = ensure_blit_encoder(); - if (!blit) { - 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); - } - } - } - } - - used_blit = true; - any_transfers_done = true; - return true; - }; - - for (const Transfer& transfer : transfers) { - if (!try_blit_transfer(transfer)) { - filtered_transfers.push_back(transfer); - } - } - } - - if (blit_encoder) { - blit_encoder->endEncoding(); - } - - const bool disable_transfer_shaders = false; - const std::vector& transfers_for_shaders = - used_blit ? filtered_transfers : transfers; - - MTL::RenderCommandEncoder* transfer_encoder = nullptr; - auto ensure_transfer_encoder = [&]() -> MTL::RenderCommandEncoder* { - if (transfer_encoder) { - return transfer_encoder; - } - MTL::RenderPassDescriptor* rp = - MTL::RenderPassDescriptor::renderPassDescriptor(); - if (dest_is_depth) { - auto* da = rp->depthAttachment(); - da->setTexture(dest_texture); - da->setLoadAction(MTL::LoadActionLoad); - da->setStoreAction(MTL::StoreActionStore); - if (dest_pixel_format == MTL::PixelFormatDepth32Float_Stencil8 || - dest_pixel_format == MTL::PixelFormatDepth24Unorm_Stencil8) { - auto* sa = rp->stencilAttachment(); - sa->setTexture(dest_texture); - sa->setLoadAction(MTL::LoadActionLoad); - sa->setStoreAction(MTL::StoreActionStore); - } - } else { - auto* ca = rp->colorAttachments()->object(0); - ca->setTexture(dest_texture); - ca->setLoadAction(MTL::LoadActionLoad); - ca->setStoreAction(MTL::StoreActionStore); - } - transfer_encoder = cmd->renderCommandEncoder(rp); - 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()) { - bool need_stencil_bit_draws = dest_is_depth; - bool stencil_clear_needed = need_stencil_bit_draws; - - transfer_invocations_.clear(); - transfer_invocations_.reserve(transfers_for_shaders.size() * - (need_stencil_bit_draws ? 2 : 1)); - - for (const Transfer& transfer : transfers_for_shaders) { - if (transfer.source) { - auto* source = static_cast(transfer.source); - source->SetTemporarySortIndex(UINT32_MAX); - } - if (transfer.host_depth_source) { - auto* host_depth = - static_cast(transfer.host_depth_source); - host_depth->SetTemporarySortIndex(UINT32_MAX); - } - } - - uint32_t rt_sort_index = 0; - auto ensure_sort_index = [&](MetalRenderTarget* rt) { - if (rt && rt->temporary_sort_index() == UINT32_MAX) { - rt->SetTemporarySortIndex(rt_sort_index++); - } - }; - - for (uint32_t pass = 0; pass <= uint32_t(need_stencil_bit_draws); - ++pass) { - for (const Transfer& transfer : transfers_for_shaders) { - if (!transfer.source) { - continue; - } - auto* source_rt = static_cast(transfer.source); - auto* host_depth_rt = - pass - ? nullptr - : static_cast(transfer.host_depth_source); - ensure_sort_index(source_rt); - 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; - } - } - - 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); - if (pass) { - transfer_invocations_.back().transfer.host_depth_source = nullptr; - } - } - } - - std::sort(transfer_invocations_.begin(), transfer_invocations_.end()); - - if (stencil_clear_needed) { - MTL::RenderPipelineState* clear_pipeline = - GetOrCreateTransferClearPipeline(dest_pixel_format, false, true, - dest_sample_count); - MTL::DepthStencilState* stencil_clear_state = - GetTransferStencilClearState(); - if (clear_pipeline && stencil_clear_state) { - MTL::RenderCommandEncoder* encoder = ensure_transfer_encoder(); - if (encoder) { - TransferClearDepthConstants constants = {}; - constants.depth = 0.0f; - encoder->setRenderPipelineState(clear_pipeline); - encoder->setDepthStencilState(stencil_clear_state); - encoder->setStencilReferenceValue(0); - 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])) { - continue; - } - encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, - NS::UInteger(0), NS::UInteger(3)); - } - } - } - } - } - MTL::RenderCommandEncoder* encoder = ensure_transfer_encoder(); - if (encoder) { - bool transfer_viewport_full_set = false; - auto set_full_transfer_viewport = [&]() { - if (transfer_viewport_full_set) { - return; - } - MTL::Viewport vp; - vp.originX = 0.0; - vp.originY = 0.0; - vp.width = double(dest_width); - vp.height = double(dest_height); - vp.znear = 0.0; - vp.zfar = 1.0; - encoder->setViewport(vp); - transfer_viewport_full_set = true; - }; - for (const auto& invocation : transfer_invocations_) { - 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_stencil = - !mode_info.source_is_color && - (mode_info.output == TransferOutput::kColor || - mode_info.output == TransferOutput::kStencilBit); - - auto* source_rt = static_cast(transfer.source); - if (!source_rt) { - continue; - } - - RenderTargetKey source_key = source_rt->key(); - bool source_is_uint = false; - MTL::PixelFormat source_transfer_format = MTL::PixelFormatInvalid; - if (mode_info.source_is_color) { - source_transfer_format = GetColorOwnershipTransferPixelFormat( - source_key.GetColorFormat(), &source_is_uint); - } - - if (is_stencil_bit) { - // Depth/stencil state set per-bit below. - } else if (dest_is_depth) { - encoder->setDepthStencilState(GetTransferDepthStencilState(true)); - } else { - MTL::DepthStencilState* no_depth_state = - GetTransferNoDepthStencilState(); - if (!no_depth_state) { - continue; - } - encoder->setDepthStencilState(no_depth_state); - } - - // Bind source textures. - if (mode_info.source_is_color) { - MTL::Texture* source_texture = source_rt->transfer_texture(); - if (!source_texture) { - continue; - } - assert_true(source_texture->pixelFormat() == source_transfer_format, - "Transfer source must use ownership pixel format"); - encoder->setFragmentTexture(source_texture, 0); - } else { - MTL::Texture* depth_texture = source_rt->texture(); - if (!depth_texture) { - continue; - } - encoder->setFragmentTexture(depth_texture, 0); - if (needs_source_stencil) { - MTL::Texture* stencil_texture = GetStencilTextureView(source_rt); - if (!stencil_texture) { - continue; - } - encoder->setFragmentTexture(stencil_texture, 1); - } - } - - // Bind host depth source if needed. - if (mode_info.uses_host_depth) { - if (shader_key.host_depth_source_is_copy) { - if (edram_buffer_) { - encoder->setFragmentBuffer(edram_buffer_, 0, 1); - } else { - MTL::Buffer* dummy = GetTransferDummyBuffer(); - if (dummy) { - encoder->setFragmentBuffer(dummy, 0, 1); - } - } - } else { - auto* host_depth_rt = - static_cast(transfer.host_depth_source); - MTL::Texture* host_depth_texture = - host_depth_rt ? host_depth_rt->texture() : nullptr; - if (!host_depth_texture) { - continue; - } - uint32_t host_depth_index = mode_info.source_is_color ? 1 : 2; - encoder->setFragmentTexture(host_depth_texture, host_depth_index); - } - } - - TransferShaderConstants constants = {}; - constants.address.dest_pitch = dest_key.GetPitchTiles(); - constants.address.source_pitch = source_key.GetPitchTiles(); - constants.address.source_to_dest = - int32_t(dest_key.base_tiles) - int32_t(source_key.base_tiles); - constants.host_depth_address = {}; - if (mode_info.uses_host_depth && - !shader_key.host_depth_source_is_copy) { - auto* host_depth_rt = - static_cast(transfer.host_depth_source); - if (host_depth_rt) { - RenderTargetKey host_depth_key = host_depth_rt->key(); - constants.host_depth_address.dest_pitch = - dest_key.GetPitchTiles(); - constants.host_depth_address.source_pitch = - host_depth_key.GetPitchTiles(); - constants.host_depth_address.source_to_dest = - int32_t(dest_key.base_tiles) - - int32_t(host_depth_key.base_tiles); - } - } - constants.source_format = source_key.resource_format; - constants.dest_format = dest_key.resource_format; - constants.source_is_depth = source_key.is_depth ? 1 : 0; - 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. - // 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.source_msaa_samples = - MsaaSamplesToCount(source_key.msaa_samples); - constants.dest_msaa_samples = - MsaaSamplesToCount(dest_key.msaa_samples); - constants.host_depth_source_msaa_samples = - MsaaSamplesToCount(shader_key.host_depth_source_msaa_samples); - constants.host_depth_source_is_copy = - shader_key.host_depth_source_is_copy ? 1 : 0; - constants.depth_round = depth_round; - constants.msaa_2x_supported = msaa_2x_supported_ ? 1 : 0; - constants.tile_width_samples = tile_width_samples; - constants.tile_height_samples = tile_height_samples; - uint32_t dest_tile_width_pixels = - tile_width_samples >> - ((constants.dest_is_64bpp != 0u) + - (constants.dest_msaa_samples >= 4u ? 1u : 0u)); - uint32_t dest_tile_height_pixels = - tile_height_samples >> - (constants.dest_msaa_samples >= 2u ? 1u : 0u); - constants.dest_tile_width_pixels = dest_tile_width_pixels; - constants.dest_tile_height_pixels = dest_tile_height_pixels; - constants.dest_tile_width_pixels_inv = - dest_tile_width_pixels ? (1.0f / float(dest_tile_width_pixels)) - : 0.0f; - constants.dest_tile_height_pixels_inv = - dest_tile_height_pixels ? (1.0f / float(dest_tile_height_pixels)) - : 0.0f; - uint32_t source_pitch_tiles = source_key.GetPitchTiles(); - constants.source_pitch_tiles_inv = - source_pitch_tiles ? (1.0f / float(source_pitch_tiles)) : 0.0f; - constants.host_depth_source_pitch_tiles_inv = 0.0f; - if (mode_info.uses_host_depth && - !shader_key.host_depth_source_is_copy) { - auto* host_depth_rt = - static_cast(transfer.host_depth_source); - if (host_depth_rt) { - uint32_t host_pitch_tiles = host_depth_rt->key().GetPitchTiles(); - constants.host_depth_source_pitch_tiles_inv = - host_pitch_tiles ? (1.0f / float(host_pitch_tiles)) : 0.0f; - } - } - constants.dest_pixel_to_ndc_x = - dest_width ? (2.0f / float(dest_width)) : 0.0f; - constants.dest_pixel_to_ndc_y = - dest_height ? (2.0f / float(dest_height)) : 0.0f; - constants.dest_sample_id = 0; - - 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) { - continue; - } - - std::vector tile_batches; - bool use_tile_instancing = false; - if (::cvars::metal_transfer_tile_instancing) { - use_tile_instancing = build_tile_batches( - rectangles, rectangle_count, constants, - mode_info.uses_host_depth, - shader_key.host_depth_source_is_copy != 0, tile_batches); - } - - MTL::RenderPipelineState* pipeline = GetOrCreateTransferPipelines( - shader_key, dest_pixel_format, dest_is_uint, use_tile_instancing); - if (!pipeline) { - continue; - } - - encoder->setRenderPipelineState(pipeline); - - bool use_sample_id_for_invocation = - shader_key.dest_sample_id_from_sample != 0; - auto draw_transfer_samples = [&](auto&& draw_fn) { - if (use_sample_id_for_invocation || dest_sample_count <= 1) { - draw_fn(0); - return; - } - for (uint32_t sample_id = 0; sample_id < dest_sample_count; - ++sample_id) { - draw_fn(sample_id); - } - }; - - auto draw_transfer = [&](uint32_t sample_id) { - constants.dest_sample_id = sample_id; - if (use_tile_instancing) { - set_full_transfer_viewport(); - encoder->setVertexBytes(&constants, sizeof(constants), 0); - encoder->setFragmentBytes(&constants, sizeof(constants), 0); - for (const auto& batch : tile_batches) { - encoder->setScissorRect(batch.scissor); - encoder->setVertexBuffer(batch.buffer, batch.buffer_offset, 1); - encoder->drawPrimitives(MTL::PrimitiveTypeTriangleStrip, - NS::UInteger(0), NS::UInteger(4), - NS::UInteger(batch.instance_count)); - } - } else { - encoder->setFragmentBytes(&constants, sizeof(constants), 0); - for (uint32_t rect_index = 0; rect_index < rectangle_count; - ++rect_index) { - if (!set_rect_viewport(encoder, rectangles[rect_index])) { - continue; - } - encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, - NS::UInteger(0), NS::UInteger(3)); - } - } - }; - - if (is_stencil_bit) { - 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; - encoder->setDepthStencilState(stencil_state); - encoder->setStencilReferenceValue(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; - } - } - } - - if (resolve_clear_needed) { - uint64_t clear_value = render_target_resolve_clear_values[i]; - if (dest_is_depth) { - uint32_t depth_guest_clear_value = - (uint32_t(clear_value) >> 8) & 0xFFFFFF; - float depth_host_clear_value = 0.0f; - switch (dest_key.GetDepthFormat()) { - case xenos::DepthRenderTargetFormat::kD24S8: - depth_host_clear_value = - xenos::UNorm24To32(depth_guest_clear_value); - break; - case xenos::DepthRenderTargetFormat::kD24FS8: - depth_host_clear_value = - xenos::Float20e4To32(depth_guest_clear_value) * 0.5f; - break; - } - MTL::RenderPipelineState* clear_pipeline = - GetOrCreateTransferClearPipeline(dest_pixel_format, false, true, - dest_sample_count); - MTL::DepthStencilState* clear_state = GetTransferDepthClearState(); - if (clear_pipeline && clear_state) { - MTL::RenderCommandEncoder* clear_encoder = ensure_transfer_encoder(); - if (clear_encoder) { - TransferClearDepthConstants constants = {}; - constants.depth = depth_host_clear_value; - clear_encoder->setRenderPipelineState(clear_pipeline); - clear_encoder->setDepthStencilState(clear_state); - clear_encoder->setStencilReferenceValue(uint32_t(clear_value) & - 0xFF); - clear_encoder->setFragmentBytes(&constants, sizeof(constants), 0); - Transfer::Rectangle clear_rect = *resolve_clear_rectangle; - if (set_rect_viewport(clear_encoder, clear_rect)) { - clear_encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, - NS::UInteger(0), NS::UInteger(3)); - } - } - } - } else { - TransferClearColorFloatConstants float_constants = {}; - TransferClearColorUintConstants uint_constants = {}; - bool clear_via_drawing = false; - switch (dest_key.GetColorFormat()) { - case xenos::ColorRenderTargetFormat::k_8_8_8_8: { - for (uint32_t j = 0; j < 4; ++j) { - float_constants.color[j] = - ((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF); - } - } break; - case xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA: { - for (uint32_t j = 0; j < 4; ++j) { - float_constants.color[j] = - ((clear_value >> (j * 8)) & 0xFF) * (1.0f / 0xFF); - } - if (gamma_render_target_as_unorm16_) { - for (uint32_t j = 0; j < 3; ++j) { - float_constants.color[j] = - xenos::PWLGammaToLinear(float_constants.color[j]); - } - } - } break; - case xenos::ColorRenderTargetFormat::k_2_10_10_10: - case xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10: { - for (uint32_t j = 0; j < 3; ++j) { - float_constants.color[j] = - ((clear_value >> (j * 10)) & 0x3FF) * (1.0f / 0x3FF); - } - float_constants.color[3] = - ((clear_value >> 30) & 0x3) * (1.0f / 0x3); - } break; - case xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT: - case xenos::ColorRenderTargetFormat:: - k_2_10_10_10_FLOAT_AS_16_16_16_16: { - for (uint32_t j = 0; j < 3; ++j) { - float_constants.color[j] = - xenos::Float7e3To32((clear_value >> (j * 10)) & 0x3FF); - } - float_constants.color[3] = - ((clear_value >> 30) & 0x3) * (1.0f / 0x3); - } break; - case xenos::ColorRenderTargetFormat::k_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: { - for (uint32_t j = 0; j < 2; ++j) { - float_constants.color[j] = - float((clear_value >> (j * 16)) & 0xFFFF); - } - } break; - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: { - for (uint32_t j = 0; j < 4; ++j) { - float_constants.color[j] = - float((clear_value >> (j * 16)) & 0xFFFF); - } - } break; - case xenos::ColorRenderTargetFormat::k_32_FLOAT: { - float_constants.color[0] = float(uint32_t(clear_value)); - if (uint64_t(float_constants.color[0]) != uint32_t(clear_value)) { - clear_via_drawing = true; - } - } break; - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: { - float_constants.color[0] = float(uint32_t(clear_value)); - float_constants.color[1] = float(uint32_t(clear_value >> 32)); - if (uint64_t(float_constants.color[0]) != uint32_t(clear_value) || - uint64_t(float_constants.color[1]) != - uint32_t(clear_value >> 32)) { - clear_via_drawing = true; - } - } break; - } - - bool clear_is_uint = false; - MTL::PixelFormat clear_format = GetColorOwnershipTransferPixelFormat( - dest_key.GetColorFormat(), &clear_is_uint); - MTL::Texture* clear_texture = dest_metal_rt->transfer_texture(); - bool clear_use_uint = clear_is_uint; - - if (clear_use_uint) { - switch (dest_key.GetColorFormat()) { - case xenos::ColorRenderTargetFormat::k_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_FLOAT: - uint_constants.color[0] = uint32_t(clear_value) & 0xFFFF; - uint_constants.color[1] = (uint32_t(clear_value) >> 16) & 0xFFFF; - uint_constants.color[2] = 0; - uint_constants.color[3] = 0; - break; - case xenos::ColorRenderTargetFormat::k_16_16_16_16: - case xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT: - uint_constants.color[0] = uint32_t(clear_value) & 0xFFFF; - uint_constants.color[1] = (uint32_t(clear_value) >> 16) & 0xFFFF; - uint_constants.color[2] = (uint32_t(clear_value >> 32)) & 0xFFFF; - uint_constants.color[3] = - (uint32_t(clear_value >> 32) >> 16) & 0xFFFF; - break; - case xenos::ColorRenderTargetFormat::k_32_FLOAT: - uint_constants.color[0] = uint32_t(clear_value); - uint_constants.color[1] = 0; - uint_constants.color[2] = 0; - uint_constants.color[3] = 0; - break; - case xenos::ColorRenderTargetFormat::k_32_32_FLOAT: - uint_constants.color[0] = uint32_t(clear_value); - uint_constants.color[1] = uint32_t(clear_value >> 32); - uint_constants.color[2] = 0; - uint_constants.color[3] = 0; - break; - default: - break; - } - } - - if (clear_via_drawing && clear_use_uint) { - uint_constants.color[0] = uint32_t(clear_value); - uint_constants.color[1] = uint32_t(clear_value >> 32); - uint_constants.color[2] = 0; - uint_constants.color[3] = 0; - } - - if (clear_texture) { - MTL::RenderPipelineState* clear_pipeline = - GetOrCreateTransferClearPipeline(clear_format, clear_use_uint, - false, dest_sample_count); - if (clear_pipeline) { - MTL::RenderCommandEncoder* clear_encoder = - ensure_transfer_encoder(); - if (clear_encoder) { - MTL::DepthStencilState* no_depth_state = - GetTransferNoDepthStencilState(); - if (!no_depth_state) { - continue; - } - clear_encoder->setRenderPipelineState(clear_pipeline); - clear_encoder->setDepthStencilState(no_depth_state); - if (clear_use_uint) { - clear_encoder->setFragmentBytes(&uint_constants, - sizeof(uint_constants), 0); - } else { - clear_encoder->setFragmentBytes(&float_constants, - sizeof(float_constants), 0); - } - Transfer::Rectangle clear_rect = *resolve_clear_rectangle; - if (set_rect_viewport(clear_encoder, clear_rect)) { - clear_encoder->drawPrimitives(MTL::PrimitiveTypeTriangle, - NS::UInteger(0), NS::UInteger(3)); - } - } - } - } - } - } - - if (transfer_encoder) { - transfer_encoder->endEncoding(); - } - } -} - -MTL::RenderPipelineState* MetalRenderTargetCache::GetOrCreateTransferPipelines( - const TransferShaderKey& key, MTL::PixelFormat dest_format, - bool dest_is_uint, bool tile_instanced) { - auto& pipeline_map = - tile_instanced ? transfer_tile_pipelines_ : transfer_pipelines_; - auto it = pipeline_map.find(key); - if (it != pipeline_map.end()) { - return it->second; - } - - 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; - - xenos::ColorRenderTargetFormat source_color_format = - xenos::ColorRenderTargetFormat(key.source_resource_format); - xenos::ColorRenderTargetFormat dest_color_format = - xenos::ColorRenderTargetFormat(key.dest_resource_format); - xenos::DepthRenderTargetFormat source_depth_format = - xenos::DepthRenderTargetFormat(key.source_resource_format); - xenos::DepthRenderTargetFormat dest_depth_format = - xenos::DepthRenderTargetFormat(key.dest_resource_format); - - bool source_is_uint = false; - if (source_is_color) { - GetColorOwnershipTransferPixelFormat(source_color_format, &source_is_uint); - } - bool source_is_64bpp = false; - if (source_is_color) { - source_is_64bpp = - 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_); - } - bool source_needs_stencil = - !source_is_color && (output == TransferOutput::kColor || - output == TransferOutput::kStencilBit); - - uint32_t dest_component_count = 1; - if (output == TransferOutput::kColor) { - dest_component_count = - xenos::GetColorRenderTargetFormatComponentCount(dest_color_format); - } - - bool source_is_multisample = - key.source_msaa_samples != xenos::MsaaSamples::k1X; - bool dest_is_multisample = key.dest_msaa_samples != xenos::MsaaSamples::k1X; - bool host_depth_is_copy = key.host_depth_source_is_copy != 0; - bool host_depth_is_multisample = - key.host_depth_source_msaa_samples != xenos::MsaaSamples::k1X; - uint32_t host_depth_texture_index = source_is_color ? 1 : 2; - - auto append_define = [](std::string& source, const char* name, - uint32_t value) { - source.append("#define "); - source.append(name); - source.push_back(' '); - source.append(std::to_string(value)); - source.push_back('\n'); - }; - - std::string source; - 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_STENCIL", - source_needs_stencil ? 1 : 0); - append_define(source, "XE_TRANSFER_SOURCE_IS_UINT", source_is_uint ? 1 : 0); - append_define(source, "XE_TRANSFER_SOURCE_IS_64BPP", source_is_64bpp ? 1 : 0); - append_define(source, "XE_TRANSFER_SOURCE_IS_MULTISAMPLE", - source_is_multisample ? 1 : 0); - 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_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_HAS_HOST_DEPTH", has_host_depth ? 1 : 0); - append_define(source, "XE_TRANSFER_HOST_DEPTH_IS_COPY", - host_depth_is_copy ? 1 : 0); - append_define(source, "XE_TRANSFER_HOST_DEPTH_IS_MULTISAMPLE", - host_depth_is_multisample ? 1 : 0); - append_define(source, "XE_TRANSFER_SOURCE_FORMAT", - key.source_resource_format); - append_define(source, "XE_TRANSFER_DEST_FORMAT", key.dest_resource_format); - append_define(source, "XE_TRANSFER_SOURCE_MSAA_SAMPLES", - MsaaSamplesToCount(key.source_msaa_samples)); - append_define(source, "XE_TRANSFER_DEST_MSAA_SAMPLES", - MsaaSamplesToCount(key.dest_msaa_samples)); - append_define(source, "XE_TRANSFER_HOST_DEPTH_MSAA_SAMPLES", - MsaaSamplesToCount(key.host_depth_source_msaa_samples)); - append_define(source, "XE_TRANSFER_SOURCE_TEXTURE_INDEX", 0); - append_define(source, "XE_TRANSFER_STENCIL_TEXTURE_INDEX", 1); - append_define(source, "XE_TRANSFER_HOST_DEPTH_TEXTURE_INDEX", - host_depth_texture_index); - append_define(source, "XE_TRANSFER_OUTPUT_COLOR", - output == TransferOutput::kColor ? 1 : 0); - append_define(source, "XE_TRANSFER_OUTPUT_DEPTH", - 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_FAST_DIVMOD", - ::cvars::metal_transfer_fast_divmod ? 1 : 0); - append_define(source, "XE_FMT_8_8_8_8", - uint32_t(xenos::ColorRenderTargetFormat::k_8_8_8_8)); - append_define(source, "XE_FMT_8_8_8_8_GAMMA", - uint32_t(xenos::ColorRenderTargetFormat::k_8_8_8_8_GAMMA)); - append_define(source, "XE_FMT_2_10_10_10", - uint32_t(xenos::ColorRenderTargetFormat::k_2_10_10_10)); - append_define( - source, "XE_FMT_2_10_10_10_AS_10_10_10_10", - uint32_t(xenos::ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10)); - append_define(source, "XE_FMT_2_10_10_10_FLOAT", - uint32_t(xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT)); - append_define( - source, "XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16", - uint32_t( - xenos::ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16)); - append_define(source, "XE_FMT_16_16", - uint32_t(xenos::ColorRenderTargetFormat::k_16_16)); - append_define(source, "XE_FMT_16_16_FLOAT", - uint32_t(xenos::ColorRenderTargetFormat::k_16_16_FLOAT)); - append_define(source, "XE_FMT_16_16_16_16", - uint32_t(xenos::ColorRenderTargetFormat::k_16_16_16_16)); - append_define(source, "XE_FMT_16_16_16_16_FLOAT", - uint32_t(xenos::ColorRenderTargetFormat::k_16_16_16_16_FLOAT)); - append_define(source, "XE_FMT_32_FLOAT", - uint32_t(xenos::ColorRenderTargetFormat::k_32_FLOAT)); - append_define(source, "XE_FMT_32_32_FLOAT", - uint32_t(xenos::ColorRenderTargetFormat::k_32_32_FLOAT)); - append_define(source, "XE_FMT_D24S8", - uint32_t(xenos::DepthRenderTargetFormat::kD24S8)); - append_define(source, "XE_FMT_D24FS8", - uint32_t(xenos::DepthRenderTargetFormat::kD24FS8)); - append_define(source, "XE_GAMMA_RT_AS_UNORM16", - gamma_render_target_as_unorm16_ ? 1 : 0); - - static const char kTransferShaderSource[] = R"METAL( -#include -using namespace metal; - -struct TransferAddressConstants { - uint dest_pitch; - uint source_pitch; - int source_to_dest; -}; - -struct TransferShaderConstants { - TransferAddressConstants address; - TransferAddressConstants host_depth_address; - uint source_format; - uint dest_format; - uint source_is_depth; - uint dest_is_depth; - uint source_is_uint; - uint dest_is_uint; - uint source_is_64bpp; - uint dest_is_64bpp; - uint source_msaa_samples; - uint dest_msaa_samples; - uint host_depth_source_msaa_samples; - uint host_depth_source_is_copy; - uint depth_round; - uint msaa_2x_supported; - uint tile_width_samples; - uint tile_height_samples; - uint dest_tile_width_pixels; - uint dest_tile_height_pixels; - float dest_tile_width_pixels_inv; - float dest_tile_height_pixels_inv; - float source_pitch_tiles_inv; - float host_depth_source_pitch_tiles_inv; - float dest_pixel_to_ndc_x; - float dest_pixel_to_ndc_y; - uint dest_sample_id; - uint stencil_mask; - uint stencil_clear; -}; - -struct TransferTileInstance { - float2 tile_origin; - uint tile_index; - uint padding; - uint2 source_base; - uint2 host_base; -}; - -constant uint kEdramTileCount = 2048u; - -inline uint XeBitFieldMask(uint count) { - if (count >= 32u) { - return 0xFFFFFFFFu; - } - return (1u << count) - 1u; -} - -inline uint XeBitFieldInsert(uint base, uint insert, uint offset, uint count) { - uint mask = XeBitFieldMask(count) << offset; - return (base & ~mask) | ((insert << offset) & mask); -} - -inline uint XeBitFieldExtract(uint value, uint offset, uint count) { - return (value >> offset) & XeBitFieldMask(count); -} - -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; -} - -inline uint XePackUnorm(float value, float scale) { - return uint(clamp(value, 0.0f, 1.0f) * scale + 0.5f); -} - -inline float XeSaturateNoNaN(float value) { - float clamped = clamp(value, 0.0f, 1.0f); - return (clamped == clamped) ? clamped : 0.0f; -} - -inline float XeTruncToFloat(float value) { - return trunc(value); -} - -inline float XePWLGammaToLinear(float value) { - float clamped = XeSaturateNoNaN(value); - float scale; - float offset; - if (clamped >= (96.0f / 255.0f)) { - if (clamped >= (192.0f / 255.0f)) { - scale = 8.0f / 1024.0f; - offset = -1024.0f; - } else { - scale = 4.0f / 1024.0f; - offset = -256.0f; - } - } else { - if (clamped >= (64.0f / 255.0f)) { - scale = 2.0f / 1024.0f; - offset = -64.0f; - } else { - scale = 1.0f / 1024.0f; - offset = 0.0f; - } - } - float linear = clamped * (255.0f * 1024.0f) * scale + offset; - linear += XeTruncToFloat(linear * scale); - return linear * (1.0f / 1023.0f); -} - -inline void XeFastDivMod(uint x, uint w, float inv_w, thread uint& q, - thread uint& r) { - 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; - } -} - -inline float XeLinearToPWLGamma(float value) { - float clamped = XeSaturateNoNaN(value); - float scale; - float 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 XeTruncToFloat(clamped * scale) * (1.0f / 255.0f) + offset; -} - -inline float3 XePWLGammaToLinear3(float3 v) { - return float3(XePWLGammaToLinear(v.r), XePWLGammaToLinear(v.g), - XePWLGammaToLinear(v.b)); -} - -inline float3 XeLinearToPWLGamma3(float3 v) { - return float3(XeLinearToPWLGamma(v.r), XeLinearToPWLGamma(v.g), - XeLinearToPWLGamma(v.b)); -} - -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); -} - -float XeFloat7e3To32(uint f10) { - f10 &= 0x3FFu; - if (f10 == 0u) { - return 0.0f; - } - uint mantissa = f10 & 0x7Fu; - uint exponent = f10 >> 7u; - if (exponent == 0u) { - uint mantissa_lzcnt = clz(mantissa) - 24u; - exponent = uint(int(1) - int(mantissa_lzcnt)); - mantissa = (mantissa << mantissa_lzcnt) & 0x7Fu; - } - uint f32 = ((exponent + 124u) << 23u) | (mantissa << 16u); - return as_type(f32); -} - -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; -} - -float XeFloat20e4To32(uint f24, bool remap_to_0_to_0_5) { - if (f24 == 0u) { - return 0.0f; - } - uint mantissa = f24 & 0xFFFFFu; - uint exponent = f24 >> 20u; - if (exponent == 0u) { - uint msb = 31u - clz(mantissa); - uint mantissa_lzcnt = 20u - msb; - exponent = 1u - mantissa_lzcnt; - mantissa = (mantissa << mantissa_lzcnt) & 0xFFFFFu; - } - uint bias = remap_to_0_to_0_5 ? 111u : 112u; - uint f32 = ((exponent + bias) << 23u) | (mantissa << 3u); - return as_type(f32); -} - -float XeUnorm24To32(uint n24) { - return float(n24 + (n24 >> 23u)) * (1.0f / 16777216.0f); -} - -uint XePackColorRGBA8(float4 color) { - 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 XePackColorRGB10A2(float4 color) { - 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); -} - -uint XePackColorRGB10A2Float(float4 color) { - 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); -} - -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; -} - -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; -} - -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d_ms xe_transfer_source \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #else - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d xe_transfer_source \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #endif - #else - #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d_ms xe_transfer_source \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #else - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d xe_transfer_source \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #endif - #endif -#else - #if XE_TRANSFER_SOURCE_NEEDS_STENCIL - #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d_ms xe_transfer_source_depth \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]], \ - texture2d_ms xe_transfer_source_stencil \ - [[texture(XE_TRANSFER_STENCIL_TEXTURE_INDEX)]] - #else - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d xe_transfer_source_depth \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]], \ - texture2d xe_transfer_source_stencil \ - [[texture(XE_TRANSFER_STENCIL_TEXTURE_INDEX)]] - #endif - #else - #if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d_ms xe_transfer_source_depth \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #else - #define XE_TRANSFER_SOURCE_PARAMS \ - , texture2d xe_transfer_source_depth \ - [[texture(XE_TRANSFER_SOURCE_TEXTURE_INDEX)]] - #endif - #endif -#endif - -#if XE_TRANSFER_HAS_HOST_DEPTH && XE_TRANSFER_HOST_DEPTH_IS_COPY - #define XE_TRANSFER_HOST_DEPTH_BUFFER_PARAM \ - , device const uint* xe_transfer_host_depth_buffer [[buffer(1)]] -#else - #define XE_TRANSFER_HOST_DEPTH_BUFFER_PARAM -#endif - -#if XE_TRANSFER_HAS_HOST_DEPTH && !XE_TRANSFER_HOST_DEPTH_IS_COPY - #if XE_TRANSFER_HOST_DEPTH_IS_MULTISAMPLE - #define XE_TRANSFER_HOST_DEPTH_TEXTURE_PARAM \ - , texture2d_ms xe_transfer_host_depth \ - [[texture(XE_TRANSFER_HOST_DEPTH_TEXTURE_INDEX)]] - #else - #define XE_TRANSFER_HOST_DEPTH_TEXTURE_PARAM \ - , texture2d xe_transfer_host_depth \ - [[texture(XE_TRANSFER_HOST_DEPTH_TEXTURE_INDEX)]] - #endif -#else - #define XE_TRANSFER_HOST_DEPTH_TEXTURE_PARAM -#endif - -#if XE_TRANSFER_DEST_IS_MULTISAMPLE && XE_TRANSFER_DEST_SAMPLE_ID_FROM_SAMPLE - #define XE_TRANSFER_SAMPLE_ID_PARAM , uint xe_sample_id [[sample_id]] -#else - #define XE_TRANSFER_SAMPLE_ID_PARAM -#endif - -#if XE_TRANSFER_OUTPUT_COLOR - #if XE_TRANSFER_DEST_IS_UINT - #if XE_TRANSFER_DEST_COMPONENTS == 1 - typedef uint XeTransferColorOutType; - #elif XE_TRANSFER_DEST_COMPONENTS == 2 - typedef uint2 XeTransferColorOutType; - #else - typedef uint4 XeTransferColorOutType; - #endif - #else - #if XE_TRANSFER_DEST_COMPONENTS == 1 - typedef float XeTransferColorOutType; - #elif XE_TRANSFER_DEST_COMPONENTS == 2 - typedef float2 XeTransferColorOutType; - #else - typedef float4 XeTransferColorOutType; - #endif - #endif - -struct TransferColorOut { - XeTransferColorOutType color [[color(0)]]; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - uint sample_mask [[sample_mask]]; -#endif -}; - -fragment TransferColorOut transfer_ps( - VSOut in [[stage_in]], - constant TransferShaderConstants& constants [[buffer(0)]] - XE_TRANSFER_HOST_DEPTH_BUFFER_PARAM - XE_TRANSFER_SOURCE_PARAMS - XE_TRANSFER_HOST_DEPTH_TEXTURE_PARAM - XE_TRANSFER_SAMPLE_ID_PARAM) { - uint2 dest_pixel = uint2(in.position.xy); - uint dest_sample_id = 0u; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - #if XE_TRANSFER_DEST_SAMPLE_ID_FROM_SAMPLE - dest_sample_id = xe_sample_id; - #else - dest_sample_id = constants.dest_sample_id; - #endif -#endif - - uint tile_width_samples = constants.tile_width_samples; - uint tile_height_samples = constants.tile_height_samples; - uint dest_tile_width_pixels = constants.dest_tile_width_pixels; - uint dest_tile_height_pixels = constants.dest_tile_height_pixels; - - 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 - XeFastDivMod(dest_pixel.x, dest_tile_width_pixels, - constants.dest_tile_width_pixels_inv, dest_tile_index_x, - dest_tile_pixel_x); - XeFastDivMod(dest_pixel.y, dest_tile_height_pixels, - constants.dest_tile_height_pixels_inv, dest_tile_index_y, - dest_tile_pixel_y); -#else - dest_tile_index_x = dest_pixel.x / dest_tile_width_pixels; - dest_tile_pixel_x = dest_pixel.x % dest_tile_width_pixels; - dest_tile_index_y = dest_pixel.y / dest_tile_height_pixels; - dest_tile_pixel_y = dest_pixel.y % dest_tile_height_pixels; -#endif - - 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; - uint source_tile_pixel_y = dest_tile_pixel_y; - uint source_color_half = 0u; - bool source_color_half_valid = false; - - bool source_is_64bpp = XE_TRANSFER_SOURCE_IS_64BPP != 0u; - bool dest_is_64bpp = XE_TRANSFER_DEST_IS_64BPP != 0u; - uint source_msaa = XE_TRANSFER_SOURCE_MSAA_SAMPLES; - uint dest_msaa = XE_TRANSFER_DEST_MSAA_SAMPLES; - bool msaa_2x_supported = constants.msaa_2x_supported != 0u; - - if (!source_is_64bpp && dest_is_64bpp) { - if (source_msaa >= 4u) { - if (dest_msaa >= 4u) { - source_sample_id = dest_sample_id & 2u; - source_tile_pixel_x = - XeBitFieldInsert(dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } else if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = (dest_sample_id ^ 1u) << 1u; - } else { - source_sample_id = dest_sample_id & 2u; - } - } else { - source_sample_id = - XeBitFieldInsert(0u, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - } else { - if (dest_msaa >= 4u) { - source_tile_pixel_x = XeBitFieldInsert( - dest_tile_pixel_x << 2u, dest_sample_id, 1u, 1u); - } else { - source_tile_pixel_x = dest_tile_pixel_x << 1u; - } - } - } else if (source_is_64bpp && !dest_is_64bpp) { - if (dest_msaa >= 4u) { - if (source_msaa >= 4u) { - source_sample_id = - XeBitFieldInsert(dest_sample_id, dest_tile_pixel_x, 0u, 1u); - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } - source_color_half = dest_sample_id & 1u; - source_color_half_valid = true; - } else { - if (source_msaa >= 4u) { - source_sample_id = XeBitFieldExtract(dest_tile_pixel_x, 1u, 1u); - if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = XeBitFieldInsert( - source_sample_id, dest_sample_id ^ 1u, 1u, 1u); - } else { - source_sample_id = XeBitFieldInsert( - dest_sample_id, source_sample_id, 0u, 1u); - } - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - source_tile_pixel_x = dest_tile_pixel_x >> 2u; - } else { - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } - source_color_half = dest_tile_pixel_x & 1u; - source_color_half_valid = true; - } - } else { - if (source_msaa != dest_msaa) { - if (source_msaa >= 4u) { - if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = XeBitFieldInsert( - dest_tile_pixel_x, dest_sample_id ^ 1u, 1u, 31u); - } else { - source_sample_id = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 0u, 1u); - } - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } else { - source_sample_id = XeBitFieldInsert( - dest_tile_pixel_x & 1u, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - } else if (dest_msaa >= 4u) { - source_tile_pixel_x = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } - } - } - - if (source_msaa < 4u && source_msaa != dest_msaa) { - if (dest_msaa >= 4u) { - if (source_msaa == 2u) { - source_sample_id = dest_sample_id >> 1u; - if (msaa_2x_supported) { - source_sample_id ^= 1u; - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, source_sample_id, 1u, 1u); - } - } else { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } else { - if (source_msaa == 2u) { - source_sample_id = dest_tile_pixel_y & 1u; - if (msaa_2x_supported) { - source_sample_id ^= 1u; - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, source_sample_id, 1u, 1u); - } - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } else { - if (msaa_2x_supported) { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id ^ 1u, dest_tile_pixel_y, 1u, 31u); - } else { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } - } - } - - uint source_pixel_width_dwords_log2 = - (source_msaa >= 4u ? 1u : 0u) + (source_is_64bpp ? 1u : 0u); - - if ((XE_TRANSFER_SOURCE_IS_DEPTH != 0u) != (XE_TRANSFER_DEST_IS_DEPTH != 0u)) { - uint source_32bpp_tile_half_pixels = - tile_width_samples >> (1u + source_pixel_width_dwords_log2); - if (source_tile_pixel_x < source_32bpp_tile_half_pixels) { - source_tile_pixel_x += source_32bpp_tile_half_pixels; - } else { - source_tile_pixel_x -= source_32bpp_tile_half_pixels; - } - } - - 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); - uint source_pitch_tiles = constants.address.source_pitch; - uint source_tile_index_y = 0u; - uint source_tile_index_x = 0u; - XeFastDivMod(source_tile_index, source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_index_y, - source_tile_index_x); - source_pixel_x = - source_tile_index_x * - (tile_width_samples >> source_pixel_width_dwords_log2) + - source_tile_pixel_x; - source_pixel_y = - 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; - uint source_sample_id1 = source_sample_id; - if (load_two) { - if (source_msaa >= 4u) { - source_sample_id1 = source_sample_id | 1u; - } else { - source_pixel_x1 = source_pixel_x | 1u; - } - } - -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - uint4 source_color0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id); -#else - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y)); -#endif - uint4 source_color1 = source_color0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1); -#else - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y)); -#endif - } - #else - float4 source_color0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id); -#else - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y)); -#endif - float4 source_color1 = source_color0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1); -#else - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y)); -#endif - } - #endif -#else - float source_depth0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id).r; -#else - xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y)).r; -#endif - float source_depth1 = source_depth0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_depth1 = xe_transfer_source_depth.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1).r; -#else - source_depth1 = xe_transfer_source_depth.read( - uint2(source_pixel_x1, source_pixel_y)).r; -#endif - } -#if XE_TRANSFER_SOURCE_NEEDS_STENCIL - uint source_stencil0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source_stencil.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id).r; -#else - xe_transfer_source_stencil.read(uint2(source_pixel_x, source_pixel_y)).r; -#endif - uint source_stencil1 = source_stencil0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_stencil1 = xe_transfer_source_stencil.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1).r; -#else - source_stencil1 = xe_transfer_source_stencil.read( - uint2(source_pixel_x1, source_pixel_y)).r; -#endif - } -#else - uint source_stencil0 = 0u; - uint source_stencil1 = 0u; -#endif -#endif - -#if XE_TRANSFER_SOURCE_IS_COLOR - if (source_is_64bpp && !dest_is_64bpp && source_color_half_valid) { - uint source_component_count = 0u; - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_32_FLOAT: - source_component_count = 1u; - break; - case XE_FMT_16_16: - case XE_FMT_16_16_FLOAT: - case XE_FMT_32_32_FLOAT: - source_component_count = 2u; - break; - default: - source_component_count = 4u; - break; - } - if (source_component_count == 2u) { - #if XE_TRANSFER_SOURCE_IS_UINT - source_color0[0] = source_color_half != 0u ? source_color0[1] - : source_color0[0]; - #else - source_color0[0] = source_color_half != 0u ? source_color0[1] - : source_color0[0]; - #endif - } else if (source_color_half != 0u) { - #if XE_TRANSFER_SOURCE_IS_UINT - source_color0[0] = source_color0[2]; - source_color0[1] = source_color0[3]; - #else - source_color0[0] = source_color0[2]; - source_color0[1] = source_color0[3]; - #endif - } - } -#endif - -#if XE_TRANSFER_DEST_IS_UINT - uint4 out_color = uint4(0u); -#else - float4 out_color = float4(0.0f); -#endif - - if (dest_is_64bpp) { - uint2 packed64 = uint2(0u); -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_16_16: - case XE_FMT_16_16_FLOAT: - packed64.x = source_color0[0] | (source_color0[1] << 16u); - packed64.y = source_color1[0] | (source_color1[1] << 16u); - break; - case XE_FMT_16_16_16_16: - case XE_FMT_16_16_16_16_FLOAT: - packed64.x = source_color0[0] | (source_color0[1] << 16u); - packed64.y = source_color0[2] | (source_color0[3] << 16u); - break; - case XE_FMT_32_FLOAT: - packed64.x = source_color0[0]; - packed64.y = source_color1[0]; - break; - case XE_FMT_32_32_FLOAT: - packed64.x = source_color0[0]; - packed64.y = source_color0[1]; - break; - default: - packed64.x = source_color0[0]; - packed64.y = source_color1[0]; - break; - } - #else - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_8_8_8_8_GAMMA: { -#if XE_GAMMA_RT_AS_UNORM16 - float4 gamma_color0 = source_color0; - float4 gamma_color1 = source_color1; - gamma_color0.rgb = XeLinearToPWLGamma3(gamma_color0.rgb); - gamma_color1.rgb = XeLinearToPWLGamma3(gamma_color1.rgb); - packed64.x = XePackColorRGBA8(gamma_color0); - packed64.y = XePackColorRGBA8(gamma_color1); -#else - packed64.x = XePackColorRGBA8(source_color0); - packed64.y = XePackColorRGBA8(source_color1); -#endif - } break; - case XE_FMT_8_8_8_8: - packed64.x = XePackColorRGBA8(source_color0); - packed64.y = XePackColorRGBA8(source_color1); - break; - case XE_FMT_2_10_10_10: - case XE_FMT_2_10_10_10_AS_10_10_10_10: - packed64.x = XePackColorRGB10A2(source_color0); - packed64.y = XePackColorRGB10A2(source_color1); - break; - case XE_FMT_2_10_10_10_FLOAT: - case XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16: - packed64.x = XePackColorRGB10A2Float(source_color0); - packed64.y = XePackColorRGB10A2Float(source_color1); - break; - case XE_FMT_32_FLOAT: - packed64.x = as_type(source_color0[0]); - packed64.y = as_type(source_color1[0]); - break; - case XE_FMT_32_32_FLOAT: - packed64.x = as_type(source_color0[0]); - packed64.y = as_type(source_color0[1]); - break; - default: - packed64.x = as_type(source_color0[0]); - packed64.y = as_type(source_color1[0]); - break; - } - #endif -#else - uint depth24_0 = 0u; - uint depth24_1 = 0u; - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_D24FS8) { - bool round_depth = constants.depth_round != 0u; - depth24_0 = XeFloat32To20e4(source_depth0 * 2.0f, round_depth); - depth24_1 = XeFloat32To20e4(source_depth1 * 2.0f, round_depth); - } else { - depth24_0 = XeRoundToNearestEven( - clamp(source_depth0, 0.0f, 1.0f) * 16777215.0f); - depth24_1 = XeRoundToNearestEven( - clamp(source_depth1, 0.0f, 1.0f) * 16777215.0f); - } - packed64.x = (depth24_0 << 8u) | (source_stencil0 & 0xFFu); - packed64.y = (depth24_1 << 8u) | (source_stencil1 & 0xFFu); -#endif - - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_32_32_FLOAT) { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed64.x, packed64.y, 0u, 0u); -#else - out_color = float4(as_type(packed64.x), - as_type(packed64.y), 0.0f, 0.0f); -#endif - } else { - uint4 components = uint4(packed64.x & 0xFFFFu, packed64.x >> 16u, - packed64.y & 0xFFFFu, packed64.y >> 16u); -#if XE_TRANSFER_DEST_IS_UINT - out_color = components; -#else - out_color = float4(components); -#endif - } - } else { - bool wrote_direct = false; - uint packed32 = 0u; -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_16_16: - case XE_FMT_16_16_FLOAT: - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_16_16 || - XE_TRANSFER_DEST_FORMAT == XE_FMT_16_16_FLOAT) { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(source_color0[0], source_color0[1], 0u, 0u); -#else - out_color = float4(float(source_color0[0]), - float(source_color0[1]), 0.0f, 0.0f); -#endif - wrote_direct = true; - } else { - packed32 = source_color0[0] | (source_color0[1] << 16u); - } - break; - case XE_FMT_16_16_16_16: - case XE_FMT_16_16_16_16_FLOAT: - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_16_16 || - XE_TRANSFER_DEST_FORMAT == XE_FMT_16_16_FLOAT) { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(source_color0[0], source_color0[1], 0u, 0u); -#else - out_color = float4(float(source_color0[0]), - float(source_color0[1]), 0.0f, 0.0f); -#endif - wrote_direct = true; - } else { - packed32 = source_color0[0] | (source_color0[1] << 16u); - } - break; - case XE_FMT_32_FLOAT: - case XE_FMT_32_32_FLOAT: - packed32 = source_color0[0]; - break; - default: - packed32 = source_color0[0]; - break; - } - #else - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_8_8_8_8: - 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 && - (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) { - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8) { - color.rgb = XeLinearToPWLGamma3(color.rgb); - } else { - color.rgb = XePWLGammaToLinear3(color.rgb); - } - } -#endif -#if !XE_TRANSFER_DEST_IS_UINT - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8 || - XE_TRANSFER_DEST_FORMAT == XE_FMT_8_8_8_8_GAMMA) { - out_color = color; - wrote_direct = true; - } else -#endif - { -#if XE_GAMMA_RT_AS_UNORM16 - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_8_8_8_8_GAMMA) { - color.rgb = XeLinearToPWLGamma3(color.rgb); - } -#endif - uint packed_component_offset = 0u; - if (XE_TRANSFER_DEST_IS_DEPTH != 0u) { - packed_component_offset = 1u; - } - packed32 = - XePackUnorm(color[packed_component_offset], 255.0f); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed32 |= XePackUnorm(color[packed_component_offset + 1], - 255.0f) << 8u; - packed32 |= XePackUnorm(color[packed_component_offset + 2], - 255.0f) << 16u; - packed32 |= XePackUnorm(color[packed_component_offset + 3], - 255.0f) << 24u; - } - } - } break; - case XE_FMT_2_10_10_10: - case XE_FMT_2_10_10_10_AS_10_10_10_10: -#if !XE_TRANSFER_DEST_IS_UINT - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_2_10_10_10 || - XE_TRANSFER_DEST_FORMAT == XE_FMT_2_10_10_10_AS_10_10_10_10) { - out_color = source_color0; - wrote_direct = true; - } else -#endif - { - packed32 = XePackUnorm(source_color0[0], 1023.0f); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed32 |= XePackUnorm(source_color0[1], 1023.0f) << 10u; - packed32 |= XePackUnorm(source_color0[2], 1023.0f) << 20u; - packed32 |= XePackUnorm(source_color0[3], 3.0f) << 30u; - } - } - break; - case XE_FMT_2_10_10_10_FLOAT: - case XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16: -#if !XE_TRANSFER_DEST_IS_UINT - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_2_10_10_10_FLOAT || - XE_TRANSFER_DEST_FORMAT == XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16) { - out_color = source_color0; - wrote_direct = true; - } else -#endif - { - packed32 = XeUnclampedFloat32To7e3(source_color0[0]); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed32 |= XeUnclampedFloat32To7e3(source_color0[1]) << 10u; - packed32 |= XeUnclampedFloat32To7e3(source_color0[2]) << 20u; - packed32 |= XePackUnorm(source_color0[3], 3.0f) << 30u; - } - } - break; - case XE_FMT_32_FLOAT: - case XE_FMT_32_32_FLOAT: - packed32 = as_type(source_color0[0]); - break; - default: - packed32 = as_type(source_color0[0]); - break; - } - #endif -#else - if (XE_TRANSFER_DEST_IS_DEPTH != 0u && - XE_TRANSFER_DEST_FORMAT == XE_TRANSFER_SOURCE_FORMAT) { - TransferColorOut out; - out.color = XeTransferColorOutType(source_depth0); -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - out.sample_mask = 1u << dest_sample_id; -#endif - return out; - } - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_D24FS8) { - bool round_depth = constants.depth_round != 0u; - packed32 = XeFloat32To20e4(source_depth0 * 2.0f, round_depth); - } else { - packed32 = XeRoundToNearestEven( - clamp(source_depth0, 0.0f, 1.0f) * 16777215.0f); - } - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed32 = (packed32 << 8u) | (source_stencil0 & 0xFFu); - } -#endif - - if (!wrote_direct) { - switch (XE_TRANSFER_DEST_FORMAT) { - case XE_FMT_8_8_8_8: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32, 0u, 0u, 0u); -#else - out_color = float4( - float((packed32 >> 0u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 8u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 16u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 24u) & 0xFFu) * (1.0f / 255.0f)); -#endif - } break; - case XE_FMT_8_8_8_8_GAMMA: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32, 0u, 0u, 0u); -#else - float4 color = float4( - float((packed32 >> 0u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 8u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 16u) & 0xFFu) * (1.0f / 255.0f), - float((packed32 >> 24u) & 0xFFu) * (1.0f / 255.0f)); -#if XE_GAMMA_RT_AS_UNORM16 - color.rgb = XePWLGammaToLinear3(color.rgb); -#endif - out_color = color; -#endif - } break; - case XE_FMT_2_10_10_10: - case XE_FMT_2_10_10_10_AS_10_10_10_10: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32, 0u, 0u, 0u); -#else - out_color = float4( - float((packed32 >> 0u) & 0x3FFu) * (1.0f / 1023.0f), - float((packed32 >> 10u) & 0x3FFu) * (1.0f / 1023.0f), - float((packed32 >> 20u) & 0x3FFu) * (1.0f / 1023.0f), - float((packed32 >> 30u) & 0x3u) * (1.0f / 3.0f)); -#endif - } break; - case XE_FMT_2_10_10_10_FLOAT: - case XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32, 0u, 0u, 0u); -#else - out_color = float4( - XeFloat7e3To32((packed32 >> 0u) & 0x3FFu), - XeFloat7e3To32((packed32 >> 10u) & 0x3FFu), - XeFloat7e3To32((packed32 >> 20u) & 0x3FFu), - float((packed32 >> 30u) & 0x3u) * (1.0f / 3.0f)); -#endif - } break; - case XE_FMT_16_16: - case XE_FMT_16_16_FLOAT: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32 & 0xFFFFu, packed32 >> 16u, 0u, 0u); -#else - out_color = float4(float(packed32 & 0xFFFFu), - float(packed32 >> 16u), 0.0f, 0.0f); -#endif - } break; - case XE_FMT_32_FLOAT: { -#if XE_TRANSFER_DEST_IS_UINT - out_color = uint4(packed32, 0u, 0u, 0u); -#else - out_color = float4(as_type(packed32), 0.0f, 0.0f, 0.0f); -#endif - } break; - default: - break; - } - } - } - - TransferColorOut out; -#if XE_TRANSFER_DEST_COMPONENTS == 1 - out.color = out_color.x; -#elif XE_TRANSFER_DEST_COMPONENTS == 2 - out.color = out_color.xy; -#else - out.color = out_color; -#endif -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - out.sample_mask = 1u << dest_sample_id; -#endif - return out; -} -#elif XE_TRANSFER_OUTPUT_DEPTH || XE_TRANSFER_OUTPUT_STENCIL_BIT -struct TransferDepthOut { - float depth [[depth(any)]]; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - uint sample_mask [[sample_mask]]; -#endif -}; - -fragment TransferDepthOut transfer_ps( - VSOut in [[stage_in]], - constant TransferShaderConstants& constants [[buffer(0)]] - XE_TRANSFER_HOST_DEPTH_BUFFER_PARAM - XE_TRANSFER_SOURCE_PARAMS - XE_TRANSFER_HOST_DEPTH_TEXTURE_PARAM - XE_TRANSFER_SAMPLE_ID_PARAM) { - uint2 dest_pixel = uint2(in.position.xy); - uint dest_sample_id = 0u; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - #if XE_TRANSFER_DEST_SAMPLE_ID_FROM_SAMPLE - dest_sample_id = xe_sample_id; - #else - dest_sample_id = constants.dest_sample_id; - #endif -#endif - - uint tile_width_samples = constants.tile_width_samples; - uint tile_height_samples = constants.tile_height_samples; - uint dest_tile_width_pixels = constants.dest_tile_width_pixels; - uint dest_tile_height_pixels = constants.dest_tile_height_pixels; - - 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 - XeFastDivMod(dest_pixel.x, dest_tile_width_pixels, - constants.dest_tile_width_pixels_inv, dest_tile_index_x, - dest_tile_pixel_x); - XeFastDivMod(dest_pixel.y, dest_tile_height_pixels, - constants.dest_tile_height_pixels_inv, dest_tile_index_y, - dest_tile_pixel_y); -#else - dest_tile_index_x = dest_pixel.x / dest_tile_width_pixels; - dest_tile_pixel_x = dest_pixel.x % dest_tile_width_pixels; - dest_tile_index_y = dest_pixel.y / dest_tile_height_pixels; - dest_tile_pixel_y = dest_pixel.y % dest_tile_height_pixels; -#endif - - 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; - uint source_tile_pixel_y = dest_tile_pixel_y; - - bool source_is_64bpp = XE_TRANSFER_SOURCE_IS_64BPP != 0u; - bool dest_is_64bpp = XE_TRANSFER_DEST_IS_64BPP != 0u; - uint source_msaa = XE_TRANSFER_SOURCE_MSAA_SAMPLES; - uint dest_msaa = XE_TRANSFER_DEST_MSAA_SAMPLES; - bool msaa_2x_supported = constants.msaa_2x_supported != 0u; - - if (!source_is_64bpp && dest_is_64bpp) { - if (source_msaa >= 4u) { - if (dest_msaa >= 4u) { - source_sample_id = dest_sample_id & 2u; - source_tile_pixel_x = - XeBitFieldInsert(dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } else if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = (dest_sample_id ^ 1u) << 1u; - } else { - source_sample_id = dest_sample_id & 2u; - } - } else { - source_sample_id = - XeBitFieldInsert(0u, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - } else { - if (dest_msaa >= 4u) { - source_tile_pixel_x = XeBitFieldInsert( - dest_tile_pixel_x << 2u, dest_sample_id, 1u, 1u); - } else { - source_tile_pixel_x = dest_tile_pixel_x << 1u; - } - } - } else if (source_is_64bpp && !dest_is_64bpp) { - if (dest_msaa >= 4u) { - if (source_msaa >= 4u) { - source_sample_id = - XeBitFieldInsert(dest_sample_id, dest_tile_pixel_x, 0u, 1u); - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } - } else { - if (source_msaa >= 4u) { - source_sample_id = XeBitFieldExtract(dest_tile_pixel_x, 1u, 1u); - if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = XeBitFieldInsert( - source_sample_id, dest_sample_id ^ 1u, 1u, 1u); - } else { - source_sample_id = XeBitFieldInsert( - dest_sample_id, source_sample_id, 0u, 1u); - } - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - source_tile_pixel_x = dest_tile_pixel_x >> 2u; - } else { - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } - } - } else { - if (source_msaa != dest_msaa) { - if (source_msaa >= 4u) { - if (dest_msaa == 2u) { - if (msaa_2x_supported) { - source_sample_id = XeBitFieldInsert( - dest_tile_pixel_x, dest_sample_id ^ 1u, 1u, 31u); - } else { - source_sample_id = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 0u, 1u); - } - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - } else { - source_sample_id = XeBitFieldInsert( - dest_tile_pixel_x & 1u, dest_tile_pixel_y, 1u, 1u); - source_tile_pixel_x = dest_tile_pixel_x >> 1u; - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - } else if (dest_msaa >= 4u) { - source_tile_pixel_x = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } - } - } - - if (source_msaa < 4u && source_msaa != dest_msaa) { - if (dest_msaa >= 4u) { - if (source_msaa == 2u) { - source_sample_id = dest_sample_id >> 1u; - if (msaa_2x_supported) { - source_sample_id ^= 1u; - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, source_sample_id, 1u, 1u); - } - } else { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } else { - if (source_msaa == 2u) { - source_sample_id = dest_tile_pixel_y & 1u; - if (msaa_2x_supported) { - source_sample_id ^= 1u; - } else { - source_sample_id = XeBitFieldInsert( - source_sample_id, source_sample_id, 1u, 1u); - } - source_tile_pixel_y = dest_tile_pixel_y >> 1u; - } else { - if (msaa_2x_supported) { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id ^ 1u, dest_tile_pixel_y, 1u, 31u); - } else { - source_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } - } - } - - uint source_pixel_width_dwords_log2 = - (source_msaa >= 4u ? 1u : 0u) + (source_is_64bpp ? 1u : 0u); - - if ((XE_TRANSFER_SOURCE_IS_DEPTH != 0u) != (XE_TRANSFER_DEST_IS_DEPTH != 0u)) { - uint source_32bpp_tile_half_pixels = - tile_width_samples >> (1u + source_pixel_width_dwords_log2); - if (source_tile_pixel_x < source_32bpp_tile_half_pixels) { - source_tile_pixel_x += source_32bpp_tile_half_pixels; - } else { - source_tile_pixel_x -= source_32bpp_tile_half_pixels; - } - } - - 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); - uint source_pitch_tiles = constants.address.source_pitch; - uint source_tile_index_y = 0u; - uint source_tile_index_x = 0u; - XeFastDivMod(source_tile_index, source_pitch_tiles, - constants.source_pitch_tiles_inv, source_tile_index_y, - source_tile_index_x); - source_pixel_x = - source_tile_index_x * - (tile_width_samples >> source_pixel_width_dwords_log2) + - source_tile_pixel_x; - source_pixel_y = - 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; - uint source_sample_id1 = source_sample_id; - if (load_two) { - if (source_msaa >= 4u) { - source_sample_id1 = source_sample_id | 1u; - } else { - source_pixel_x1 = source_pixel_x | 1u; - } - } - -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - uint4 source_color0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id); -#else - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y)); -#endif - uint4 source_color1 = source_color0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1); -#else - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y)); -#endif - } - #else - float4 source_color0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id); -#else - xe_transfer_source.read(uint2(source_pixel_x, source_pixel_y)); -#endif - float4 source_color1 = source_color0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1); -#else - source_color1 = xe_transfer_source.read( - uint2(source_pixel_x1, source_pixel_y)); -#endif - } - #endif -#else - float source_depth0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id).r; -#else - xe_transfer_source_depth.read(uint2(source_pixel_x, source_pixel_y)).r; -#endif - float source_depth1 = source_depth0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_depth1 = xe_transfer_source_depth.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1).r; -#else - source_depth1 = xe_transfer_source_depth.read( - uint2(source_pixel_x1, source_pixel_y)).r; -#endif - } -#if XE_TRANSFER_SOURCE_NEEDS_STENCIL - uint source_stencil0 = -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - xe_transfer_source_stencil.read(uint2(source_pixel_x, source_pixel_y), - source_sample_id).r; -#else - xe_transfer_source_stencil.read(uint2(source_pixel_x, source_pixel_y)).r; -#endif - uint source_stencil1 = source_stencil0; - if (load_two) { -#if XE_TRANSFER_SOURCE_IS_MULTISAMPLE - source_stencil1 = xe_transfer_source_stencil.read( - uint2(source_pixel_x1, source_pixel_y), source_sample_id1).r; -#else - source_stencil1 = xe_transfer_source_stencil.read( - uint2(source_pixel_x1, source_pixel_y)).r; -#endif - } -#else - uint source_stencil0 = 0u; - uint source_stencil1 = 0u; -#endif -#endif - - uint packed = 0u; -#if !XE_TRANSFER_OUTPUT_STENCIL_BIT - bool packed_only_depth = false; -#endif -#if XE_TRANSFER_SOURCE_IS_COLOR - #if XE_TRANSFER_SOURCE_IS_UINT - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_16_16: - case XE_FMT_16_16_FLOAT: - case XE_FMT_16_16_16_16: - case XE_FMT_16_16_16_16_FLOAT: - packed = source_color0[0] | (source_color0[1] << 16u); - break; - case XE_FMT_32_FLOAT: - case XE_FMT_32_32_FLOAT: - packed = source_color0[0]; - break; - default: - packed = source_color0[0]; - break; - } - #else - switch (XE_TRANSFER_SOURCE_FORMAT) { - case XE_FMT_8_8_8_8: - case XE_FMT_8_8_8_8_GAMMA: { - float4 color = source_color0; - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_8_8_8_8_GAMMA) { -#if XE_GAMMA_RT_AS_UNORM16 || XE_TRANSFER_OUTPUT_STENCIL_BIT - color.rgb = XeLinearToPWLGamma3(color.rgb); -#endif - } - uint packed_component_offset = 0u; - if (XE_TRANSFER_DEST_IS_DEPTH != 0u) { - packed_component_offset = 1u; -#if !XE_TRANSFER_OUTPUT_STENCIL_BIT - packed_only_depth = true; -#endif - } - packed = XePackUnorm(color[packed_component_offset], 255.0f); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed |= XePackUnorm(color[packed_component_offset + 1], - 255.0f) << 8u; - packed |= XePackUnorm(color[packed_component_offset + 2], - 255.0f) << 16u; - packed |= XePackUnorm(color[packed_component_offset + 3], - 255.0f) << 24u; - } - } break; - case XE_FMT_2_10_10_10: - case XE_FMT_2_10_10_10_AS_10_10_10_10: { - packed = XePackUnorm(source_color0[0], 1023.0f); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed |= XePackUnorm(source_color0[1], 1023.0f) << 10u; - packed |= XePackUnorm(source_color0[2], 1023.0f) << 20u; - packed |= XePackUnorm(source_color0[3], 3.0f) << 30u; - } - } break; - case XE_FMT_2_10_10_10_FLOAT: - case XE_FMT_2_10_10_10_FLOAT_AS_16_16_16_16: { - packed = XeUnclampedFloat32To7e3(source_color0[0]); - if (XE_TRANSFER_DEST_IS_DEPTH == 0u) { - packed |= XeUnclampedFloat32To7e3(source_color0[1]) << 10u; - packed |= XeUnclampedFloat32To7e3(source_color0[2]) << 20u; - packed |= XePackUnorm(source_color0[3], 3.0f) << 30u; - } - } break; - case XE_FMT_32_FLOAT: - case XE_FMT_32_32_FLOAT: - packed = as_type(source_color0[0]); - break; - default: - packed = as_type(source_color0[0]); - break; - } - #endif -#else - if (XE_TRANSFER_SOURCE_FORMAT == XE_FMT_D24FS8) { - bool round_depth = constants.depth_round != 0u; - packed = XeFloat32To20e4(source_depth0 * 2.0f, round_depth); - } else { - packed = XeRoundToNearestEven( - clamp(source_depth0, 0.0f, 1.0f) * 16777215.0f); - } - if (XE_TRANSFER_DEST_IS_DEPTH != 0u) { -#if !XE_TRANSFER_OUTPUT_STENCIL_BIT - packed_only_depth = true; -#endif - } else { - 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 (constants.stencil_clear == 0u) { - if ((packed & constants.stencil_mask) == 0u) { - discard_fragment(); - } - } - TransferDepthOut out; - out.depth = 0.0f; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - out.sample_mask = 1u << dest_sample_id; -#endif - return out; -#else - uint guest_depth24 = packed; - if (!packed_only_depth) { - guest_depth24 = packed >> 8u; - } - - float host_depth32 = 0.0f; - bool has_host_depth = false; - -#if XE_TRANSFER_HAS_HOST_DEPTH && !XE_TRANSFER_HOST_DEPTH_IS_COPY - uint host_tile_pixel_x = dest_tile_pixel_x; - uint host_tile_pixel_y = dest_tile_pixel_y; - uint host_sample_id = dest_sample_id; - uint host_msaa = XE_TRANSFER_HOST_DEPTH_MSAA_SAMPLES; - - if (host_msaa != dest_msaa) { - if (host_msaa >= 4u) { - if (dest_msaa == 2u) { - if (msaa_2x_supported) { - host_sample_id = XeBitFieldInsert( - dest_tile_pixel_x, dest_sample_id ^ 1u, 1u, 31u); - } else { - host_sample_id = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 0u, 1u); - } - host_tile_pixel_x = dest_tile_pixel_x >> 1u; - } else { - host_sample_id = XeBitFieldInsert( - dest_tile_pixel_x & 1u, dest_tile_pixel_y, 1u, 1u); - host_tile_pixel_x = dest_tile_pixel_x >> 1u; - host_tile_pixel_y = dest_tile_pixel_y >> 1u; - } - } else if (dest_msaa >= 4u) { - host_tile_pixel_x = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } - - if (host_msaa < 4u) { - if (dest_msaa >= 4u) { - if (host_msaa == 2u) { - host_sample_id = dest_sample_id >> 1u; - if (msaa_2x_supported) { - host_sample_id ^= 1u; - } else { - host_sample_id = XeBitFieldInsert( - host_sample_id, host_sample_id, 1u, 1u); - } - } else { - host_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } else { - if (host_msaa == 2u) { - host_sample_id = dest_tile_pixel_y & 1u; - if (msaa_2x_supported) { - host_sample_id ^= 1u; - } else { - host_sample_id = XeBitFieldInsert( - host_sample_id, host_sample_id, 1u, 1u); - } - host_tile_pixel_y = dest_tile_pixel_y >> 1u; - } else { - if (msaa_2x_supported) { - host_tile_pixel_y = XeBitFieldInsert( - dest_sample_id ^ 1u, dest_tile_pixel_y, 1u, 31u); - } else { - host_tile_pixel_y = XeBitFieldInsert( - dest_sample_id >> 1u, dest_tile_pixel_y, 1u, 31u); - } - } - } - } - } - - 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) & - (kEdramTileCount - 1u); - uint host_pitch_tiles = constants.host_depth_address.source_pitch; - uint host_tile_index_y = 0u; - uint host_tile_index_x = 0u; - XeFastDivMod(host_tile_index, host_pitch_tiles, - constants.host_depth_source_pitch_tiles_inv, host_tile_index_y, - host_tile_index_x); - host_pixel_x = - host_tile_index_x * - (tile_width_samples >> (host_msaa >= 4u ? 1u : 0u)) + - host_tile_pixel_x; - host_pixel_y = - 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( - uint2(host_pixel_x, host_pixel_y), host_sample_id).r; -#else - host_depth32 = - xe_transfer_host_depth.read(uint2(host_pixel_x, host_pixel_y)).r; -#endif - has_host_depth = true; -#endif - -#if XE_TRANSFER_HAS_HOST_DEPTH && XE_TRANSFER_HOST_DEPTH_IS_COPY - uint dest_tile_sample_x = dest_tile_pixel_x; - uint dest_tile_sample_y = dest_tile_pixel_y; - if (dest_msaa >= 2u) { - if (dest_msaa >= 4u) { - dest_tile_sample_x = XeBitFieldInsert( - dest_sample_id, dest_tile_pixel_x, 1u, 31u); - } - uint vert_sample = 0u; - if (dest_msaa == 2u && msaa_2x_supported) { - vert_sample = dest_sample_id ^ 1u; - } else { - vert_sample = dest_sample_id >> 1u; - } - dest_tile_sample_y = XeBitFieldInsert( - vert_sample, dest_tile_pixel_y, 1u, 31u); - } - uint host_depth_offset = - (tile_width_samples * tile_height_samples) * dest_tile_index + - tile_width_samples * dest_tile_sample_y + dest_tile_sample_x; - host_depth32 = as_type(xe_transfer_host_depth_buffer[host_depth_offset]); - has_host_depth = true; -#endif - - float fragment_depth = 0.0f; - if (XE_TRANSFER_DEST_FORMAT == XE_FMT_D24FS8) { - float guest_depth32 = XeFloat20e4To32(guest_depth24, true); - fragment_depth = guest_depth32; - } else { - fragment_depth = XeUnorm24To32(guest_depth24); - } - - if (has_host_depth) { - 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); - } else { - host_depth24 = XeRoundToNearestEven( - clamp(host_depth32, 0.0f, 1.0f) * 16777215.0f); - } - if (host_depth24 == guest_depth24) { - fragment_depth = host_depth32; - } - } - - TransferDepthOut out; - out.depth = fragment_depth; -#if XE_TRANSFER_DEST_IS_MULTISAMPLE - out.sample_mask = 1u << dest_sample_id; -#endif - return out; -#endif -} -#endif -)METAL"; - - source.append(kTransferShaderSource); - - NS::Error* error = nullptr; - auto src_str = NS::String::string(source.c_str(), NS::UTF8StringEncoding); - MTL::Library* lib = device_->newLibrary(src_str, nullptr, &error); - if (!lib) { - XELOGE( - "GetOrCreateTransferPipelines: failed to compile transfer MSL " - "(mode={}): " - "{}", - int(key.mode), - error && error->localizedDescription() - ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - auto vs_name = - NS::String::string(tile_instanced ? "transfer_tile_vs" : "transfer_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); - if (!vs || !ps) { - XELOGE("GetOrCreateTransferPipelines: failed to get transfer_vs/ps"); - if (vs) vs->release(); - if (ps) ps->release(); - lib->release(); - return nullptr; - } - - MTL::RenderPipelineDescriptor* desc = - MTL::RenderPipelineDescriptor::alloc()->init(); - desc->setVertexFunction(vs); - 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); - } - } - - uint32_t sample_count = 1; - if (key.dest_msaa_samples == xenos::MsaaSamples::k2X) { - sample_count = 2; - } else if (key.dest_msaa_samples == xenos::MsaaSamples::k4X) { - sample_count = 4; - } - desc->setSampleCount(sample_count); - - MTL::RenderPipelineState* pipeline = - device_->newRenderPipelineState(desc, &error); - - desc->release(); - vs->release(); - ps->release(); - lib->release(); - - if (!pipeline) { - XELOGE( - "GetOrCreateTransferPipelines: failed to create pipeline (mode={}): {}", - int(key.mode), - error && error->localizedDescription() - ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - pipeline_map.emplace(key, pipeline); - - return pipeline; -} - -MTL::Library* MetalRenderTargetCache::GetOrCreateTransferLibrary() { - if (transfer_library_) { - return transfer_library_; - } - static const char kTransferLibrarySource[] = R"METAL( -#include -using namespace metal; - -struct VSOut { - float4 position [[position]]; -}; - -struct TransferClearColorFloatConstants { - float4 color; -}; - -struct TransferClearColorUintConstants { - uint4 color; -}; - -struct TransferClearDepthConstants { - float4 depth; -}; - -vertex VSOut transfer_clear_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); - return out; -} - -fragment float4 transfer_clear_color_float_ps( - VSOut in [[stage_in]], - constant TransferClearColorFloatConstants& constants [[buffer(0)]]) { - return constants.color; -} - -fragment uint4 transfer_clear_color_uint_ps( - VSOut in [[stage_in]], - constant TransferClearColorUintConstants& constants [[buffer(0)]]) { - return constants.color; -} - -struct TransferDepthOut { - float depth [[depth(any)]]; -}; - -fragment TransferDepthOut transfer_clear_depth_ps( - VSOut in [[stage_in]], - constant TransferClearDepthConstants& constants [[buffer(0)]]) { - TransferDepthOut out; - out.depth = constants.depth.x; - return out; -} -)METAL"; - - NS::Error* error = nullptr; - auto source_str = - NS::String::string(kTransferLibrarySource, NS::UTF8StringEncoding); - transfer_library_ = device_->newLibrary(source_str, nullptr, &error); - if (!transfer_library_) { - XELOGE("GetOrCreateTransferLibrary: failed to compile transfer library: {}", - error && error->localizedDescription() - ? error->localizedDescription()->utf8String() - : "unknown error"); - } - return transfer_library_; -} - -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; - } - if (is_depth) { - key ^= 1u << 31; - } - auto it = transfer_clear_pipelines_.find(key); - if (it != transfer_clear_pipelines_.end()) { - return it->second; - } - - MTL::Library* lib = GetOrCreateTransferLibrary(); - if (!lib) { - return nullptr; - } - - auto vs_name = - NS::String::string("transfer_clear_vs", NS::UTF8StringEncoding); - const char* ps_name_cstr = nullptr; - if (is_depth) { - ps_name_cstr = "transfer_clear_depth_ps"; - } else { - ps_name_cstr = dest_is_uint ? "transfer_clear_color_uint_ps" - : "transfer_clear_color_float_ps"; - } - auto ps_name = NS::String::string(ps_name_cstr, NS::UTF8StringEncoding); - - MTL::Function* vs = lib->newFunction(vs_name); - MTL::Function* ps = lib->newFunction(ps_name); - if (!vs || !ps) { - XELOGE( - "GetOrCreateTransferClearPipeline: missing transfer clear functions"); - if (vs) { - vs->release(); - } - if (ps) { - ps->release(); - } - return nullptr; - } - - MTL::RenderPipelineDescriptor* desc = - MTL::RenderPipelineDescriptor::alloc()->init(); - desc->setVertexFunction(vs); - desc->setFragmentFunction(ps); - desc->setSampleCount(sample_count ? sample_count : 1); - - 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); - } - - NS::Error* error = nullptr; - MTL::RenderPipelineState* pipeline = - device_->newRenderPipelineState(desc, &error); - - desc->release(); - vs->release(); - ps->release(); - - if (!pipeline) { - XELOGE("GetOrCreateTransferClearPipeline: failed to create pipeline: {}", - error && error->localizedDescription() - ? error->localizedDescription()->utf8String() - : "unknown error"); - return nullptr; - } - - transfer_clear_pipelines_.emplace(key, pipeline); - return pipeline; -} - -MTL::Texture* MetalRenderTargetCache::GetTransferDummyTexture( - MTL::PixelFormat format, uint32_t sample_count) { - if (!device_) { - return nullptr; - } - MTL::TextureDescriptor* desc = MTL::TextureDescriptor::alloc()->init(); - desc->setWidth(1); - desc->setHeight(1); - desc->setPixelFormat(format); - desc->setTextureType(sample_count > 1 ? MTL::TextureType2DMultisample - : MTL::TextureType2D); - desc->setSampleCount(sample_count ? sample_count : 1); - MTL::TextureUsage usage = MTL::TextureUsageShaderRead; - if (format == MTL::PixelFormatDepth32Float_Stencil8 || - format == MTL::PixelFormatDepth24Unorm_Stencil8) { - usage |= MTL::TextureUsagePixelFormatView; - } - desc->setUsage(usage); - desc->setStorageMode(MTL::StorageModePrivate); - MTL::Texture* tex = nullptr; - if (render_target_heap_pool_) { - tex = render_target_heap_pool_->CreateTexture(desc); - } - if (!tex) { - tex = device_->newTexture(desc); - } - desc->release(); - return tex; -} - -MTL::Texture* MetalRenderTargetCache::GetTransferDummyColorFloatTexture( - uint32_t sample_count) { - size_t index = sample_count >= 4 ? 2 : (sample_count == 2 ? 1 : 0); - if (!transfer_dummy_color_float_[index]) { - transfer_dummy_color_float_[index] = - GetTransferDummyTexture(MTL::PixelFormatRGBA8Unorm, sample_count); - } - return transfer_dummy_color_float_[index]; -} - -MTL::Texture* MetalRenderTargetCache::GetTransferDummyColorUintTexture( - uint32_t sample_count) { - size_t index = sample_count >= 4 ? 2 : (sample_count == 2 ? 1 : 0); - if (!transfer_dummy_color_uint_[index]) { - transfer_dummy_color_uint_[index] = - GetTransferDummyTexture(MTL::PixelFormatRGBA8Uint, sample_count); - } - return transfer_dummy_color_uint_[index]; -} - -MTL::Texture* MetalRenderTargetCache::GetTransferDummyDepthTexture( - uint32_t sample_count) { - size_t index = sample_count >= 4 ? 2 : (sample_count == 2 ? 1 : 0); - if (!transfer_dummy_depth_[index]) { - transfer_dummy_depth_[index] = GetTransferDummyTexture( - MTL::PixelFormatDepth32Float_Stencil8, sample_count); - } - return transfer_dummy_depth_[index]; -} - -MTL::Texture* MetalRenderTargetCache::GetTransferDummyStencilTexture( - uint32_t sample_count) { - size_t index = sample_count >= 4 ? 2 : (sample_count == 2 ? 1 : 0); - if (!transfer_dummy_stencil_[index]) { - MTL::Texture* depth_tex = GetTransferDummyDepthTexture(sample_count); - if (!depth_tex) { - return nullptr; - } - transfer_dummy_stencil_[index] = - depth_tex->newTextureView(MTL::PixelFormatX32_Stencil8); - } - return transfer_dummy_stencil_[index]; -} - -MTL::Buffer* MetalRenderTargetCache::GetTransferDummyBuffer() { - if (!transfer_dummy_buffer_ && device_) { - transfer_dummy_buffer_ = - device_->newBuffer(sizeof(uint32_t), MTL::ResourceStorageModeShared); - if (transfer_dummy_buffer_) { - } - if (transfer_dummy_buffer_) { - std::memset(transfer_dummy_buffer_->contents(), 0, sizeof(uint32_t)); - } - } - return transfer_dummy_buffer_; -} - -MTL::DepthStencilState* MetalRenderTargetCache::GetTransferDepthStencilState( - bool depth_write) { - if (transfer_depth_state_) { - return transfer_depth_state_; - } - MTL::DepthStencilDescriptor* desc = - MTL::DepthStencilDescriptor::alloc()->init(); - desc->setDepthCompareFunction(::cvars::depth_transfer_not_equal_test - ? MTL::CompareFunctionNotEqual - : MTL::CompareFunctionAlways); - desc->setDepthWriteEnabled(depth_write); - transfer_depth_state_ = device_->newDepthStencilState(desc); - desc->release(); - return transfer_depth_state_; -} - -MTL::DepthStencilState* -MetalRenderTargetCache::GetTransferNoDepthStencilState() { - if (transfer_depth_state_none_) { - return transfer_depth_state_none_; - } - 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_; - } - 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_; - } - 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::GetTransferStencilBitState( - uint32_t bit) { - if (bit >= 8) { - return nullptr; - } - if (transfer_stencil_bit_states_[bit]) { - return transfer_stencil_bit_states_[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]; -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_render_target_cache.h b/src/xenia/gpu/metal/metal_render_target_cache.h deleted file mode 100644 index cfba15027..000000000 --- a/src/xenia/gpu/metal/metal_render_target_cache.h +++ /dev/null @@ -1,522 +0,0 @@ -/** - ****************************************************************************** - * 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_RENDER_TARGET_CACHE_H_ -#define XENIA_GPU_METAL_METAL_RENDER_TARGET_CACHE_H_ - -#include -#include -#include -#include -#include -#include - -#include "xenia/gpu/register_file.h" -#include "xenia/gpu/render_target_cache.h" -#include "xenia/gpu/trace_writer.h" -#include "xenia/gpu/xenos.h" -#include "xenia/memory.h" - -#include "third_party/metal-cpp/Metal/Metal.hpp" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalCommandProcessor; -class MetalHeapPool; - -class MetalRenderTargetCache final : public gpu::RenderTargetCache { - public: - // Metal-specific render target - defined inside cache class to access - // protected RenderTarget - class MetalRenderTarget final : public RenderTarget { - public: - ~MetalRenderTarget() override; - - MTL::Texture* texture() const { return texture_; } - MTL::Texture* msaa_texture() const { return msaa_texture_; } - MTL::Texture* draw_texture() const { - return draw_texture_ ? draw_texture_ : texture_; - } - MTL::Texture* transfer_texture() const { - return transfer_texture_ ? transfer_texture_ : texture_; - } - MTL::Texture* msaa_draw_texture() const { - return msaa_draw_texture_ ? msaa_draw_texture_ : msaa_texture_; - } - MTL::Texture* msaa_transfer_texture() const { - return msaa_transfer_texture_ ? msaa_transfer_texture_ : msaa_texture_; - } - MTL::Texture* stencil_view() const { return stencil_view_; } - void SetStencilView(MTL::Texture* view) { stencil_view_ = view; } - - void SetTemporarySortIndex(uint32_t index) { - temporary_sort_index_ = index; - } - uint32_t temporary_sort_index() const { return temporary_sort_index_; } - - void SetTexture(MTL::Texture* texture) { - if (texture_ != texture) { - if (stencil_view_) { - stencil_view_->release(); - stencil_view_ = nullptr; - } - texture_ = texture; - } - } - void SetMsaaTexture(MTL::Texture* texture) { msaa_texture_ = texture; } - void SetDrawTexture(MTL::Texture* texture) { draw_texture_ = texture; } - void SetTransferTexture(MTL::Texture* texture) { - transfer_texture_ = texture; - } - void SetMsaaDrawTexture(MTL::Texture* texture) { - msaa_draw_texture_ = texture; - } - void SetMsaaTransferTexture(MTL::Texture* texture) { - msaa_transfer_texture_ = texture; - } - bool needs_initial_clear() const { return needs_initial_clear_; } - void SetNeedsInitialClear(bool needs_initial_clear) { - needs_initial_clear_ = needs_initial_clear; - } - - // Public constructor for creating render targets - MetalRenderTarget(RenderTargetKey key) : RenderTarget(key) {} - - private: - MTL::Texture* texture_ = nullptr; - MTL::Texture* msaa_texture_ = nullptr; // If MSAA is enabled - MTL::Texture* draw_texture_ = nullptr; - MTL::Texture* transfer_texture_ = nullptr; - MTL::Texture* msaa_draw_texture_ = nullptr; - MTL::Texture* msaa_transfer_texture_ = nullptr; - MTL::Texture* stencil_view_ = nullptr; - uint32_t temporary_sort_index_ = UINT32_MAX; - bool needs_initial_clear_ = true; - }; - - public: - MetalRenderTargetCache(const RegisterFile& register_file, - const Memory& memory, TraceWriter* trace_writer, - uint32_t draw_resolution_scale_x, - uint32_t draw_resolution_scale_y, - MetalCommandProcessor& command_processor); - ~MetalRenderTargetCache() override; - - bool Initialize(); - void Shutdown(bool from_destructor = false); - - // RenderTargetCache implementation - Path GetPath() const override; - - // Fixed-point render targets (k_16_16 / k_16_16_16_16) are backed by *_SNORM - // formats in the host render targets path, which are -1...1 rather than the - // Xbox 360's -32...32 range. When this is true, resolve/copy must compensate - // to match the guest packing expectations. - bool IsFixedRG16TruncatedToMinus1To1() const { - return !cvars::snorm16_render_target_full_range; - } - bool IsFixedRGBA16TruncatedToMinus1To1() const { - return !cvars::snorm16_render_target_full_range; - } - - // Whether 2x MSAA is supported on this device. - bool msaa_2x_supported() const { return msaa_2x_supported_; } - - // Whether gamma render targets use UNORM16 storage (separate from sRGB). - // When true, gamma correction is done in shaders rather than via sRGB format. - bool gamma_render_target_as_unorm16() const { - return gamma_render_target_as_unorm16_; - } - - bool IsGammaFormatHostStorageSeparate() const override; - - // Check if the render target key uses a 64bpp format. - bool IsKey64bpp(RenderTargetKey key) const; - - void ClearCache() override; - void BeginFrame() override; - - bool Update(bool is_rasterization_done, - reg::RB_DEPTHCONTROL normalized_depth_control, - uint32_t normalized_color_mask, - const Shader& vertex_shader) override; - - // Metal-specific methods - MTL::RenderPassDescriptor* GetRenderPassDescriptor( - uint32_t expected_sample_count = 1); - - bool IsRenderPassDescriptorDirty() const { - return render_pass_descriptor_dirty_; - } - - // Get current render targets for capture - 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; - - // Restore EDRAM contents from snapshot (for trace playback), matching - // D3D12RenderTargetCache::RestoreEdramSnapshot. - void RestoreEdramSnapshot(const void* snapshot); - - MTL::Buffer* GetEdramBuffer() const { return edram_buffer_; } - - // Resolve (copy) render targets to shared memory - bool Resolve(Memory& memory, uint32_t& written_address, - uint32_t& written_length, - MTL::CommandBuffer* command_buffer = nullptr); - - protected: - // Virtual methods from RenderTargetCache - uint32_t GetMaxRenderTargetWidth() const override; - uint32_t GetMaxRenderTargetHeight() const override; - - RenderTarget* CreateRenderTarget(RenderTargetKey key) override; - - bool IsHostDepthEncodingDifferent( - xenos::DepthRenderTargetFormat format) const 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); - - 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; - - // EDRAM compute shaders for tile operations - MTL::ComputePipelineState* edram_load_pipeline_ = nullptr; // Tiled → Linear - MTL::ComputePipelineState* edram_store_pipeline_ = nullptr; // Linear → Tiled - 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; - - // 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; - - // 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 { - kColorToColor, - kColorToDepth, - kDepthToColor, - kDepthToDepth, - kColorToStencilBit, - kDepthToStencilBit, - kColorAndHostDepthToDepth, - kDepthAndHostDepthToDepth, - }; - - struct TransferShaderKey { - TransferMode mode; - xenos::MsaaSamples source_msaa_samples; - xenos::MsaaSamples dest_msaa_samples; - xenos::MsaaSamples host_depth_source_msaa_samples; - uint32_t source_resource_format; - uint32_t dest_resource_format; - uint32_t dest_sample_id_from_sample; - uint32_t host_depth_source_is_copy; - - bool operator==(const TransferShaderKey& other) const { - 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; - } - - struct Hasher { - size_t operator()(const TransferShaderKey& key) const { - size_t h = size_t(key.mode); - h ^= (size_t(key.source_msaa_samples) << 4); - h ^= (size_t(key.dest_msaa_samples) << 8); - h ^= (size_t(key.host_depth_source_msaa_samples) << 12); - h ^= (size_t(key.source_resource_format) << 16); - h ^= (size_t(key.dest_resource_format) << 24); - h ^= (size_t(key.dest_sample_id_from_sample) << 28); - h ^= (size_t(key.host_depth_source_is_copy) << 29); - return h ^ (h >> 16); - } - }; - }; - - struct TransferInvocation { - Transfer transfer; - TransferShaderKey shader_key; - TransferInvocation(const Transfer& transfer, - const TransferShaderKey& shader_key) - : transfer(transfer), shader_key(shader_key) {} - bool operator<(const TransferInvocation& other) const { - if (shader_key != other.shader_key) { - return shader_key < other.shader_key; - } - assert_not_null(transfer.source); - assert_not_null(other.transfer.source); - uint32_t source_index = - static_cast(transfer.source) - ->temporary_sort_index(); - uint32_t other_source_index = - static_cast(other.transfer.source) - ->temporary_sort_index(); - if (source_index != other_source_index) { - return source_index < other_source_index; - } - return transfer.start_tiles < other.transfer.start_tiles; - } - bool CanBeMergedIntoOneDraw(const TransferInvocation& other) const { - return shader_key == other.shader_key && - transfer.AreSourcesSame(other.transfer); - } - }; - - std::unordered_map - transfer_pipelines_; - std::unordered_map - transfer_tile_pipelines_; - std::vector transfer_invocations_; - MTL::Library* transfer_library_ = nullptr; - 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; - 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_bit_states_[8] = {}; - MTL::Buffer* transfer_dummy_buffer_ = nullptr; - MTL::Texture* transfer_dummy_color_float_[3] = {}; - MTL::Texture* transfer_dummy_color_uint_[3] = {}; - MTL::Texture* transfer_dummy_depth_[3] = {}; - MTL::Texture* transfer_dummy_stencil_[3] = {}; - bool msaa_2x_supported_ = true; - - // Current render targets - updated by base class Update() call - - MetalRenderTarget* current_color_targets_[4] = {}; - MetalRenderTarget* current_depth_target_ = nullptr; - - // Track the last REAL (non-dummy) render targets for capture - MetalRenderTarget* last_real_color_targets_[4] = {}; - MetalRenderTarget* last_real_depth_target_ = nullptr; - - // Track all created render targets so we can find them - std::unordered_map render_target_map_; - - // Render pass descriptor cache - MTL::RenderPassDescriptor* cached_render_pass_descriptor_ = nullptr; - bool render_pass_descriptor_dirty_ = true; - uint32_t cached_render_pass_descriptor_sample_count_ = 0; - - // Dummy render target for when no render targets are bound - struct DummyColorTargetEntry { - std::unique_ptr target; - uint64_t last_used_frame = 0; - uint64_t last_cleared_frame = 0; - }; - mutable std::unordered_map - dummy_color_targets_; - 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 - MTL::Texture* CreateColorTexture(uint32_t width, uint32_t height, - xenos::ColorRenderTargetFormat format, - uint32_t samples); - MTL::Texture* CreateDepthTexture(uint32_t width, uint32_t height, - xenos::DepthRenderTargetFormat format, - uint32_t samples); - MTL::Texture* GetStencilTextureView(MetalRenderTarget* render_target); - - MTL::PixelFormat GetColorResourcePixelFormat( - xenos::ColorRenderTargetFormat format) const; - MTL::PixelFormat GetColorDrawPixelFormat( - xenos::ColorRenderTargetFormat format) const; - MTL::PixelFormat GetColorOwnershipTransferPixelFormat( - xenos::ColorRenderTargetFormat format, bool* is_integer_out) const; - MTL::PixelFormat GetDepthPixelFormat( - xenos::DepthRenderTargetFormat format) const; - - // EDRAM compute shader setup - bool InitializeEdramComputeShaders(); - void ShutdownEdramComputeShaders(); - - // Transfer pipeline setup (host RT ownership transfers) - Metal analogue of - // D3D12RenderTargetCache::GetOrCreateTransferPipelines. - MTL::RenderPipelineState* GetOrCreateTransferPipelines( - const TransferShaderKey& key, MTL::PixelFormat dest_format, - bool dest_is_uint, bool tile_instanced); - MTL::RenderPipelineState* GetOrCreateTransferClearPipeline( - MTL::PixelFormat dest_format, bool dest_is_uint, bool is_depth, - uint32_t sample_count); - MTL::Library* GetOrCreateTransferLibrary(); - MTL::Texture* GetTransferDummyTexture(MTL::PixelFormat format, - uint32_t sample_count); - MTL::Texture* GetTransferDummyColorFloatTexture(uint32_t sample_count); - MTL::Texture* GetTransferDummyColorUintTexture(uint32_t sample_count); - MTL::Texture* GetTransferDummyDepthTexture(uint32_t sample_count); - MTL::Texture* GetTransferDummyStencilTexture(uint32_t sample_count); - MTL::Buffer* GetTransferDummyBuffer(); - MTL::DepthStencilState* GetTransferDepthStencilState(bool depth_write); - MTL::DepthStencilState* GetTransferNoDepthStencilState(); - MTL::DepthStencilState* GetTransferDepthClearState(); - MTL::DepthStencilState* GetTransferStencilClearState(); - MTL::DepthStencilState* GetTransferStencilBitState(uint32_t bit); - - // 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( - 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); - - // 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); -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_RENDER_TARGET_CACHE_H_ diff --git a/src/xenia/gpu/metal/metal_shader.cc b/src/xenia/gpu/metal/metal_shader.cc deleted file mode 100644 index 1a28317ef..000000000 --- a/src/xenia/gpu/metal/metal_shader.cc +++ /dev/null @@ -1,292 +0,0 @@ -/** - ****************************************************************************** - * 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 - -#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/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_cache.h" -#include "xenia/gpu/metal/metal_shader_converter.h" -#include "xenia/ui/metal/metal_api.h" - -namespace xe { -namespace gpu { -namespace metal { - -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) {} - -MetalShader::MetalTranslation::~MetalTranslation() { - if (metal_function_) { - metal_function_->release(); - metal_function_ = nullptr; - } - if (metal_library_) { - metal_library_->release(); - metal_library_ = nullptr; - } -} - -bool MetalShader::MetalTranslation::TranslateToMetal( - MTL::Device* device, DxbcToDxilConverter& dxbc_converter, - MetalShaderConverter& metal_converter) { - if (!device) { - XELOGE("MetalShader: No Metal device provided"); - return false; - } - - // Get the translated DXBC bytecode from the base class - const std::vector& dxbc_data = translated_binary(); - if (dxbc_data.empty()) { - XELOGE("MetalShader: No translated DXBC data available"); - return false; - } - - const uint64_t shader_cache_key = - MetalShaderCache::GetCacheKey(shader().ucode_data_hash(), modification(), - static_cast(shader().type())); - - if (cvars::metal_shader_disk_cache && g_metal_shader_cache && - g_metal_shader_cache->IsInitialized()) { - MetalShaderCache::CachedMetallib cached; - if (g_metal_shader_cache->Load(shader_cache_key, &cached)) { - NS::Error* error = nullptr; - dispatch_data_t cached_data = dispatch_data_create( - cached.metallib_data.data(), cached.metallib_data.size(), nullptr, - DISPATCH_DATA_DESTRUCTOR_NONE); - metal_library_ = device->newLibrary(cached_data, &error); - dispatch_release(cached_data); - if (metal_library_) { - function_name_ = cached.function_name; - metallib_data_ = std::move(cached.metallib_data); - NS::String* function_name_ns = - NS::String::string(function_name_.c_str(), NS::UTF8StringEncoding); - metal_function_ = metal_library_->newFunction(function_name_ns); - if (metal_function_) { - return true; - } - metal_library_->release(); - metal_library_ = nullptr; - } - } - } - auto dump_msc_failure = [&](const char* reason) { - if (cvars::dump_shaders.empty()) { - return; - } - static std::atomic dump_counter{0}; - uint32_t dump_id = dump_counter.fetch_add(1); - const char* type_str = - (shader().type() == xenos::ShaderType::kVertex) ? "vs" : "ps"; - char base_name[128]; - snprintf(base_name, sizeof(base_name), "shader_%016" PRIx64 "_%s_%u", - shader().ucode_data_hash(), type_str, dump_id); - - std::filesystem::path base_dir = - cvars::dump_shaders / "metal_shaders" / "failures"; - std::filesystem::path dxbc_path = - base_dir / (std::string(base_name) + ".dxbc"); - std::filesystem::path dxil_path = - base_dir / (std::string(base_name) + ".dxil"); - std::filesystem::path info_path = - base_dir / (std::string(base_name) + ".txt"); - - xe::filesystem::CreateParentFolder(dxbc_path); - - FILE* info_file = xe::filesystem::OpenFile(info_path, "wb"); - if (info_file) { - std::string info = - fmt::format("reason={}\nshader_type={}\nucode_hash=0x{:016X}\n", - reason, type_str, shader().ucode_data_hash()); - fwrite(info.data(), 1, info.size(), info_file); - fclose(info_file); - } - - if (!dxbc_data.empty()) { - FILE* f = xe::filesystem::OpenFile(dxbc_path, "wb"); - if (f) { - fwrite(dxbc_data.data(), 1, dxbc_data.size(), f); - fclose(f); - } - } - - if (!dxil_data_.empty()) { - FILE* f = xe::filesystem::OpenFile(dxil_path, "wb"); - if (f) { - fwrite(dxil_data_.data(), 1, dxil_data_.size(), f); - fclose(f); - } - } - - XELOGE("MetalShader: dumped MSC failure artifacts to {} (reason={})", - xe::path_to_utf8(info_path.parent_path()), reason); - }; - - // Step 1: Convert DXBC to DXIL in-process (dxilconv) - std::string dxbc_error; - if (!dxbc_converter.Convert(dxbc_data, dxil_data_, &dxbc_error)) { - XELOGE("MetalShader: DXBC to DXIL conversion failed: {}", dxbc_error); - dump_msc_failure("dxbc2dxil_failed"); - return false; - } - XELOGD("MetalShader: Converted {} bytes DXBC to {} bytes DXIL", - dxbc_data.size(), dxil_data_.size()); - - // Step 2: Convert DXIL to MetalLib using Metal Shader Converter - MetalShaderConversionResult msc_result; - if (!metal_converter.Convert(shader().type(), dxil_data_, msc_result)) { - XELOGE("MetalShader: DXIL to Metal conversion failed: {}", - msc_result.error_message); - dump_msc_failure("msc_convert_failed"); - return false; - } - function_name_ = msc_result.function_name; - metallib_data_ = std::move(msc_result.metallib_data); - XELOGD("MetalShader: Converted {} bytes DXIL to {} bytes MetalLib", - dxil_data_.size(), metallib_data_.size()); - - // Debug: Dump shader artifacts (DXBC, DXIL, MetalLib) to files when enabled. - static int 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); - } - } - } - - // Step 3: Create Metal library from the metallib data - NS::Error* error = nullptr; - dispatch_data_t data = - dispatch_data_create(metallib_data_.data(), metallib_data_.size(), - nullptr, DISPATCH_DATA_DESTRUCTOR_NONE); - - metal_library_ = device->newLibrary(data, &error); - 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; - } - - // Step 4: Get the main function from the library - // MSC generates functions with specific names based on shader type - NS::String* function_name = NS::String::string( - msc_result.function_name.c_str(), NS::UTF8StringEncoding); - - metal_function_ = metal_library_->newFunction(function_name); - - if (!metal_function_) { - // Try alternative function names - const char* alt_names[] = {"main0", "main", "vertexMain", "fragmentMain"}; - for (const char* alt_name : alt_names) { - NS::String* alt_func_name = - NS::String::string(alt_name, NS::UTF8StringEncoding); - metal_function_ = metal_library_->newFunction(alt_func_name); - if (metal_function_) { - XELOGD("MetalShader: Found function with alternative name: {}", - alt_name); - break; - } - } - } - - if (!metal_function_) { - // List available functions for debugging - NS::Array* function_names = metal_library_->functionNames(); - XELOGE("MetalShader: Could not find shader function. Available functions:"); - for (NS::UInteger i = 0; i < function_names->count(); i++) { - NS::String* name = static_cast(function_names->object(i)); - XELOGE(" - {}", name->utf8String()); - } - return false; - } - - if (cvars::metal_shader_disk_cache && g_metal_shader_cache && - g_metal_shader_cache->IsInitialized()) { - g_metal_shader_cache->Store(shader_cache_key, function_name_, - metallib_data_.data(), metallib_data_.size()); - } - - return true; -} - -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 deleted file mode 100644 index cd02988f0..000000000 --- a/src/xenia/gpu/metal/metal_shader.h +++ /dev/null @@ -1,101 +0,0 @@ -/** - ****************************************************************************** - * 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 "xenia/gpu/dxbc_shader.h" -#include "xenia/ui/metal/metal_api.h" - -namespace xe { -namespace gpu { -namespace metal { - -class DxbcToDxilConverter; -class MetalShaderConverter; - -class MetalShader : public DxbcShader { - public: - class MetalTranslation : public DxbcTranslation { - public: - MetalTranslation(MetalShader& shader, uint64_t modification) - : DxbcTranslation(shader, modification) {} - ~MetalTranslation(); - - // Convert DXBC -> DXIL -> Metal IR using the shader converter - bool TranslateToMetal(MTL::Device* device, - DxbcToDxilConverter& dxbc_converter, - MetalShaderConverter& metal_converter); - - // 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; } - - // 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: - 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); - - // 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; -}; - -} // 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 deleted file mode 100644 index a11f4f0a8..000000000 --- a/src/xenia/gpu/metal/metal_shader_cache.cc +++ /dev/null @@ -1,247 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2026 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/gpu/metal/metal_shader_cache.h" - -#include -#include -#include -#include -#include - -#include "third_party/xxhash/xxhash.h" -#include "xenia/base/logging.h" - -namespace xe { -namespace gpu { -namespace metal { - -std::unique_ptr g_metal_shader_cache = - std::make_unique(); - -namespace { - -constexpr uint32_t kCacheFileMagic = 0x4D4C4358; // 'XCLM' -constexpr uint32_t kCacheFileVersion = 1; - -struct CacheFileHeader { - 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."); - -} // 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; -} - -void MetalShaderCache::Shutdown() { - cache_.clear(); - cache_dir_.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(); - } - stats.total_bytes = stats.memory_total_bytes; - return stats; -} - -bool MetalShaderCache::Load(uint64_t cache_key, CachedMetallib* out) { - if (!out) { - return false; - } - { - 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; - } - } - - CachedMetallib disk_entry; - if (!LoadFromDisk(cache_key, &disk_entry)) { - 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)); - } - - *out = std::move(disk_entry); - 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) { - 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); - - { - 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); - } - - StoreToDisk(cache_key, entry); -} - -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; - { - 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_) { - 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()) { - 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); - return false; - } - return true; -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_shader_cache.h b/src/xenia/gpu/metal/metal_shader_cache.h deleted file mode 100644 index 308e0d7e7..000000000 --- a/src/xenia/gpu/metal/metal_shader_cache.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2026 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_GPU_METAL_METAL_SHADER_CACHE_H_ -#define XENIA_GPU_METAL_METAL_SHADER_CACHE_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xe { -namespace gpu { -namespace metal { - -class MetalShaderCache { - public: - struct CachedMetallib { - std::string function_name; - std::vector metallib_data; - }; - - struct CacheStats { - size_t entry_count = 0; - size_t total_bytes = 0; - size_t memory_entry_count = 0; - size_t memory_total_bytes = 0; - }; - - MetalShaderCache() = default; - ~MetalShaderCache() = default; - - void Initialize(const std::filesystem::path& cache_dir); - void Shutdown(); - - bool IsInitialized() const { return initialized_; } - std::filesystem::path cache_dir() const { return cache_dir_; } - - static uint64_t GetCacheKey(uint64_t ucode_hash, uint64_t modification, - uint32_t stage); - - 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); - - CacheStats GetStats() const; - - private: - struct MemoryEntry { - std::string function_name; - std::vector metallib_data; - }; - - 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; - - mutable std::mutex mutex_; - bool initialized_ = false; - std::filesystem::path cache_dir_; - std::unordered_map cache_; -}; - -extern std::unique_ptr g_metal_shader_cache; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_SHADER_CACHE_H_ diff --git a/src/xenia/gpu/metal/metal_shader_converter.cc b/src/xenia/gpu/metal/metal_shader_converter.cc deleted file mode 100644 index 09a9c92ef..000000000 --- a/src/xenia/gpu/metal/metal_shader_converter.cc +++ /dev/null @@ -1,679 +0,0 @@ -/** - ****************************************************************************** - * 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" - -namespace xe { -namespace gpu { -namespace metal { - -constexpr uint32_t kFunctionConstantRegisterSpace = 2147420894u; - -MetalShaderConverter::MetalShaderConverter() = default; - -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; -} - -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( - MetalShaderStage stage, bool force_all_visibility) { - 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"; - } - }; - IRShaderVisibility visibility = IRShaderVisibilityAll; - if (!force_all_visibility) { - switch (stage) { - case MetalShaderStage::kVertex: - visibility = IRShaderVisibilityVertex; - break; - case MetalShaderStage::kFragment: - visibility = IRShaderVisibilityPixel; - break; - case MetalShaderStage::kHull: - visibility = IRShaderVisibilityHull; - break; - case MetalShaderStage::kDomain: - visibility = IRShaderVisibilityDomain; - break; - case MetalShaderStage::kCompute: - case MetalShaderStage::kGeometry: - default: - visibility = IRShaderVisibilityAll; - break; - } - } - - // Create descriptor ranges for Xbox 360 shader resources - // This matches the layout in xbox360_rootsig_helper.h - IRDescriptorRange1 ranges[20] = {}; - int rangeIdx = 0; - - // SRVs in spaces 0-3 - // Use 1025 descriptors (1024 + 1 padding) to match heap allocation - for (int space = 0; space < 4; space++) { - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeSRV; - ranges[rangeIdx].NumDescriptors = - 1025; // Match kResourceHeapSlots (1024 + 1) - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = space; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - } - - // SRV in space 10 for hull shaders - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeSRV; - ranges[rangeIdx].NumDescriptors = - 1025; // Match kResourceHeapSlots (1024 + 1) - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = 10; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - - // UAVs in spaces 0-3 - // Use 1025 descriptors (1024 + 1 padding) to match heap allocation - for (int space = 0; space < 4; space++) { - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeUAV; - ranges[rangeIdx].NumDescriptors = - 1025; // Match kResourceHeapSlots (1024 + 1) - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = space; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - } - - // Samplers in space 0 - // Use 257 descriptors (256 + 1 padding) to match heap allocation - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeSampler; - ranges[rangeIdx].NumDescriptors = 257; // Match kSamplerHeapSlots (256 + 1) - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = 0; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - - // CBVs in spaces 0-3 - // Xenia uses 5 CBVs (b0-b4) in space 0: - // b0 = system constants - // b1 = float constants - // b2 = bool/loop constants - // b3 = fetch constants - // b4 = descriptor indices (bindless) - // We limit to 5 descriptors to match our heap allocation - for (int space = 0; space < 4; space++) { - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeCBV; - ranges[rangeIdx].NumDescriptors = - (space == 0) ? 5 : 1; // Only space 0 has multiple CBVs - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = space; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - } - - // Function-constant CBV space for MSC. - ranges[rangeIdx].RangeType = IRDescriptorRangeTypeCBV; - ranges[rangeIdx].NumDescriptors = 1; - ranges[rangeIdx].BaseShaderRegister = 0; - ranges[rangeIdx].RegisterSpace = kFunctionConstantRegisterSpace; - ranges[rangeIdx].Flags = IRDescriptorRangeFlagNone; - ranges[rangeIdx].OffsetInDescriptorsFromTableStart = 0; - rangeIdx++; - - // Create descriptor tables and parameters - IRRootDescriptorTable1 tables[20] = {}; - IRRootParameter1 params[20] = {}; - - for (int i = 0; i < rangeIdx; i++) { - tables[i].NumDescriptorRanges = 1; - tables[i].pDescriptorRanges = &ranges[i]; - params[i].ParameterType = IRRootParameterTypeDescriptorTable; - params[i].DescriptorTable = tables[i]; - params[i].ShaderVisibility = visibility; - } - - // Create root signature descriptor - IRRootSignatureDescriptor1 desc = {}; - desc.NumParameters = rangeIdx; - desc.pParameters = params; - desc.NumStaticSamplers = 0; - desc.pStaticSamplers = nullptr; - desc.Flags = IRRootSignatureFlagNone; - - IRVersionedRootSignatureDescriptor versionedDesc = {}; - versionedDesc.version = IRRootSignatureVersion_1_1; - versionedDesc.desc_1_1 = desc; - - static bool logged_root_sig = false; - if (!logged_root_sig) { - logged_root_sig = true; - const char* json = - IRVersionedRootSignatureDescriptorCopyJSONString(&versionedDesc); - if (json) { - XELOGI( - "MetalShaderConverter: root signature (stage={}, visibility={}, " - "force_all_visibility={}): {}", - stage_name(stage), static_cast(visibility), force_all_visibility, - json); - IRVersionedRootSignatureDescriptorReleaseString(json); - } - } - - // 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; -} - -void MetalShaderConverter::DestroyRootSignature(void* root_sig) { - if (root_sig) { - IRRootSignatureDestroy(static_cast(root_sig)); - } -} - -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) { - if (!is_available_) { - result.success = false; - result.error_message = "MetalShaderConverter not initialized"; - return false; - } - - if (dxil_data.empty()) { - result.success = false; - result.error_message = "Empty DXIL data"; - return false; - } - - // 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 false; - } - - // Create compiler - IRCompiler* compiler = IRCompilerCreate(); - if (!compiler) { - IRObjectDestroy(dxilObject); - result.success = false; - result.error_message = "Failed to create IR compiler"; - return false; - } - - // 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(IRCompatibilityFlagForceTextureArray | - IRCompatibilityFlagBoundsCheck)); - - 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, true); - // Enable function-constant register space for MSC specialization. - IRCompilerSetFunctionConstantResourceSpace(compiler, - kFunctionConstantRegisterSpace); - if (has_minimum_target_) { - IRCompilerSetMinimumGPUFamily( - compiler, static_cast(minimum_gpu_family_)); - IRCompilerSetMinimumDeploymentTarget( - compiler, static_cast(minimum_os_), - minimum_os_version_.c_str()); - } - - // Create and set Xbox 360 root signature - IRRootSignature* rootSig = - static_cast(CreateXbox360RootSignature(stage, true)); - if (!rootSig) { - IRCompilerDestroy(compiler); - IRObjectDestroy(dxilObject); - result.success = false; - result.error_message = "Failed to create root signature"; - return false; - } - 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 false; - } - - if (!metalObject) { - result.success = false; - result.error_message = "MSC returned null object without error"; - IRRootSignatureDestroy(rootSig); - IRCompilerDestroy(compiler); - IRObjectDestroy(dxilObject); - return false; - } - - 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 false; - } - - 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)) { - 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; - } - } - - if (stage == MetalShaderStage::kVertex && stage_in_metallib && 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) { - stage_in_metallib->resize(stage_in_size); - IRMetalLibGetBytecode(stage_in_lib, 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 true; -} - -} // 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 deleted file mode 100644 index 1e809d864..000000000 --- a/src/xenia/gpu/metal/metal_shader_converter.h +++ /dev/null @@ -1,138 +0,0 @@ -/** - ****************************************************************************** - * 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 - -struct IRVersionedInputLayoutDescriptor; - -#include "xenia/gpu/xenos.h" - -namespace xe { -namespace gpu { -namespace metal { - -// 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; -}; - -// 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); - - void SetMinimumTarget(uint32_t gpu_family, uint32_t os, - const std::string& version); - - private: - 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_; - - // Create Xbox 360 root signature for the given shader visibility - void* CreateXbox360RootSignature(MetalShaderStage stage, - bool force_all_visibility); - - // Destroy a root signature - void DestroyRootSignature(void* root_sig); -}; - -} // 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 deleted file mode 100644 index 5d609fc21..000000000 --- a/src/xenia/gpu/metal/metal_shared_memory.cc +++ /dev/null @@ -1,197 +0,0 @@ -/** - ****************************************************************************** - * 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_shared_memory.h" - -#include "xenia/base/logging.h" -#include "xenia/base/memory.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/ui/metal/metal_util.h" - -namespace xe { -namespace gpu { -namespace metal { - -MetalSharedMemory::MetalSharedMemory(MetalCommandProcessor& command_processor, - Memory& memory) - : SharedMemory(memory), command_processor_(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(); - - const ui::metal::MetalProvider& provider = - command_processor_.GetMetalProvider(); - MTL::Device* device = provider.GetDevice(); - - if (!device) { - XELOGE("Metal device is null in MetalSharedMemory::Initialize"); - return false; - } - - // Create Metal buffer - similar to D3D12's approach - // On Apple Silicon, ResourceStorageModeShared gives CPU/GPU access - void* xbox_ram = memory().TranslatePhysical(0); - if (!xbox_ram) { - XELOGE("Metal shared memory: Xbox RAM is null"); - 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); - } - 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)"); - } - - return true; -} - -void MetalSharedMemory::ClearCache() { SharedMemory::ClearCache(); } - -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; - const uint32_t page_size = 1u << page_size_log2(); - XELOGD("MetalSharedMemory::UploadRanges: page_size={}, {} ranges to upload", - page_size, num_upload_ranges); - for (uint32_t i = 0; i < std::min(5u, num_upload_ranges); i++) { - uint32_t start_byte = upload_page_ranges[i].first * page_size; - uint32_t length_bytes = upload_page_ranges[i].second * page_size; - XELOGD(" Range[{}]: page={} count={} -> byte offset=0x{:08X} length={}", - i, upload_page_ranges[i].first, upload_page_ranges[i].second, - start_byte, length_bytes); - } - } - - if (!buffer_ || num_upload_ranges == 0) { - 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); - } - - const uint32_t page_size = 1u << page_size_log2(); - - uint32_t merged_start = 0; - uint32_t merged_end = 0; - bool have_merged = false; - - auto flush_merged_range = [&](uint32_t start, uint32_t end) { - 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); - } - }; - - for (uint32_t i = 0; i < num_upload_ranges; ++i) { - const auto& range = upload_page_ranges[i]; - uint32_t start = range.first * page_size; - uint32_t end = start + range.second * page_size; - if (start >= kBufferSize) { - continue; - } - if (end > kBufferSize) { - end = kBufferSize; - } - - if (!have_merged) { - merged_start = start; - merged_end = end; - have_merged = true; - continue; - } - - // Merge overlapping/adjacent ranges. - if (start <= merged_end) { - if (end > merged_end) { - merged_end = end; - } - } else { - flush_merged_range(merged_start, merged_end); - merged_start = start; - merged_end = end; - } - } - - if (have_merged) { - flush_merged_range(merged_start, merged_end); - } - - XELOGD("MetalSharedMemory::UploadRanges: Copied {} ranges to Metal buffer", - num_upload_ranges); - - return true; -} - -void MetalSharedMemory::Shutdown() { - if (buffer_) { - buffer_->release(); - buffer_ = nullptr; - } - use_zero_copy_ = false; - - ShutdownCommon(); // Base class cleanup -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_shared_memory.h b/src/xenia/gpu/metal/metal_shared_memory.h deleted file mode 100644 index 114dc1536..000000000 --- a/src/xenia/gpu/metal/metal_shared_memory.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - ****************************************************************************** - * 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_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. - -#include "xenia/gpu/shared_memory.h" -#include "xenia/ui/metal/metal_api.h" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalCommandProcessor; -class MetalSharedMemory : public SharedMemory { - public: - MetalSharedMemory(MetalCommandProcessor& command_processor, Memory& memory); - ~MetalSharedMemory() override; - bool Initialize(); - void Shutdown(); - void ClearCache() override; - - MTL::Buffer* GetBuffer() const { return buffer_; } - const uint8_t* GetXboxRamBase() const { - return static_cast(memory().TranslatePhysical(0)); - } - - // For trace dump, simplified - just make buffer available for reading - void UseForReading() { - // No state transitions needed in Metal - } - // Override pure virtual function from SharedMemory - bool UploadRanges(const std::pair* upload_page_ranges, - uint32_t num_upload_ranges) override; - - private: - MetalCommandProcessor& command_processor_; - MTL::Buffer* buffer_ = nullptr; - bool use_zero_copy_ = false; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif diff --git a/src/xenia/gpu/metal/metal_texture_cache.cc b/src/xenia/gpu/metal/metal_texture_cache.cc deleted file mode 100644 index 1144e77e1..000000000 --- a/src/xenia/gpu/metal/metal_texture_cache.cc +++ /dev/null @@ -1,3100 +0,0 @@ -/** - ****************************************************************************** - * 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_texture_cache.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/metal/metal_heap_pool.h" - -#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" -#include "xenia/base/byte_order.h" -#include "xenia/base/cvar.h" -#include "xenia/base/logging.h" -#include "xenia/base/math.h" -#include "xenia/base/profiling.h" -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/gpu/metal/metal_shared_memory.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_128bpb_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_128bpb_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_16bpb_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_16bpb_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_32bpb_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_32bpb_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_64bpb_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_64bpb_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_8bpb_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_8bpb_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_bgrg8_rgb8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_bgrg8_rgbg8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_ctx1_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_depth_float_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_depth_float_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_depth_unorm_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_depth_unorm_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxn_rg8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt1_rgba8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt3_rgba8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt3a_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt3aas1111_argb4_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt3aas1111_bgra4_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt5_rgba8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_dxt5a_r8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_gbgr8_grgb8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_gbgr8_rgb8_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r10g11b11_rgba16_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r10g11b11_rgba16_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r10g11b11_rgba16_snorm_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r10g11b11_rgba16_snorm_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r11g11b10_rgba16_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r11g11b10_rgba16_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r11g11b10_rgba16_snorm_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r11g11b10_rgba16_snorm_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r16_snorm_float_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r16_snorm_float_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r16_unorm_float_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r16_unorm_float_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r4g4b4a4_a4r4g4b4_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r4g4b4a4_a4r4g4b4_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r4g4b4a4_b4g4r4a4_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r4g4b4a4_b4g4r4a4_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g5b5a1_b5g5r5a1_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g5b5a1_b5g5r5a1_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g6b5_b5g6r5_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_r5g6b5_b5g6r5_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_rg16_snorm_float_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_rg16_snorm_float_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_rg16_unorm_float_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_rg16_unorm_float_scaled_cs.h" -#include "xenia/gpu/shaders/bytecode/metal/texture_load_rgba16_snorm_float_cs.h" -#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/texture_info.h" -#include "xenia/gpu/texture_util.h" -#include "xenia/gpu/xenos.h" - -DEFINE_bool(metal_force_bc_decompress, false, - "Force BC1/2/3/5/DXN decompression to RGBA8/RG8 (debug).", "GPU"); - -namespace xe { -namespace gpu { -namespace metal { -namespace { - -struct MetalLoadConstants { - uint32_t is_tiled_3d_endian_scale; - uint32_t guest_offset; - uint32_t guest_pitch_aligned; - uint32_t guest_z_stride_block_rows_aligned; - uint32_t size_blocks[3]; - uint32_t padding0; // Pad to 16-byte boundary for uint3 in MSL. - uint32_t host_offset; - uint32_t host_pitch; - uint32_t height_texels; - uint32_t padding1[5]; // Pad to 64 bytes to match HLSL CB size. -}; -static_assert(sizeof(MetalLoadConstants) == 64); - -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_; -}; - -bool SupportsPixelFormat(MTL::Device* device, MTL::PixelFormat format) { - if (!device || format == MTL::PixelFormatInvalid) { - return false; - } - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); - descriptor->setTextureType(MTL::TextureType2D); - descriptor->setPixelFormat(format); - descriptor->setWidth(1); - descriptor->setHeight(1); - descriptor->setDepth(1); - descriptor->setArrayLength(1); - descriptor->setMipmapLevelCount(1); - descriptor->setSampleCount(1); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - descriptor->setStorageMode(MTL::StorageModeShared); - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); - if (texture) { - texture->release(); - } - return texture != nullptr; -} - -uint32_t GetEstimatedBytesPerPixel(MTL::PixelFormat format) { - switch (format) { - case MTL::PixelFormatRGBA8Unorm: - case MTL::PixelFormatRGBA8Unorm_sRGB: - case MTL::PixelFormatBGRA8Unorm: - case MTL::PixelFormatBGRA8Unorm_sRGB: - case MTL::PixelFormatR32Float: - case MTL::PixelFormatR32Uint: - case MTL::PixelFormatR32Sint: - case MTL::PixelFormatDepth32Float: - case MTL::PixelFormatDepth24Unorm_Stencil8: - case MTL::PixelFormatX32_Stencil8: - return 4; - case MTL::PixelFormatRG16Float: - case MTL::PixelFormatRG16Uint: - case MTL::PixelFormatRG16Sint: - return 4; - case MTL::PixelFormatRGBA16Float: - case MTL::PixelFormatRGBA16Uint: - case MTL::PixelFormatRGBA16Sint: - case MTL::PixelFormatRG32Float: - case MTL::PixelFormatRG32Uint: - case MTL::PixelFormatRG32Sint: - case MTL::PixelFormatDepth32Float_Stencil8: - return 8; - case MTL::PixelFormatR16Float: - case MTL::PixelFormatR16Uint: - case MTL::PixelFormatR16Sint: - case MTL::PixelFormatDepth16Unorm: - return 2; - default: - return 4; - } -} - -uint64_t EstimateTextureBytes(MTL::Texture* texture) { - if (!texture) { - return 0; - } - - const uint32_t bytes_per_pixel = - GetEstimatedBytesPerPixel(texture->pixelFormat()); - const uint32_t sample_count = - std::max(1, static_cast(texture->sampleCount())); - const uint32_t mip_count = - std::max(1, static_cast(texture->mipmapLevelCount())); - const uint32_t array_length = - std::max(1, static_cast(texture->arrayLength())); - - uint64_t total = 0; - for (uint32_t level = 0; level < mip_count; ++level) { - uint32_t width = - std::max(1, static_cast(texture->width() >> level)); - uint32_t height = std::max( - 1, static_cast(texture->height() >> level)); - uint32_t depth = - std::max(1, static_cast(texture->depth() >> level)); - uint64_t level_bytes = uint64_t(width) * uint64_t(height) * - uint64_t(depth) * bytes_per_pixel * sample_count; - total += level_bytes; - } - - return total * array_length; -} - -bool AreDimensionsCompatible(xenos::FetchOpDimension shader_dimension, - xenos::DataDimension texture_dimension) { - switch (shader_dimension) { - case xenos::FetchOpDimension::k1D: - case xenos::FetchOpDimension::k2D: - return texture_dimension == xenos::DataDimension::k1D || - texture_dimension == xenos::DataDimension::k2DOrStacked || - texture_dimension == xenos::DataDimension::k3D; - case xenos::FetchOpDimension::k3DOrStacked: - return texture_dimension == xenos::DataDimension::k3D; - case xenos::FetchOpDimension::kCube: - return texture_dimension == xenos::DataDimension::kCube; - default: - return false; - } -} - -MTL::TextureSwizzleChannels ToMetalTextureSwizzle(uint32_t xenos_swizzle) { - MTL::TextureSwizzleChannels swizzle; - // Xenos: R=0, G=1, B=2, A=3, 0=4, 1=5 - // Metal: Zero=0, One=1, Red=2, Green=3, Blue=4, Alpha=5 - static const MTL::TextureSwizzle kMap[] = { - MTL::TextureSwizzleRed, // 0 - MTL::TextureSwizzleGreen, // 1 - MTL::TextureSwizzleBlue, // 2 - MTL::TextureSwizzleAlpha, // 3 - MTL::TextureSwizzleZero, // 4 - MTL::TextureSwizzleOne, // 5 - MTL::TextureSwizzleZero, // 6 (Unused) - MTL::TextureSwizzleZero, // 7 (Unused) - }; - swizzle.red = kMap[(xenos_swizzle >> 0) & 0x7]; - swizzle.green = kMap[(xenos_swizzle >> 3) & 0x7]; - swizzle.blue = kMap[(xenos_swizzle >> 6) & 0x7]; - swizzle.alpha = kMap[(xenos_swizzle >> 9) & 0x7]; - return swizzle; -} - -} // namespace - -class MetalTextureCache::UploadBufferPool - : public std::enable_shared_from_this { - public: - explicit UploadBufferPool(MTL::Device* device) : device_(device) {} - - MTL::Buffer* Acquire(size_t size) { - if (!device_) { - return nullptr; - } - size = xe::round_up(size, size_t(256)); - std::lock_guard lock(mutex_); - 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; - } - if (entry.size < best_size) { - best_index = i; - best_size = entry.size; - } - } - if (best_index < entries_.size()) { - entries_[best_index].in_use = true; - return entries_[best_index].buffer; - } - - MTL::Buffer* buffer = - device_->newBuffer(size, MTL::ResourceStorageModeShared); - if (!buffer) { - return nullptr; - } - entries_.push_back({buffer, size, true}); - return buffer; - } - - void ReleaseImmediate(MTL::Buffer* buffer) { - if (!buffer) { - return; - } - std::lock_guard lock(mutex_); - for (Entry& entry : entries_) { - if (entry.buffer == buffer) { - entry.in_use = false; - return; - } - } - } - - void ReleaseAfter(MTL::CommandBuffer* cmd, MTL::Buffer* buffer) { - if (!buffer) { - return; - } - if (!cmd) { - ReleaseImmediate(buffer); - return; - } - std::shared_ptr self = shared_from_this(); - cmd->addCompletedHandler(^(MTL::CommandBuffer*) { - self->ReleaseImmediate(buffer); - }); - } - - void Shutdown() { - std::lock_guard lock(mutex_); - for (Entry& entry : entries_) { - if (entry.buffer) { - entry.buffer->release(); - entry.buffer = nullptr; - } - } - entries_.clear(); - } - - size_t GetEntryCount() const { - std::lock_guard lock(mutex_); - return entries_.size(); - } - - uint64_t GetTotalBytes() const { - std::lock_guard lock(mutex_); - uint64_t total = 0; - for (const Entry& entry : entries_) { - total += entry.size; - } - return total; - } - - private: - struct Entry { - MTL::Buffer* buffer = nullptr; - size_t size = 0; - bool in_use = false; - }; - - mutable std::mutex mutex_; - std::vector entries_; - MTL::Device* device_ = nullptr; -}; - -MetalTextureCache::MetalTextureCache(MetalCommandProcessor* command_processor, - const RegisterFile& register_file, - MetalSharedMemory& shared_memory, - uint32_t draw_resolution_scale_x, - uint32_t draw_resolution_scale_y) - : TextureCache(register_file, shared_memory, draw_resolution_scale_x, - draw_resolution_scale_y), - command_processor_(command_processor) {} - -MetalTextureCache::~MetalTextureCache() { Shutdown(); } - -MTL::StorageMode MetalTextureCache::GetCacheTextureStorageMode() const { - if (!::cvars::metal_texture_upload_via_blit || - !::cvars::metal_texture_cache_use_private) { - return MTL::StorageModeShared; - } - return MTL::StorageModePrivate; -} - -bool MetalTextureCache::ShouldUploadViaBlit() const { - return ::cvars::metal_texture_upload_via_blit; -} - -bool MetalTextureCache::IsDecompressionNeededForKey(TextureKey key) const { - switch (key.format) { - case xenos::TextureFormat::k_DXT1: - case xenos::TextureFormat::k_DXT2_3: - case xenos::TextureFormat::k_DXT4_5: - case xenos::TextureFormat::k_DXN: { - if (::cvars::metal_force_bc_decompress) { - return true; - } - const FormatInfo* format_info = FormatInfo::Get(key.format); - if (!format_info) { - return false; - } - if (!(key.GetWidth() & (format_info->block_width - 1)) && - !(key.GetHeight() & (format_info->block_height - 1))) { - return false; - } - return true; - } - case xenos::TextureFormat::k_CTX1: - // CTX1 must be decompressed (no hardware support on Metal). - return true; - default: - return false; - } -} - -TextureCache::LoadShaderIndex MetalTextureCache::GetLoadShaderIndexForKey( - TextureKey key) const { - bool decompress = IsDecompressionNeededForKey(key); - switch (key.format) { - case xenos::TextureFormat::k_8: - case xenos::TextureFormat::k_8_A: - return kLoadShaderIndex8bpb; - case xenos::TextureFormat::k_8_8: - return kLoadShaderIndex16bpb; - case xenos::TextureFormat::k_1_5_5_5: - return kLoadShaderIndexR5G5B5A1ToB5G5R5A1; - case xenos::TextureFormat::k_5_6_5: - return kLoadShaderIndexR5G6B5ToB5G6R5; - case xenos::TextureFormat::k_6_5_5: - return kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle; - case xenos::TextureFormat::k_8_8_8_8: - return kLoadShaderIndex32bpb; - case xenos::TextureFormat::k_2_10_10_10: - return kLoadShaderIndex32bpb; - case xenos::TextureFormat::k_4_4_4_4: - return kLoadShaderIndexRGBA4ToBGRA4; - case xenos::TextureFormat::k_10_11_11: - return key.signed_separate ? kLoadShaderIndexR11G11B10ToRGBA16SNorm - : kLoadShaderIndexR11G11B10ToRGBA16; - case xenos::TextureFormat::k_11_11_10: - return key.signed_separate ? kLoadShaderIndexR10G11B11ToRGBA16SNorm - : kLoadShaderIndexR10G11B11ToRGBA16; - - case xenos::TextureFormat::k_DXT1: - return decompress ? kLoadShaderIndexDXT1ToRGBA8 : kLoadShaderIndex64bpb; - case xenos::TextureFormat::k_DXT2_3: - return decompress ? kLoadShaderIndexDXT3ToRGBA8 : kLoadShaderIndex128bpb; - case xenos::TextureFormat::k_DXT4_5: - return decompress ? kLoadShaderIndexDXT5ToRGBA8 : kLoadShaderIndex128bpb; - case xenos::TextureFormat::k_DXN: - return decompress ? kLoadShaderIndexDXNToRG8 : kLoadShaderIndex128bpb; - case xenos::TextureFormat::k_DXT3A: - return kLoadShaderIndexDXT3A; - case xenos::TextureFormat::k_DXT5A: - return kLoadShaderIndexDXT5AToR8; - case xenos::TextureFormat::k_DXT3A_AS_1_1_1_1: - return kLoadShaderIndexDXT3AAs1111ToBGRA4; - case xenos::TextureFormat::k_CTX1: - return kLoadShaderIndexCTX1; - - case xenos::TextureFormat::k_24_8: - return kLoadShaderIndexDepthUnorm; - case xenos::TextureFormat::k_24_8_FLOAT: - return kLoadShaderIndexDepthFloat; - - case xenos::TextureFormat::k_16: - if (key.signed_separate) { - return r16_selection_.signed_uses_float - ? kLoadShaderIndexR16SNormToFloat - : kLoadShaderIndex16bpb; - } - return r16_selection_.unsigned_uses_float - ? kLoadShaderIndexR16UNormToFloat - : kLoadShaderIndex16bpb; - case xenos::TextureFormat::k_16_EXPAND: - case xenos::TextureFormat::k_16_FLOAT: - return kLoadShaderIndex16bpb; - case xenos::TextureFormat::k_16_16: - if (key.signed_separate) { - return rg16_selection_.signed_uses_float - ? kLoadShaderIndexRG16SNormToFloat - : kLoadShaderIndex32bpb; - } - return rg16_selection_.unsigned_uses_float - ? kLoadShaderIndexRG16UNormToFloat - : kLoadShaderIndex32bpb; - case xenos::TextureFormat::k_16_16_EXPAND: - case xenos::TextureFormat::k_16_16_FLOAT: - return kLoadShaderIndex32bpb; - case xenos::TextureFormat::k_16_16_16_16: - if (key.signed_separate) { - return rgba16_selection_.signed_uses_float - ? kLoadShaderIndexRGBA16SNormToFloat - : kLoadShaderIndex64bpb; - } - return rgba16_selection_.unsigned_uses_float - ? kLoadShaderIndexRGBA16UNormToFloat - : kLoadShaderIndex64bpb; - case xenos::TextureFormat::k_16_16_16_16_EXPAND: - case xenos::TextureFormat::k_16_16_16_16_FLOAT: - return kLoadShaderIndex64bpb; - - case xenos::TextureFormat::k_32: - case xenos::TextureFormat::k_32_FLOAT: - return kLoadShaderIndex32bpb; - case xenos::TextureFormat::k_32_32: - case xenos::TextureFormat::k_32_32_FLOAT: - return kLoadShaderIndex64bpb; - case xenos::TextureFormat::k_32_32_32_32: - case xenos::TextureFormat::k_32_32_32_32_FLOAT: - return kLoadShaderIndex128bpb; - - case xenos::TextureFormat::k_8_B: - return kLoadShaderIndex8bpb; - case xenos::TextureFormat::k_8_8_8_8_A: - return kLoadShaderIndex32bpb; - - default: - return kLoadShaderIndexUnknown; - } -} - -MTL::PixelFormat MetalTextureCache::GetPixelFormatForKey(TextureKey key) const { - bool decompress = IsDecompressionNeededForKey(key); - switch (key.format) { - case xenos::TextureFormat::k_8: - case xenos::TextureFormat::k_8_A: - return MTL::PixelFormatR8Unorm; - case xenos::TextureFormat::k_8_8: - return MTL::PixelFormatRG8Unorm; - case xenos::TextureFormat::k_1_5_5_5: - return MTL::PixelFormatA1BGR5Unorm; - case xenos::TextureFormat::k_5_6_5: - case xenos::TextureFormat::k_6_5_5: - return MTL::PixelFormatB5G6R5Unorm; - case xenos::TextureFormat::k_4_4_4_4: - return MTL::PixelFormatABGR4Unorm; - case xenos::TextureFormat::k_8_8_8_8: - return MTL::PixelFormatRGBA8Unorm; - case xenos::TextureFormat::k_2_10_10_10: - return MTL::PixelFormatRGB10A2Unorm; - case xenos::TextureFormat::k_10_11_11: - case xenos::TextureFormat::k_11_11_10: - return key.signed_separate ? MTL::PixelFormatRGBA16Snorm - : MTL::PixelFormatRGBA16Unorm; - - case xenos::TextureFormat::k_16: - if (key.signed_separate) { - return r16_selection_.signed_uses_float ? MTL::PixelFormatR16Float - : MTL::PixelFormatR16Snorm; - } - return r16_selection_.unsigned_uses_float ? MTL::PixelFormatR16Float - : MTL::PixelFormatR16Unorm; - case xenos::TextureFormat::k_16_16: - if (key.signed_separate) { - return rg16_selection_.signed_uses_float ? MTL::PixelFormatRG16Float - : MTL::PixelFormatRG16Snorm; - } - return rg16_selection_.unsigned_uses_float ? MTL::PixelFormatRG16Float - : MTL::PixelFormatRG16Unorm; - case xenos::TextureFormat::k_16_16_16_16: - if (key.signed_separate) { - return rgba16_selection_.signed_uses_float - ? MTL::PixelFormatRGBA16Float - : MTL::PixelFormatRGBA16Snorm; - } - return rgba16_selection_.unsigned_uses_float - ? MTL::PixelFormatRGBA16Float - : MTL::PixelFormatRGBA16Unorm; - case xenos::TextureFormat::k_16_EXPAND: - case xenos::TextureFormat::k_16_FLOAT: - return MTL::PixelFormatR16Float; - case xenos::TextureFormat::k_16_16_EXPAND: - case xenos::TextureFormat::k_16_16_FLOAT: - return MTL::PixelFormatRG16Float; - case xenos::TextureFormat::k_16_16_16_16_EXPAND: - case xenos::TextureFormat::k_16_16_16_16_FLOAT: - return MTL::PixelFormatRGBA16Float; - - case xenos::TextureFormat::k_DXT1: - return decompress ? MTL::PixelFormatRGBA8Unorm : MTL::PixelFormatBC1_RGBA; - case xenos::TextureFormat::k_DXT2_3: - return decompress ? MTL::PixelFormatRGBA8Unorm : MTL::PixelFormatBC2_RGBA; - case xenos::TextureFormat::k_DXT4_5: - return decompress ? MTL::PixelFormatRGBA8Unorm : MTL::PixelFormatBC3_RGBA; - case xenos::TextureFormat::k_DXN: - return decompress ? MTL::PixelFormatRG8Unorm - : MTL::PixelFormatBC5_RGUnorm; - case xenos::TextureFormat::k_DXT3A: - case xenos::TextureFormat::k_DXT5A: - return MTL::PixelFormatR8Unorm; - case xenos::TextureFormat::k_DXT3A_AS_1_1_1_1: - return MTL::PixelFormatABGR4Unorm; - case xenos::TextureFormat::k_CTX1: - // CTX1 is always decoded via the texture load shader to RG8. - return MTL::PixelFormatRG8Unorm; - - case xenos::TextureFormat::k_24_8: - case xenos::TextureFormat::k_24_8_FLOAT: - return MTL::PixelFormatR32Float; - - case xenos::TextureFormat::k_8_B: - return MTL::PixelFormatR8Unorm; - case xenos::TextureFormat::k_8_8_8_8_A: - return MTL::PixelFormatRGBA8Unorm; - - 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; - - default: - return MTL::PixelFormatInvalid; - } -} - -bool MetalTextureCache::TryGpuLoadTexture(Texture& texture, bool load_base, - bool load_mips) { - 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(); - uint32_t texture_resolution_scale_x = - texture_resolution_scaled ? draw_resolution_scale_x() : 1; - uint32_t texture_resolution_scale_y = - texture_resolution_scaled ? draw_resolution_scale_y() : 1; - uint32_t texture_resolution_scale_area = - texture_resolution_scale_x * texture_resolution_scale_y; - - const texture_util::TextureGuestLayout& guest_layout = texture.guest_layout(); - xenos::DataDimension dimension = key.dimension; - bool is_3d = dimension == xenos::DataDimension::k3D; - bool is_3d_tiling = is_3d || texture.force_load_3d_tiling(); - - uint32_t width = key.GetWidth(); - uint32_t height = key.GetHeight(); - uint32_t depth_or_array_size = key.GetDepthOrArraySize(); - uint32_t depth = is_3d ? depth_or_array_size : 1; - uint32_t array_size = is_3d ? 1 : depth_or_array_size; - - const FormatInfo* guest_format_info = FormatInfo::Get(key.format); - if (!guest_format_info) { - return false; - } - uint32_t block_width = guest_format_info->block_width; - uint32_t block_height = guest_format_info->block_height; - uint32_t bytes_per_block = guest_format_info->bytes_per_block(); - - uint32_t level_first = load_base ? 0 : 1; - uint32_t level_last = load_mips ? key.mip_max_level : 0; - if (level_first > level_last) { - return false; - } - - bool decompress = IsDecompressionNeededForKey(key); - TextureCache::LoadShaderIndex load_shader = GetLoadShaderIndexForKey(key); - if (load_shader == TextureCache::kLoadShaderIndexUnknown) { - return false; - } - - MTL::ComputePipelineState* pipeline = - texture_resolution_scaled - ? load_pipelines_scaled_[static_cast(load_shader)] - : load_pipelines_[static_cast(load_shader)]; - if (!pipeline) { - return false; - } - - 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 || - key.format == xenos::TextureFormat::k_DXT2_3 || - key.format == xenos::TextureFormat::k_DXT4_5 || - key.format == xenos::TextureFormat::k_DXN; - bool host_block_compressed = is_block_compressed_format && !decompress; - uint32_t host_block_width = host_block_compressed ? block_width : 1; - uint32_t host_block_height = host_block_compressed ? block_height : 1; - uint32_t host_x_blocks_per_thread = - UINT32_C(1) << load_shader_info.guest_x_blocks_per_thread_log2; - if (!host_block_compressed) { - host_x_blocks_per_thread *= block_width; - } - - struct StoredLevelHostLayout { - bool is_base; - uint32_t level; - uint32_t dest_offset_bytes; - uint32_t slice_size_bytes; - uint32_t row_pitch_bytes; - uint32_t height_blocks; - uint32_t depth_slices; - uint32_t width_texels; - uint32_t height_texels; - }; - - uint32_t level_packed = guest_layout.packed_level; - uint32_t level_stored_first = std::min(level_first, level_packed); - uint32_t level_stored_last = std::min(level_last, level_packed); - - uint32_t loop_level_first, loop_level_last; - if (level_packed == 0) { - loop_level_first = uint32_t(level_first != 0); - loop_level_last = uint32_t(level_last != 0); - } else { - loop_level_first = level_stored_first; - loop_level_last = level_stored_last; - } - - std::vector stored_levels; - stored_levels.reserve(loop_level_last - loop_level_first + 1); - uint64_t dest_buffer_size = 0; - - for (uint32_t loop_level = loop_level_first; loop_level <= loop_level_last; - ++loop_level) { - bool is_base = loop_level == 0; - uint32_t level = (level_packed == 0) ? 0 : loop_level; - const texture_util::TextureGuestLayout::Level& level_guest_layout = - is_base ? guest_layout.base : guest_layout.mips[level]; - if (!level_guest_layout.level_data_extent_bytes) { - continue; - } - - uint32_t level_width_unscaled, level_height_unscaled, level_depth; - if (level == level_packed) { - level_width_unscaled = level_guest_layout.x_extent_blocks * block_width; - level_height_unscaled = level_guest_layout.y_extent_blocks * block_height; - level_depth = level_guest_layout.z_extent; - } else { - level_width_unscaled = std::max(width >> level, uint32_t(1)); - level_height_unscaled = std::max(height >> level, uint32_t(1)); - level_depth = std::max(depth >> level, uint32_t(1)); - } - - uint32_t width_texels_scaled = xe::round_up( - level_width_unscaled * texture_resolution_scale_x, host_block_width); - uint32_t height_texels_scaled = xe::round_up( - level_height_unscaled * texture_resolution_scale_y, host_block_height); - uint32_t width_blocks = width_texels_scaled / host_block_width; - uint32_t height_blocks = height_texels_scaled / host_block_height; - - const uint32_t row_pitch_alignment = - ShouldUploadViaBlit() ? uint32_t(256) : uint32_t(16); - uint32_t row_pitch_bytes = - xe::align(xe::round_up(width_blocks, host_x_blocks_per_thread) * - load_shader_info.bytes_per_host_block, - row_pitch_alignment); - uint32_t slice_size_bytes = xe::align( - row_pitch_bytes * height_blocks * level_depth, row_pitch_alignment); - - StoredLevelHostLayout host_layout = {}; - host_layout.is_base = is_base; - host_layout.level = level; - host_layout.dest_offset_bytes = uint32_t(dest_buffer_size); - host_layout.slice_size_bytes = slice_size_bytes; - host_layout.row_pitch_bytes = row_pitch_bytes; - host_layout.height_blocks = height_blocks; - host_layout.depth_slices = level_depth; - host_layout.width_texels = level_width_unscaled; - host_layout.height_texels = level_height_unscaled; - stored_levels.push_back(host_layout); - - dest_buffer_size += uint64_t(slice_size_bytes) * uint64_t(array_size); - } - - if (stored_levels.empty()) { - return false; - } - if (dest_buffer_size > SIZE_MAX) { - return false; - } - - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - return false; - } - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!queue) { - return false; - } - - MetalSharedMemory& metal_shared_memory = - static_cast(shared_memory()); - MTL::Buffer* shared_buffer = metal_shared_memory.GetBuffer(); - if (!shared_buffer) { - return false; - } - - auto buffer_pool = upload_buffer_pool_; - auto acquire_buffer = [&](size_t size) -> MTL::Buffer* { - if (buffer_pool) { - return buffer_pool->Acquire(size); - } - MTL::Buffer* buffer = - device->newBuffer(size, MTL::ResourceStorageModeShared); - if (buffer) { - } - return buffer; - }; - auto release_buffer_immediate = [&](MTL::Buffer* buffer, size_t size) { - if (!buffer) { - return; - } - if (buffer_pool) { - buffer_pool->ReleaseImmediate(buffer); - return; - } - buffer->release(); - }; - auto release_buffer_after = [&](MTL::CommandBuffer* cmd, MTL::Buffer* buffer, - size_t size) { - if (!buffer) { - return; - } - if (buffer_pool) { - buffer_pool->ReleaseAfter(cmd, buffer); - return; - } - cmd->addCompletedHandler(^(MTL::CommandBuffer*) { - buffer->release(); - }); - }; - - MTL::Buffer* dest_buffer = acquire_buffer(size_t(dest_buffer_size)); - if (!dest_buffer) { - return false; - } - - uint32_t base_guest_address = key.base_page << 12; - uint32_t mips_guest_address = key.mip_page << 12; - - size_t constants_size = xe::align(sizeof(MetalLoadConstants), size_t(16)); - size_t dispatch_count = stored_levels.size() * size_t(array_size); - size_t constants_buffer_size = constants_size * dispatch_count; - MTL::Buffer* constants_buffer = acquire_buffer(constants_buffer_size); - if (!constants_buffer) { - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - return false; - } - - const bool use_blit_upload = ShouldUploadViaBlit(); - ScopedAutoreleasePool autorelease_pool; - MTL::CommandBuffer* cmd = queue->commandBuffer(); - if (!cmd) { - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - return false; - } - MTL::ComputeCommandEncoder* encoder = cmd->computeCommandEncoder(); - if (!encoder) { - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - 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, - load_shader_info.source_bpe_log2) || - !GetCurrentScaledResolveBuffer(source_buffer, source_buffer_offset, - source_buffer_length)) { - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - 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) { - 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; - } - 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); - ++dispatch_index; - } - } - - encoder->endEncoding(); - - MTL::Texture* mtl_texture = metal_texture->metal_texture(); - if (use_blit_upload) { - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (!blit) { - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - return false; - } - - uint32_t bytes_per_host_block = load_shader_info.bytes_per_host_block; - const uint32_t blit_alignment = 256; - - auto find_stored_level = - [&](bool is_base_storage, - uint32_t stored_level) -> const StoredLevelHostLayout* { - for (const StoredLevelHostLayout& layout : stored_levels) { - if (layout.is_base == is_base_storage && layout.level == stored_level) { - return &layout; - } - } - return nullptr; - }; - - for (uint32_t level = level_first; level <= level_last; ++level) { - uint32_t stored_level = std::min(level, level_packed); - bool is_base_storage = - stored_level == 0 && (level_packed != 0 || level == 0); - const StoredLevelHostLayout* stored_layout = - find_stored_level(is_base_storage, stored_level); - if (!stored_layout) { - continue; - } - - uint32_t level_width_unscaled = std::max(width >> level, uint32_t(1)); - uint32_t level_height_unscaled = std::max(height >> level, uint32_t(1)); - uint32_t level_depth = std::max(depth >> level, uint32_t(1)); - uint32_t level_width_scaled = - level_width_unscaled * texture_resolution_scale_x; - uint32_t level_height_scaled = - level_height_unscaled * texture_resolution_scale_y; - - const uint32_t upload_width_texels = level_width_scaled; - const uint32_t upload_height_texels = level_height_scaled; - - uint32_t packed_offset_blocks_x = 0; - uint32_t packed_offset_blocks_y = 0; - uint32_t packed_offset_z = 0; - if (level >= level_packed) { - texture_util::GetPackedMipOffset( - width, height, depth, key.format, level, packed_offset_blocks_x, - packed_offset_blocks_y, packed_offset_z); - } - - uint32_t upload_blocks_x = - xe::round_up(upload_width_texels, host_block_width) / - host_block_width; - uint32_t upload_blocks_y = - xe::round_up(upload_height_texels, host_block_height) / - host_block_height; - uint32_t upload_row_bytes = upload_blocks_x * bytes_per_host_block; - uint32_t upload_row_count = upload_blocks_y; - uint32_t blit_row_pitch = xe::align(upload_row_bytes, blit_alignment); - size_t blit_bytes_per_image = size_t(blit_row_pitch) * upload_row_count; - size_t bytes_per_image = - size_t(stored_layout->row_pitch_bytes) * stored_layout->height_blocks; - - for (uint32_t slice = 0; slice < array_size; ++slice) { - size_t source_offset_bytes = stored_layout->dest_offset_bytes + - slice * stored_layout->slice_size_bytes; - if (level >= level_packed) { - if (host_block_compressed) { - uint32_t packed_offset_blocks_x_scaled = - packed_offset_blocks_x * texture_resolution_scale_x; - uint32_t packed_offset_blocks_y_scaled = - packed_offset_blocks_y * texture_resolution_scale_y; - source_offset_bytes += packed_offset_z * bytes_per_image; - source_offset_bytes += - packed_offset_blocks_y_scaled * stored_layout->row_pitch_bytes; - source_offset_bytes += - packed_offset_blocks_x_scaled * bytes_per_block; - } else { - uint32_t packed_offset_texels_x = - packed_offset_blocks_x * block_width; - uint32_t packed_offset_texels_y = - packed_offset_blocks_y * block_height; - packed_offset_texels_x *= texture_resolution_scale_x; - packed_offset_texels_y *= texture_resolution_scale_y; - source_offset_bytes += packed_offset_z * bytes_per_image; - source_offset_bytes += - packed_offset_texels_y * stored_layout->row_pitch_bytes; - source_offset_bytes += - packed_offset_texels_x * bytes_per_host_block; - } - } - - bool requires_staging = (source_offset_bytes % blit_alignment) != 0; - if (requires_staging) { - size_t staging_size = blit_bytes_per_image * level_depth; - MTL::Buffer* staging_buffer = acquire_buffer(staging_size); - if (!staging_buffer) { - blit->endEncoding(); - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - 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); - } 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)); - } - } - } - - blit->endEncoding(); - release_buffer_after(cmd, constants_buffer, constants_buffer_size); - release_buffer_after(cmd, dest_buffer, size_t(dest_buffer_size)); - cmd->retain(); - cmd->addCompletedHandler(^(MTL::CommandBuffer* cb) { - cb->release(); - }); - cmd->commit(); - } else { - cmd->commit(); - cmd->waitUntilCompleted(); - - uint8_t* dest_data = static_cast(dest_buffer->contents()); - if (!dest_data) { - 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, - uint32_t stored_level) -> const StoredLevelHostLayout* { - for (const StoredLevelHostLayout& layout : stored_levels) { - if (layout.is_base == is_base_storage && layout.level == stored_level) { - return &layout; - } - } - return nullptr; - }; - - uint32_t bytes_per_host_block = load_shader_info.bytes_per_host_block; - - for (uint32_t level = level_first; level <= level_last; ++level) { - uint32_t stored_level = std::min(level, level_packed); - bool is_base_storage = - stored_level == 0 && (level_packed != 0 || level == 0); - const StoredLevelHostLayout* stored_layout = - find_stored_level(is_base_storage, stored_level); - if (!stored_layout) { - continue; - } - - uint32_t level_width_unscaled = std::max(width >> level, uint32_t(1)); - uint32_t level_height_unscaled = std::max(height >> level, uint32_t(1)); - uint32_t level_depth = std::max(depth >> level, uint32_t(1)); - uint32_t level_width_scaled = - level_width_unscaled * texture_resolution_scale_x; - uint32_t level_height_scaled = - level_height_unscaled * texture_resolution_scale_y; - - uint32_t upload_width = level_width_scaled; - uint32_t upload_height = level_height_scaled; - if (host_block_compressed) { - upload_width = xe::round_up(upload_width, host_block_width); - upload_height = xe::round_up(upload_height, host_block_height); - } - - uint32_t packed_offset_blocks_x = 0; - uint32_t packed_offset_blocks_y = 0; - uint32_t packed_offset_z = 0; - if (level >= level_packed) { - texture_util::GetPackedMipOffset( - width, height, depth, key.format, level, packed_offset_blocks_x, - packed_offset_blocks_y, packed_offset_z); - } - - for (uint32_t slice = 0; slice < array_size; ++slice) { - const uint8_t* slice_base = dest_data + - stored_layout->dest_offset_bytes + - slice * stored_layout->slice_size_bytes; - - const uint8_t* source_ptr = slice_base; - size_t bytes_per_image = size_t(stored_layout->row_pitch_bytes) * - stored_layout->height_blocks; - if (level >= level_packed) { - if (host_block_compressed) { - uint32_t packed_offset_blocks_x_scaled = - packed_offset_blocks_x * texture_resolution_scale_x; - uint32_t packed_offset_blocks_y_scaled = - packed_offset_blocks_y * texture_resolution_scale_y; - source_ptr += packed_offset_z * bytes_per_image; - source_ptr += - packed_offset_blocks_y_scaled * stored_layout->row_pitch_bytes; - source_ptr += packed_offset_blocks_x_scaled * bytes_per_block; - } else { - uint32_t packed_offset_texels_x = - packed_offset_blocks_x * block_width; - uint32_t packed_offset_texels_y = - packed_offset_blocks_y * block_height; - packed_offset_texels_x *= texture_resolution_scale_x; - packed_offset_texels_y *= texture_resolution_scale_y; - source_ptr += packed_offset_z * bytes_per_image; - source_ptr += - packed_offset_texels_y * stored_layout->row_pitch_bytes; - source_ptr += packed_offset_texels_x * bytes_per_host_block; - } - } - - if (dimension == xenos::DataDimension::k3D) { - MTL::Region region = MTL::Region::Make3D(0, 0, 0, upload_width, - upload_height, level_depth); - mtl_texture->replaceRegion(region, level, 0, source_ptr, - stored_layout->row_pitch_bytes, - bytes_per_image); - } else { - MTL::Region region = - MTL::Region::Make2D(0, 0, upload_width, upload_height); - mtl_texture->replaceRegion(region, level, slice, source_ptr, - stored_layout->row_pitch_bytes, 0); - } - } - } - } - - if (!use_blit_upload) { - release_buffer_immediate(constants_buffer, constants_buffer_size); - release_buffer_immediate(dest_buffer, size_t(dest_buffer_size)); - } - - 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"); - - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE( - "Metal texture cache: Failed to get Metal device from command " - "processor"); - return false; - } - if (::cvars::metal_texture_cache_use_private && - !::cvars::metal_texture_upload_via_blit) { - XELOGW( - "Metal texture cache: private textures requested but blit uploads " - "disabled; forcing shared textures"); - } - - upload_buffer_pool_ = std::make_shared(device); - if (::cvars::metal_use_heaps) { - 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"); - } - - InitializeNorm16Selection(device); - - // Create null textures following existing factory pattern - null_texture_2d_ = CreateNullTexture2D(); - null_texture_3d_ = CreateNullTexture3D(); - null_texture_cube_ = CreateNullTextureCube(); - - if (!null_texture_2d_ || !null_texture_3d_ || !null_texture_cube_) { - XELOGE("Failed to create null textures"); - return false; - } - - if (!InitializeLoadPipelines()) { - XELOGE("Metal texture cache: Failed to initialize texture_load pipelines"); - return false; - } - - XELOGD( - "Metal texture cache: Initialized successfully (null textures + GPU " - "texture_load pipelines)"); - - return true; -} - -bool MetalTextureCache::InitializeLoadPipelines() { - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - return false; - } - - NS::Error* error = nullptr; - - auto create_pipeline_from_metallib = - [&](const uint8_t* data, size_t size) -> MTL::ComputePipelineState* { - if (!data || !size) { - return nullptr; - } - dispatch_data_t dispatch_data = dispatch_data_create( - data, size, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT); - MTL::Library* lib = device->newLibrary(dispatch_data, &error); - dispatch_release(dispatch_data); - if (!lib) { - XELOGE("MetalTextureCache: failed to create texture_load library: {}", - 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( - "MetalTextureCache: texture_load metallib missing entry_xe " - "function"); - lib->release(); - return nullptr; - } - MTL::ComputePipelineState* pipeline = - device->newComputePipelineState(fn, &error); - fn->release(); - lib->release(); - if (!pipeline) { - XELOGE("MetalTextureCache: failed to create texture_load pipeline: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - return nullptr; - } - return pipeline; - }; - - auto init_pipeline = [&](TextureCache::LoadShaderIndex index, - const uint8_t* data, size_t size) -> void { - load_pipelines_[index] = create_pipeline_from_metallib(data, size); - }; - auto init_pipeline_scaled = [&](TextureCache::LoadShaderIndex index, - const uint8_t* data, size_t size) -> void { - load_pipelines_scaled_[index] = create_pipeline_from_metallib(data, size); - }; - - init_pipeline(TextureCache::kLoadShaderIndex8bpb, - texture_load_8bpb_cs_metallib, - sizeof(texture_load_8bpb_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndex8bpb, - texture_load_8bpb_scaled_cs_metallib, - sizeof(texture_load_8bpb_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndex16bpb, - texture_load_16bpb_cs_metallib, - sizeof(texture_load_16bpb_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndex16bpb, - texture_load_16bpb_scaled_cs_metallib, - sizeof(texture_load_16bpb_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndex32bpb, - texture_load_32bpb_cs_metallib, - sizeof(texture_load_32bpb_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndex32bpb, - texture_load_32bpb_scaled_cs_metallib, - sizeof(texture_load_32bpb_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndex64bpb, - texture_load_64bpb_cs_metallib, - sizeof(texture_load_64bpb_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndex64bpb, - texture_load_64bpb_scaled_cs_metallib, - sizeof(texture_load_64bpb_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndex128bpb, - texture_load_128bpb_cs_metallib, - sizeof(texture_load_128bpb_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndex128bpb, - texture_load_128bpb_scaled_cs_metallib, - sizeof(texture_load_128bpb_scaled_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexR5G5B5A1ToB5G5R5A1, - texture_load_r5g5b5a1_b5g5r5a1_cs_metallib, - sizeof(texture_load_r5g5b5a1_b5g5r5a1_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR5G5B5A1ToB5G5R5A1, - texture_load_r5g5b5a1_b5g5r5a1_scaled_cs_metallib, - sizeof(texture_load_r5g5b5a1_b5g5r5a1_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR5G6B5ToB5G6R5, - texture_load_r5g6b5_b5g6r5_cs_metallib, - sizeof(texture_load_r5g6b5_b5g6r5_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndexR5G6B5ToB5G6R5, - texture_load_r5g6b5_b5g6r5_scaled_cs_metallib, - sizeof(texture_load_r5g6b5_b5g6r5_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle, - texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs_metallib, - sizeof(texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle, - texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs_metallib, - sizeof(texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexR10G11B11ToRGBA16, - texture_load_r10g11b11_rgba16_cs_metallib, - sizeof(texture_load_r10g11b11_rgba16_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR10G11B11ToRGBA16, - texture_load_r10g11b11_rgba16_scaled_cs_metallib, - sizeof(texture_load_r10g11b11_rgba16_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR10G11B11ToRGBA16SNorm, - texture_load_r10g11b11_rgba16_snorm_cs_metallib, - sizeof(texture_load_r10g11b11_rgba16_snorm_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR10G11B11ToRGBA16SNorm, - texture_load_r10g11b11_rgba16_snorm_scaled_cs_metallib, - sizeof(texture_load_r10g11b11_rgba16_snorm_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR11G11B10ToRGBA16, - texture_load_r11g11b10_rgba16_cs_metallib, - sizeof(texture_load_r11g11b10_rgba16_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR11G11B10ToRGBA16, - texture_load_r11g11b10_rgba16_scaled_cs_metallib, - sizeof(texture_load_r11g11b10_rgba16_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR11G11B10ToRGBA16SNorm, - texture_load_r11g11b10_rgba16_snorm_cs_metallib, - sizeof(texture_load_r11g11b10_rgba16_snorm_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexR11G11B10ToRGBA16SNorm, - texture_load_r11g11b10_rgba16_snorm_scaled_cs_metallib, - sizeof(texture_load_r11g11b10_rgba16_snorm_scaled_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexR16UNormToFloat, - texture_load_r16_unorm_float_cs_metallib, - sizeof(texture_load_r16_unorm_float_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndexR16UNormToFloat, - texture_load_r16_unorm_float_scaled_cs_metallib, - sizeof(texture_load_r16_unorm_float_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexR16SNormToFloat, - texture_load_r16_snorm_float_cs_metallib, - sizeof(texture_load_r16_snorm_float_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndexR16SNormToFloat, - texture_load_r16_snorm_float_scaled_cs_metallib, - sizeof(texture_load_r16_snorm_float_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexRG16UNormToFloat, - texture_load_rg16_unorm_float_cs_metallib, - sizeof(texture_load_rg16_unorm_float_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRG16UNormToFloat, - texture_load_rg16_unorm_float_scaled_cs_metallib, - sizeof(texture_load_rg16_unorm_float_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexRG16SNormToFloat, - texture_load_rg16_snorm_float_cs_metallib, - sizeof(texture_load_rg16_snorm_float_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRG16SNormToFloat, - texture_load_rg16_snorm_float_scaled_cs_metallib, - sizeof(texture_load_rg16_snorm_float_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexRGBA16UNormToFloat, - texture_load_rgba16_unorm_float_cs_metallib, - sizeof(texture_load_rgba16_unorm_float_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRGBA16UNormToFloat, - texture_load_rgba16_unorm_float_scaled_cs_metallib, - sizeof(texture_load_rgba16_unorm_float_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexRGBA16SNormToFloat, - texture_load_rgba16_snorm_float_cs_metallib, - sizeof(texture_load_rgba16_snorm_float_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRGBA16SNormToFloat, - texture_load_rgba16_snorm_float_scaled_cs_metallib, - sizeof(texture_load_rgba16_snorm_float_scaled_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexRGBA4ToBGRA4, - texture_load_r4g4b4a4_b4g4r4a4_cs_metallib, - sizeof(texture_load_r4g4b4a4_b4g4r4a4_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRGBA4ToBGRA4, - texture_load_r4g4b4a4_b4g4r4a4_scaled_cs_metallib, - sizeof(texture_load_r4g4b4a4_b4g4r4a4_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexRGBA4ToARGB4, - texture_load_r4g4b4a4_a4r4g4b4_cs_metallib, - sizeof(texture_load_r4g4b4a4_a4r4g4b4_cs_metallib)); - init_pipeline_scaled( - TextureCache::kLoadShaderIndexRGBA4ToARGB4, - texture_load_r4g4b4a4_a4r4g4b4_scaled_cs_metallib, - sizeof(texture_load_r4g4b4a4_a4r4g4b4_scaled_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexGBGR8ToGRGB8, - texture_load_gbgr8_grgb8_cs_metallib, - sizeof(texture_load_gbgr8_grgb8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexGBGR8ToRGB8, - texture_load_gbgr8_rgb8_cs_metallib, - sizeof(texture_load_gbgr8_rgb8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexBGRG8ToRGBG8, - texture_load_bgrg8_rgbg8_cs_metallib, - sizeof(texture_load_bgrg8_rgbg8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexBGRG8ToRGB8, - texture_load_bgrg8_rgb8_cs_metallib, - sizeof(texture_load_bgrg8_rgb8_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexDXT1ToRGBA8, - texture_load_dxt1_rgba8_cs_metallib, - sizeof(texture_load_dxt1_rgba8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT3A, - texture_load_dxt3a_cs_metallib, - sizeof(texture_load_dxt3a_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT3AAs1111ToBGRA4, - texture_load_dxt3aas1111_bgra4_cs_metallib, - sizeof(texture_load_dxt3aas1111_bgra4_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT3AAs1111ToARGB4, - texture_load_dxt3aas1111_argb4_cs_metallib, - sizeof(texture_load_dxt3aas1111_argb4_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT3ToRGBA8, - texture_load_dxt3_rgba8_cs_metallib, - sizeof(texture_load_dxt3_rgba8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT5ToRGBA8, - texture_load_dxt5_rgba8_cs_metallib, - sizeof(texture_load_dxt5_rgba8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXT5AToR8, - texture_load_dxt5a_r8_cs_metallib, - sizeof(texture_load_dxt5a_r8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDXNToRG8, - texture_load_dxn_rg8_cs_metallib, - sizeof(texture_load_dxn_rg8_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexCTX1, - texture_load_ctx1_cs_metallib, - sizeof(texture_load_ctx1_cs_metallib)); - - init_pipeline(TextureCache::kLoadShaderIndexDepthUnorm, - texture_load_depth_unorm_cs_metallib, - sizeof(texture_load_depth_unorm_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndexDepthUnorm, - texture_load_depth_unorm_scaled_cs_metallib, - sizeof(texture_load_depth_unorm_scaled_cs_metallib)); - init_pipeline(TextureCache::kLoadShaderIndexDepthFloat, - texture_load_depth_float_cs_metallib, - sizeof(texture_load_depth_float_cs_metallib)); - init_pipeline_scaled(TextureCache::kLoadShaderIndexDepthFloat, - texture_load_depth_float_scaled_cs_metallib, - sizeof(texture_load_depth_float_scaled_cs_metallib)); - - // Require at least the common loaders. - return load_pipelines_[TextureCache::kLoadShaderIndex32bpb] != nullptr && - load_pipelines_[TextureCache::kLoadShaderIndex16bpb] != nullptr && - load_pipelines_[TextureCache::kLoadShaderIndex8bpb] != nullptr; -} - -void MetalTextureCache::InitializeNorm16Selection(MTL::Device* device) { - r16_selection_.unsigned_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatR16Unorm); - r16_selection_.signed_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatR16Snorm); - - rg16_selection_.unsigned_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatRG16Unorm); - rg16_selection_.signed_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatRG16Snorm); - - rgba16_selection_.unsigned_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatRGBA16Unorm); - rgba16_selection_.signed_uses_float = - !SupportsPixelFormat(device, MTL::PixelFormatRGBA16Snorm); -} - -void MetalTextureCache::Shutdown() { - SCOPE_profile_cpu_f("gpu"); - - ClearCache(); - - for (size_t i = 0; i < kLoadShaderCount; ++i) { - if (load_pipelines_[i]) { - load_pipelines_[i]->release(); - load_pipelines_[i] = nullptr; - } - if (load_pipelines_scaled_[i]) { - load_pipelines_scaled_[i]->release(); - load_pipelines_scaled_[i] = nullptr; - } - } - - // Follow existing shutdown pattern - explicit null checks and release - if (null_texture_2d_) { - null_texture_2d_->release(); - null_texture_2d_ = nullptr; - } - if (null_texture_3d_) { - null_texture_3d_->release(); - null_texture_3d_ = nullptr; - } - if (null_texture_cube_) { - null_texture_cube_->release(); - null_texture_cube_ = nullptr; - } - - if (upload_buffer_pool_) { - upload_buffer_pool_->Shutdown(); - upload_buffer_pool_.reset(); - } - if (texture_heap_pool_) { - texture_heap_pool_->Shutdown(); - texture_heap_pool_.reset(); - } - - XELOGD("Metal texture cache: Shutdown complete"); -} - -void MetalTextureCache::ClearScaledResolveBuffers() { - for (auto& buffer : scaled_resolve_buffers_) { - if (buffer.buffer) { - buffer.buffer->release(); - buffer.buffer = nullptr; - } - } - scaled_resolve_buffers_.clear(); - for (auto& buffer : scaled_resolve_retired_buffers_) { - if (buffer.buffer) { - buffer.buffer->release(); - buffer.buffer = nullptr; - } - } - scaled_resolve_retired_buffers_.clear(); - scaled_resolve_current_buffer_index_ = size_t(-1); - scaled_resolve_current_range_start_scaled_ = 0; - scaled_resolve_current_range_length_scaled_ = 0; -} - -void MetalTextureCache::ClearCache() { - SCOPE_profile_cpu_f("gpu"); - - for (auto& sampler_pair : sampler_cache_) { - if (sampler_pair.second) { - sampler_pair.second->release(); - } - } - sampler_cache_.clear(); - ClearScaledResolveBuffers(); - - 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::Texture* MetalTextureCache::GetTexture2D(const TextureInfo& texture_info) { - // Legacy method - now uses base class texture management - // This method is kept for compatibility but should be migrated to use - // the standard texture binding flow via RequestTextures - XELOGD("GetTexture2D: Legacy method called - use RequestTextures instead"); - return null_texture_2d_; -} - -MTL::Texture* MetalTextureCache::GetTextureCube( - const TextureInfo& texture_info) { - // Legacy method - now uses base class texture management - // This method is kept for compatibility but should be migrated to use - // the standard texture binding flow via RequestTextures - XELOGD("GetTextureCube: Legacy method called - use RequestTextures instead"); - return null_texture_cube_; -} - -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; - } -} - -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 - // textures. - array_length = std::max(array_length, 1u); - - MTL::TextureDescriptor* descriptor = MTL::TextureDescriptor::alloc()->init(); - descriptor->setTextureType(MTL::TextureType2DArray); - descriptor->setPixelFormat(format); - descriptor->setWidth(width); - descriptor->setHeight(std::max(height, 1u)); - descriptor->setDepth(1); - descriptor->setArrayLength(array_length); - descriptor->setMipmapLevelCount(mip_levels); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - 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(); - - if (!texture) { - XELOGE( - "Metal texture cache: Failed to create 2D array texture {}x{} (layers " - "{})", - width, height, array_length); - return nullptr; - } - - return texture; -} - -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(); - descriptor->setTextureType(MTL::TextureType3D); - descriptor->setPixelFormat(format); - descriptor->setWidth(width); - descriptor->setHeight(std::max(height, 1u)); - descriptor->setDepth(depth); - descriptor->setArrayLength(1); - descriptor->setMipmapLevelCount(mip_levels); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - 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(); - - if (!texture) { - XELOGE("Metal texture cache: Failed to create 3D texture {}x{}x{}", width, - height, depth); - return nullptr; - } - - return texture; -} - -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(); - // Always use TextureTypeCubeArray to match the shader binding type (which is - // always texturecube_array in the translated MSL). - descriptor->setTextureType(MTL::TextureTypeCubeArray); - descriptor->setArrayLength(std::max(cube_count, 1u)); - descriptor->setPixelFormat(format); - descriptor->setWidth(width); - descriptor->setHeight(width); - descriptor->setDepth(1); - descriptor->setMipmapLevelCount(mip_levels); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - 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(); - - if (!texture) { - XELOGE("Metal texture cache: Failed to create Cube texture {}x{}", width, - width); - return nullptr; - } - - return texture; -} - -bool MetalTextureCache::UpdateTexture2D(MTL::Texture* texture, - const TextureInfo& texture_info) { - // Legacy method - memory access will be handled by base class during - // RequestTextures For now, return success to avoid build errors. Real texture - // loading happens in LoadTextureDataFromResidentMemoryImpl which is called by - // the base class. - XELOGD( - "UpdateTexture2D: Legacy method called - base class handles memory " - "access"); - return true; -} - -bool MetalTextureCache::UpdateTextureCube(MTL::Texture* texture, - const TextureInfo& texture_info) { - // Legacy method - memory access will be handled by base class during - // RequestTextures For now, return success to avoid build errors. Real texture - // loading happens in LoadTextureDataFromResidentMemoryImpl which is called by - // the base class. - XELOGD( - "UpdateTextureCube: Legacy method called - base class handles memory " - "access"); - return true; -} - -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. - descriptor->setTextureType(MTL::TextureType2DArray); - descriptor->setPixelFormat(MTL::PixelFormatRGBA8Unorm); - descriptor->setWidth(1); - descriptor->setHeight(1); - descriptor->setArrayLength(1); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - descriptor->setStorageMode(MTL::StorageModeShared); - - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - - if (texture) { - // Initialize with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; - MTL::Region region = MTL::Region::Make2D(0, 0, 1, 1); - texture->replaceRegion(region, 0, &default_color, 4); - } else { - XELOGE("Failed to create null 2D texture"); - } - - return texture; // No retain needed - newTexture returns retained object -} - -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); - descriptor->setWidth(1); - descriptor->setHeight(1); - descriptor->setDepth(1); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - descriptor->setStorageMode(MTL::StorageModeShared); - - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - - if (texture) { - // Initialize with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; - MTL::Region region = MTL::Region::Make3D(0, 0, 0, 1, 1, 1); - texture->replaceRegion(region, 0, 0, &default_color, 4, 4); - } else { - XELOGE("Failed to create null 3D texture"); - } - - return texture; // No retain needed - newTexture returns retained object -} - -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(); - // Always create as CubeArray for binding compatibility. - descriptor->setTextureType(MTL::TextureTypeCubeArray); - descriptor->setPixelFormat(MTL::PixelFormatRGBA8Unorm); - descriptor->setWidth(1); - descriptor->setHeight(1); - descriptor->setDepth(1); - descriptor->setArrayLength(1); - descriptor->setUsage(MTL::TextureUsageShaderRead | - MTL::TextureUsagePixelFormatView); - descriptor->setStorageMode(MTL::StorageModeShared); - - MTL::Texture* texture = device->newTexture(descriptor); - descriptor->release(); // Immediate release following pattern - - if (texture) { - // Initialize all 6 faces with black color (0xFF000000 for RGBA8) - uint32_t default_color = 0xFF000000; - 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); - } - } else { - XELOGE("Failed to create null cube texture"); - } - - return texture; // No retain needed - newTexture returns retained object -} - -// RequestTextures override - integrates with standard texture binding pipeline -void MetalTextureCache::RequestTextures(uint32_t used_texture_mask) { - SCOPE_profile_cpu_f("gpu"); - - // Call base class implementation first - TextureCache::RequestTextures(used_texture_mask); - - // 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{}; - - auto get_null_texture_for_dimension = [&]() -> MTL::Texture* { - switch (dimension) { - case xenos::FetchOpDimension::k1D: - case xenos::FetchOpDimension::k2D: - return null_texture_2d_; - case xenos::FetchOpDimension::k3DOrStacked: - return null_texture_3d_; - case xenos::FetchOpDimension::kCube: - return null_texture_cube_; - default: - return null_texture_2d_; - } - }; - - 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(); - } - - if (!AreDimensionsCompatible(dimension, binding->key.dimension)) { - return get_null_texture_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) { - 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(); - } - - 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); - } - } - return result ? result : get_null_texture_for_dimension(); -} - -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, - kView = 1, - kLoad = 2, - kScaledResolve = 3, - kInvalidPixelFormat = 4, - kUnsupportedPixelFormat = 5, - kMissingLoadShader = 6, - kMissingPipeline = 7, - }; - auto log_swap_failure_once = - [&](SwapFailure reason, const TextureKey& log_key, const char* detail) { - static std::unordered_set logged_failures; - uint64_t tag = (uint64_t(reason) << 56) | - (uint64_t(log_key.format) << 48) | - (uint64_t(log_key.dimension) << 40) | - (uint64_t(log_key.scaled_resolve) << 39) | - (uint64_t(log_key.endianness) << 37) | - (uint64_t(log_key.signed_separate) << 36); - if (!logged_failures.insert(tag).second) { - return; - } - XELOGW("MetalSwap: request failed: {}", detail); - XELOGW( - "MetalSwap: base=0x{:X} mip=0x{:X} {}x{} pitch={} mip_levels={} " - "format={} dim={} scaled={} endian={} signed={}", - log_key.base_page << 12, log_key.mip_page << 12, log_key.GetWidth(), - log_key.GetHeight(), log_key.pitch, log_key.mip_max_level + 1, - static_cast(log_key.format), - static_cast(log_key.dimension), - log_key.scaled_resolve ? 1 : 0, - static_cast(log_key.endianness), - log_key.signed_separate ? 1 : 0); - }; - - const auto& regs = register_file(); - xenos::xe_gpu_texture_fetch_t fetch = regs.GetTextureFetch(0); - TextureKey key; - BindingInfoFromFetchConstant(fetch, key, nullptr); - if (!key.is_valid || key.base_page == 0 || - key.dimension != xenos::DataDimension::k2DOrStacked) { - if (!logged_invalid) { - XELOGW("MetalSwap: fetch0 invalid (valid={}, base_page=0x{:X}, dim={})", - key.is_valid ? 1 : 0, key.base_page, - static_cast(key.dimension)); - logged_invalid = true; - } - return nullptr; - } - - auto* texture = static_cast(FindOrCreateTexture(key)); - if (!texture) { - log_swap_failure_once(SwapFailure::kCreateTexture, key, - "failed to create swap texture"); - return nullptr; - } - - uint32_t host_swizzle = - GuestToHostSwizzle(fetch.swizzle, GetHostFormatSwizzle(key)); - MTL::Texture* view = texture->GetOrCreateView( - host_swizzle, xenos::FetchOpDimension::k2D, false); - if (!view) { - log_swap_failure_once(SwapFailure::kView, key, - "failed to create swap texture view"); - return nullptr; - } - - if (!LoadTextureData(*texture)) { - bool logged_reason = false; - if (key.scaled_resolve && !IsScaledResolveSupportedForFormat(key)) { - log_swap_failure_once(SwapFailure::kScaledResolve, key, - "scaled resolve not supported"); - logged_reason = true; - } - MTL::PixelFormat pixel_format = GetPixelFormatForKey(key); - if (pixel_format == MTL::PixelFormatInvalid) { - log_swap_failure_once(SwapFailure::kInvalidPixelFormat, key, - "invalid Metal pixel format"); - logged_reason = true; - } - MTL::Device* device = - command_processor_ ? command_processor_->GetMetalDevice() : nullptr; - if (device && !SupportsPixelFormat(device, pixel_format)) { - log_swap_failure_once(SwapFailure::kUnsupportedPixelFormat, key, - "unsupported Metal pixel format"); - logged_reason = true; - } - TextureCache::LoadShaderIndex load_shader = GetLoadShaderIndexForKey(key); - if (load_shader == TextureCache::kLoadShaderIndexUnknown) { - log_swap_failure_once(SwapFailure::kMissingLoadShader, key, - "missing load shader"); - logged_reason = true; - } else { - MTL::ComputePipelineState* load_pipeline = - key.scaled_resolve - ? load_pipelines_scaled_[static_cast(load_shader)] - : load_pipelines_[static_cast(load_shader)]; - if (!load_pipeline) { - log_swap_failure_once(SwapFailure::kMissingPipeline, key, - "missing load pipeline"); - logged_reason = true; - } - } - if (!logged_reason) { - log_swap_failure_once(SwapFailure::kLoad, key, "LoadTextureData failed"); - } - return nullptr; - } - - texture->MarkAsUsed(); - key = texture->key(); - width_scaled_out = - key.GetWidth() * (key.scaled_resolve ? draw_resolution_scale_x() : 1); - 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; -} - -MetalTextureCache::SamplerParameters MetalTextureCache::GetSamplerParameters( - const DxbcShader::SamplerBinding& binding) const { - const RegisterFile& regs = register_file(); - xenos::xe_gpu_texture_fetch_t fetch = - regs.GetTextureFetch(binding.fetch_constant); - - SamplerParameters parameters; - - xenos::ClampMode fetch_clamp_x, fetch_clamp_y, fetch_clamp_z; - texture_util::GetClampModesForDimension(fetch, fetch_clamp_x, fetch_clamp_y, - fetch_clamp_z); - parameters.clamp_x = NormalizeClampMode(fetch_clamp_x); - parameters.clamp_y = NormalizeClampMode(fetch_clamp_y); - parameters.clamp_z = NormalizeClampMode(fetch_clamp_z); - - if (xenos::ClampModeUsesBorder(parameters.clamp_x) || - xenos::ClampModeUsesBorder(parameters.clamp_y) || - xenos::ClampModeUsesBorder(parameters.clamp_z)) { - parameters.border_color = fetch.border_color; - } else { - 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); - parameters.mip_min_level = mip_min_level; - - xenos::AnisoFilter aniso_filter = - binding.aniso_filter == xenos::AnisoFilter::kUseFetchConst - ? fetch.aniso_filter - : binding.aniso_filter; - aniso_filter = std::min(aniso_filter, xenos::AnisoFilter::kMax_16_1); - parameters.aniso_filter = aniso_filter; - - xenos::TextureFilter mip_filter = - binding.mip_filter == xenos::TextureFilter::kUseFetchConst - ? fetch.mip_filter - : binding.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 = - binding.mag_filter == xenos::TextureFilter::kUseFetchConst - ? fetch.mag_filter - : binding.mag_filter; - parameters.mag_linear = mag_filter == xenos::TextureFilter::kLinear; - - xenos::TextureFilter min_filter = - binding.min_filter == xenos::TextureFilter::kUseFetchConst - ? fetch.min_filter - : binding.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; - - return parameters; -} - -MTL::SamplerState* MetalTextureCache::GetOrCreateSampler( - SamplerParameters parameters) { - auto it = sampler_cache_.find(parameters.value); - if (it != sampler_cache_.end()) { - return it->second; - } - - MTL::SamplerDescriptor* desc = MTL::SamplerDescriptor::alloc()->init(); - if (!desc) { - XELOGE("Failed to allocate Metal sampler descriptor"); - return nullptr; - } - - auto convert_clamp = [](xenos::ClampMode mode) { - switch (mode) { - case xenos::ClampMode::kRepeat: - return MTL::SamplerAddressModeRepeat; - case xenos::ClampMode::kMirroredRepeat: - return MTL::SamplerAddressModeMirrorRepeat; - case xenos::ClampMode::kClampToEdge: - return MTL::SamplerAddressModeClampToEdge; - case xenos::ClampMode::kMirrorClampToEdge: - return MTL::SamplerAddressModeMirrorClampToEdge; - case xenos::ClampMode::kClampToBorder: - return MTL::SamplerAddressModeClampToBorderColor; - default: - return MTL::SamplerAddressModeClampToEdge; - } - }; - - if (parameters.aniso_filter != xenos::AnisoFilter::kDisabled) { - desc->setMinFilter(MTL::SamplerMinMagFilterLinear); - desc->setMagFilter(MTL::SamplerMinMagFilterLinear); - desc->setMipFilter(MTL::SamplerMipFilterLinear); - desc->setMaxAnisotropy(1u << (uint32_t(parameters.aniso_filter) - 1)); - } else { - desc->setMinFilter(parameters.min_linear ? MTL::SamplerMinMagFilterLinear - : MTL::SamplerMinMagFilterNearest); - desc->setMagFilter(parameters.mag_linear ? MTL::SamplerMinMagFilterLinear - : MTL::SamplerMinMagFilterNearest); - desc->setMipFilter(parameters.mip_linear ? MTL::SamplerMipFilterLinear - : MTL::SamplerMipFilterNearest); - desc->setMaxAnisotropy(1); - } - - desc->setSAddressMode(convert_clamp(xenos::ClampMode(parameters.clamp_x))); - desc->setTAddressMode(convert_clamp(xenos::ClampMode(parameters.clamp_y))); - desc->setRAddressMode(convert_clamp(xenos::ClampMode(parameters.clamp_z))); - - switch (parameters.border_color) { - case xenos::BorderColor::k_ABGR_White: - desc->setBorderColor(MTL::SamplerBorderColorOpaqueWhite); - break; - case xenos::BorderColor::k_ABGR_Black: - desc->setBorderColor(MTL::SamplerBorderColorTransparentBlack); - break; - default: - desc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); - break; - } - - desc->setLodMinClamp(static_cast(parameters.mip_min_level)); - float max_lod = parameters.mip_base_map - ? static_cast(parameters.mip_min_level) - : FLT_MAX; - if (parameters.mip_base_map && - parameters.aniso_filter == xenos::AnisoFilter::kDisabled && - !parameters.mip_linear) { - max_lod += 0.25f; - } - desc->setLodMaxClamp(max_lod); - desc->setLodAverage(false); - desc->setSupportArgumentBuffers(true); - - MTL::SamplerState* sampler_state = - command_processor_->GetMetalDevice()->newSamplerState(desc); - desc->release(); - - if (!sampler_state) { - XELOGE("Failed to create Metal sampler state"); - return nullptr; - } - - sampler_cache_.emplace(parameters.value, sampler_state); - return sampler_state; -} - -xenos::ClampMode MetalTextureCache::NormalizeClampMode( - xenos::ClampMode clamp_mode) const { - if (clamp_mode == xenos::ClampMode::kClampToHalfway) { - return xenos::ClampMode::kClampToEdge; - } - if (clamp_mode == xenos::ClampMode::kMirrorClampToHalfway || - clamp_mode == xenos::ClampMode::kMirrorClampToBorder) { - return xenos::ClampMode::kMirrorClampToEdge; - } - return clamp_mode; -} - -// GetHostFormatSwizzle implementation -uint32_t MetalTextureCache::GetHostFormatSwizzle(TextureKey key) const { - switch (key.format) { - case xenos::TextureFormat::k_8: - case xenos::TextureFormat::k_8_A: - case xenos::TextureFormat::k_8_B: - case xenos::TextureFormat::k_DXT3A: - case xenos::TextureFormat::k_DXT5A: - case xenos::TextureFormat::k_16: - case xenos::TextureFormat::k_16_EXPAND: - case xenos::TextureFormat::k_16_FLOAT: - case xenos::TextureFormat::k_24_8: - case xenos::TextureFormat::k_24_8_FLOAT: - case xenos::TextureFormat::k_32_FLOAT: - return xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR; - - case xenos::TextureFormat::k_8_8: - case xenos::TextureFormat::k_16_16: - case xenos::TextureFormat::k_16_16_EXPAND: - case xenos::TextureFormat::k_16_16_FLOAT: - case xenos::TextureFormat::k_DXN: - case xenos::TextureFormat::k_32_32_FLOAT: - return xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG; - - case xenos::TextureFormat::k_5_6_5: - case xenos::TextureFormat::k_10_11_11: - case xenos::TextureFormat::k_11_11_10: - return xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB; - - 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: - // Stored as BGRA after endian swap; CPU path swaps to RGBA8. - return xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA; - - default: - return xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA; - } -} - -bool MetalTextureCache::IsSignedVersionSeparateForFormat(TextureKey key) const { - switch (key.format) { - case xenos::TextureFormat::k_16: - return r16_selection_.unsigned_uses_float || - r16_selection_.signed_uses_float; - case xenos::TextureFormat::k_16_16: - return rg16_selection_.unsigned_uses_float || - rg16_selection_.signed_uses_float; - case xenos::TextureFormat::k_16_16_16_16: - return rgba16_selection_.unsigned_uses_float || - rgba16_selection_.signed_uses_float; - case xenos::TextureFormat::k_10_11_11: - case xenos::TextureFormat::k_11_11_10: - return true; - default: - return false; - } -} - -bool MetalTextureCache::IsScaledResolveSupportedForFormat( - TextureKey key) const { - LoadShaderIndex load_shader = GetLoadShaderIndexForKey(key); - return load_shader != kLoadShaderIndexUnknown && - load_pipelines_scaled_[load_shader] != nullptr; -} - -bool MetalTextureCache::EnsureScaledResolveMemoryCommitted( - uint32_t start_unscaled, uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2) { - if (!IsDrawResolutionScaled()) { - return false; - } - if (!length_unscaled) { - return true; - } - - uint64_t start_scaled = 0; - uint64_t length_scaled = 0; - if (!GetScaledResolveRange(start_unscaled, length_unscaled, - length_scaled_alignment_log2, start_scaled, - length_scaled)) { - return false; - } - return EnsureScaledResolveBufferRange(start_scaled, length_scaled); -} - -bool MetalTextureCache::MakeScaledResolveRangeCurrent( - uint32_t start_unscaled, uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2) { - if (!IsDrawResolutionScaled()) { - return false; - } - if (!length_unscaled) { - return false; - } - - uint64_t start_scaled = 0; - uint64_t length_scaled = 0; - if (!GetScaledResolveRange(start_unscaled, length_unscaled, - length_scaled_alignment_log2, start_scaled, - length_scaled)) { - return false; - } - if (!length_scaled) { - return false; - } - - uint64_t end_scaled = start_scaled + length_scaled - 1; - for (size_t i = scaled_resolve_buffers_.size(); i-- > 0;) { - const ScaledResolveBuffer& buffer = scaled_resolve_buffers_[i]; - uint64_t buffer_end = buffer.base_scaled + buffer.length_scaled - 1; - if (start_scaled >= buffer.base_scaled && end_scaled <= buffer_end) { - scaled_resolve_current_buffer_index_ = i; - scaled_resolve_current_range_start_scaled_ = start_scaled; - scaled_resolve_current_range_length_scaled_ = length_scaled; - return true; - } - } - return false; -} - -bool MetalTextureCache::GetCurrentScaledResolveBuffer( - MTL::Buffer*& buffer_out, size_t& buffer_offset_out, - size_t& buffer_length_out) const { - if (scaled_resolve_current_buffer_index_ == size_t(-1) || - scaled_resolve_current_buffer_index_ >= scaled_resolve_buffers_.size()) { - return false; - } - const ScaledResolveBuffer& buffer = - scaled_resolve_buffers_[scaled_resolve_current_buffer_index_]; - uint64_t offset = - scaled_resolve_current_range_start_scaled_ - buffer.base_scaled; - uint64_t end_offset = offset + scaled_resolve_current_range_length_scaled_; - if (end_offset > buffer.length_scaled) { - return false; - } - if (offset > std::numeric_limits::max() || - scaled_resolve_current_range_length_scaled_ > - std::numeric_limits::max()) { - return false; - } - buffer_out = buffer.buffer; - buffer_offset_out = size_t(offset); - buffer_length_out = size_t(scaled_resolve_current_range_length_scaled_); - return buffer_out != nullptr; -} - -bool MetalTextureCache::GetScaledResolveRange( - uint32_t start_unscaled, uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2, uint64_t& start_scaled_out, - uint64_t& length_scaled_out) const { - if (!length_unscaled) { - start_scaled_out = 0; - length_scaled_out = 0; - return true; - } - if (start_unscaled >= SharedMemory::kBufferSize || - (SharedMemory::kBufferSize - start_unscaled) < length_unscaled) { - return false; - } - - uint32_t scale_area = draw_resolution_scale_x() * draw_resolution_scale_y(); - uint64_t start_scaled = uint64_t(start_unscaled) * scale_area; - uint64_t end_scaled = - uint64_t(start_unscaled + (length_unscaled - 1)) * scale_area; - if (length_scaled_alignment_log2) { - uint64_t alignment_mask = (uint64_t(1) << length_scaled_alignment_log2) - 1; - end_scaled = (end_scaled + alignment_mask) & ~alignment_mask; - } - start_scaled_out = start_scaled; - length_scaled_out = end_scaled - start_scaled + 1; - return true; -} - -bool MetalTextureCache::EnsureScaledResolveBufferRange(uint64_t start_scaled, - uint64_t length_scaled) { - if (!length_scaled) { - return true; - } - uint64_t end_scaled = start_scaled + length_scaled - 1; - - for (const ScaledResolveBuffer& buffer : scaled_resolve_buffers_) { - uint64_t buffer_end = buffer.base_scaled + buffer.length_scaled - 1; - if (start_scaled >= buffer.base_scaled && end_scaled <= buffer_end) { - return true; - } - } - - uint64_t new_base_scaled = start_scaled; - uint64_t new_end_scaled = end_scaled; - std::vector overlap_indices; - overlap_indices.reserve(scaled_resolve_buffers_.size()); - - for (size_t i = 0; i < scaled_resolve_buffers_.size(); ++i) { - const ScaledResolveBuffer& buffer = scaled_resolve_buffers_[i]; - uint64_t buffer_end = buffer.base_scaled + buffer.length_scaled - 1; - if (buffer.base_scaled <= end_scaled && buffer_end >= start_scaled) { - overlap_indices.push_back(i); - new_base_scaled = std::min(new_base_scaled, buffer.base_scaled); - new_end_scaled = std::max(new_end_scaled, buffer_end); - } - } - - uint64_t new_length_scaled = new_end_scaled - new_base_scaled + 1; - new_length_scaled = xe::align(new_length_scaled, uint64_t(16)); - if (new_length_scaled > std::numeric_limits::max()) { - XELOGE("Metal scaled resolve: buffer size too large ({} bytes)", - new_length_scaled); - return false; - } - - MTL::Device* device = command_processor_->GetMetalDevice(); - if (!device) { - XELOGE("Metal scaled resolve: missing Metal device"); - return false; - } - if (new_length_scaled > device->maxBufferLength()) { - XELOGE("Metal scaled resolve: requested {} bytes exceeds maxBufferLength", - new_length_scaled); - return false; - } - - MTL::Buffer* new_buffer = device->newBuffer(size_t(new_length_scaled), - MTL::ResourceStorageModePrivate); - if (!new_buffer) { - XELOGE("Metal scaled resolve: failed to allocate {} bytes", - new_length_scaled); - return false; - } - new_buffer->setLabel( - NS::String::string("XeniaScaledResolveBuffer", NS::UTF8StringEncoding)); - - if (!overlap_indices.empty()) { - MTL::CommandBuffer* cmd = command_processor_->GetCurrentCommandBuffer(); - if (!cmd) { - MTL::CommandQueue* queue = command_processor_->GetMetalCommandQueue(); - if (!queue) { - new_buffer->release(); - return false; - } - ScopedAutoreleasePool autorelease_pool; - cmd = queue->commandBuffer(); - if (!cmd) { - new_buffer->release(); - return false; - } - } - - MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder(); - if (!blit) { - new_buffer->release(); - return false; - } - - for (size_t index : overlap_indices) { - const ScaledResolveBuffer& old_buffer = scaled_resolve_buffers_[index]; - uint64_t dst_offset = old_buffer.base_scaled - new_base_scaled; - if (dst_offset > std::numeric_limits::max()) { - continue; - } - blit->copyFromBuffer(old_buffer.buffer, 0, new_buffer, size_t(dst_offset), - size_t(old_buffer.length_scaled)); - } - - blit->endEncoding(); - if (cmd != command_processor_->GetCurrentCommandBuffer()) { - cmd->commit(); - cmd->waitUntilCompleted(); - } - } - - std::vector new_buffers; - bool retain_overlaps = - command_processor_->GetCurrentCommandBuffer() != nullptr; - new_buffers.reserve(scaled_resolve_buffers_.size() - overlap_indices.size() + - 1); - for (size_t i = 0; i < scaled_resolve_buffers_.size(); ++i) { - bool overlapping = false; - for (size_t overlap_index : overlap_indices) { - if (overlap_index == i) { - overlapping = true; - break; - } - } - if (overlapping) { - if (retain_overlaps) { - scaled_resolve_retired_buffers_.push_back(scaled_resolve_buffers_[i]); - } else if (scaled_resolve_buffers_[i].buffer) { - scaled_resolve_buffers_[i].buffer->release(); - } - continue; - } - new_buffers.push_back(scaled_resolve_buffers_[i]); - } - - ScaledResolveBuffer new_entry; - new_entry.buffer = new_buffer; - new_entry.base_scaled = new_base_scaled; - new_entry.length_scaled = new_length_scaled; - new_buffers.push_back(new_entry); - - scaled_resolve_buffers_.swap(new_buffers); - scaled_resolve_current_buffer_index_ = size_t(-1); - scaled_resolve_current_range_start_scaled_ = 0; - scaled_resolve_current_range_length_scaled_ = 0; - - return true; -} - -// GetMaxHostTextureWidthHeight implementation -uint32_t MetalTextureCache::GetMaxHostTextureWidthHeight( - xenos::DataDimension dimension) const { - // Metal supports up to 16384x16384 for 2D textures on most devices - switch (dimension) { - case xenos::DataDimension::k1D: - return 16384; - case xenos::DataDimension::k2DOrStacked: - return 16384; - case xenos::DataDimension::k3D: - return 2048; // 3D textures have lower limits - case xenos::DataDimension::kCube: - return 16384; - default: - return 16384; - } -} - -// GetMaxHostTextureDepthOrArraySize implementation -uint32_t MetalTextureCache::GetMaxHostTextureDepthOrArraySize( - xenos::DataDimension dimension) const { - // Metal array and 3D texture limits - switch (dimension) { - case xenos::DataDimension::k1D: - return 2048; // Array size limit - case xenos::DataDimension::k2DOrStacked: - return 2048; // Array size limit - case xenos::DataDimension::k3D: - return 2048; // Depth limit for 3D textures - case xenos::DataDimension::kCube: - return 2048; // Array size for cube arrays - default: - return 2048; - } -} - -// CreateTexture implementation - creates MetalTexture from TextureKey -std::unique_ptr MetalTextureCache::CreateTexture( - TextureKey key) { - SCOPE_profile_cpu_f("gpu"); - - MTL::PixelFormat metal_format = GetPixelFormatForKey(key); - if (metal_format == MTL::PixelFormatInvalid) { - XELOGE("CreateTexture: Unsupported texture format {}", - static_cast(key.format)); - return nullptr; - } - - MTL::TextureSwizzleChannels metal_swizzle = - ToMetalTextureSwizzle(xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA); - - MTL::Texture* metal_texture = nullptr; - uint32_t width = key.GetWidth(); - uint32_t height = key.GetHeight(); - if (key.scaled_resolve) { - width *= draw_resolution_scale_x(); - height *= draw_resolution_scale_y(); - } - - // Create Metal texture based on dimension - switch (key.dimension) { - case xenos::DataDimension::k1D: { - metal_texture = - CreateTexture2D(width, height, key.GetDepthOrArraySize(), - metal_format, metal_swizzle, key.mip_max_level + 1); - break; - } - case xenos::DataDimension::k2DOrStacked: { - metal_texture = - CreateTexture2D(width, height, key.GetDepthOrArraySize(), - metal_format, metal_swizzle, key.mip_max_level + 1); - break; - } - case xenos::DataDimension::k3D: { - metal_texture = - CreateTexture3D(width, height, key.GetDepthOrArraySize(), - metal_format, metal_swizzle, key.mip_max_level + 1); - break; - } - case xenos::DataDimension::kCube: { - uint32_t array_size = key.GetDepthOrArraySize(); - if (array_size % 6 != 0) { - XELOGW( - "CreateTexture: Cube texture array size {} is not divisible by 6", - array_size); - } - uint32_t cube_count = std::max(1u, array_size / 6); - metal_texture = CreateTextureCube(width, metal_format, metal_swizzle, - key.mip_max_level + 1, cube_count); - break; - } - default: { - XELOGE("CreateTexture: Unsupported texture dimension {}", - static_cast(key.dimension)); - return nullptr; - } - } - - if (!metal_texture) { - XELOGE("CreateTexture: Failed to create Metal texture"); - return nullptr; - } - - // Create MetalTexture wrapper - return std::make_unique(*this, key, metal_texture); -} - -// LoadTextureDataFromResidentMemoryImpl implementation -bool MetalTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, - bool load_base, - bool load_mips) { - SCOPE_profile_cpu_f("gpu"); - - MetalTexture* metal_texture = static_cast(&texture); - if (!metal_texture || !metal_texture->metal_texture()) { - XELOGE("LoadTextureDataFromResidentMemoryImpl: Invalid Metal texture"); - return false; - } - - // 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); -} - -// MetalTexture implementation -MetalTextureCache::MetalTexture::MetalTexture(MetalTextureCache& texture_cache, - const TextureKey& key, - MTL::Texture* metal_texture, - bool track_usage) - : Texture(texture_cache, key, track_usage), - texture_cache_(texture_cache), - metal_texture_(metal_texture) { - if (metal_texture_) { - SetHostMemoryUsage(EstimateTextureBytes(metal_texture_)); - } -} - -MetalTextureCache::MetalTexture::~MetalTexture() { - uint64_t views_released = 0; - for (auto& entry : swizzled_view_cache_) { - if (entry.second) { - ++views_released; - entry.second->release(); - } - } - if (metal_texture_) { - metal_texture_->release(); - metal_texture_ = nullptr; - } -} - -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 = - get_view_pixel_format(key(), is_signed, metal_texture_->pixelFormat()); - - if (host_swizzle == xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA) { - if (!is_signed || view_format == metal_texture_->pixelFormat()) { - 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); - auto found = swizzled_view_cache_.find(view_key); - if (found != swizzled_view_cache_.end()) { - return found->second; - } - - MTL::TextureType view_type = metal_texture_->textureType(); - switch (dimension) { - case xenos::FetchOpDimension::kCube: - view_type = MTL::TextureTypeCubeArray; - break; - case xenos::FetchOpDimension::k3DOrStacked: - view_type = key().dimension == xenos::DataDimension::k3D - ? MTL::TextureType3D - : MTL::TextureType2DArray; - break; - default: - view_type = MTL::TextureType2DArray; - break; - } - - uint32_t slice_count = 1; - switch (view_type) { - case MTL::TextureType2DArray: - slice_count = metal_texture_->arrayLength(); - break; - case MTL::TextureTypeCubeArray: - slice_count = metal_texture_->arrayLength() * 6; - break; - case MTL::TextureType3D: - // Metal requires a single slice range for 3D texture views. - slice_count = 1; - break; - default: - slice_count = 1; - break; - } - - NS::Range level_range = - NS::Range::Make(0, metal_texture_->mipmapLevelCount()); - NS::Range slice_range = NS::Range::Make(0, slice_count); - MTL::TextureSwizzleChannels swizzle = ToMetalTextureSwizzle(host_swizzle); - MTL::Texture* view = metal_texture_->newTextureView( - view_format, view_type, level_range, slice_range, swizzle); - if (!view) { - return metal_texture_; - } - - swizzled_view_cache_.emplace(view_key, view); - return view; -} - -MTL::Texture* MetalTextureCache::MetalTexture::GetOrCreate3DAs2DView( - uint32_t host_swizzle, xenos::FetchOpDimension dimension, bool is_signed) { - if (!metal_texture_ || key().dimension != xenos::DataDimension::k3D) { - return nullptr; - } - if (!::cvars::gpu_3d_to_2d_texture) { - return nullptr; - } - - if (!texture_3d_as_2d_) { - TextureKey key_2d = key(); - key_2d.depth_or_array_size_minus_1 = 0; - key_2d.mip_max_level = 0; - - uint32_t width = key_2d.GetWidth(); - uint32_t height = key_2d.GetHeight(); - if (key_2d.scaled_resolve && texture_cache_.IsDrawResolutionScaled()) { - width *= texture_cache_.draw_resolution_scale_x(); - height *= texture_cache_.draw_resolution_scale_y(); - } - - MTL::TextureSwizzleChannels metal_swizzle = - ToMetalTextureSwizzle(xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA); - MTL::Texture* texture_2d = texture_cache_.CreateTexture2D( - width, height, 1, metal_texture_->pixelFormat(), metal_swizzle, 1); - if (!texture_2d) { - XELOGE("MetalTexture: Failed to create 3D-as-2D wrapper texture"); - return nullptr; - } - - texture_3d_as_2d_ = std::make_unique(texture_cache_, key_2d, - texture_2d, false); - texture_3d_as_2d_->SetForceLoad3DTiling(true); - if (!texture_cache_.LoadTextureData(*texture_3d_as_2d_)) { - XELOGE("MetalTexture: Failed to load 3D-as-2D texture data"); - texture_3d_as_2d_.reset(); - return nullptr; - } - } - - return texture_3d_as_2d_->GetOrCreateView(host_swizzle, dimension, is_signed); -} - -} // namespace metal -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/metal/metal_texture_cache.h b/src/xenia/gpu/metal/metal_texture_cache.h deleted file mode 100644 index 37224f17b..000000000 --- a/src/xenia/gpu/metal/metal_texture_cache.h +++ /dev/null @@ -1,253 +0,0 @@ -/** - ****************************************************************************** - * 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_TEXTURE_CACHE_H_ -#define XENIA_GPU_METAL_METAL_TEXTURE_CACHE_H_ - -#include -#include -#include -#include -#include -#include - -#include "xenia/gpu/dxbc_shader.h" -#include "xenia/gpu/register_file.h" -#include "xenia/gpu/texture_cache.h" -#include "xenia/gpu/texture_info.h" -#include "xenia/gpu/xenos.h" -#include "xenia/memory.h" - -#include "third_party/metal-cpp/Metal/Metal.hpp" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalCommandProcessor; -class MetalSharedMemory; -class MetalHeapPool; - -class MetalTextureCache : public TextureCache { - public: - MetalTextureCache(MetalCommandProcessor* command_processor, - const RegisterFile& register_file, - MetalSharedMemory& shared_memory, - uint32_t draw_resolution_scale_x, - uint32_t draw_resolution_scale_y); - ~MetalTextureCache(); - - bool Initialize(); - void Shutdown(); - void ClearCache(); - - // Texture management - bool UploadTexture2D(const TextureInfo& texture_info); - bool UploadTextureCube(const TextureInfo& texture_info); - - // Get Metal textures for rendering - MTL::Texture* GetTexture2D(const TextureInfo& texture_info); - MTL::Texture* GetTextureCube(const TextureInfo& texture_info); - - // Pixel format conversion - MTL::PixelFormat ConvertXenosFormat( - xenos::TextureFormat format, - xenos::Endian endian = xenos::Endian::k8in32); - - // 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); - - MTL::Texture* RequestSwapTexture(uint32_t& width_scaled_out, - uint32_t& height_scaled_out, - xenos::TextureFormat& format_out); - - union SamplerParameters { - uint32_t value; - struct { - xenos::ClampMode clamp_x : 3; - xenos::ClampMode clamp_y : 3; - xenos::ClampMode clamp_z : 3; - xenos::BorderColor border_color : 2; - uint32_t mag_linear : 1; - uint32_t min_linear : 1; - uint32_t mip_linear : 1; - xenos::AnisoFilter aniso_filter : 3; - uint32_t mip_min_level : 4; - uint32_t mip_base_map : 1; - }; - - SamplerParameters() : value(0) { static_assert_size(*this, sizeof(value)); } - bool operator==(const SamplerParameters& other) const { - return value == other.value; - } - bool operator!=(const SamplerParameters& other) const { - return value != other.value; - } - }; - - SamplerParameters GetSamplerParameters( - const DxbcShader::SamplerBinding& binding) const; - MTL::SamplerState* GetOrCreateSampler(SamplerParameters parameters); - - // TextureCache virtual method overrides - void RequestTextures(uint32_t used_texture_mask) override; - - bool IsSignedVersionSeparateForFormat(TextureKey key) const override; - bool IsScaledResolveSupportedForFormat(TextureKey key) const override; - bool EnsureScaledResolveMemoryCommitted( - uint32_t start_unscaled, uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2 = 0) override; - bool MakeScaledResolveRangeCurrent(uint32_t start_unscaled, - uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2 = 0); - 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; - uint32_t GetMaxHostTextureDepthOrArraySize( - xenos::DataDimension dimension) const override; - std::unique_ptr CreateTexture(TextureKey key) override; - bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, - bool load_mips) override; - - private: - // GPU-based texture loading entry point. Returns true on success. - bool TryGpuLoadTexture(Texture& texture, bool load_base, bool load_mips); - MTL::StorageMode GetCacheTextureStorageMode() const; - bool ShouldUploadViaBlit() const; - - // Format / load shader mapping for Metal texture loading. - bool IsDecompressionNeededForKey(TextureKey key) const; - LoadShaderIndex GetLoadShaderIndexForKey(TextureKey key) const; - MTL::PixelFormat GetPixelFormatForKey(TextureKey key) const; - - // Initialize GPU texture_load_* pipelines for Metal. - bool InitializeLoadPipelines(); - - struct Norm16Selection { - bool unsigned_uses_float = false; - bool signed_uses_float = false; - }; - - void InitializeNorm16Selection(MTL::Device* device); - - // Metal compute pipelines for texture_load_* shaders (unscaled and - // resolution-scaled variants), indexed by TextureCache::LoadShaderIndex. - MTL::ComputePipelineState* load_pipelines_[kLoadShaderCount] = {}; - MTL::ComputePipelineState* load_pipelines_scaled_[kLoadShaderCount] = {}; - - // Metal-specific Texture implementation - - class MetalTexture : public Texture { - public: - MetalTexture(MetalTextureCache& texture_cache, const TextureKey& key, - MTL::Texture* metal_texture, bool track_usage = true); - ~MetalTexture() override; - - MTL::Texture* metal_texture() const { return metal_texture_; } - MTL::Texture* GetOrCreateView(uint32_t host_swizzle, - xenos::FetchOpDimension dimension, - bool is_signed); - MTL::Texture* GetOrCreate3DAs2DView(uint32_t host_swizzle, - xenos::FetchOpDimension dimension, - bool is_signed); - - private: - MetalTextureCache& texture_cache_; - MTL::Texture* metal_texture_; - std::unique_ptr texture_3d_as_2d_; - std::unordered_map swizzled_view_cache_; - }; - - private: - // Metal texture creation helpers - MTL::Texture* CreateTexture2D(uint32_t width, uint32_t height, - uint32_t array_length, MTL::PixelFormat format, - MTL::TextureSwizzleChannels swizzle, - uint32_t mip_levels = 1); - MTL::Texture* CreateTexture3D(uint32_t width, uint32_t height, uint32_t depth, - MTL::PixelFormat format, - MTL::TextureSwizzleChannels swizzle, - uint32_t mip_levels = 1); - MTL::Texture* CreateTextureCube(uint32_t width, MTL::PixelFormat format, - MTL::TextureSwizzleChannels swizzle, - uint32_t mip_levels = 1, - uint32_t cube_count = 1); - bool UpdateTexture2D(MTL::Texture* texture, const TextureInfo& texture_info); - bool UpdateTextureCube(MTL::Texture* texture, - const TextureInfo& texture_info); - 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; - uint64_t length_scaled = 0; - }; - - bool GetScaledResolveRange(uint32_t start_unscaled, uint32_t length_unscaled, - uint32_t length_scaled_alignment_log2, - uint64_t& start_scaled_out, - uint64_t& length_scaled_out) const; - bool EnsureScaledResolveBufferRange(uint64_t start_scaled, - uint64_t length_scaled); - void ClearScaledResolveBuffers(); - - // Null texture factory methods (following existing CreateTexture pattern) - MTL::Texture* CreateNullTexture2D(); - MTL::Texture* CreateNullTexture3D(); - MTL::Texture* CreateNullTextureCube(); - - xenos::ClampMode NormalizeClampMode(xenos::ClampMode clamp_mode) const; - - MetalCommandProcessor* command_processor_; - - // Pre-created null textures for invalid bindings (following existing - // patterns) - MTL::Texture* null_texture_2d_ = nullptr; - MTL::Texture* null_texture_3d_ = nullptr; - MTL::Texture* null_texture_cube_ = nullptr; - - Norm16Selection r16_selection_; - Norm16Selection rg16_selection_; - Norm16Selection rgba16_selection_; - - std::unordered_map sampler_cache_; - - class UploadBufferPool; - std::shared_ptr upload_buffer_pool_; - std::unique_ptr texture_heap_pool_; - - std::vector scaled_resolve_buffers_; - std::vector scaled_resolve_retired_buffers_; - size_t scaled_resolve_current_buffer_index_ = size_t(-1); - uint64_t scaled_resolve_current_range_start_scaled_ = 0; - uint64_t scaled_resolve_current_range_length_scaled_ = 0; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_METAL_METAL_TEXTURE_CACHE_H_ diff --git a/src/xenia/gpu/metal/metal_trace_dump_main.cc b/src/xenia/gpu/metal/metal_trace_dump_main.cc deleted file mode 100644 index 1729d69c3..000000000 --- a/src/xenia/gpu/metal/metal_trace_dump_main.cc +++ /dev/null @@ -1,160 +0,0 @@ -/** - ****************************************************************************** - * 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 -#include -#include - -#include "third_party/metal-cpp/Metal/Metal.hpp" -#include "third_party/stb/stb_image_write.h" -#include "xenia/base/console_app_main.h" -#include "xenia/base/filesystem.h" -#include "xenia/base/logging.h" -#include "xenia/base/string.h" -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/gpu/metal/metal_graphics_system.h" -#include "xenia/gpu/trace_dump.h" - -namespace xe { -namespace gpu { -namespace metal { - -using namespace xe::gpu::xenos; - -class MetalTraceDump : public TraceDump { - public: - std::unique_ptr CreateGraphicsSystem() override { - auto graphics_system = std::make_unique(); - metal_graphics_system_ = graphics_system.get(); - return graphics_system; - } - - void BeginHostCapture() override { - // Check if GPU capture is enabled via environment variable - const char* capture_enabled = std::getenv("XENIA_GPU_CAPTURE_ENABLED"); - if (!capture_enabled || std::string(capture_enabled) != "1") { - XELOGI( - "Metal GPU capture disabled (set XENIA_GPU_CAPTURE_ENABLED=1 to " - "enable)"); - return; - } - - // Get capture output directory from environment - const char* capture_dir = std::getenv("XENIA_GPU_CAPTURE_DIR"); - if (!capture_dir) { - capture_dir = "."; - } - - // Get the command queue from the command processor - if (!metal_graphics_system_) { - XELOGW("MetalTraceDump: No graphics system for GPU capture"); - return; - } - - auto* cmd_proc = static_cast( - metal_graphics_system_->command_processor()); - if (!cmd_proc) { - XELOGW("MetalTraceDump: No command processor for GPU capture"); - return; - } - - MTL::CommandQueue* command_queue = cmd_proc->GetMetalCommandQueue(); - if (!command_queue) { - XELOGW("MetalTraceDump: No command queue for GPU capture"); - return; - } - - // Start programmatic GPU capture - capture_manager_ = MTL::CaptureManager::sharedCaptureManager(); - if (!capture_manager_) { - XELOGW("MetalTraceDump: GPU capture manager not available"); - return; - } - - auto* descriptor = MTL::CaptureDescriptor::alloc()->init(); - descriptor->setCaptureObject(command_queue); - descriptor->setDestination(MTL::CaptureDestinationGPUTraceDocument); - - // Create output path - std::string capture_path = - std::string(capture_dir) + "/gpu_capture.gputrace"; - auto* url = NS::URL::fileURLWithPath( - NS::String::string(capture_path.c_str(), NS::UTF8StringEncoding)); - descriptor->setOutputURL(url); - - NS::Error* error = nullptr; - if (capture_manager_->startCapture(descriptor, &error)) { - XELOGI("MetalTraceDump: Started GPU capture to {}", capture_path); - is_capturing_ = true; - } else { - XELOGE("MetalTraceDump: Failed to start GPU capture: {}", - error ? error->localizedDescription()->utf8String() : "unknown"); - } - - descriptor->release(); - } - - void EndHostCapture() override { - // Ensure the final frame is pushed to the presenter even if the trace - // didn't contain a swap command. - if (metal_graphics_system_) { - auto* cmd_proc = static_cast( - metal_graphics_system_->command_processor()); - if (cmd_proc) { - if (!cmd_proc->HasSeenSwap()) { - XELOGI("MetalTraceDump: Forcing swap to ensure frame capture..."); - cmd_proc->ForceIssueSwap(); - } else { - XELOGI("MetalTraceDump: swap already seen; skipping forced swap"); - } - } - } - - // Stop GPU capture if we started one - if (capture_manager_ && is_capturing_) { - capture_manager_->stopCapture(); - XELOGI("MetalTraceDump: GPU capture completed"); - is_capturing_ = false; - } - } - - private: - MetalGraphicsSystem* metal_graphics_system_ = nullptr; - MTL::CaptureManager* capture_manager_ = nullptr; - bool is_capturing_ = false; - - public: - int Main(const std::vector& args) { - // Store args for PNG path generation since base class members are private - png_output_path_ = "trace_output.png"; // Default - if (args.size() >= 2) { - png_output_path_ = - std::filesystem::path(args[1]).replace_extension(".png"); - } - - // Use base implementation to set up, but our overridden Run() method - return TraceDump::Main(args); - } - - private: - std::filesystem::path png_output_path_; -}; - -int trace_dump_main(const std::vector& args) { - MetalTraceDump trace_dump; - return trace_dump.Main(args); -} - -} // namespace metal -} // namespace gpu -} // namespace xe - -XE_DEFINE_CONSOLE_APP("xenia-gpu-metal-trace-dump", - xe::gpu::metal::trace_dump_main, "some.trace", - "target_trace_file"); diff --git a/src/xenia/gpu/metal/metal_trace_viewer_main.cc b/src/xenia/gpu/metal/metal_trace_viewer_main.cc deleted file mode 100644 index 17c7ecb5a..000000000 --- a/src/xenia/gpu/metal/metal_trace_viewer_main.cc +++ /dev/null @@ -1,190 +0,0 @@ -/** - ****************************************************************************** - * 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 -#include - -#include "xenia/base/logging.h" -#include "xenia/gpu/metal/metal_command_processor.h" -#include "xenia/gpu/metal/metal_graphics_system.h" -#include "xenia/gpu/metal/metal_render_target_cache.h" -#include "xenia/gpu/metal/metal_texture_cache.h" -#include "xenia/gpu/sampler_info.h" -#include "xenia/gpu/texture_info.h" -#include "xenia/gpu/trace_viewer.h" -#include "xenia/ui/metal/metal_immediate_drawer.h" - -namespace xe { -namespace gpu { -namespace metal { - -class MetalTraceViewer final : public TraceViewer { - public: - static std::unique_ptr Create( - xe::ui::WindowedAppContext& app_context) { - return std::unique_ptr(new MetalTraceViewer(app_context)); - } - - std::unique_ptr CreateGraphicsSystem() override { - return std::unique_ptr(new MetalGraphicsSystem()); - } - - uintptr_t GetColorRenderTarget( - uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, - xenos::ColorRenderTargetFormat format) override { - auto* command_processor = GetMetalCommandProcessor(); - if (!command_processor) { - return 0; - } - auto* render_target_cache = command_processor->render_target_cache(); - if (!render_target_cache) { - return 0; - } - MTL::Texture* texture = render_target_cache->GetColorRenderTargetTexture( - pitch, samples, base, format); - if (!texture || texture->sampleCount() > 1) { - return 0; - } - return WrapTexture(texture, nullptr, 0); - } - - uintptr_t GetDepthRenderTarget( - uint32_t pitch, xenos::MsaaSamples samples, uint32_t base, - xenos::DepthRenderTargetFormat format) override { - // TODO(Triang3l): Depth viewer. Immediate shader expects RGBA color. - return 0; - } - - uintptr_t GetTextureEntry(const TextureInfo& texture_info, - const SamplerInfo& sampler_info, - uint32_t fetch_constant) override { - auto* command_processor = GetMetalCommandProcessor(); - if (!command_processor) { - return 0; - } - auto* texture_cache = command_processor->texture_cache(); - if (!texture_cache) { - return 0; - } - - xenos::FetchOpDimension dimension = - GetFetchDimension(texture_info.dimension); - MTL::Texture* texture = - texture_cache->GetTextureForBinding(fetch_constant, dimension, false); - if (!texture || texture == texture_cache->GetNullTexture2D() || - texture == texture_cache->GetNullTexture3D() || - texture == texture_cache->GetNullTextureCube()) { - return 0; - } - - MetalTextureCache::SamplerParameters params = - GetSamplerParameters(sampler_info); - MTL::SamplerState* sampler = texture_cache->GetOrCreateSampler(params); - return WrapTexture(texture, sampler, params.value); - } - - private: - explicit MetalTraceViewer(xe::ui::WindowedAppContext& app_context) - : TraceViewer(app_context, "xenia-gpu-metal-trace-viewer") {} - - struct TextureCacheKey { - MTL::Texture* texture = nullptr; - uint32_t sampler_key = 0; - - bool operator==(const TextureCacheKey& other) const { - return texture == other.texture && sampler_key == other.sampler_key; - } - }; - - struct TextureCacheKeyHasher { - size_t operator()(const TextureCacheKey& key) const { - size_t h = std::hash{}(key.texture); - return h ^ (std::hash{}(key.sampler_key) << 1); - } - }; - - MetalCommandProcessor* GetMetalCommandProcessor() const { - auto* system = graphics_system(); - if (!system) { - return nullptr; - } - return dynamic_cast(system->command_processor()); - } - - static xenos::FetchOpDimension GetFetchDimension( - xenos::DataDimension dimension) { - switch (dimension) { - case xenos::DataDimension::k3D: - return xenos::FetchOpDimension::k3DOrStacked; - case xenos::DataDimension::kCube: - return xenos::FetchOpDimension::kCube; - case xenos::DataDimension::k1D: - case xenos::DataDimension::k2DOrStacked: - default: - return xenos::FetchOpDimension::k2D; - } - } - - static MetalTextureCache::SamplerParameters GetSamplerParameters( - const SamplerInfo& sampler_info) { - MetalTextureCache::SamplerParameters params; - params.value = 0; - params.clamp_x = sampler_info.clamp_u; - params.clamp_y = sampler_info.clamp_v; - params.clamp_z = sampler_info.clamp_w; - params.border_color = sampler_info.border_color; - params.mag_linear = - sampler_info.mag_filter == xenos::TextureFilter::kLinear; - params.min_linear = - sampler_info.min_filter == xenos::TextureFilter::kLinear; - params.mip_linear = - sampler_info.mip_filter == xenos::TextureFilter::kLinear; - params.aniso_filter = sampler_info.aniso_filter; - params.mip_min_level = sampler_info.mip_min_level & 0xF; - params.mip_base_map = - sampler_info.mip_filter == xenos::TextureFilter::kBaseMap; - return params; - } - - uintptr_t WrapTexture(MTL::Texture* texture, MTL::SamplerState* sampler, - uint32_t sampler_key) { - if (!texture) { - return 0; - } - auto* metal_drawer = - dynamic_cast(immediate_drawer()); - if (!metal_drawer) { - return 0; - } - TextureCacheKey cache_key{texture, sampler_key}; - auto it = texture_cache_.find(cache_key); - if (it != texture_cache_.end()) { - return reinterpret_cast(it->second.get()); - } - std::unique_ptr wrapped = - metal_drawer->CreateTextureFromMetal(texture, sampler); - if (!wrapped) { - return 0; - } - auto* wrapped_ptr = wrapped.get(); - texture_cache_.emplace(cache_key, std::move(wrapped)); - return reinterpret_cast(wrapped_ptr); - } - - std::unordered_map, - TextureCacheKeyHasher> - texture_cache_; -}; - -} // namespace metal -} // namespace gpu -} // namespace xe - -XE_DEFINE_WINDOWED_APP(xenia_gpu_metal_trace_viewer, - xe::gpu::metal::MetalTraceViewer::Create); diff --git a/third_party/metal-cpp b/third_party/metal-cpp index 13a670136..9a8bc57c0 160000 --- a/third_party/metal-cpp +++ b/third_party/metal-cpp @@ -1 +1 @@ -Subproject commit 13a670136712be815f7e386a470eafac7971d702 +Subproject commit 9a8bc57c0318b84863bec2faf6605594877c5f5d diff --git a/third_party/metal-shader-converter b/third_party/metal-shader-converter deleted file mode 160000 index eb320d8a6..000000000 --- a/third_party/metal-shader-converter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit eb320d8a6d65fc15ef2c226da7a405f39cb0d697