From c83851e88489fbb571e79748816759ba46a9d7c4 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Wed, 6 Jul 2022 10:26:30 +0100 Subject: [PATCH] First Apple platform support for Vulkan demos Uses Metal-CPP-Extensions code to create window and the event loop. Does not include support for Nuklear --- util/test/demos/CMakeLists.txt | 36 ++++- util/test/demos/apple/apple_platform.cpp | 97 ++++++++++++ util/test/demos/apple/apple_platform.h | 44 ++++++ util/test/demos/apple/apple_window.cpp | 192 +++++++++++++++++++++++ util/test/demos/apple/apple_window.h | 43 +++++ util/test/demos/main.cpp | 24 +++ util/test/demos/test_common.cpp | 8 +- util/test/demos/test_common.h | 2 + util/test/demos/vk/vk_test.cpp | 32 +++- 9 files changed, 470 insertions(+), 8 deletions(-) create mode 100644 util/test/demos/apple/apple_platform.cpp create mode 100644 util/test/demos/apple/apple_platform.h create mode 100644 util/test/demos/apple/apple_window.cpp create mode 100644 util/test/demos/apple/apple_window.h diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index fa27cc7b7..b7f62165e 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 2.8.12) +if(APPLE) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../bin") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endif() + set(PROJECT demos) set(VULKAN_SRC @@ -127,7 +133,17 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${warning_flags}") endif() -if(NOT APPLE AND UNIX) +if(APPLE) + list(APPEND SRC + apple/official/metal-cpp.cpp + apple/official/metal-cpp.h + apple/apple_platform.cpp + apple/apple_platform.h + apple/apple_window.cpp + apple/apple_window.h) + add_executable(demos ${SRC} ${VULKAN_SRC}) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") +elseif(UNIX) list(APPEND OPENGL_SRC 3rdparty/glad/glad_glx.c gl/gl_test_linux.cpp) @@ -161,7 +177,21 @@ find_package(Threads REQUIRED) target_include_directories(demos PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vk/official PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_definitions(demos PRIVATE -DVK_USE_PLATFORM_XCB_KHR=1) -target_link_libraries(demos PRIVATE -lX11 -lxcb -lX11-xcb ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) + +if(APPLE) + target_include_directories(demos + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/apple/official) + find_library(COCOA_LIBRARY Cocoa) + find_library(QUARTZCORE_LIBRARY QuartzCore) + find_library(METAL_LIBRARY Metal) + target_compile_definitions(demos PRIVATE + -DVK_USE_PLATFORM_MACOS_MVK=1) + set(LIBS ${COCOA_LIBRARY} ${QUARTZCORE_LIBRARY} ${METAL_LIBRARY}) +elseif(UNIX) + target_compile_definitions(demos PRIVATE -DVK_USE_PLATFORM_XCB_KHR=1) + set(LIBS -lX11 -lxcb -lX11-xcb) +endif() + +target_link_libraries(demos PRIVATE ${LIBS} ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS}) set_target_properties(demos PROPERTIES OUTPUT_NAME demos_${BIN_SUFFIX}) diff --git a/util/test/demos/apple/apple_platform.cpp b/util/test/demos/apple/apple_platform.cpp new file mode 100644 index 000000000..cede6eefd --- /dev/null +++ b/util/test/demos/apple/apple_platform.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2022 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include +#include +#include "test_common.h" + +uint64_t GetMemoryUsage() +{ + mach_task_basic_info taskInfo; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + + int ret = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount); + + if(ret != KERN_SUCCESS) + return 0; + + return taskInfo.resident_size; +} + +std::string GetCWD() +{ + char cwd[MAX_PATH + 1] = {0}; + getcwd(cwd, MAX_PATH); + + std::string cwdstr = cwd; + + for(size_t i = 0; i < cwdstr.size(); i++) + if(cwdstr[i] == '\\') + cwdstr[i] = '/'; + + while(cwdstr.back() == '/' || cwdstr.back() == '\\') + cwdstr.pop_back(); + + return cwdstr; +} + +std::string GetEnvVar(const char *var) +{ + const char *data = getenv(var); + if(data) + return data; + + return ""; +} + +std::string GetExecutableName() +{ + char path[512] = {0}; + std::string selfName; + + uint32_t pathSize = (uint32_t)sizeof(path); + if(_NSGetExecutablePath(path, &pathSize) == 0) + { + selfName = path; + } + else + { + pathSize++; + if(_NSGetExecutablePath(path, &pathSize) == 0) + { + selfName = path; + } + else + { + return "/unknown/unknown"; + } + } + + memset(path, 0, sizeof(path)); + readlink(selfName.c_str(), path, 511); + if(path[0] != 0) + selfName = path; + + return selfName; +} diff --git a/util/test/demos/apple/apple_platform.h b/util/test/demos/apple/apple_platform.h new file mode 100644 index 000000000..59efd91e9 --- /dev/null +++ b/util/test/demos/apple/apple_platform.h @@ -0,0 +1,44 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2022 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#define DEBUG_BREAK() raise(SIGTRAP) + +#define MAX_PATH 256 + +#define msleep(time) usleep((time)*1000) + +#define EXECUTABLE_SUFFIX "" + +#define MakeDir(dir) mkdir(dir, 0755) + +std::string GetExecutableName(); diff --git a/util/test/demos/apple/apple_window.cpp b/util/test/demos/apple/apple_window.cpp new file mode 100644 index 000000000..2fe08e83a --- /dev/null +++ b/util/test/demos/apple/apple_window.cpp @@ -0,0 +1,192 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2022 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include "apple_window.h" + +#include "official/metal-cpp.h" + +static NS::Application *pSharedApplication = NULL; +MyAppDelegate *AppleWindow::pAppDelegate = NULL; + +class MyAppDelegate : public NS::ApplicationDelegate +{ +public: + ~MyAppDelegate(); + + NS::Menu *createMenuBar(); + + virtual void applicationWillFinishLaunching(NS::Notification *pNotification) override; + virtual void applicationDidFinishLaunching(NS::Notification *pNotification) override; + virtual bool applicationShouldTerminateAfterLastWindowClosed(NS::Application *pSender) override; + + void CreateWindow(int width, int height, const char *title); + NS::View *GetContentView(); + NS::Window *GetWindow(); + +private: + NS::Window *_pWindow; +}; + +MyAppDelegate::~MyAppDelegate() +{ + _pWindow->release(); +} + +NS::Menu *MyAppDelegate::createMenuBar() +{ + using NS::StringEncoding::UTF8StringEncoding; + + NS::Menu *pMainMenu = NS::Menu::alloc()->init(); + NS::MenuItem *pAppMenuItem = NS::MenuItem::alloc()->init(); + NS::Menu *pAppMenu = NS::Menu::alloc()->init(NS::String::string("Appname", UTF8StringEncoding)); + + NS::String *appName = NS::RunningApplication::currentApplication()->localizedName(); + NS::String *quitItemName = + NS::String::string("Quit ", UTF8StringEncoding)->stringByAppendingString(appName); + SEL quitCb = + NS::MenuItem::registerActionCallback("appQuit", [](void *, SEL, const NS::Object *pSender) { + NS::Application *pApp = NS::Application::sharedApplication(); + pApp->terminate(pSender); + }); + + NS::MenuItem *pAppQuitItem = + pAppMenu->addItem(quitItemName, quitCb, NS::String::string("q", UTF8StringEncoding)); + pAppQuitItem->setKeyEquivalentModifierMask(NS::EventModifierFlagCommand); + pAppMenuItem->setSubmenu(pAppMenu); + + NS::MenuItem *pWindowMenuItem = NS::MenuItem::alloc()->init(); + NS::Menu *pWindowMenu = NS::Menu::alloc()->init(NS::String::string("Window", UTF8StringEncoding)); + + SEL closeWindowCb = + NS::MenuItem::registerActionCallback("windowClose", [](void *, SEL, const NS::Object *) { + NS::Application *pApp = NS::Application::sharedApplication(); + pApp->windows()->object(0)->close(); + }); + NS::MenuItem *pCloseWindowItem = + pWindowMenu->addItem(NS::String::string("Close Window", UTF8StringEncoding), closeWindowCb, + NS::String::string("w", UTF8StringEncoding)); + pCloseWindowItem->setKeyEquivalentModifierMask(NS::EventModifierFlagCommand); + + pWindowMenuItem->setSubmenu(pWindowMenu); + + pMainMenu->addItem(pAppMenuItem); + pMainMenu->addItem(pWindowMenuItem); + + pAppMenuItem->release(); + pWindowMenuItem->release(); + pAppMenu->release(); + pWindowMenu->release(); + + return pMainMenu->autorelease(); +} + +void MyAppDelegate::applicationWillFinishLaunching(NS::Notification *pNotification) +{ + NS::Menu *pMenu = createMenuBar(); + NS::Application *pApp = (NS::Application *)pNotification->object(); + pApp->setMainMenu(pMenu); + pApp->setActivationPolicy(NS::ActivationPolicy::ActivationPolicyRegular); +} + +void MyAppDelegate::applicationDidFinishLaunching(NS::Notification *pNotification) +{ + NS::Application *pApp = (NS::Application *)pNotification->object(); + pApp->activateIgnoringOtherApps(true); +} + +bool MyAppDelegate::applicationShouldTerminateAfterLastWindowClosed(NS::Application *pSender) +{ + return true; +} + +void MyAppDelegate::CreateWindow(int width, int height, const char *title) +{ + CGRect frame = CGRectMake(100.0f, 100.0f, width, height); + + _pWindow = NS::Window::alloc()->init(frame, NS::WindowStyleMaskClosable | NS::WindowStyleMaskTitled, + NS::BackingStoreBuffered, false); + + NS::View *view = _pWindow->contentView(); + view->setWantsLayer(true); + view->setLayer(CA::MetalLayer::layer()); + + _pWindow->setTitle(NS::String::string(title, NS::StringEncoding::UTF8StringEncoding)); + _pWindow->makeKeyAndOrderFront(nullptr); +} + +NS::View *MyAppDelegate::GetContentView() +{ + return _pWindow->contentView(); +} + +NS::Window *MyAppDelegate::GetWindow() +{ + return _pWindow; +} + +AppleWindow::~AppleWindow() +{ + pAppDelegate = nullptr; +} + +AppleWindow::AppleWindow(int width, int height, const char *title) : GraphicsWindow(title) +{ + pAppDelegate->CreateWindow(width, height, title); + view = pAppDelegate->GetContentView(); +} + +bool AppleWindow::Init() +{ + pSharedApplication = NS::Application::sharedApplication(); + pAppDelegate = new MyAppDelegate(); + pSharedApplication->setDelegate(pAppDelegate); + + if(!NS::RunningApplication::currentApplication()->finishedLaunching()) + pSharedApplication->finishLaunching(); + + return true; +} + +void AppleWindow::Resize(int width, int height) +{ + TEST_ERROR("Resize is not implemented"); +} + +extern "C" void *const NSDefaultRunLoopMode; + +bool AppleWindow::Update() +{ + NS::AutoreleasePool *pAutoreleasePool = NS::AutoreleasePool::alloc()->init(); + while(true) + { + NS::Event *event = pSharedApplication->nextEventMatchingMask( + (int)NS::EventMaskAny, NS::Date::distantPast(), (NS::String *)NSDefaultRunLoopMode, true); + if(event == NULL) + break; + pSharedApplication->sendEvent(event); + } + pAutoreleasePool->release(); + + return pAppDelegate->GetWindow()->visible(); +} diff --git a/util/test/demos/apple/apple_window.h b/util/test/demos/apple/apple_window.h new file mode 100644 index 000000000..4868e8c3f --- /dev/null +++ b/util/test/demos/apple/apple_window.h @@ -0,0 +1,43 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2022 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#pragma once + +#include "../test_common.h" + +class MyAppDelegate; + +struct AppleWindow : GraphicsWindow +{ + AppleWindow(int width, int height, const char *title); + ~AppleWindow(); + + static bool Init(); + + void Resize(int width, int height); + bool Update(); + + static MyAppDelegate *pAppDelegate; + void *view; +}; diff --git a/util/test/demos/main.cpp b/util/test/demos/main.cpp index 8af9d45c7..f3d457f13 100644 --- a/util/test/demos/main.cpp +++ b/util/test/demos/main.cpp @@ -214,6 +214,30 @@ void NuklearShutdown() XCloseDisplay(dpy); } +#elif defined(__APPLE__) + +nk_context *NuklearInit(int width, int height, const char *title) +{ + TEST_ERROR("NuklearInit: Not Implemented"); + return NULL; +} + +bool NuklearTick(nk_context *ctx) +{ + TEST_ERROR("NuklearTick: Not Implemented"); + return false; +} + +void NuklearRender() +{ + TEST_ERROR("NuklearRender: Not Implemented"); +} + +void NuklearShutdown() +{ + TEST_ERROR("NuklearShutdown: Not Implemented"); +} + #else #error UNKNOWN PLATFORM diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp index 3ecb180a0..b0c728289 100644 --- a/util/test/demos/test_common.cpp +++ b/util/test/demos/test_common.cpp @@ -569,10 +569,16 @@ bool GraphicsTest::Init() HMODULE mod = GetModuleHandleA("renderdoc.dll"); if(mod) RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); -#else +#elif defined(__linux__) void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); if(mod) RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI"); +#elif defined(__APPLE__) + void *mod = dlopen("librenderdoc.dylib", RTLD_NOW | RTLD_NOLOAD); + if(mod) + RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI"); +#else +#error UNKNOWN PLATFORM #endif if(RENDERDOC_GetAPI) diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h index ae782800e..be9886e89 100644 --- a/util/test/demos/test_common.h +++ b/util/test/demos/test_common.h @@ -28,6 +28,8 @@ #include "win32/win32_platform.h" #elif defined(__linux__) #include "linux/linux_platform.h" +#elif defined(__APPLE__) +#include "apple/apple_platform.h" #else #error UNKNOWN PLATFORM #endif diff --git a/util/test/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp index 7ecfe4f66..b53995bc6 100644 --- a/util/test/demos/vk/vk_test.cpp +++ b/util/test/demos/vk/vk_test.cpp @@ -102,8 +102,12 @@ void main() #if defined(WIN32) #include "../win32/win32_window.h" -#else +#elif defined(__linux__) #include "../linux/linux_window.h" +#elif defined(__APPLE__) +#include "../apple/apple_window.h" +#else +#error UNKNOWN PLATFORM #endif static VkBool32 VKAPI_PTR vulkanCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, @@ -157,10 +161,16 @@ void VulkanGraphicsTest::Prepare(int argc, char **argv) #if defined(WIN32) enabledInstExts.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); -#else +#elif defined(__linux__) enabledInstExts.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); X11Window::Init(); +#elif defined(__APPLE__) + enabledInstExts.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); + + AppleWindow::Init(); +#else +#error UNKNOWN PLATFORM #endif std::vector optInstExts; @@ -706,8 +716,12 @@ VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char * { #if defined(WIN32) GraphicsWindow *platWin = new Win32Window(width, height, title); -#else +#elif defined(__linux__) GraphicsWindow *platWin = new X11Window(width, height, 0, title); +#elif defined(__APPLE__) + GraphicsWindow *platWin = new AppleWindow(width, height, title); +#else +#error UNKNOWN PLATFORM #endif return new VulkanWindow(this, platWin); @@ -1269,7 +1283,7 @@ VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) createInfo.hinstance = GetModuleHandleA(NULL); vkCreateWin32SurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); -#else +#elif defined(__linux__) VkXcbSurfaceCreateInfoKHR createInfo; createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; @@ -1279,6 +1293,16 @@ VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) createInfo.window = ((X11Window *)win)->xcb.window; vkCreateXcbSurfaceKHR(m_Test->instance, &createInfo, NULL, &surface); +#elif defined(__APPLE__) + VkMacOSSurfaceCreateInfoMVK createInfo; + + createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.pView = ((AppleWindow *)win)->view; + vkCreateMacOSSurfaceMVK(m_Test->instance, &createInfo, NULL, &surface); +#else +#error UNKNOWN PLATFORM #endif }