diff --git a/.gitignore b/.gitignore index dcc08c7..ac6b63b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ build/ .vscode/ .idea +.cache +compile_commands.json cmake-build-debug* diff --git a/CMakeLists.txt b/CMakeLists.txt index 00a9276..257be0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,12 @@ cmake_minimum_required(VERSION 3.20) -project(rt64) +project(rt64 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_VISIBILITY_PRESET hidden) +if (APPLE) + enable_language(OBJC OBJCXX) +endif() + option(RT64_STATIC "Build RT64 as a static library" OFF) option(RT64_BUILD_EXAMPLES "Build examples for RT64" OFF) @@ -379,6 +383,13 @@ target_link_libraries(rt64 libzstd_static) add_subdirectory(src/tools/texture_hasher) add_subdirectory(src/tools/texture_packer) +# Add any Apple-specific source files and libraries +if (APPLE) + target_sources(rt64 PRIVATE + "${PROJECT_SOURCE_DIR}/src/common/rt64_apple.mm" + ) +endif() + # Add any Windows-specific source files and libraries if (WIN32) target_sources(rt64 PRIVATE diff --git a/examples/rt64_render_interface.cpp b/examples/rt64_render_interface.cpp index 6eb573d..ccb0633 100644 --- a/examples/rt64_render_interface.cpp +++ b/examples/rt64_render_interface.cpp @@ -9,6 +9,11 @@ #include #include +#ifdef __APPLE__ +#include +#include +#endif + #ifdef _WIN64 #include "shaders/RenderInterfaceTestPS.hlsl.dxil.h" #include "shaders/RenderInterfaceTestRT.hlsl.dxil.h" @@ -800,7 +805,54 @@ namespace RT64 { } #elif defined(__APPLE__) void RenderInterfaceTest(RenderInterface* renderInterface) { - assert(false); + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError()); + return; + } + + SDL_Window *window = SDL_CreateWindow("Render Interface Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_RESIZABLE | SDL_WINDOW_METAL); + if (window == nullptr) { + fprintf(stderr, "SDL_CreateWindow Error: %s\n", SDL_GetError()); + SDL_Quit(); + return; + } + + // Setup Metal view. + SDL_MetalView view = SDL_Metal_CreateView(window); + + // SDL_Window's handle can be used directly if needed + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); + + TestInitialize(renderInterface, { wmInfo.info.cocoa.window, view }); + TestResize(); + + bool running = true; + while (running) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + running = false; + break; + + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + TestResize(); + } + break; + } + } + + TestDraw(); + SDL_Delay(16); // Approximate 60 FPS + } + + TestShutdown(); + SDL_Metal_DestroyView(view); + SDL_DestroyWindow(window); + SDL_Quit(); } #endif -}; \ No newline at end of file +}; diff --git a/src/common/rt64_apple.h b/src/common/rt64_apple.h new file mode 100644 index 0000000..f3ba0a1 --- /dev/null +++ b/src/common/rt64_apple.h @@ -0,0 +1,18 @@ +// +// RT64 +// + +#pragma once + +#include + +struct CocoaWindowAttributes { + int x, y; + int width, height; +}; + +const char* GetHomeDirectory(); + +void GetWindowAttributes(void* window, CocoaWindowAttributes *attributes); +int GetWindowRefreshRate(void* window); +void WindowToggleFullscreen(void* window); diff --git a/src/common/rt64_apple.mm b/src/common/rt64_apple.mm new file mode 100644 index 0000000..80b419a --- /dev/null +++ b/src/common/rt64_apple.mm @@ -0,0 +1,38 @@ + +#include "rt64_apple.h" + +#import +#import + +const char* GetHomeDirectory() { + return strdup([NSHomeDirectory() UTF8String]); +} + +void GetWindowAttributes(void* window, CocoaWindowAttributes *attributes) { + NSWindow *nsWindow = (NSWindow *)window; + NSRect contentFrame = [[nsWindow contentView] frame]; + attributes->x = contentFrame.origin.x; + attributes->y = contentFrame.origin.y; + attributes->width = contentFrame.size.width; + attributes->height = contentFrame.size.height; +} + +int GetWindowRefreshRate(void* window) { + NSWindow *nsWindow = (NSWindow *)window; + NSScreen *screen = [nsWindow screen]; + + if (@available(macOS 12.0, *)) { + return (int)[screen maximumFramesPerSecond]; + } + + // TODO: Implement this. + return 0; +} + +void WindowToggleFullscreen(void* window) { + // UI operations have to happen on the main thread. + dispatch_async(dispatch_get_main_queue(), ^{ + NSWindow *nsWindow = (NSWindow *)window; + [nsWindow toggleFullScreen:NULL]; + }); +} diff --git a/src/common/rt64_hlslpp.h b/src/common/rt64_hlslpp.h index 265f729..f77793b 100644 --- a/src/common/rt64_hlslpp.h +++ b/src/common/rt64_hlslpp.h @@ -1,7 +1,11 @@ // Disable SIMD on GCC due to UB when compiling with optimizations. - #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #define HLSLPP_SCALAR #endif +// Disable SIMD on Apple Silicon due to UB when compiling with optimizations. +#if defined(__APPLE__) && defined(__aarch64__) +#define HLSLPP_SCALAR +#endif + #include "hlsl++.h" \ No newline at end of file diff --git a/src/common/rt64_user_paths.cpp b/src/common/rt64_user_paths.cpp index cf1e0b7..0dad204 100644 --- a/src/common/rt64_user_paths.cpp +++ b/src/common/rt64_user_paths.cpp @@ -9,6 +9,8 @@ # include #elif defined(_WIN32) # include +#elif defined(__APPLE__) +# include "common/rt64_apple.h" #endif namespace RT64 { @@ -30,10 +32,14 @@ namespace RT64 { } CoTaskMemFree(knownPath); -# elif defined(__linux__) +# elif defined(__linux__) || defined(__APPLE__) const char *homeDir = getenv("HOME"); if (homeDir == nullptr) { + #if defined(__linux__) homeDir = getpwuid(getuid())->pw_dir; + #elif defined(__APPLE__) + homeDir = GetHomeDirectory(); + #endif } if (homeDir != nullptr) { diff --git a/src/hle/rt64_application_window.cpp b/src/hle/rt64_application_window.cpp index 58788aa..c4feff0 100644 --- a/src/hle/rt64_application_window.cpp +++ b/src/hle/rt64_application_window.cpp @@ -13,6 +13,8 @@ #elif defined(__linux__) # define Status int # include +#elif defined(__APPLE__) +# include "common/rt64_apple.h" #endif #include "common/rt64_common.h" @@ -198,6 +200,9 @@ namespace RT64 { } } + fullScreen = newFullScreen; +# elif defined(__APPLE__) + WindowToggleFullscreen(windowHandle.window); fullScreen = newFullScreen; # endif } @@ -271,6 +276,8 @@ namespace RT64 { } XRRFreeScreenResources(screenResources); +# elif defined(__APPLE__) + refreshRate = GetWindowRefreshRate(windowHandle.window); # endif } @@ -292,6 +299,11 @@ namespace RT64 { XGetWindowAttributes(windowHandle.display, windowHandle.window, &attributes); newWindowLeft = attributes.x; newWindowTop = attributes.y; +# elif defined(__APPLE__) + CocoaWindowAttributes attributes; + GetWindowAttributes(windowHandle.window, &attributes); + newWindowLeft = attributes.x; + newWindowTop = attributes.y; # endif if ((windowLeft != newWindowLeft) || (windowTop != newWindowTop)) { diff --git a/src/rhi/rt64_render_interface_types.h b/src/rhi/rt64_render_interface_types.h index f2996ba..17e7d6f 100644 --- a/src/rhi/rt64_render_interface_types.h +++ b/src/rhi/rt64_render_interface_types.h @@ -43,7 +43,9 @@ namespace RT64 { }; #elif defined(__APPLE__) struct RenderWindow { - NSWindow* window; + void* window; + void* layer; + bool operator==(const struct RenderWindow& rhs) const { return window == rhs.window; } @@ -1689,4 +1691,4 @@ namespace RT64 { struct RenderInterfaceCapabilities { RenderShaderFormat shaderFormat = RenderShaderFormat::UNKNOWN; }; -}; \ No newline at end of file +}; diff --git a/src/vulkan/rt64_vulkan.cpp b/src/vulkan/rt64_vulkan.cpp index 6a47256..1953b65 100644 --- a/src/vulkan/rt64_vulkan.cpp +++ b/src/vulkan/rt64_vulkan.cpp @@ -21,6 +21,12 @@ //# define VULKAN_OBJECT_NAMES_ENABLED #endif +#ifdef __APPLE__ +#include "vulkan/vulkan_beta.h" +#include "vulkan/vulkan_metal.h" +#include "common/rt64_apple.h" +#endif + // TODO: // - Fix resource pools. @@ -48,6 +54,9 @@ namespace RT64 { VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, # elif defined(__linux__) VK_KHR_XLIB_SURFACE_EXTENSION_NAME, +# elif defined(__APPLE__) + VK_EXT_METAL_SURFACE_EXTENSION_NAME, + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, # endif }; @@ -58,6 +67,10 @@ namespace RT64 { static const std::unordered_set RequiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, +# ifdef __APPLE__ + VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, +# endif # ifdef VULKAN_OBJECT_NAMES_ENABLED VK_EXT_DEBUG_UTILS_EXTENSION_NAME # endif @@ -73,6 +86,7 @@ namespace RT64 { VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, + VK_EXT_LAYER_SETTINGS_EXTENSION_NAME }; // Common functions. @@ -546,7 +560,9 @@ namespace RT64 { flags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT; flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; + #ifndef __APPLE__ flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; + #endif flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; flags |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; flags |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; @@ -1909,6 +1925,19 @@ namespace RT64 { fprintf(stderr, "vkCreateXlibSurfaceKHR failed with error code 0x%X.\n", res); return; } +# elif defined(__APPLE__) + assert(renderWindow.window != 0); + assert(renderWindow.layer != 0); + VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {}; + surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + surfaceCreateInfo.pLayer = renderWindow.layer; + + VulkanInterface *renderInterface = commandQueue->device->renderInterface; + res = vkCreateMetalSurfaceEXT(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface); + if (res != VK_SUCCESS) { + fprintf(stderr, "vkCreateMetalSurfaceEXT failed with error code 0x%X.\n", res); + return; + } # endif VkBool32 presentSupported = false; @@ -2039,7 +2068,14 @@ namespace RT64 { } // Handle the error silently. +#if defined(__APPLE__) + // Under MoltenVK, VK_SUBOPTIMAL_KHR does not result in a valid state for rendering. We intentionally + // only check for this error during present to avoid having to synchronize manually against the semaphore + // signalled by vkAcquireNextImageKHR. + if (res != VK_SUCCESS) { +#else if ((res != VK_SUCCESS) && (res != VK_SUBOPTIMAL_KHR)) { +#endif return false; } @@ -2183,6 +2219,11 @@ namespace RT64 { // The attributes width and height members do not include the border. dstWidth = attributes.width; dstHeight = attributes.height; +# elif defined(__APPLE__) + CocoaWindowAttributes attributes; + GetWindowAttributes(renderWindow.window, &attributes); + dstWidth = attributes.width; + dstHeight = attributes.height; # endif } @@ -3919,6 +3960,10 @@ namespace RT64 { createInfo.ppEnabledLayerNames = nullptr; createInfo.enabledLayerCount = 0; + #ifdef __APPLE__ + createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + #endif + // Check for extensions. uint32_t extensionCount; vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); @@ -4022,4 +4067,4 @@ namespace RT64 { std::unique_ptr createdInterface = std::make_unique(); return createdInterface->isValid() ? std::move(createdInterface) : nullptr; } -}; \ No newline at end of file +}; diff --git a/src/vulkan/rt64_vulkan.h b/src/vulkan/rt64_vulkan.h index 68a54f2..4f6bc0d 100644 --- a/src/vulkan/rt64_vulkan.h +++ b/src/vulkan/rt64_vulkan.h @@ -17,6 +17,8 @@ #define VK_USE_PLATFORM_ANDROID_KHR #elif defined(__linux__) #define VK_USE_PLATFORM_XLIB_KHR +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT #endif #include "volk/volk.h" @@ -403,4 +405,4 @@ namespace RT64 { const RenderInterfaceCapabilities &getCapabilities() const override; bool isValid() const; }; -}; \ No newline at end of file +};