From ad6e36023f614fc7dc89e954ce978a0b1fd0264f Mon Sep 17 00:00:00 2001 From: daemyung jang Date: Sat, 12 Sep 2020 03:19:28 +0900 Subject: [PATCH] Run on MacOS (#762) * Run on MacOS * Update BUILD.md --- BUILD.md | 8 +- CMakeLists.txt | 72 ++++++---- base/vulkanexamplebase.cpp | 251 +++++++++++++++++++++++++++++++++ base/vulkanexamplebase.h | 23 +++ examples/triangle/triangle.cpp | 16 +++ 5 files changed, 338 insertions(+), 32 deletions(-) diff --git a/BUILD.md b/BUILD.md index 8bc9e06f..411895db 100644 --- a/BUILD.md +++ b/BUILD.md @@ -36,4 +36,10 @@ If you want to build and install on a connected device or emulator image, run `` ## [iOS and macOS](xcode/) -Building for *iOS* and *macOS* is done using the [examples](xcode/examples.xcodeproj) *Xcode* project found in the [xcode](xcode) directory. These examples use the [**MoltenVK**](https://moltengl.com/moltenvk) Vulkan driver to provide Vulkan support on *iOS* and *macOS*, and require an *iOS* or *macOS* device that supports *Metal*. Please see the [MoltenVK Examples readme](xcode/README_MoltenVK_Examples.md) for more info on acquiring **MoltenVK** and building and deploying the examples on *iOS* and *macOS*. \ No newline at end of file +Building for *iOS* and *macOS* is done using the [examples](xcode/examples.xcodeproj) *Xcode* project found in the [xcode](xcode) directory. These examples use the [**MoltenVK**](https://moltengl.com/moltenvk) Vulkan driver to provide Vulkan support on *iOS* and *macOS*, and require an *iOS* or *macOS* device that supports *Metal*. Please see the [MoltenVK Examples readme](xcode/README_MoltenVK_Examples.md) for more info on acquiring **MoltenVK** and building and deploying the examples on *iOS* and *macOS*. + +##### MacOS +Use the provided CMakeLists.txt with [CMake](https://cmake.org) to generate a build configuration for your favorite IDE or compiler, e.g.: +``` +cmake -G "Xcode" +``` \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 77dc848e..fd2814b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,10 @@ if (NOT CMAKE_VERSION VERSION_LESS 3.7.0) find_package(Vulkan) endif() +IF(UNIX AND NOT APPLE) + set(LINUX TRUE) +ENDIF() + IF(WIN32) IF (NOT Vulkan_FOUND) find_library(Vulkan_LIBRARY NAMES vulkan-1 vulkan PATHS ${CMAKE_SOURCE_DIR}/libs/vulkan) @@ -35,7 +39,7 @@ IF(WIN32) ENDIF() ENDIF() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WIN32_KHR") -ELSE(WIN32) +ELSEIF(LINUX) IF (NOT Vulkan_FOUND) find_library(Vulkan_LIBRARY NAMES vulkan HINTS "$ENV{VULKAN_SDK}/lib" "${CMAKE_SOURCE_DIR}/libs/vulkan" REQUIRED) IF (Vulkan_LIBRARY) @@ -44,36 +48,38 @@ ELSE(WIN32) ENDIF() ENDIF() find_package(Threads REQUIRED) -IF(USE_D2D_WSI) - MESSAGE("Using direct to display extension...") - add_definitions(-D_DIRECT2DISPLAY) -ELSEIF(USE_WAYLAND_WSI) - find_program(PKG_CONFIG pkg-config) - if (NOT PKG_CONFIG) - message(FATAL_ERROR "pkg-config binary not found") - endif () - find_package(Wayland REQUIRED) - if (NOT WAYLAND_FOUND) - message(FATAL_ERROR "Wayland development package not found") - endif () - pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) - if (NOT WAYLAND_PROTOCOLS_FOUND) - message(FATAL_ERROR "Wayland protocols package not found") - endif () - find_program(WAYLAND_SCANNER wayland-scanner) - if (NOT WAYLAND_SCANNER) - message(FATAL_ERROR "wayland-scanner binary not found") - endif () - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WAYLAND_KHR") - include_directories(${WAYLAND_INCLUDE_DIR}) - execute_process(COMMAND ${PKG_CONFIG} --variable=pkgdatadir wayland-protocols OUTPUT_VARIABLE protocol_dir OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${WAYLAND_SCANNER} client-header ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-client-protocol.h - COMMAND ${WAYLAND_SCANNER} private-code ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-protocol.c) - include_directories(${CMAKE_BINARY_DIR}) -ELSE(USE_D2D_WSI) - find_package(XCB REQUIRED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_XCB_KHR") -ENDIF(USE_D2D_WSI) + IF(USE_D2D_WSI) + MESSAGE("Using direct to display extension...") + add_definitions(-D_DIRECT2DISPLAY) + ELSEIF(USE_WAYLAND_WSI) + find_program(PKG_CONFIG pkg-config) + if (NOT PKG_CONFIG) + message(FATAL_ERROR "pkg-config binary not found") + endif () + find_package(Wayland REQUIRED) + if (NOT WAYLAND_FOUND) + message(FATAL_ERROR "Wayland development package not found") + endif () + pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) + if (NOT WAYLAND_PROTOCOLS_FOUND) + message(FATAL_ERROR "Wayland protocols package not found") + endif () + find_program(WAYLAND_SCANNER wayland-scanner) + if (NOT WAYLAND_SCANNER) + message(FATAL_ERROR "wayland-scanner binary not found") + endif () + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WAYLAND_KHR") + include_directories(${WAYLAND_INCLUDE_DIR}) + execute_process(COMMAND ${PKG_CONFIG} --variable=pkgdatadir wayland-protocols OUTPUT_VARIABLE protocol_dir OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${WAYLAND_SCANNER} client-header ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-client-protocol.h + COMMAND ${WAYLAND_SCANNER} private-code ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-protocol.c) + include_directories(${CMAKE_BINARY_DIR}) + ELSE(USE_D2D_WSI) + find_package(XCB REQUIRED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_XCB_KHR") + ENDIF(USE_D2D_WSI) +ELSEIF(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_MACOS_MVK -DVK_EXAMPLE_XCODE_GENERATED") # Todo : android? ENDIF(WIN32) @@ -115,10 +121,14 @@ endif() # Compiler specific stuff IF(MSVC) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") +ELSEIF(APPLE) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc -xobjective-c++") ENDIF(MSVC) IF(WIN32) # Nothing here (yet) +ELSEIF(APPLE) + link_libraries(${Vulkan_LIBRARY} "-framework AppKit" "-framework QuartzCore") ELSE(WIN32) link_libraries(${XCB_LIBRARIES} ${Vulkan_LIBRARY} ${Vulkan_LIBRARY} ${WAYLAND_CLIENT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) ENDIF(WIN32) diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index bb544bf8..9e4a2b5b 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -8,6 +8,13 @@ #include "vulkanexamplebase.h" +#if (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED)) +#include +#include +#include +#include +#endif + std::vector VulkanExampleBase::args; VkResult VulkanExampleBase::createInstance(bool enableValidation) @@ -532,6 +539,8 @@ void VulkanExampleBase::renderLoop() } updateOverlay(); } +#elif (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED)) + [NSApp run]; #endif // Flush device to make sure all resources can be freed if (device != VK_NULL_HANDLE) { @@ -1427,11 +1436,253 @@ void VulkanExampleBase::handleAppCommand(android_app * app, int32_t cmd) } } #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) +#if defined(VK_EXAMPLE_XCODE_GENERATED) +@interface AppDelegate : NSObject +{ +} + +@end + +@implementation AppDelegate +{ +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +@end + +static CVReturn displayLinkOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, + const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, + void *displayLinkContext) +{ + @autoreleasepool + { + auto vulkanExample = static_cast(displayLinkContext); + vulkanExample->displayLinkOutputCb(); + } + return kCVReturnSuccess; +} + +@interface View : NSView +{ +@public + VulkanExampleBase *vulkanExample; +} + +@end + +@implementation View +{ + CVDisplayLinkRef displayLink; +} + +- (instancetype)initWithFrame:(NSRect)frameRect +{ + self = [super initWithFrame:(frameRect)]; + if (self) + { + self.wantsLayer = YES; + self.layer = [CAMetalLayer layer]; + } + return self; +} + +- (void)viewDidMoveToWindow +{ + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + CVDisplayLinkSetOutputCallback(displayLink, &displayLinkOutputCallback, vulkanExample); + CVDisplayLinkStart(displayLink); +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)keyDown:(NSEvent*)event +{ + switch (event.keyCode) + { + case kVK_ANSI_P: + vulkanExample->paused = !vulkanExample->paused; + break; + case kVK_Escape: + [NSApp terminate:nil]; + break; + case kVK_ANSI_W: + vulkanExample->camera.keys.up = true; + break; + case kVK_ANSI_S: + vulkanExample->camera.keys.down = true; + break; + case kVK_ANSI_A: + vulkanExample->camera.keys.left = true; + break; + case kVK_ANSI_D: + vulkanExample->camera.keys.right = true; + break; + default: + break; + } +} + +- (void)keyUp:(NSEvent*)event +{ + switch (event.keyCode) + { + case kVK_ANSI_W: + vulkanExample->camera.keys.up = false; + break; + case kVK_ANSI_S: + vulkanExample->camera.keys.down = false; + break; + case kVK_ANSI_A: + vulkanExample->camera.keys.left = false; + break; + case kVK_ANSI_D: + vulkanExample->camera.keys.right = false; + break; + default: + break; + } +} + +- (NSPoint)getMouseLocalPoint:(NSEvent*)event +{ + NSPoint location = [event locationInWindow]; + NSPoint point = [self convertPoint:location fromView:nil]; + point.y = self.frame.size.height - point.y; + return point; +} + +- (void)mouseDown:(NSEvent *)event +{ + auto point = [self getMouseLocalPoint:event]; + vulkanExample->mousePos = glm::vec2(point.x, point.y); + vulkanExample->mouseButtons.left = true; +} + +- (void)mouseUp:(NSEvent *)event +{ + auto point = [self getMouseLocalPoint:event]; + vulkanExample->mousePos = glm::vec2(point.x, point.y); + vulkanExample->mouseButtons.left = false; +} + +- (void)otherMouseDown:(NSEvent *)event +{ + vulkanExample->mouseButtons.right = true; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + vulkanExample->mouseButtons.right = false; +} + +- (void)mouseDragged:(NSEvent *)event +{ + auto point = [self getMouseLocalPoint:event]; + vulkanExample->mouseDragged(point.x, point.y); +} + +- (void)mouseMoved:(NSEvent *)event +{ + auto point = [self getMouseLocalPoint:event]; + vulkanExample->mouseDragged(point.x, point.y); +} + +- (void)scrollWheel:(NSEvent *)event +{ + short wheelDelta = [event deltaY]; + vulkanExample->camera.translate(glm::vec3(0.0f, 0.0f, + -(float)wheelDelta * 0.05f * vulkanExample->camera.movementSpeed)); +} + +- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize +{ + CVDisplayLinkStop(displayLink); + vulkanExample->windowWillResize(frameSize.width, frameSize.height); + return frameSize; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + vulkanExample->windowDidResize(); + CVDisplayLinkStart(displayLink); +} + +- (BOOL)windowShouldClose:(NSWindow *)sender +{ + return TRUE; +} + +- (void)windowWillClose:(NSNotification *)notification +{ + CVDisplayLinkStop(displayLink); +} + +@end +#endif + void* VulkanExampleBase::setupWindow(void* view) { +#if defined(VK_EXAMPLE_XCODE_GENERATED) + NSApp = [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp setDelegate:[AppDelegate new]]; + + const auto kContentRect = NSMakeRect(0.0f, 0.0f, width, height); + const auto kWindowStyle = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable; + + auto window = [[NSWindow alloc] initWithContentRect:kContentRect + styleMask:kWindowStyle + backing:NSBackingStoreBuffered + defer:NO]; + [window setTitle:@(title.c_str())]; + [window setAcceptsMouseMovedEvents:YES]; + [window center]; + [window makeKeyAndOrderFront:nil]; + + auto nsView = [[View alloc] initWithFrame:kContentRect]; + nsView->vulkanExample = this; + [window setDelegate:nsView]; + [window setContentView:nsView]; + this->view = (__bridge void*)nsView; +#else this->view = view; +#endif return view; } + +void VulkanExampleBase::displayLinkOutputCb() +{ + if (prepared) + nextFrame(); +} + +void VulkanExampleBase::mouseDragged(float x, float y) +{ + handleMouseMove(static_cast(x), static_cast(y)); +} + +void VulkanExampleBase::windowWillResize(float x, float y) +{ + resizing = true; + if (prepared) + { + destWidth = x; + destHeight = y; + windowResize(); + } +} + +void VulkanExampleBase::windowDidResize() +{ + resizing = false; +} #elif defined(_DIRECT2DISPLAY) #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) /*static*/void VulkanExampleBase::registryGlobalCb(void *data, diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index ee17b630..0dacf59f 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -269,6 +269,10 @@ public: static void handleAppCommand(android_app* app, int32_t cmd); #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) void* setupWindow(void* view); + void displayLinkOutputCb(); + void mouseDragged(float x, float y); + void windowWillResize(float x, float y); + void windowDidResize(); #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) struct xdg_surface *setupWindow(); void initWaylandConnection(); @@ -457,5 +461,24 @@ int main(const int argc, const char *argv[]) \ return 0; \ } #elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) +#if defined(VK_EXAMPLE_XCODE_GENERATED) +#define VULKAN_EXAMPLE_MAIN() \ +VulkanExample *vulkanExample; \ +int main(const int argc, const char *argv[]) \ +{ \ + @autoreleasepool \ + { \ + for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; \ + vulkanExample = new VulkanExample(); \ + vulkanExample->initVulkan(); \ + vulkanExample->setupWindow(nullptr); \ + vulkanExample->prepare(); \ + vulkanExample->renderLoop(); \ + delete(vulkanExample); \ + } \ + return 0; \ +} +#else #define VULKAN_EXAMPLE_MAIN() #endif +#endif diff --git a/examples/triangle/triangle.cpp b/examples/triangle/triangle.cpp index 0f593640..3c7f5041 100644 --- a/examples/triangle/triangle.cpp +++ b/examples/triangle/triangle.cpp @@ -1186,4 +1186,20 @@ int main(const int argc, const char *argv[]) delete(vulkanExample); return 0; } +#elif (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED)) +VulkanExample *vulkanExample; +int main(const int argc, const char *argv[]) +{ + @autoreleasepool + { + for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); }; + vulkanExample = new VulkanExample(); + vulkanExample->initVulkan(); + vulkanExample->setupWindow(nullptr); + vulkanExample->prepare(); + vulkanExample->renderLoop(); + delete(vulkanExample); + } + return 0; +} #endif