From a9cad5238d84d9ac6c9da79b7e194d63daa36f6e Mon Sep 17 00:00:00 2001 From: aomizu Date: Tue, 23 Dec 2025 12:42:14 +0900 Subject: [PATCH] init metal backend --- src/app/CMakeLists.txt | 2 + src/app/mac/EngineMTLLayerView.h | 9 + src/app/mac/EngineMTLLayerView.mm | 36 ++++ src/app/mac/View.mm | 6 + src/app/mac/WindowCallbacks.mm | 7 +- src/console/Device.cpp | 8 + src/gx/CGxDevice.cpp | 8 + src/gx/CGxDevice.hpp | 1 + src/gx/CMakeLists.txt | 1 + src/gx/Device.cpp | 2 + src/gx/Types.hpp | 3 +- src/gx/mtl/CGxDeviceMTL.hpp | 49 +++++ src/gx/mtl/CGxDeviceMTL.mm | 323 ++++++++++++++++++++++++++++++ test/CMakeLists.txt | 2 + 14 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 src/app/mac/EngineMTLLayerView.h create mode 100644 src/app/mac/EngineMTLLayerView.mm create mode 100644 src/gx/mtl/CGxDeviceMTL.hpp create mode 100644 src/gx/mtl/CGxDeviceMTL.mm diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index a829594..33a6a5d 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -32,6 +32,8 @@ if(WHOA_SYSTEM_MAC) "-framework AppKit" "-framework Carbon" "-framework IOKit" + "-framework Metal" + "-framework QuartzCore" ) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mac/MainMenu.nib DESTINATION "bin") diff --git a/src/app/mac/EngineMTLLayerView.h b/src/app/mac/EngineMTLLayerView.h new file mode 100644 index 0000000..21c99a7 --- /dev/null +++ b/src/app/mac/EngineMTLLayerView.h @@ -0,0 +1,9 @@ +#ifndef APP_MAC_ENGINE_MTL_LAYER_VIEW_H +#define APP_MAC_ENGINE_MTL_LAYER_VIEW_H + +#include "app/mac/EngineGLLayerView.h" + +@interface EngineMTLLayerView : EngineGLLayerView +@end + +#endif diff --git a/src/app/mac/EngineMTLLayerView.mm b/src/app/mac/EngineMTLLayerView.mm new file mode 100644 index 0000000..590d69f --- /dev/null +++ b/src/app/mac/EngineMTLLayerView.mm @@ -0,0 +1,36 @@ +#include "app/mac/EngineMTLLayerView.h" +#import + +@implementation EngineMTLLayerView + +- (CALayer*)makeBackingLayer { + return [CAMetalLayer layer]; +} + +- (id)initWithFrame:(NSRect)frame glWindow:(GLWindow*)window { + self = [super initWithFrame:frame glWindow:window]; + + if (self) { + [self setWantsLayer:YES]; + } + + return self; +} + +- (void)drawRect:(NSRect)dirtyRect { + // Rendering is driven by CGxDeviceMTL. +} + +- (void)update { + [super update]; + + if (![self.layer isKindOfClass:[CAMetalLayer class]]) { + return; + } + + CAMetalLayer* layer = (CAMetalLayer*)self.layer; + CGSize size = [self convertSizeToBacking:self.bounds.size]; + layer.drawableSize = size; +} + +@end diff --git a/src/app/mac/View.mm b/src/app/mac/View.mm index 729115b..a7c8fbb 100644 --- a/src/app/mac/View.mm +++ b/src/app/mac/View.mm @@ -1,7 +1,9 @@ #include "app/mac/View.h" #include "app/mac/EngineGLLayerView.h" +#include "app/mac/EngineMTLLayerView.h" #include "app/mac/WindowCallbacks.h" #include "gx/gll/GLWindow.h" +#include "gx/Device.hpp" GLWindowCallbacks EngineViewCallbacks = { &MacOnResized, @@ -23,5 +25,9 @@ void AssignEngineViewCallbacks(GLWindowCallbacks* callbacks) { } Class GetEngineViewClass() { + if (GxDevApi() == GxApi_Metal) { + return [EngineMTLLayerView class]; + } + return [EngineGLLayerView class]; } diff --git a/src/app/mac/WindowCallbacks.mm b/src/app/mac/WindowCallbacks.mm index ccdd816..841becc 100644 --- a/src/app/mac/WindowCallbacks.mm +++ b/src/app/mac/WindowCallbacks.mm @@ -2,6 +2,7 @@ #include "app/mac/MacClient.h" #include "event/Input.hpp" #include "gx/gll/CGxDeviceGLL.hpp" +#include "gx/mtl/CGxDeviceMTL.hpp" #include "gx/Device.hpp" #include "gx/Window.hpp" #include @@ -171,7 +172,11 @@ void MacOnResized(int32_t width, int32_t height, bool a3) { return; } - static_cast(g_theGxDevicePtr)->Resize(width, height); + if (GxDevApi() == GxApi_GLL) { + static_cast(g_theGxDevicePtr)->Resize(width, height); + } else if (GxDevApi() == GxApi_Metal) { + static_cast(g_theGxDevicePtr)->Resize(width, height); + } OsQueuePut(OS_INPUT_SIZE, width, height, 0, 0); diff --git a/src/console/Device.cpp b/src/console/Device.cpp index dcfbb12..5178595 100644 --- a/src/console/Device.cpp +++ b/src/console/Device.cpp @@ -6,6 +6,7 @@ #include "gx/Adapter.hpp" #include "gx/Device.hpp" #include +#include #include static CGxDevice* s_device; @@ -417,6 +418,13 @@ void ConsoleDeviceInitialize(const char* title) { api = GxApi_GLL; #endif +#if defined(WHOA_SYSTEM_MAC) + const char* apiOverride = getenv("WHOA_GX_API"); + if (apiOverride && !strcmp(apiOverride, "metal")) { + api = GxApi_Metal; + } +#endif + s_device = GxDevCreate(api, OsWindowProc, format); // TODO diff --git a/src/gx/CGxDevice.cpp b/src/gx/CGxDevice.cpp index 880a107..c0fa16d 100644 --- a/src/gx/CGxDevice.cpp +++ b/src/gx/CGxDevice.cpp @@ -18,6 +18,7 @@ #if defined(WHOA_SYSTEM_MAC) #include "gx/gll/CGxDeviceGLL.hpp" + #include "gx/mtl/CGxDeviceMTL.hpp" #include "gx/mac/Display.hpp" #include #include @@ -117,6 +118,8 @@ int32_t CGxDevice::AdapterFormats(EGxApi api, TSGrowableArray& adapte CGxDevice::OpenGlAdapterFormats(adapterFormats); } else if (api == GxApi_GLL) { CGxDevice::GLLAdapterFormats(adapterFormats); + } else if (api == GxApi_Metal) { + CGxDevice::OpenGlAdapterFormats(adapterFormats); } #elif defined(WHOA_SYSTEM_LINUX) @@ -228,6 +231,11 @@ CGxDevice* CGxDevice::NewGLL() { auto m = SMemAlloc(sizeof(CGxDeviceGLL), __FILE__, __LINE__, 0x0); return new (m) CGxDeviceGLL(); } + +CGxDevice* CGxDevice::NewMTL() { + auto m = SMemAlloc(sizeof(CGxDeviceMTL), __FILE__, __LINE__, 0x0); + return new (m) CGxDeviceMTL(); +} #endif CGxDevice* CGxDevice::NewOpenGl() { diff --git a/src/gx/CGxDevice.hpp b/src/gx/CGxDevice.hpp index de72727..c0cd76e 100644 --- a/src/gx/CGxDevice.hpp +++ b/src/gx/CGxDevice.hpp @@ -70,6 +70,7 @@ class CGxDevice { #endif #if defined(WHOA_SYSTEM_MAC) static CGxDevice* NewGLL(); + static CGxDevice* NewMTL(); #endif static CGxDevice* NewOpenGl(); static void OpenGlAdapterFormats(TSGrowableArray& adapterFormats); diff --git a/src/gx/CMakeLists.txt b/src/gx/CMakeLists.txt index a9c1ede..3bd2abd 100644 --- a/src/gx/CMakeLists.txt +++ b/src/gx/CMakeLists.txt @@ -20,6 +20,7 @@ if(WHOA_SYSTEM_MAC) file(GLOB MAC_SOURCES "gll/*.cpp" "gll/*.mm" + "mtl/*.mm" "mac/*.cpp" ) diff --git a/src/gx/Device.cpp b/src/gx/Device.cpp index d30db2c..2cedd26 100644 --- a/src/gx/Device.cpp +++ b/src/gx/Device.cpp @@ -24,6 +24,8 @@ CGxDevice* GxDevCreate(EGxApi api, int32_t (*windowProc)(void* window, uint32_t device = CGxDevice::NewOpenGl(); } else if (api == GxApi_GLL) { device = CGxDevice::NewGLL(); + } else if (api == GxApi_Metal) { + device = CGxDevice::NewMTL(); } else { // Error } diff --git a/src/gx/Types.hpp b/src/gx/Types.hpp index 9ceaf2d..438b051 100644 --- a/src/gx/Types.hpp +++ b/src/gx/Types.hpp @@ -35,7 +35,8 @@ enum EGxApi { GxApi_D3d10 = 3, GxApi_D3d11 = 4, GxApi_GLL = 5, - GxApis_Last = 6 + GxApi_Metal = 6, + GxApis_Last = 7 }; enum EGxBlend { diff --git a/src/gx/mtl/CGxDeviceMTL.hpp b/src/gx/mtl/CGxDeviceMTL.hpp new file mode 100644 index 0000000..a9f6be0 --- /dev/null +++ b/src/gx/mtl/CGxDeviceMTL.hpp @@ -0,0 +1,49 @@ +#ifndef GX_MTL_C_GX_DEVICE_MTL_HPP +#define GX_MTL_C_GX_DEVICE_MTL_HPP + +#include "gx/CGxDevice.hpp" +#include "gx/gll/GLWindow.h" + +class CGxBatch; +class CGxShader; + +class CGxDeviceMTL : public CGxDevice { + public: + // Member variables + GLWindow m_window; + + // Virtual member functions + void ITexMarkAsUpdated(CGxTex*) override; + void IRsSendToHw(EGxRenderState) override; + int32_t DeviceCreate(int32_t (*windowProc)(void* window, uint32_t message, uintptr_t wparam, intptr_t lparam), const CGxFormat&) override; + int32_t DeviceSetFormat(const CGxFormat&) override; + void* DeviceWindow() override; + void DeviceWM(EGxWM wm, uintptr_t param1, uintptr_t param2) override {}; + void CapsWindowSize(CRect&) override; + void CapsWindowSizeInScreenCoords(CRect& dst) override; + void ScenePresent() override; + void SceneClear(uint32_t, CImVector) override; + void Draw(CGxBatch* batch, int32_t indexed) override; + void PoolSizeSet(CGxPool*, uint32_t) override; + char* BufLock(CGxBuf*) override; + int32_t BufUnlock(CGxBuf*, uint32_t) override; + void BufData(CGxBuf* buf, const void* data, size_t size, uintptr_t offset) override; + void TexDestroy(CGxTex* texId) override; + void IShaderCreate(CGxShader*) override; + void ShaderCreate(CGxShader*[], EGxShTarget, const char*, const char*, int32_t) override; + int32_t StereoEnabled(void) override; + + // Member functions + CGxDeviceMTL(); + void Resize(uint32_t width, uint32_t height); + + private: + void ISetCaps(const CGxFormat& format); + void* m_device = nullptr; + void* m_commandQueue = nullptr; + void* m_layer = nullptr; + uint32_t m_clearMask = 0; + uint32_t m_clearColor = 0; +}; + +#endif diff --git a/src/gx/mtl/CGxDeviceMTL.mm b/src/gx/mtl/CGxDeviceMTL.mm new file mode 100644 index 0000000..13c13e6 --- /dev/null +++ b/src/gx/mtl/CGxDeviceMTL.mm @@ -0,0 +1,323 @@ +#include "gx/mtl/CGxDeviceMTL.hpp" +#include "app/mac/View.h" +#include "gx/Window.hpp" +#include "util/Autorelease.hpp" +#include +#include +#include + +#import +#import +#import + +CGxDeviceMTL::CGxDeviceMTL() : CGxDevice() { + this->m_api = GxApi_Metal; + this->m_caps.m_colorFormat = GxCF_rgba; + + this->DeviceCreatePools(); + this->DeviceCreateStreamBufs(); +} + +void CGxDeviceMTL::ITexMarkAsUpdated(CGxTex* texId) { + CGxDevice::ITexMarkAsUpdated(texId); +} + +void CGxDeviceMTL::IRsSendToHw(EGxRenderState which) { + (void)which; +} + +int32_t CGxDeviceMTL::DeviceCreate(int32_t (*windowProc)(void* window, uint32_t message, uintptr_t wparam, intptr_t lparam), const CGxFormat& format) { + System_Autorelease::ScopedPool autorelease; + + CGRect rect; + Rect* bounds; + Rect* zoomedBounds = GetSavedZoomedWindowBounds(); + + if ( + zoomedBounds + && zoomedBounds->bottom - zoomedBounds->top > 599 + && zoomedBounds->right - zoomedBounds->left > 799 + ) { + bounds = GetSavedZoomedWindowBounds(); + } else { + bounds = GetSavedWindowBounds(); + } + + if ( + bounds->bottom - bounds->top > 599 + && bounds->right - bounds->left > 799 + ) { + rect.origin.x = bounds->left; + rect.origin.y = bounds->top; + rect.size.width = bounds->right - bounds->left; + rect.size.height = bounds->bottom - bounds->top; + } else { + Rect newBounds = { + 0, + 0, + static_cast(std::floor((static_cast(format.size.y) / static_cast(format.size.x)) * 1024.0f)), + 1024, + }; + + SetSavedWindowBounds(newBounds); + + rect.origin.x = newBounds.left; + rect.origin.y = newBounds.top; + rect.size.width = newBounds.right; + rect.size.height = newBounds.bottom; + } + + this->m_window.SetViewClass(GetEngineViewClass()); + this->m_window.Init(rect, nullptr); + this->m_window.SetTitle("World of Warcraft"); + this->m_window.CreateView(); + + id device = MTLCreateSystemDefaultDevice(); + if (!device) { + return 0; + } + + id commandQueue = [device newCommandQueue]; + if (!commandQueue) { + return 0; + } + + NSView* view = this->m_window.GetNSView(); + CAMetalLayer* layer = view ? (CAMetalLayer*)[view layer] : nil; + if (!layer) { + return 0; + } + + layer.device = device; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + layer.framebufferOnly = YES; + + CGSize drawableSize = [view convertSizeToBacking:view.bounds.size]; + layer.drawableSize = drawableSize; + + this->m_device = device; + this->m_commandQueue = commandQueue; + this->m_layer = layer; + + if (CGxDevice::DeviceCreate(windowProc, format)) { + auto callbacks = new GLWindowCallbacks(); + AssignEngineViewCallbacks(callbacks); + this->m_window.SetCallbacks(callbacks); + this->m_window.Show(); + + this->ISetCaps(format); + + this->m_context = 1; + this->ICursorCreate(format); + + return 1; + } + + return 0; +} + +void CGxDeviceMTL::ISetCaps(const CGxFormat& format) { + (void)format; + + this->m_caps.m_pixelCenterOnEdge = 1; + this->m_caps.m_texelCenterOnEdge = 1; + this->m_caps.m_colorFormat = GxCF_rgba; + this->m_caps.m_generateMipMaps = 1; + this->m_caps.int10 = 1; + + this->m_caps.m_texFmt[GxTex_Dxt1] = 1; + this->m_caps.m_texFmt[GxTex_Dxt3] = 1; + this->m_caps.m_texFmt[GxTex_Dxt5] = 1; + + this->m_caps.m_texFilterAnisotropic = 1; + this->m_caps.m_maxTexAnisotropy = 16; + + for (int32_t i = 0; i < GxTexTargets_Last; ++i) { + this->m_caps.m_texTarget[i] = 1; + this->m_caps.m_texMaxSize[i] = 4096; + } +} + +int32_t CGxDeviceMTL::DeviceSetFormat(const CGxFormat& format) { + CGxDevice::DeviceSetFormat(format); + + CRect rect = { 0.0f, 0.0f, static_cast(format.size.y), static_cast(format.size.x) }; + this->DeviceSetDefWindow(rect); + + if (this->m_window.m_Window) { + this->m_window.Resize(format.size.x, format.size.y); + } + + return 1; +} + +void* CGxDeviceMTL::DeviceWindow() { + return &this->m_window; +} + +void CGxDeviceMTL::CapsWindowSize(CRect& rect) { + CRect windowRect = this->DeviceCurWindow(); + + rect.minX = windowRect.minX; + rect.minY = windowRect.minY; + rect.maxX = windowRect.maxX; + rect.maxY = windowRect.maxY; +} + +void CGxDeviceMTL::CapsWindowSizeInScreenCoords(CRect& dst) { + if (this->IDevIsWindowed()) { + auto windowRect = this->DeviceCurWindow(); + auto deviceRect = this->m_window.GetRect(); + + dst.minX = windowRect.minX + deviceRect.origin.x; + dst.maxX = windowRect.maxX + deviceRect.origin.x; + dst.minY = windowRect.minY + deviceRect.origin.y; + dst.maxY = windowRect.maxY + deviceRect.origin.y; + } else { + dst = this->DeviceCurWindow(); + } +} + +void CGxDeviceMTL::SceneClear(uint32_t mask, CImVector color) { + CGxDevice::SceneClear(mask, color); + + if (!this->m_context) { + return; + } + + this->m_clearMask = mask; + this->m_clearColor = color.value; +} + +void CGxDeviceMTL::ScenePresent() { + if (!this->m_context) { + return; + } + + auto device = (id)this->m_device; + auto commandQueue = (id)this->m_commandQueue; + auto layer = (CAMetalLayer*)this->m_layer; + + if (!device || !commandQueue || !layer) { + return; + } + + System_Autorelease::ScopedPool autorelease; + + id drawable = [layer nextDrawable]; + if (!drawable) { + return; + } + + const uint8_t r = (this->m_clearColor >> 16) & 0xFF; + const uint8_t g = (this->m_clearColor >> 8) & 0xFF; + const uint8_t b = this->m_clearColor & 0xFF; + const uint8_t a = (this->m_clearColor >> 24) & 0xFF; + + auto pass = [MTLRenderPassDescriptor renderPassDescriptor]; + pass.colorAttachments[0].texture = drawable.texture; + pass.colorAttachments[0].loadAction = (this->m_clearMask & 0x1) ? MTLLoadActionClear : MTLLoadActionLoad; + pass.colorAttachments[0].storeAction = MTLStoreActionStore; + pass.colorAttachments[0].clearColor = MTLClearColorMake( + r / 255.0f, + g / 255.0f, + b / 255.0f, + a / 255.0f + ); + + id commandBuffer = [commandQueue commandBuffer]; + id encoder = [commandBuffer renderCommandEncoderWithDescriptor: pass]; + [encoder endEncoding]; + + [commandBuffer presentDrawable: drawable]; + [commandBuffer commit]; +} + +void CGxDeviceMTL::Draw(CGxBatch* batch, int32_t indexed) { + (void)batch; + (void)indexed; +} + +void CGxDeviceMTL::PoolSizeSet(CGxPool* pool, uint32_t size) { + if (!pool || pool->m_size >= static_cast(size)) { + return; + } + + pool->m_size = static_cast(size); + pool->unk1C = 0; + + if (pool->m_mem) { + SMemFree(pool->m_mem, __FILE__, __LINE__, 0x0); + pool->m_mem = nullptr; + } +} + +char* CGxDeviceMTL::BufLock(CGxBuf* buf) { + CGxDevice::BufLock(buf); + + if (!this->m_context) { + return nullptr; + } + + CGxPool* pool = buf->m_pool; + if (!pool->m_mem) { + pool->m_mem = SMemAlloc(pool->m_size, __FILE__, __LINE__, 0x0); + } + + if (pool->m_usage == GxPoolUsage_Stream) { + uint32_t v7 = pool->unk1C + buf->m_itemSize - 1; + uint32_t alignedNext = v7 - v7 % buf->m_itemSize; + + if (alignedNext + buf->m_size > static_cast(pool->m_size)) { + pool->Discard(); + alignedNext = 0; + } + + buf->m_index = alignedNext; + pool->unk1C = alignedNext + buf->m_size; + } + + if (!pool->m_mem) { + return nullptr; + } + + return static_cast(pool->m_mem) + buf->m_index; +} + +int32_t CGxDeviceMTL::BufUnlock(CGxBuf* buf, uint32_t size) { + CGxDevice::BufUnlock(buf, size); + buf->unk1D = 1; + return 1; +} + +void CGxDeviceMTL::BufData(CGxBuf* buf, const void* data, size_t size, uintptr_t offset) { + CGxDevice::BufData(buf, data, size, offset); + + auto bufData = this->BufLock(buf); + if (bufData) { + memcpy(&bufData[offset], data, size); + } + this->BufUnlock(buf, static_cast(size)); +} + +void CGxDeviceMTL::TexDestroy(CGxTex* texId) { + CGxDevice::TexDestroy(texId); +} + +void CGxDeviceMTL::IShaderCreate(CGxShader* shader) { + shader->loaded = 1; + shader->valid = 1; +} + +void CGxDeviceMTL::ShaderCreate(CGxShader* shaders[], EGxShTarget target, const char* a4, const char* a5, int32_t permutations) { + CGxDevice::ShaderCreate(shaders, target, a4, a5, permutations); +} + +int32_t CGxDeviceMTL::StereoEnabled() { + return 0; +} + +void CGxDeviceMTL::Resize(uint32_t width, uint32_t height) { + CRect rect = { 0.0f, 0.0f, static_cast(height), static_cast(width) }; + this->DeviceSetDefWindow(rect); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a2292d6..9ddef9b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,6 +22,8 @@ if(WHOA_SYSTEM_MAC) "-framework AppKit" "-framework Carbon" "-framework IOKit" + "-framework Metal" + "-framework QuartzCore" ) endif()