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