[Metal] Remove stale Metal backend

Drop the orphaned Metal GPU backend that was imported on edge but never
wired into the CMake build.

Switch third_party/metal-cpp to bkaradzic/metal-cpp and remove
third_party/metal-shader-converter to be added later if needed.
This commit is contained in:
Herman S.
2026-04-22 12:52:02 +09:00
parent 1f8d873459
commit 3d02eae07d
30 changed files with 3 additions and 23722 deletions
+2 -4
View File
@@ -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
@@ -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 <unistd.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#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<wchar_t>(c));
}
return out;
}
std::string HResultHex(HRESULT hr) {
char buffer[11];
std::snprintf(buffer, sizeof(buffer), "%08X", static_cast<unsigned>(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<void**>(&test_converter));
if (hr != S_OK || !test_converter) {
XELOGE("DxbcToDxilConverter: Failed to create IDxbcConverter (hr=0x{:08X})",
static_cast<unsigned>(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<uint8_t>& dxbc_data,
std::vector<uint8_t>& 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<UINT32>(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<char>(*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<const uint8_t*>(dxil_ptr),
reinterpret_cast<const uint8_t*>(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<void**>(&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<uint8_t>& data) {
std::ofstream file(path, std::ios::binary);
if (!file) {
return false;
}
file.write(reinterpret_cast<const char*>(data.data()), data.size());
return file.good();
}
} // namespace metal
} // namespace gpu
} // namespace xe
@@ -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 <cstdint>
#include <string>
#include <vector>
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<uint8_t>& dxbc_data,
std::vector<uint8_t>& 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<uint8_t>& data);
};
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // DXBC_TO_DXIL_CONVERTER_H_
-17
View File
@@ -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"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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 <cstdint>
#include <functional>
#include <vector>
#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<uint32_t>{}(key.key);
}
};
bool operator==(const GeometryShaderKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const GeometryShaderKey& other_key) const {
return !(*this == other_key);
}
};
bool GetGeometryShaderKey(
PipelineGeometryShader geometry_shader_type,
DxbcShaderTranslator::Modification vertex_shader_modification,
DxbcShaderTranslator::Modification pixel_shader_modification,
GeometryShaderKey& key_out);
const std::vector<uint32_t>& GetGeometryShader(GeometryShaderKey key);
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_METAL_METAL_GEOMETRY_SHADER_H_
@@ -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 <algorithm>
#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<CommandProcessor>
MetalGraphicsSystem::CreateCommandProcessor() {
return std::unique_ptr<CommandProcessor>(
new MetalCommandProcessor(this, kernel_state_));
}
} // namespace metal
} // namespace gpu
} // namespace xe
@@ -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 <memory>
#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<CommandProcessor> CreateCommandProcessor() override;
};
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_METAL_GRAPHICS_SYSTEM_H
-127
View File
@@ -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 <algorithm>
#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<uint64_t>(budget, kMinMaxHeapBytes);
budget = std::min<uint64_t>(budget, kMaxMaxHeapBytes);
return static_cast<size_t>(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<NS::UInteger>(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
-53
View File
@@ -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 <cstddef>
#include <string>
#include <vector>
#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<HeapEntry> heaps_;
};
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_METAL_METAL_HEAP_POOL_H_
@@ -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 <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#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<MTL::Buffer*>(handle);
offset_bytes_out = 0; // We use the full buffer from the start
return buffer;
}
bool MetalPrimitiveProcessor::InitializeBuiltinIndexBuffer(
size_t size_bytes, std::function<void(void*)> 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<size_t>(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<uint8_t*>(cpu_buffer) + offset;
}
return cpu_buffer;
}
} // namespace metal
} // namespace gpu
} // namespace xe
@@ -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 <memory>
#include <unordered_map>
#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<void(void*)> 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<ConvertedIndexBufferBinding> 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<FrameIndexBuffer> frame_index_buffers_;
};
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_METAL_METAL_PRIMITIVE_PROCESSOR_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-292
View File
@@ -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 <dispatch/dispatch.h>
#include <inttypes.h>
#include <atomic>
#include <cstdlib>
#include <cstring>
#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<uint8_t>& 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<uint32_t>(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<uint32_t> 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<NS::String*>(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
-101
View File
@@ -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 <string>
#include <vector>
#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<uint8_t>& dxil_data() const { return dxil_data_; }
const std::vector<uint8_t>& 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<uint8_t> dxil_data_;
std::vector<uint8_t> 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_
-247
View File
@@ -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 <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <limits>
#include "third_party/xxhash/xxhash.h"
#include "xenia/base/logging.h"
namespace xe {
namespace gpu {
namespace metal {
std::unique_ptr<MetalShaderCache> g_metal_shader_cache =
std::make_unique<MetalShaderCache>();
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<unsigned long long>(cache_key));
return cache_dir_ / name;
}
bool MetalShaderCache::LoadFromDisk(uint64_t cache_key, CachedMetallib* out) {
std::filesystem::path path;
{
std::lock_guard<std::mutex> 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<char*>(&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<uint32_t>::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<uint8_t> data;
data.resize(hdr.metallib_size);
file.read(reinterpret_cast<char*>(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<std::mutex> 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<uint32_t>(in.function_name.size());
hdr.metallib_size = static_cast<uint32_t>(in.metallib_data.size());
file.write(reinterpret_cast<const char*>(&hdr), sizeof(hdr));
file.write(in.function_name.data(), in.function_name.size());
file.write(reinterpret_cast<const char*>(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
-81
View File
@@ -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 <cstdint>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace xe {
namespace gpu {
namespace metal {
class MetalShaderCache {
public:
struct CachedMetallib {
std::string function_name;
std::vector<uint8_t> 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<uint8_t> 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<uint64_t, MemoryEntry> cache_;
};
extern std::unique_ptr<MetalShaderCache> g_metal_shader_cache;
} // namespace metal
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_METAL_METAL_SHADER_CACHE_H_

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