diff options
| -rw-r--r-- | premake5.lua | 76 | ||||
| -rw-r--r-- | src/Core/Application.cpp | 13 | ||||
| -rw-r--r-- | src/Engine/Engine.h | 24 | ||||
| -rw-r--r-- | src/Platform/Metal/MetalContext.h | 16 | ||||
| -rw-r--r-- | src/Platform/Metal/MetalContext.mm | 124 |
5 files changed, 207 insertions, 46 deletions
diff --git a/premake5.lua b/premake5.lua index 742c552..8729d3f 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" { "Vendor/glad/include" } - + filter "system:windows" systemversion "latest" @@ -101,7 +105,7 @@ project "GLFW" filter "system:linux" pic "On" systemversion "latest" - + files { "Vendor/glfw/src/x11_init.c", @@ -110,22 +114,22 @@ project "GLFW" "Vendor/glfw/src/x11_window.c", "Vendor/glfw/src/xkb_unicode.c", "Vendor/glfw/src/xkb_unicode.h", - + "Vendor/glfw/src/wl_init.c", "Vendor/glfw/src/wl_monitor.c", "Vendor/glfw/src/wl_platform.h", "Vendor/glfw/src/wl_window.c", - + "Vendor/glfw/src/posix_module.c", "Vendor/glfw/src/posix_time.c", "Vendor/glfw/src/posix_time.h", "Vendor/glfw/src/posix_thread.c", "Vendor/glfw/src/posix_thread.h", - + "Vendor/glfw/src/glx_context.c", "Vendor/glfw/src/egl_context.c", "Vendor/glfw/src/osmesa_context.c", - + "Vendor/glfw/src/linux_joystick.c", "Vendor/glfw/src/linux_joystick.h" } @@ -148,11 +152,11 @@ project "GLFW" "Vendor/glfw/src/cocoa_time.c", "Vendor/glfw/src/cocoa_time.h", "Vendor/glfw/src/cocoa_window.m", - + "Vendor/glfw/src/nsgl_context.m", "Vendor/glfw/src/egl_context.c", "Vendor/glfw/src/osmesa_context.c", - + "Vendor/glfw/src/posix_module.c", "Vendor/glfw/src/posix_thread.c", "Vendor/glfw/src/posix_thread.h" @@ -184,8 +188,8 @@ project "GLFW" "Vendor/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 { "Vendor/imgui/imgui.cpp", @@ -225,24 +229,24 @@ project "ImGui" "Vendor/imgui/imgui_widgets.cpp", "Vendor/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 { "Vendor/ImGuizmo/ImGuizmo.cpp", "Vendor/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/OpenGLFramebuffer.h", "src/Platform/OpenGL/OpenGLFramebuffer.cpp", - + "Vendor/imgui/backends/imgui_impl_glfw.cpp", "Vendor/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" @@ -380,4 +392,4 @@ project "Donut" filter "configurations:Dist" defines "DONUT_DIST" runtime "Release" - optimize "on"
\ No newline at end of file + optimize "on" diff --git a/src/Core/Application.cpp b/src/Core/Application.cpp index eefa482..df42877 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/MetalContext.h" +#endif + namespace Donut { Application* Application::s_Instance = nullptr; @@ -103,9 +107,14 @@ namespace Donut m_StateManager->OnEvent(event); } - void Application::OnInit() - { + void Application::OnInit() + { Logger::Init(); + +#ifdef __APPLE__ + MetalProbe(); // Phase 1: native Metal device available? + MetalComputeSelfTest(); // Phase 1: full compute dispatch + read-back works? +#endif SettingsManager::Initialize(); diff --git a/src/Engine/Engine.h b/src/Engine/Engine.h index f8ef628..a4cbdc7 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); @@ -102,33 +102,33 @@ namespace Donut int GetComputeHeight() const { return m_ComputeHeight; } int GetComputeWidth() const { return (m_Width * m_ComputeHeight) / m_Height; } void UpdateComputeDimensions(); - + int GetMaxStepsMoving() const { return m_MaxStepsMoving; } int GetMaxStepsStatic() const { return m_MaxStepsStatic; } float GetEarlyExitDistance() const { return m_EarlyExitDistance; } void SetMaxStepsMoving(int steps) { m_MaxStepsMoving = steps; } void SetMaxStepsStatic(int steps) { m_MaxStepsStatic = steps; } void SetEarlyExitDistance(float distance) { m_EarlyExitDistance = distance; } - + float GetDiskThickness() const { return m_DiskThickness; } void SetDiskThickness(float thickness) { m_DiskThickness = thickness; } - + float GetDiskDensity() const { return m_DiskDensity; } void SetDiskDensity(float density) { m_DiskDensity = density; } - + float GetRotationSpeed() const { return m_RotationSpeed; } void SetRotationSpeed(float speed) { m_RotationSpeed = speed; } - + float GetBlurStrength() const { return m_BlurStrength; } void SetBlurStrength(float strength) { m_BlurStrength = strength; } - + float GetGlowIntensity() const { return m_GlowIntensity; } void SetGlowIntensity(float intensity) { m_GlowIntensity = intensity; } - + void LoadObjectsFromScene(const std::vector<Donut::Object>& objects); void ExportHighResFrame(const std::string& filename, int width = 4096, int height = 3072); void PrintObjectInfo() const; - + void SetHDRIEnvironment(Ref<CubemapTexture> hdri) { m_HDRIEnvironment = hdri; } Ref<CubemapTexture> GetHDRIEnvironment() const { return m_HDRIEnvironment; } private: @@ -170,12 +170,12 @@ namespace Donut BlackHole m_SagA; Camera m_Camera; bool m_Gravity = false; - + int m_MaxStepsMoving = 60000; int m_MaxStepsStatic = 30000; float m_EarlyExitDistance = 5.0e11f; - - float m_DiskThickness = 0.1f; + + float m_DiskThickness = 0.1f; float m_DiskDensity = 0.1f; float m_RotationSpeed = 1.0f; float m_BlurStrength = 2.0f; diff --git a/src/Platform/Metal/MetalContext.h b/src/Platform/Metal/MetalContext.h new file mode 100644 index 0000000..5d9eb08 --- /dev/null +++ b/src/Platform/Metal/MetalContext.h @@ -0,0 +1,16 @@ +#pragma once + +// Pure-C++ interface to the Metal backend (no Objective-C leaks into the rest +// of the engine). Implemented in MetalContext.mm. +namespace Donut +{ + // Phase 1 bring-up: creates the default Metal device + command queue and + // logs its capabilities, proving the Metal toolchain and build integration + // work natively on Apple Silicon. This grows into the Metal compute island + // that runs the geodesic ray tracer in real time. + bool MetalProbe(); + + // Compiles + dispatches a trivial compute kernel and validates the read-back, + // proving the Metal compute path the geodesic ray tracer will run on. + bool MetalComputeSelfTest(); +} diff --git a/src/Platform/Metal/MetalContext.mm b/src/Platform/Metal/MetalContext.mm new file mode 100644 index 0000000..1bb9e70 --- /dev/null +++ b/src/Platform/Metal/MetalContext.mm @@ -0,0 +1,124 @@ +#import <Metal/Metal.h> +#import <Foundation/Foundation.h> + +#include "MetalContext.h" +#include "Core/Log.h" + +#include <vector> + +namespace Donut +{ + bool MetalProbe() + { + @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; + } + } + + // Compiles a trivial compute kernel, dispatches it over a small texture, and + // reads the result back to confirm the full Metal compute path works: source + // compilation, pipeline state, command encoding, dispatch, and shared-memory + // read-back. This is the mechanism the geodesic ray tracer will run on. + bool MetalComputeSelfTest() + { + @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]; + + // Read back the far corner; the kernel writes (~1, ~1, 0.5, 1) there. + 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; + } + } +} |
