mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[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:
+2
-4
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user