hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-19 01:00:48 +0200
committerhachem <im@hachem.wtf>2026-08-19 01:00:48 +0200
commit6253adaf4b762046f9a5c89a0ef67dc61fead689 (patch)
tree640191fc4478a33d6276552d9c057fc2d4cc0ab1
parenta504b5c8a6b6687864234f79e6e03586b90d0ecd (diff)
feat: support macOS arm64
-rw-r--r--premake5.lua74
-rw-r--r--src/core/application.cpp13
-rw-r--r--src/engine/engine.h24
-rw-r--r--src/platform/metal/metal_context.h8
-rw-r--r--src/platform/metal/metal_context.mm119
5 files changed, 193 insertions, 45 deletions
diff --git a/premake5.lua b/premake5.lua
index 98e5757..236347c 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -8,7 +8,7 @@ workspace "Donut"
"Release",
"Dist"
}
-
+
flags
{
"MultiProcessorCompile"
@@ -20,6 +20,10 @@ workspace "Donut"
defines "DONUT_LINUX"
filter "system:macosx"
defines "DONUT_MACOS"
+ -- Build natively for Apple Silicon (was x86_64 under Rosetta). Native
+ -- arm64 is required for clean Metal work and better performance.
+ architecture "ARM64"
+ filter {}
outputdir = "%{cfg.buildcfg}-%{cfg.system}/%{prj.name}"
@@ -55,7 +59,7 @@ project "GLAD"
{
"ext/glad/include"
}
-
+
filter "system:windows"
systemversion "latest"
@@ -101,7 +105,7 @@ project "GLFW"
filter "system:linux"
pic "On"
systemversion "latest"
-
+
files
{
"ext/glfw/src/x11_init.c",
@@ -110,22 +114,22 @@ project "GLFW"
"ext/glfw/src/x11_window.c",
"ext/glfw/src/xkb_unicode.c",
"ext/glfw/src/xkb_unicode.h",
-
+
"ext/glfw/src/wl_init.c",
"ext/glfw/src/wl_monitor.c",
"ext/glfw/src/wl_platform.h",
"ext/glfw/src/wl_window.c",
-
+
"ext/glfw/src/posix_module.c",
"ext/glfw/src/posix_time.c",
"ext/glfw/src/posix_time.h",
"ext/glfw/src/posix_thread.c",
"ext/glfw/src/posix_thread.h",
-
+
"ext/glfw/src/glx_context.c",
"ext/glfw/src/egl_context.c",
"ext/glfw/src/osmesa_context.c",
-
+
"ext/glfw/src/linux_joystick.c",
"ext/glfw/src/linux_joystick.h"
}
@@ -148,11 +152,11 @@ project "GLFW"
"ext/glfw/src/cocoa_time.c",
"ext/glfw/src/cocoa_time.h",
"ext/glfw/src/cocoa_window.m",
-
+
"ext/glfw/src/nsgl_context.m",
"ext/glfw/src/egl_context.c",
"ext/glfw/src/osmesa_context.c",
-
+
"ext/glfw/src/posix_module.c",
"ext/glfw/src/posix_thread.c",
"ext/glfw/src/posix_thread.h"
@@ -184,8 +188,8 @@ project "GLFW"
"ext/glfw/src/osmesa_context.c"
}
- defines
- {
+ defines
+ {
"_GLFW_WIN32",
"_CRT_SECURE_NO_WARNINGS",
}
@@ -194,7 +198,7 @@ project "GLFW"
runtime "Debug"
symbols "on"
- filter { "system:windows", "configurations:Debug-AS" }
+ filter { "system:windows", "configurations:Debug-AS" }
runtime "Debug"
symbols "on"
sanitize { "Address" }
@@ -213,10 +217,10 @@ project "ImGui"
kind "StaticLib"
language "C++"
staticruntime "on"
-
+
targetdir ("bin/" .. outputdir)
objdir ("bin-int/" .. outputdir)
-
+
files
{
"ext/imgui/imgui.cpp",
@@ -225,24 +229,24 @@ project "ImGui"
"ext/imgui/imgui_widgets.cpp",
"ext/imgui/imgui_demo.cpp"
}
-
+
filter "system:windows"
systemversion "latest"
cppdialect "C++17"
-
+
filter "system:linux"
pic "On"
systemversion "latest"
cppdialect "C++17"
-
+
filter "configurations:Debug"
runtime "Debug"
symbols "on"
-
+
filter "configurations:Release"
runtime "Release"
optimize "on"
-
+
filter "configurations:Dist"
runtime "Release"
optimize "on"
@@ -252,39 +256,39 @@ project "ImGuizmo"
kind "StaticLib"
language "C++"
staticruntime "on"
-
+
targetdir ("bin/" .. outputdir)
objdir ("bin-int/" .. outputdir)
-
+
files
{
"ext/ImGuizmo/ImGuizmo.cpp",
"ext/ImGuizmo/ImGuizmo.h"
}
-
+
includedirs
{
"%{IncludeDir.imgui}",
"%{IncludeDir.imguizmo}"
}
-
+
filter "system:windows"
systemversion "latest"
cppdialect "C++17"
-
+
filter "system:linux"
pic "On"
systemversion "latest"
cppdialect "C++17"
-
+
filter "configurations:Debug"
runtime "Debug"
symbols "on"
-
+
filter "configurations:Release"
runtime "Release"
optimize "on"
-
+
filter "configurations:Dist"
runtime "Release"
optimize "on"
@@ -318,10 +322,10 @@ project "Donut"
"src/rendering/framebuffer.cpp",
"src/platform/opengl/opengl_framebuffer.h",
"src/platform/opengl/opengl_framebuffer.cpp",
-
+
"ext/imgui/backends/imgui_impl_glfw.cpp",
"ext/imgui/backends/imgui_impl_opengl3.cpp",
-
+
"assets/fonts/inter/static/Inter_18pt-Regular.ttf",
"assets/fonts/inter/static/Inter_18pt-Bold.ttf",
"assets/fonts/inter/static/Inter_18pt-Light.ttf"
@@ -330,7 +334,7 @@ project "Donut"
includedirs
{
"src",
-
+
"%{IncludeDir.glm}",
"%{IncludeDir.glfw}",
"%{IncludeDir.glad}",
@@ -359,12 +363,20 @@ project "Donut"
}
filter "system:macosx"
+ -- Objective-C++ (Metal) sources compile only on macOS.
+ files
+ {
+ "src/**.mm"
+ }
+
links
{
"Cocoa.framework",
"IOKit.framework",
"CoreFoundation.framework",
- "QuartzCore.framework"
+ "QuartzCore.framework",
+ "Metal.framework",
+ "Foundation.framework"
}
filter "configurations:Debug"
diff --git a/src/core/application.cpp b/src/core/application.cpp
index 37d7fa0..fa5fd6e 100644
--- a/src/core/application.cpp
+++ b/src/core/application.cpp
@@ -10,6 +10,10 @@
#include <GLFW/glfw3.h>
#include <ImGuizmo.h>
+#ifdef __APPLE__
+#include "platform/metal/metal_context.h"
+#endif
+
namespace Donut
{
Application* Application::s_instance = nullptr;
@@ -103,9 +107,14 @@ namespace Donut
m_state_manager->on_event(event);
}
- void Application::on_init()
- {
+ void Application::on_init()
+ {
Logger::init();
+
+#ifdef __APPLE__
+ metal_probe();
+ metal_compute_self_test();
+#endif
SettingsManager::initialize();
diff --git a/src/engine/engine.h b/src/engine/engine.h
index 0a50612..1c25466 100644
--- a/src/engine/engine.h
+++ b/src/engine/engine.h
@@ -35,7 +35,7 @@ namespace Donut
double m_radius;
double m_rs;
- BlackHole(glm::vec3 pos, float mass)
+ BlackHole(glm::vec3 pos, float mass)
: m_position(pos), m_mass(mass)
{
m_rs = 2.0 * G * m_mass / (c * c);
@@ -99,33 +99,33 @@ namespace Donut
int get_compute_height() const { return m_compute_height; }
int get_compute_width() const { return (m_width * m_compute_height) / m_height; }
void update_compute_dimensions();
-
+
int get_max_steps_moving() const { return m_max_steps_moving; }
int get_max_steps_static() const { return m_max_steps_static; }
float get_early_exit_distance() const { return m_early_exit_distance; }
void set_max_steps_moving(int steps) { m_max_steps_moving = steps; }
void set_max_steps_static(int steps) { m_max_steps_static = steps; }
void set_early_exit_distance(float distance) { m_early_exit_distance = distance; }
-
+
float get_disk_thickness() const { return m_disk_thickness; }
void set_disk_thickness(float thickness) { m_disk_thickness = thickness; }
-
+
float get_disk_density() const { return m_disk_density; }
void set_disk_density(float density) { m_disk_density = density; }
-
+
float get_rotation_speed() const { return m_rotation_speed; }
void set_rotation_speed(float speed) { m_rotation_speed = speed; }
-
+
float get_blur_strength() const { return m_blur_strength; }
void set_blur_strength(float strength) { m_blur_strength = strength; }
-
+
float get_glow_intensity() const { return m_glow_intensity; }
void set_glow_intensity(float intensity) { m_glow_intensity = intensity; }
-
+
void load_objects_from_scene(const std::vector<Donut::Object>& objects);
void export_high_res_frame(const std::string& filename, int width = 4096, int height = 3072);
void print_object_info() const;
-
+
void set_hdri_environment(Ref<CubemapTexture> hdri) { m_hdri_environment = hdri; }
Ref<CubemapTexture> get_hdri_environment() const { return m_hdri_environment; }
private:
@@ -161,12 +161,12 @@ namespace Donut
BlackHole m_sag_a;
Camera m_camera;
bool m_gravity = false;
-
+
int m_max_steps_moving = 60000;
int m_max_steps_static = 30000;
float m_early_exit_distance = 5.0e11f;
-
- float m_disk_thickness = 0.1f;
+
+ float m_disk_thickness = 0.1f;
float m_disk_density = 0.1f;
float m_rotation_speed = 1.0f;
float m_blur_strength = 2.0f;
diff --git a/src/platform/metal/metal_context.h b/src/platform/metal/metal_context.h
new file mode 100644
index 0000000..66e8ae2
--- /dev/null
+++ b/src/platform/metal/metal_context.h
@@ -0,0 +1,8 @@
+#pragma once
+
+namespace Donut
+{
+ bool metal_probe();
+
+ bool metal_compute_self_test();
+}
diff --git a/src/platform/metal/metal_context.mm b/src/platform/metal/metal_context.mm
new file mode 100644
index 0000000..a616865
--- /dev/null
+++ b/src/platform/metal/metal_context.mm
@@ -0,0 +1,119 @@
+#import <Metal/Metal.h>
+#import <Foundation/Foundation.h>
+
+#include "metal_context.h"
+#include "core/log.h"
+
+#include <vector>
+
+namespace Donut
+{
+ bool metal_probe()
+ {
+ @autoreleasepool
+ {
+ id<MTLDevice> device = MTLCreateSystemDefaultDevice();
+ if (device == nil)
+ {
+ DONUT_ERROR("Metal: no default device available");
+ return false;
+ }
+
+ id<MTLCommandQueue> queue = [device newCommandQueue];
+ const char* name = [[device name] UTF8String];
+
+ DONUT_INFO("Metal device: {}", name ? name : "(unknown)");
+ DONUT_INFO("Metal: unified memory = {}, max threads/threadgroup = {}",
+ device.hasUnifiedMemory ? "yes" : "no",
+ (unsigned long)device.maxThreadsPerThreadgroup.width);
+
+ if (queue == nil)
+ {
+ DONUT_WARN("Metal: failed to create command queue");
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ bool metal_compute_self_test()
+ {
+ @autoreleasepool
+ {
+ id<MTLDevice> device = MTLCreateSystemDefaultDevice();
+ id<MTLCommandQueue> queue = [device newCommandQueue];
+ if (device == nil || queue == nil)
+ return false;
+
+ NSString* src =
+ @"#include <metal_stdlib>\n"
+ "using namespace metal;\n"
+ "kernel void selfTest(texture2d<float, access::write> outTex [[texture(0)]],\n"
+ " uint2 gid [[thread_position_in_grid]])\n"
+ "{\n"
+ " uint w = outTex.get_width();\n"
+ " uint h = outTex.get_height();\n"
+ " if (gid.x >= w || gid.y >= h) return;\n"
+ " outTex.write(float4(float(gid.x) / float(w - 1),\n"
+ " float(gid.y) / float(h - 1), 0.5, 1.0), gid);\n"
+ "}\n";
+
+ NSError* err = nil;
+ id<MTLLibrary> lib = [device newLibraryWithSource:src options:nil error:&err];
+ if (lib == nil)
+ {
+ DONUT_ERROR("Metal self-test: kernel compile failed: {}",
+ err ? [[err localizedDescription] UTF8String] : "unknown");
+ return false;
+ }
+
+ id<MTLFunction> fn = [lib newFunctionWithName:@"selfTest"];
+ id<MTLComputePipelineState> pipeline =
+ [device newComputePipelineStateWithFunction:fn error:&err];
+ if (pipeline == nil)
+ {
+ DONUT_ERROR("Metal self-test: pipeline creation failed");
+ return false;
+ }
+
+ const uint32_t W = 64, H = 64;
+ MTLTextureDescriptor* desc =
+ [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
+ width:W
+ height:H
+ mipmapped:NO];
+ desc.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
+ desc.storageMode = MTLStorageModeShared;
+ id<MTLTexture> tex = [device newTextureWithDescriptor:desc];
+
+ id<MTLCommandBuffer> cb = [queue commandBuffer];
+ id<MTLComputeCommandEncoder> enc = [cb computeCommandEncoder];
+ [enc setComputePipelineState:pipeline];
+ [enc setTexture:tex atIndex:0];
+
+ MTLSize tg = MTLSizeMake(16, 16, 1);
+ MTLSize grid = MTLSizeMake(W, H, 1);
+ [enc dispatchThreads:grid threadsPerThreadgroup:tg];
+ [enc endEncoding];
+ [cb commit];
+ [cb waitUntilCompleted];
+
+ std::vector<uint8_t> px(W * H * 4);
+ [tex getBytes:px.data()
+ bytesPerRow:W * 4
+ fromRegion:MTLRegionMake2D(0, 0, W, H)
+ mipmapLevel:0];
+
+ size_t corner = ((size_t)(H - 1) * W + (W - 1)) * 4;
+ DONUT_INFO("Metal compute self-test: corner pixel RGBA = ({}, {}, {}, {})",
+ (int)px[corner + 0], (int)px[corner + 1],
+ (int)px[corner + 2], (int)px[corner + 3]);
+
+ bool ok = px[corner + 0] > 250 && px[corner + 1] > 250 &&
+ px[corner + 3] == 255;
+ DONUT_INFO("Metal compute self-test: {}", ok ? "PASS" : "FAIL");
+ return ok;
+ }
+ }
+}