From 9ceac06c003b0bd2cc9005f200199b6393b9cc3d Mon Sep 17 00:00:00 2001 From: Karl Schultz Date: Tue, 12 Dec 2017 10:33:01 -0500 Subject: [PATCH] macOS: Add macOS support --- .gitignore | 1 + BUILD.md | 159 +++++++++++++++++++-- CMakeLists.txt | 24 +++- README.md | 2 +- demos/CMakeLists.txt | 52 +++++-- demos/cube.cpp | 66 +++++++++ demos/macOS/common.cmake | 39 ++++++ demos/macOS/cube/AppDelegate.h | 23 ++++ demos/macOS/cube/AppDelegate.m | 39 ++++++ demos/macOS/cube/DemoViewController.h | 33 +++++ demos/macOS/cube/DemoViewController.m | 87 ++++++++++++ demos/macOS/cube/Info.plist | 38 ++++++ demos/macOS/cube/Resources/LunarGIcon.icns | Bin 0 -> 54120 bytes demos/macOS/cube/Resources/Main.storyboard | 131 ++++++++++++++++++ demos/macOS/cube/cube.cmake | 98 +++++++++++++ demos/macOS/cube/main.m | 21 +++ demos/macOS/cubepp/AppDelegate.h | 23 ++++ demos/macOS/cubepp/AppDelegate.mm | 39 ++++++ demos/macOS/cubepp/DemoViewController.h | 33 +++++ demos/macOS/cubepp/DemoViewController.mm | 87 ++++++++++++ demos/macOS/cubepp/Info.plist | 38 ++++++ demos/macOS/cubepp/Resources/LunarGIcon.icns | Bin 0 -> 54120 bytes demos/macOS/cubepp/Resources/Main.storyboard | 131 ++++++++++++++++++ demos/macOS/cubepp/cubepp.cmake | 104 ++++++++++++++ demos/macOS/cubepp/main.mm | 21 +++ demos/macOS/vulkaninfo/Info.plist | 34 +++++ demos/macOS/vulkaninfo/Resources/LunarGIcon.icns | Bin 0 -> 54120 bytes demos/macOS/vulkaninfo/vulkaninfo.cmake | 53 +++++++ demos/macOS/vulkaninfo/vulkaninfo.sh | 3 + demos/smoke/CMakeLists.txt | 95 ++++++++++++- demos/smoke/Main.cpp | 2 + demos/smoke/generate-dispatch-table.py | 10 ++ demos/smoke/macOS/AppDelegate.h | 23 ++++ demos/smoke/macOS/AppDelegate.m | 39 ++++++ demos/smoke/macOS/DemoViewController.h | 33 +++++ demos/smoke/macOS/DemoViewController.mm | 126 +++++++++++++++++ demos/smoke/macOS/Info.plist | 36 +++++ demos/smoke/macOS/Resources/LunarGIcon.icns | Bin 0 -> 54120 bytes demos/smoke/macOS/Resources/Main.storyboard | 132 ++++++++++++++++++ demos/smoke/macOS/ShellMVK.cpp | 155 +++++++++++++++++++++ demos/smoke/macOS/ShellMVK.h | 64 +++++++++ demos/smoke/macOS/main.m | 21 +++ icd/CMakeLists.txt | 10 ++ include/vulkan/vk_icd.h | 38 ++++-- layers/CMakeLists.txt | 43 +++++- layers/core_validation.cpp | 20 +++ layers/macos/VkLayer_core_validation.json | 29 ++++ layers/macos/VkLayer_device_simulation.json | 11 ++ layers/macos/VkLayer_object_tracker.json | 29 ++++ layers/macos/VkLayer_parameter_validation.json | 29 ++++ layers/macos/VkLayer_standard_validation.json | 17 +++ layers/macos/VkLayer_threading.json | 17 +++ layers/macos/VkLayer_unique_objects.json | 11 ++ layers/threading.h | 15 +- loader/CMakeLists.txt | 75 ++++++++-- loader/LoaderAndLayerInterface.md | 167 ++++++++++++++++------- loader/loader.c | 47 ++++++- loader/loader.h | 6 + loader/vk_loader_platform.h | 2 +- loader/wsi.c | 163 +++++++++++++++++++++- loader/wsi.h | 13 +- scripts/loader_extension_generator.py | 2 + update_external_sources.sh | 54 ++++++++ 63 files changed, 2802 insertions(+), 111 deletions(-) create mode 100644 demos/macOS/common.cmake create mode 100644 demos/macOS/cube/AppDelegate.h create mode 100644 demos/macOS/cube/AppDelegate.m create mode 100644 demos/macOS/cube/DemoViewController.h create mode 100644 demos/macOS/cube/DemoViewController.m create mode 100644 demos/macOS/cube/Info.plist create mode 100644 demos/macOS/cube/Resources/LunarGIcon.icns create mode 100644 demos/macOS/cube/Resources/Main.storyboard create mode 100644 demos/macOS/cube/cube.cmake create mode 100644 demos/macOS/cube/main.m create mode 100644 demos/macOS/cubepp/AppDelegate.h create mode 100644 demos/macOS/cubepp/AppDelegate.mm create mode 100644 demos/macOS/cubepp/DemoViewController.h create mode 100644 demos/macOS/cubepp/DemoViewController.mm create mode 100644 demos/macOS/cubepp/Info.plist create mode 100644 demos/macOS/cubepp/Resources/LunarGIcon.icns create mode 100644 demos/macOS/cubepp/Resources/Main.storyboard create mode 100644 demos/macOS/cubepp/cubepp.cmake create mode 100644 demos/macOS/cubepp/main.mm create mode 100644 demos/macOS/vulkaninfo/Info.plist create mode 100644 demos/macOS/vulkaninfo/Resources/LunarGIcon.icns create mode 100644 demos/macOS/vulkaninfo/vulkaninfo.cmake create mode 100755 demos/macOS/vulkaninfo/vulkaninfo.sh create mode 100644 demos/smoke/macOS/AppDelegate.h create mode 100644 demos/smoke/macOS/AppDelegate.m create mode 100644 demos/smoke/macOS/DemoViewController.h create mode 100644 demos/smoke/macOS/DemoViewController.mm create mode 100644 demos/smoke/macOS/Info.plist create mode 100644 demos/smoke/macOS/Resources/LunarGIcon.icns create mode 100644 demos/smoke/macOS/Resources/Main.storyboard create mode 100644 demos/smoke/macOS/ShellMVK.cpp create mode 100644 demos/smoke/macOS/ShellMVK.h create mode 100644 demos/smoke/macOS/main.m create mode 100644 layers/macos/VkLayer_core_validation.json create mode 100644 layers/macos/VkLayer_device_simulation.json create mode 100644 layers/macos/VkLayer_object_tracker.json create mode 100644 layers/macos/VkLayer_parameter_validation.json create mode 100644 layers/macos/VkLayer_standard_validation.json create mode 100644 layers/macos/VkLayer_threading.json create mode 100644 layers/macos/VkLayer_unique_objects.json diff --git a/.gitignore b/.gitignore index abe3812..460495b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ build-android/external *.files *.includes .vscode/ +.DS_Store diff --git a/BUILD.md b/BUILD.md index e4ea309..a483481 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,6 +1,6 @@ # Build Instructions -Instructions for building this repository on Linux, Windows, and Android. +Instructions for building this repository on Linux, Windows, Android, and MacOS. ## Index @@ -9,6 +9,7 @@ Instructions for building this repository on Linux, Windows, and Android. 3. [Windows Build](#windows-build) 4. [Linux Build](#linux-build) 5. [Android Build](#android-build) +6. [MacOS build](#macos-build) [](#contributing) @@ -177,14 +178,11 @@ See **Loader and Validation Layer Dependencies** for more information and other 2. Execute `./update_external_sources.sh` -- this will download and build external components 3. Create a `build` directory, change into that directory, and run cmake: - ```script mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Debug .. - ``` -4. Change into the newly-created build directory -5. Run `make -j8` to begin the build +4. Run `make -j8` to begin the build If your build system supports ccache, you can enable that via CMake option `-DUSE_CCACHE=On` @@ -502,12 +500,155 @@ To build, install, and run the Smoke demo for Android, run the following, and an ./build-and-install adb shell am start -a android.intent.action.MAIN -c android-intent.category.LAUNCH -n com.example.Smoke/android.app.NativeActivity --es args "--validate" +[](#macos-build) + +## Building on MacOS + +### MacOS Build Requirements + +Tested on OSX version 10.12.6 + +Setup Homebrew and components + +- Follow instructions on [brew.sh](http://brew.sh) to get Homebrew installed. + + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +- Ensure Homebrew is at the beginning of your PATH: + + export PATH=/usr/local/bin:$PATH + +- Add packages with the following (may need refinement) + + brew install cmake python python3 git + +### Clone the Repository + +Clone the Vulkan-LoaderAndValidationLayers repository: + + git clone https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers.git + +### Get the External Libraries + +Change to the cloned directory (`cd Vulkan-LoaderAndValidationLayers`) and run the script: + + ./update_external_sources.sh + +This script downloads and builds the `glslang` and `MoltenVK` repositories. + +### MacOS build + +#### CMake Generators + +This repository uses CMake to generate build or project files that are +then used to build the repository. +The CMake generators explicitly supported in this repository are: + +- Unix Makefiles +- Xcode + +#### Building with the Unix Makefiles Generator + +This generator is the default generator, so all that is needed for a debug +build is: + + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Debug .. + make + +To speed up the build on a multi-core machine, use the `-j` option for `make` +to specify the number of cores to use for the build. +For example: + + make -j4 + +You can now run the demo applications from the command line: + + open demos/cube.app + open demos/cubepp.app + open demos/smoketest.app + open demos/vulkaninfo.app + +Or you can locate them from `Finder` and launch them from there. + +##### The Install Target and RPATH + +The applications you just built are "bundled applications", but the executables +are using the `RPATH` mechanism to locate runtime dependencies that are still +in your build tree. + +To see this, run this command from your `build` directory: + + otool -l demos/cube.app/Contents/MacOS/cube + +and note that the `cube` executable contains loader commands: + +- `LC_LOAD_DYLIB` to load `libvulkan.1.dylib` via an `@rpath` +- `LC_RPATH` that contains an absolute path to the build location of the Vulkan loader + +This makes the bundled application "non-transportable", meaning that it won't run +unless the Vulkan loader is on that specific absolute path. +This is useful for debugging the loader or other components built in this repository, +but not if you want to move the application to another machine or remove your build tree. + +To address this problem, run: + + make install + +This step "cleans up" the `RPATH` to remove any external references +and performs other bundle fix-ups. +After running `make install`, re-run the `otool` command again and note: + +- `LC_LOAD_DYLIB` is now `@executable_path/../MacOS/libvulkan.1.dylib` +- `LC_RPATH` is no longer present + +The "bundle fix-up" operation also puts a copy of the Vulkan loader into the bundle, +making the bundle completely self-contained and self-referencing. + +Note that the "install" target has a very different meaning compared to the Linux +"make install" target. +The Linux "install" copies the targets to system directories. +In MacOS, "install" means fixing up application bundles. +In both cases, the "install" target operations clean up the `RPATH`. + +##### The Non-bundled vulkaninfo Application + +There is also a non-bundled version of the `vulkaninfo` application that you can +run from the command line: + + demos/vulkaninfo + +If you run this before you run "make install", vulkaninfo's RPATH is already set +to point to the Vulkan loader in the build tree, so it has no trouble finding it. +But the loader will not find the MoltenVK driver and you'll see a message about an +incompatible driver. To remedy this: + + VK_ICD_FILENAMES=../external/MoltenVK/Package/Latest/MoltenVK/macOS/MoltenVK_icd.json demos/vulkaninfo + +If you run `vulkaninfo` after doing a "make install", the `RPATH` in the `vulkaninfo` application +got removed and the OS needs extra help to locate the Vulkan loader: + + DYLD_LIBRARY_PATH=loader VK_ICD_FILENAMES=../external/MoltenVK/Package/Latest/MoltenVK/macOS/MoltenVK_icd.json demos/vulkaninfo + +#### Building with the Xcode Generator + +To create and open an Xcode project: + + mkdir build-xcode + cd build-xcode + cmake -GXcode .. + open VULKAN.xcodeproj + +Within Xcode, you can select Debug or Release builds in the project's Build Settings. +You can also select individual schemes for working with specific applications like `cube`. + ## Ninja Builds - All Platforms The [Qt Creator IDE](https://qt.io/download-open-source/#section-2) can open a root CMakeList.txt as a project directly, and it provides tools within Creator to configure and generate Vulkan SDK build files for one to many targets concurrently. -Alternatively, when invoking CMake, use the `-G Codeblocks` Ninja option to generate Ninja build +Alternatively, when invoking CMake, use the `-G "Codeblocks - Ninja"` option to generate Ninja build files to be used as project files for QtCreator - Follow the steps defined elsewhere for the OS using the update\_external\_sources script or as @@ -518,7 +659,8 @@ files to be used as project files for QtCreator - In order to debug with QtCreator, a [Microsoft WDK: eg WDK 10](http://go.microsoft.com/fwlink/p/?LinkId=526733) is required. -Note that installing the WDK breaks the MSVC vcvarsall.bat build scripts provided by MSVC, requiring that the LIB, INCLUDE, and PATHenv variables be set to the WDK paths by some other means +Note that installing the WDK breaks the MSVC vcvarsall.bat build scripts provided by MSVC, +requiring that the LIB, INCLUDE, and PATHenv variables be set to the WDK paths by some other means [](#update-external-sources) @@ -577,7 +719,8 @@ glslang\_revision file at the root of the Vulkan-LoaderAndValidationLayers tree 3) Configure the glslang source tree with CMake and build it with your IDE of choice -4) Enable the `CUSTOM_GLSLANG_BIN_PATH` and `CUSTOM_SPIRV_TOOLS_BIN_PATH` options in the Vulkan-LoaderAndValidationLayers CMake configuration and point the `GLSLANG_BINARY_PATH` and `SPIRV_TOOLS_BINARY_PATH` variables to the correct location +4) Enable the `CUSTOM_GLSLANG_BIN_PATH` and `CUSTOM_SPIRV_TOOLS_BIN_PATH` options in the Vulkan-LoaderAndValidationLayers + CMake configuration and point the `GLSLANG_BINARY_PATH` and `SPIRV_TOOLS_BINARY_PATH` variables to the correct location 5) If building on Windows with MSVC, set `DISABLE_BUILDTGT_DIR_DECORATION` to _On_. If building on Windows, but without MSVC set `DISABLE_BUILD_PATH_DECORATION` to _On_ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cf85d4..34e10dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,16 +24,26 @@ if (USE_CCACHE) endif(CCACHE_FOUND) endif() +if(APPLE) + # CMake versions 3 or later need CMAKE_MACOSX_RPATH defined. + # This avoids the CMP0042 policy message. + set(CMAKE_MACOSX_RPATH 1) + # The "install" target for MacOS fixes up bundles in place. + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) +endif() + # Enable cmake folders set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(LVL_TARGET_FOLDER lvl_cmake_targets) -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(FALLBACK_CONFIG_DIRS "/etc/xdg" CACHE STRING "Search path to use when XDG_CONFIG_DIRS is unset or empty or the current process is SUID/SGID. Default is freedesktop compliant.") set(FALLBACK_DATA_DIRS "/usr/local/share:/usr/share" CACHE STRING "Search path to use when XDG_DATA_DIRS is unset or empty or the current process is SUID/SGID. Default is freedesktop compliant.") +endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") include(FindPkgConfig) option(BUILD_WSI_XCB_SUPPORT "Build XCB WSI support" ON) option(BUILD_WSI_XLIB_SUPPORT "Build Xlib WSI support" ON) @@ -80,7 +90,11 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") set(COMMON_COMPILE_FLAGS "${COMMON_COMPILE_FLAGS} -Wimplicit-fallthrough=0") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 ${COMMON_COMPILE_FLAGS}") + if (APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_COMPILE_FLAGS}") + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 ${COMMON_COMPILE_FLAGS}") + endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_COMPILE_FLAGS} -std=c++11 -fno-rtti") if (UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") @@ -136,7 +150,11 @@ option(BUILD_LOADER "Build loader" ON) option(BUILD_TESTS "Build tests" ON) option(BUILD_LAYERS "Build layers" ON) option(BUILD_DEMOS "Build demos" ON) -option(BUILD_VKJSON "Build vkjson" ON) +if (APPLE) + option(BUILD_VKJSON "Build vkjson" OFF) +else() + option(BUILD_VKJSON "Build vkjson" ON) +endif() option(BUILD_ICD "Build icd" ON) option(CUSTOM_GLSLANG_BIN_ROOT "Use the user defined GLSLANG_BINARY_ROOT" OFF) option(CUSTOM_SPIRV_TOOLS_BIN_ROOT "Use the user defined SPIRV_TOOLS*BINARY_ROOT paths" OFF) diff --git a/README.md b/README.md index 35356ae..65d6e4c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Vulkan Ecosystem Components -This project provides the Khronos official Vulkan ICD desktop loader and the Vulkan validation layers for Windows, Linux, and Android. +This project provides the Khronos official Vulkan ICD desktop loader and the Vulkan validation layers for Windows, Linux, Android, and MacOS. ## CI Build Status | Platform | Build Status | diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 0f024b3..becf72e 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -51,6 +51,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") include_directories ("${PROJECT_SOURCE_DIR}/icd/common") link_libraries(${API_LOWERCASE} m) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) else() message(FATAL_ERROR "Unsupported Platform!") endif() @@ -119,10 +121,40 @@ if(WIN32) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS -D_USE_MATH_DEFINES") endif() +# MacOS setup common to all demos +if(APPLE) + include(macOS/common.cmake) +endif() + +###################################################################################### +# vulkaninfo + add_executable(${API_LOWERCASE}info vulkaninfo.c) target_link_libraries(${API_LOWERCASE}info ${LIBRARIES}) +if(APPLE) + set_target_properties(${API_LOWERCASE}info PROPERTIES + INSTALL_RPATH "@loader_path/../lib" + ) + install(TARGETS ${API_LOWERCASE}info DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +endif() + +# Create vulkaninfo application bundle for MacOS +if(APPLE) + include(macOS/vulkaninfo/vulkaninfo.cmake) +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + if(INSTALL_LVL_FILES) + install(TARGETS ${API_LOWERCASE}info DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() +endif() + +###################################################################################### +# cube -if(NOT WIN32) +if(APPLE) + include(macOS/cube/cube.cmake) +elseif(NOT WIN32) if (${CMAKE_SYSTEM_PROCESSOR} STREQUAL ${CMAKE_HOST_SYSTEM_PROCESSOR}) add_executable(cube cube.c ${PROJECT_SOURCE_DIR}/demos/cube.vert ${PROJECT_SOURCE_DIR}/demos/cube.frag cube.vert.inc cube.frag.inc) target_link_libraries(cube ${LIBRARIES}) @@ -138,7 +170,12 @@ else() target_link_libraries(cube ${LIBRARIES}) endif() -if(NOT WIN32) +###################################################################################### +# cubepp + +if(APPLE) + include(macOS/cubepp/cubepp.cmake) +elseif(NOT WIN32) if (${CMAKE_SYSTEM_PROCESSOR} STREQUAL ${CMAKE_HOST_SYSTEM_PROCESSOR}) add_executable(cubepp cube.cpp ${PROJECT_SOURCE_DIR}/demos/cube.vert ${PROJECT_SOURCE_DIR}/demos/cube.frag cube.vert.inc cube.frag.inc) target_link_libraries(cubepp ${LIBRARIES}) @@ -154,14 +191,13 @@ else() target_link_libraries(cubepp ${LIBRARIES}) endif() +###################################################################################### +# smoke + if ((${CMAKE_SYSTEM_PROCESSOR} STREQUAL ${CMAKE_HOST_SYSTEM_PROCESSOR})) - if ((DEMOS_WSI_SELECTION STREQUAL "XCB") OR (DEMOS_WSI_SELECTION STREQUAL "WAYLAND") OR WIN32 OR (CMAKE_SYSTEM_NAME STREQUAL "Android")) + if ((DEMOS_WSI_SELECTION STREQUAL "XCB") OR (DEMOS_WSI_SELECTION STREQUAL "WAYLAND") OR WIN32 OR + (CMAKE_SYSTEM_NAME STREQUAL "Android") OR (CMAKE_SYSTEM_NAME STREQUAL "Darwin")) add_subdirectory(smoke) endif() endif() -if(UNIX) - if(INSTALL_LVL_FILES) - install(TARGETS ${API_LOWERCASE}info DESTINATION ${CMAKE_INSTALL_BINDIR}) - endif() -endif() diff --git a/demos/cube.cpp b/demos/cube.cpp index fe8cb7c..5596437 100644 --- a/demos/cube.cpp +++ b/demos/cube.cpp @@ -292,6 +292,8 @@ struct Demo { wl_pointer *pointer; wl_keyboard *keyboard; #elif defined(VK_USE_PLATFORM_MIR_KHR) +#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) + void *window; #endif vk::SurfaceKHR surface; @@ -1105,6 +1107,16 @@ void Demo::init_vk() { platformSurfaceExtFound = 1; extension_names[enabled_extension_count++] = VK_KHR_DISPLAY_EXTENSION_NAME; } +#elif defined(VK_USE_PLATFORM_IOS_MVK) + if (!strcmp(VK_MVK_IOS_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + extension_names[enabled_extension_count++] = VK_MVK_IOS_SURFACE_EXTENSION_NAME; + } +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + if (!strcmp(VK_MVK_MACOS_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) { + platformSurfaceExtFound = 1; + extension_names[enabled_extension_count++] = VK_MVK_MACOS_SURFACE_EXTENSION_NAME; + } #endif assert(enabled_extension_count < 64); @@ -1151,6 +1163,20 @@ void Demo::init_vk() { "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" "Please look at the Getting Started guide for additional information.\n", "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_IOS_MVK) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_MVK_IOS_SURFACE_EXTENSION_NAME + " extension.\n\nDo you have a compatible " + "Vulkan installable client driver (ICD) installed?\nPlease " + "look at the Getting Started guide for additional " + "information.\n", + "vkCreateInstance Failure"); +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_MVK_MACOS_SURFACE_EXTENSION_NAME + " extension.\n\nDo you have a compatible " + "Vulkan installable client driver (ICD) installed?\nPlease " + "look at the Getting Started guide for additional " + "information.\n", + "vkCreateInstance Failure"); #endif } auto const app = vk::ApplicationInfo() @@ -1283,6 +1309,20 @@ void Demo::init_vk_swapchain() { auto result = inst.createXcbSurfaceKHR(&createInfo, nullptr, &surface); VERIFY(result == vk::Result::eSuccess); } +#elif defined(VK_USE_PLATFORM_IOS_MVK) + { + auto const createInfo = vk::IOSSurfaceCreateInfoMVK().setPView(nullptr); + + auto result = inst.createIOSSurfaceMVK(&createInfo, nullptr, &surface); + VERIFY(result == vk::Result::eSuccess); + } +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + { + auto const createInfo = vk::MacOSSurfaceCreateInfoMVK().setPView(window); + + auto result = inst.createMacOSSurfaceMVK(&createInfo, nullptr, &surface); + VERIFY(result == vk::Result::eSuccess); + } #elif defined(VK_USE_PLATFORM_DISPLAY_KHR) { auto result = create_display_surface(); @@ -2261,6 +2301,10 @@ void Demo::update_data_buffer() { } bool Demo::loadTexture(const char *filename, uint8_t *rgba_data, vk::SubresourceLayout *layout, int32_t *width, int32_t *height) { +#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK)) + filename = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@(filename)].UTF8String; +#endif + FILE *fPtr = fopen(filename, "rb"); if (!fPtr) { return false; @@ -2926,6 +2970,28 @@ int main(int argc, char **argv) { return validation_error; } +#elif defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) + +// Global function invoked from NS or UI views and controllers to create demo +static void demo_main(struct Demo &demo, void *view) { + const char *argv[] = {"CubeSample"}; + int argc = sizeof(argv) / sizeof(char *); + + demo.init(argc, (char **)argv); + demo.window = view; + demo.init_vk_swapchain(); + demo.prepare(); + demo.spin_angle = 0.4f; +} + +// Global function invoked from NS or UI views and controllers on each demo frame +static void demo_update_and_draw(struct Demo &demo) { + // Wait for work to finish before updating MVP. + vkDeviceWaitIdle(demo.device); + demo.update_data_buffer(); + demo.draw(); +} + #else #error "Platform not supported" #endif diff --git a/demos/macOS/common.cmake b/demos/macOS/common.cmake new file mode 100644 index 0000000..6a35db9 --- /dev/null +++ b/demos/macOS/common.cmake @@ -0,0 +1,39 @@ +# Set up common settings for building all demos on Apple platforms. + +# Source for the MoltenVK ICD library and JSON file +set(MOLTENVK_DIR "${PROJECT_SOURCE_DIR}/external/MoltenVK") + +# MoltenVK JSON File + +# Modify the ICD JSON file to adjust the library path. +# The ICD JSON file goes in the Resources/vulkan/icd.d directory, so adjust the +# library_path to the relative path to the Frameworks directory in the bundle.. +# The regex does: substitute ':"' with: +# ': "../../../Frameworks/' +add_custom_target(MoltenVK_icd-staging-json ALL + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/staging-json + COMMAND sed -e "/\"library_path\":/s$:[[:space:]]*\"[[:space:]]*[\\.\\/]*$: \"..\\/..\\/..\\/Frameworks\\/$" + ${MOLTENVK_DIR}/MoltenVK/icd/MoltenVK_icd.json > + ${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json + VERBATIM + DEPENDS "${MOLTENVK_DIR}/MoltenVK/icd/MoltenVK_icd.json" +) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json PROPERTIES + GENERATED TRUE +) + +find_library(COCOA NAMES Cocoa) + +# Locate Interface Builder Tool, needed to build things like Storyboards outside of Xcode. +if (NOT ${CMAKE_GENERATOR} MATCHES "^Xcode.*") + # Make sure we can find the 'ibtool' program. If we can NOT find it we + # skip generation of this project + find_program(IBTOOL ibtool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin") + if (${IBTOOL} STREQUAL "IBTOOL-NOTFOUND") + message(SEND_ERROR + "ibtool can not be found and is needed to compile the .xib files. " + "It should have been installed with the Apple developer tools. " + "The default system paths were searched in addition to ${OSX_DEVELOPER_ROOT}/usr/bin." + ) + endif() +endif() diff --git a/demos/macOS/cube/AppDelegate.h b/demos/macOS/cube/AppDelegate.h new file mode 100644 index 0000000..0b1dfe4 --- /dev/null +++ b/demos/macOS/cube/AppDelegate.h @@ -0,0 +1,23 @@ +/* + * AppDelegate.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface AppDelegate : NSObject + +@end diff --git a/demos/macOS/cube/AppDelegate.m b/demos/macOS/cube/AppDelegate.m new file mode 100644 index 0000000..2a7bcaf --- /dev/null +++ b/demos/macOS/cube/AppDelegate.m @@ -0,0 +1,39 @@ +/* + * AppDelegate.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { + return YES; +} + +@end diff --git a/demos/macOS/cube/DemoViewController.h b/demos/macOS/cube/DemoViewController.h new file mode 100644 index 0000000..7f90cc7 --- /dev/null +++ b/demos/macOS/cube/DemoViewController.h @@ -0,0 +1,33 @@ +/* + * DemoViewController.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#pragma mark - +#pragma mark DemoViewController + +/** The main view controller for the demo storyboard. */ +@interface DemoViewController : NSViewController +@end + +#pragma mark - +#pragma mark DemoView + +/** The Metal-compatibile view for the demo Storyboard. */ +@interface DemoView : NSView +@end diff --git a/demos/macOS/cube/DemoViewController.m b/demos/macOS/cube/DemoViewController.m new file mode 100644 index 0000000..8e37618 --- /dev/null +++ b/demos/macOS/cube/DemoViewController.m @@ -0,0 +1,87 @@ +/* + * DemoViewController.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "DemoViewController.h" +#import + +#include + +#include "cube.c" + +#pragma mark - +#pragma mark DemoViewController + +@implementation DemoViewController { + CVDisplayLinkRef _displayLink; + struct demo demo; +} + +- (void)dealloc { + demo_cleanup(&demo); + CVDisplayLinkRelease(_displayLink); + [super dealloc]; +} + +/** Since this is a single-view app, initialize Vulkan during view loading. */ +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.wantsLayer = YES; // Back the view with a layer created by the makeBackingLayer method. + + demo_main(&demo, self.view); + + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, &demo); + CVDisplayLinkStart(_displayLink); +} + +#pragma mark Display loop callback function + +/** Rendering loop callback function for use with a CVDisplayLink. */ +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* target) { + demo_update_and_draw((struct demo*)target); + return kCVReturnSuccess; +} + +@end + +#pragma mark - +#pragma mark DemoView + +@implementation DemoView + +/** Indicates that the view wants to draw using the backing layer instead of using drawRect:. */ +- (BOOL)wantsUpdateLayer { + return YES; +} + +/** Returns a Metal-compatible layer. */ ++ (Class)layerClass { + return [CAMetalLayer class]; +} + +/** If the wantsLayer property is set to YES, this method will be invoked to return a layer instance. */ +- (CALayer*)makeBackingLayer { + CALayer* layer = [self.class.layerClass layer]; + CGSize viewScale = [self convertSizeToBacking:CGSizeMake(1.0, 1.0)]; + layer.contentsScale = MIN(viewScale.width, viewScale.height); + return layer; +} + +@end diff --git a/demos/macOS/cube/Info.plist b/demos/macOS/cube/Info.plist new file mode 100644 index 0000000..b7869d6 --- /dev/null +++ b/demos/macOS/cube/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + Cube + CFBundleIconFile + LunarGIcon.icns + CFBundleIdentifier + com.lunarg.cube + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + 1.0 + CFBundleName + Cube + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright (c) 2018 The Khronos Group Inc. LunarG Inc. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + \ No newline at end of file diff --git a/demos/macOS/cube/Resources/LunarGIcon.icns b/demos/macOS/cube/Resources/LunarGIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..640b756ff4157663d047c003c0e2181f490f3ef5 GIT binary patch literal 54120 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/macOS/cube/cube.cmake b/demos/macOS/cube/cube.cmake new file mode 100644 index 0000000..b298933 --- /dev/null +++ b/demos/macOS/cube/cube.cmake @@ -0,0 +1,98 @@ +# Cube Application Bundle + +set(cube_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/main.m + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/AppDelegate.m + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/DemoViewController.m +) +set(cube_HDRS + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/AppDelegate.h + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/DemoViewController.h +) +set(cube_RESOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/lunarg.ppm + ${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/Resources/LunarGIcon.icns +) + +# Have Xcode handle the Storyboard +if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + set(cube_RESOURCES ${cube_RESOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/Resources/Main.storyboard + ) +endif() + +add_executable(cube MACOSX_BUNDLE + ${cube_SRCS} + ${cube_HDRS} + ${cube_RESOURCES} + cube.vert.inc cube.frag.inc +) + +# Handle the Storyboard ourselves +if(NOT ${CMAKE_GENERATOR} MATCHES "^Xcode.*") + # Compile the storyboard file with the ibtool. + add_custom_command(TARGET cube POST_BUILD + COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text + --compile ${CMAKE_CURRENT_BINARY_DIR}/cube.app/Contents/Resources/Main.storyboardc + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/Resources/Main.storyboard + COMMENT "Compiling storyboard" + ) +endif() + +add_dependencies(cube MoltenVK_icd-staging-json) + +# Include demo source code dir because the MacOS cube's Objective-C source includes +# the "original" cube application C source code. +# Also include the MoltenVK helper files. +target_include_directories(cube PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${MOLTENVK_DIR}/MoltenVK/include +) + +target_link_libraries(cube ${LIBRARIES} "-framework Cocoa -framework QuartzCore") + +set_target_properties(cube PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cube/Info.plist +) + +# The RESOURCE target property cannot be used in conjunction with the MACOSX_PACKAGE_LOCATION +# property. We need fine-grained control over the Resource directory, so we have to specify +# the destination of all the resource files on a per-destination-directory basis. +# If all the files went into the top-level Resource directory, then we could simply set +# the RESOURCE property to a list of all the resource files. +set_source_files_properties(${cube_RESOURCES} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources" +) +set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json" PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources/vulkan/icd.d" +) + +# Copy the MoltenVK lib into the bundle. +if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + add_custom_command(TARGET cube POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/$/cube.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +else() + add_custom_command(TARGET cube POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/cube.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +endif() + +# Fix up the library search path in the executable to find (loader) libraries in the bundle. +install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_INSTALL_PREFIX}/demos/cube.app \"\" \"\") + " COMPONENT Runtime +) + +# Not sure this is needed. When activated, it makes a symlink from +# libvulkan.dylib to libvulkan.1.dylib (which in turn symlinks to libvulkan.1.0.xx.dylib.) +# install(FILES +# "${CMAKE_BINARY_DIR}/loader/libvulkan.dylib" +# DESTINATION "demos/cube.app/Contents/MacOS" +# COMPONENT Runtime) diff --git a/demos/macOS/cube/main.m b/demos/macOS/cube/main.m new file mode 100644 index 0000000..cf9e0d8 --- /dev/null +++ b/demos/macOS/cube/main.m @@ -0,0 +1,21 @@ +/* + * main.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +int main(int argc, const char* argv[]) { return NSApplicationMain(argc, argv); } diff --git a/demos/macOS/cubepp/AppDelegate.h b/demos/macOS/cubepp/AppDelegate.h new file mode 100644 index 0000000..0b1dfe4 --- /dev/null +++ b/demos/macOS/cubepp/AppDelegate.h @@ -0,0 +1,23 @@ +/* + * AppDelegate.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface AppDelegate : NSObject + +@end diff --git a/demos/macOS/cubepp/AppDelegate.mm b/demos/macOS/cubepp/AppDelegate.mm new file mode 100644 index 0000000..2a7bcaf --- /dev/null +++ b/demos/macOS/cubepp/AppDelegate.mm @@ -0,0 +1,39 @@ +/* + * AppDelegate.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { + return YES; +} + +@end diff --git a/demos/macOS/cubepp/DemoViewController.h b/demos/macOS/cubepp/DemoViewController.h new file mode 100644 index 0000000..7f90cc7 --- /dev/null +++ b/demos/macOS/cubepp/DemoViewController.h @@ -0,0 +1,33 @@ +/* + * DemoViewController.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#pragma mark - +#pragma mark DemoViewController + +/** The main view controller for the demo storyboard. */ +@interface DemoViewController : NSViewController +@end + +#pragma mark - +#pragma mark DemoView + +/** The Metal-compatibile view for the demo Storyboard. */ +@interface DemoView : NSView +@end diff --git a/demos/macOS/cubepp/DemoViewController.mm b/demos/macOS/cubepp/DemoViewController.mm new file mode 100644 index 0000000..a175be5 --- /dev/null +++ b/demos/macOS/cubepp/DemoViewController.mm @@ -0,0 +1,87 @@ +/* + * DemoViewController.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "DemoViewController.h" +#import + +#include + +#include "cube.cpp" + +#pragma mark - +#pragma mark DemoViewController + +@implementation DemoViewController { + CVDisplayLinkRef _displayLink; + struct Demo demo; +} + +- (void)dealloc { + demo.cleanup(); + CVDisplayLinkRelease(_displayLink); + [super dealloc]; +} + +/** Since this is a single-view app, initialize Vulkan during view loading. */ +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.wantsLayer = YES; // Back the view with a layer created by the makeBackingLayer method. + + demo_main(demo, self.view); + + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, &demo); + CVDisplayLinkStart(_displayLink); +} + +#pragma mark Display loop callback function + +/** Rendering loop callback function for use with a CVDisplayLink. */ +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* target) { + demo_update_and_draw(reinterpret_cast(*(struct Demo*)target)); + return kCVReturnSuccess; +} + +@end + +#pragma mark - +#pragma mark DemoView + +@implementation DemoView + +/** Indicates that the view wants to draw using the backing layer instead of using drawRect:. */ +- (BOOL)wantsUpdateLayer { + return YES; +} + +/** Returns a Metal-compatible layer. */ ++ (Class)layerClass { + return [CAMetalLayer class]; +} + +/** If the wantsLayer property is set to YES, this method will be invoked to return a layer instance. */ +- (CALayer*)makeBackingLayer { + CALayer* layer = [self.class.layerClass layer]; + CGSize viewScale = [self convertSizeToBacking:CGSizeMake(1.0, 1.0)]; + layer.contentsScale = MIN(viewScale.width, viewScale.height); + return layer; +} + +@end diff --git a/demos/macOS/cubepp/Info.plist b/demos/macOS/cubepp/Info.plist new file mode 100644 index 0000000..e8a276c --- /dev/null +++ b/demos/macOS/cubepp/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + Cubepp + CFBundleIconFile + LunarGIcon.icns + CFBundleIdentifier + com.lunarg.cubepp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + 1.0 + CFBundleName + Cubepp + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright (c) 2018 The Khronos Group Inc. LunarG Inc. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + \ No newline at end of file diff --git a/demos/macOS/cubepp/Resources/LunarGIcon.icns b/demos/macOS/cubepp/Resources/LunarGIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..640b756ff4157663d047c003c0e2181f490f3ef5 GIT binary patch literal 54120 zcmeF)Wo#os+b(FwVP3u9X+0ASwS!kCF00ANTDSCp54hrxjX008h(lA_800Px>S zAOPy$Ghh}qk^%sL+q4i7QIrx9AyRa-Gqte(0RU(Q7ivIis*GY}>m4PbS6UPZcF@uBD5=YXuxFYw~<>gUD;s*?dfWulrw!^kjQJwD6aM0PIL}_|XPIi7~IZgfIF|!3I9x#Ce319#t`;ox`N_Roj(BZ0cdPI;70W39;W3nldg0W#x z!F2vPgJwVNsx;04@%+^*QcEu>AJv$|!SvsIj~uuhRYWUf0de7gxHc`|Z!CZZE0-OQ z4qed*0#D>1N?wo3gC4CRoGABZ0gal zLJ@0N2e~pPB8=~hVRnvWLdxGj0J5~PsHljw!~?x==|($9?IIIK;ohjkO@S>5{ic$) zV8$U?M-kY}NFqMVtVZfdNn1Do@9!{c(|+~@3X>x+@vDZwYau1qhR>Qw54^1!Aw735 z9#$^kci1q4I*$OQpQlK7V7pK!8CO}`!$-lv2rpt(KO#*&`rL%u(@;q#(@6oMsI^QK zmNOjRWobD5K^y=B+;~Qc3%T6``M~UoDMQ(?%ix5PWg$W-5NqEGXE_T!IzyOuP)K7j zOP~W3YK;&^8uTSy4`h})zz0M(JvpN{?Ys&T*r$b$kalU`z<#Huy=Ok?>m~?LgIRt8Hm4e_k1GSbtX+r68rpXhp;E!agb)B{nsQ5|D zC%vc=naSRhb7=3#)PX`Lb&^AOWyrK8;C7o2p8_m7lZ8 zNq!#r0bz{&i@?I^aYVf7nP0v69gl%4*u#;Y1al#mq9mGC1ZuFn5Y^Dtk>qv2NYZy< z=t6T5OQgYsT?uSfl-M2ZBlsU_uY(^14O}3~p|k;vrwpgQ-;U;=eQ$#91xZh%IT8J%HhJL~1v^&|y=Kk*fUIPa8N!YEkrmMnt1_pr_rX1nbWK^(g9-2DVS$LJe5 z{281JmE@0ZNKXZs*~_5_Z`tc>kHywY;02)zNEO0u0i8}{xI()N&@coDA)yG1FCgm; zKru^LBS{RPUkks*E0V!T2`0p|1;t9nd7B_B2UZA=#nVf&*CAR6$`c*M(T>m;pq>bQ zh;RPJDhsmAg(v~%L2wZ}F^9tWDK9cVqh(H@9mXIsJ|o=#Q7NL3i#fy50O}L*U<^ctMI|RBNaUFbE?U;%KH!MNukpRb&KY`F?~AF?TUCltx2e zUBgY3u#*Aqfx?040ltB~0osAk0bi8x0+~8ukAyxc*@QGzQEi1XVti6Pa+mnc5$ZAQ zQSLGD5%RI?1DJj31J_afT}S-a|VscU)s0^P#f zqVfXFVpCa`s#i%&X=d4LS^WZLshx^I+@P_;_r1E}cu; zKH9L`Xk8FbH|P8BEAC0|O;2s+cq^B9#f_tevHqm(GQSS5|B<+_ywAToI<{N1TfF3C zVuNBs!m8lpvY4?d`EhKVX4^7!Rbmty-xi;Wyks6d%bZWCDo2wwmY<@ep!6DbAN@0m zd`KsX5eG3%Gh-kFE$xiC)vV0$uVsd1+fc(w!}P?6b)8n%if#Sc;mu*yA>OUaA^l-B zJstfnJ%$cm({a;6t%>HL)-gQt0u)WX`8@wICZ*g3eVwr$$8uKnl^0P<-O=%6<>j`{SVVa>CrNH&5pX`EwOBr zIEdM{Zg>lLf%q_ZIQSo~4X!5K>RkGS3Iv2qz8_C7*VB~CvW5!FY&%7XZSy9@gYuK| zgunED1y40j@iCY(j56Tr+O}A>h_^7f@STd=XYO-sRgRulw%4~W`*gn=5V#Ux6SRM8 zqtuo+QCR1={#_hSIqwu{7K*t@yK-t)jbLYNSL9as2ui4ugO)>+GkjNj=j;jpzCyN2 zwo@RIsQ=pbI(5%?5&8`Ng8r&^r*Rv5&-mC)XowPrQNx?dI74kOzkN+4tSC7%mWvAq z3K6L^c?|0cosH{FBT7xiQ}AtHsKlj&slkDThui(;@2FeZ3MnZGEeW9*x9EXHqiDIrbl4{PJB_LG#7!D+t$J<3@*&qjOF_%umZfXo>+)UR zqNq$3x3OTbPB2?x9};S2hFY3hzp~GTMwRx2UkMQjtHoOK~`X5ZHzYECU%jzYsW*w$C@l=NH zO;R7+Ir_97tRI~Son+1%m<5@gjXVuR9-WUSjmu71ZB|l5ntTUe9uI!qG(||EdvP4wY1ZxB4<$uwa6Vg?ZCKZ|n%uVMvho^{E=AOZECc3+tAl5)KF8tH-hw&7@jK0DlXG2D|b7&)b7IFpkLWgNmO-u z#_TruYjtkfd^WqPQipk#eCAWf=wa~s5%)d`sf)Nkz$VbL6Zi6am#2WCx$2VX$X>!n z_&qsfJLFJ2c1Waq^|LL1w54=6>@fa70zC$U;N6+-QuEWH_j(i6f|7t^+PurjacuOo zm_$ME8@}VqS-L+frOq+^@RVn^4bPEp)lSFhb3f)U#)U?nMuL~glV%ajEXF^2j;*SG zE9Z`T$>AC?`uAO)yBS-JtV`HSj19q!dKR%Z-5#VT;=_|knH$aS&DmR1U3~UtAa7xe zUB!;ocU>RvV_U-?UB&&ady>TlW~V=oA>9b-2>9Pc?uK8h`Xu%x3S*-QG~9SyIq$*l zbbVRwrIu8B=L0tL1k^s)uO|Mk^mcoHSe|FjE#2!rz0GgQ7#w#kUpw}VAAW>=yjHUq zz;`hC=D(UA=`1zVU+!-45v09m-*uh!FC#`1itul~Cq19c`vK_nuJF8_1G)eJVp}Ou zAys#f^DGbl1<{A$dDeL+R=7uYIHFL;ar$x+eSi0QB4AyOKMYlNdET4VY! zc_|Yd1`+_|Yx{p-1_G!^w!^~n#xk@QY1ZtS2h%9hcY|o^F?D`um#ru^6?jYs3dm@9JWvG2!dZ7VWo5I< z#TFSTxDia}b+h6QBNeusD7dRbH}0#ul^rcoP6bskaX>8s>P)x8S3qvlIOYb#4j=AHl&)DYQJKTJnJ9)K*p@S6_A=4ut4D zqU~Rf>5=}8a@3;4-(`Tc)p7iWtl7vlg>Du&$xTV3JyT6=b#b+%0jpM0U?y(0#%nVa zIKcb5Cn$3XQdn$W6!W@B(Voo*S8Lo=QIXtvC4S%xfTu~-7rX&OzpR-=!VWZ&+ zxEOwdO8eQ~->bEvviqTo0)jrhT<>D8W)J+_Zuj5)-AhhvBm<;2xJI_$RcNxt$3g!k zc)RrswH!;_yIpZc?Vy892$E^FR^Y+)d+}uCC){V6yLcAk1`j*2Eh2F7&#j*SE4LRL zWaW%pt_=bqNU+=KpP)cKsBaT|)Km1WT*wWf0xr#cUPy)l!{z;LD=Q*9f4_CX+VdI3 z9_FIAK}Q{4!qsEGp8Oj`55<*ws|R?+rHHQ$7P#lkyN6Z)ql=lZhK1)va-Ty5pp%}> zU^NH{1QgIIfZ^Gd8ernM64~noPy*G(pBsrTz_Fr+NZG$RjWqIiF6M%lh;aspL-OhM zFyn#ct;L5}2_*UPNBz*C#N_}PG^ar7?wbd1A&59s&%GFezN5Ys?eROsX;wPP;DTnm z2<&*M^f>TL?c%fr%TSWd9p+ZRv@g=R;{Xy$!4A6FeLI-d=PjW_V`b4RW-U~|x1^T3 zt|b>k(6F#j@)ar_Cr4!^1pz^V8KmW4-(TG5PYL=?A-I&jSz)nWMeLPUZVMY zlHXoRPuL9sD9BkS7#tNn$=A*(izxf2J8R}GB<4Ir*{Afc^F`N&z?*(SH=Mbq;5PNN z1(DZH1X2|58sGaObv1F@V({dA)!y45&Ufyoi17yZXTLt~T;W7;`)u=+MSA^mu&`8Qy1Z<{31Y`hHr9rX z`%q9jEd~}?^{de~ay59*s|CC^nc|++6#9NsZ{8|)4vK8z0kOID-RS3+zzf!!rDAi~ zO`HsJ+dP?yTWd&2d;AprZxw!LGxW@w{XCj=gdd(WJNIg6tI(E&!VD zrZdH3lco640cq4=h%BVMlS)Fk=Q?JI+svF13}>PRX&~pNe&dwcDtv(1XsvVcRTuE8 z&_;5(T)x$fhI3>vXV|2=im3mHXC-FTHsze?<>zN@a-T~@NOFjZMX5X4iMH-q zeeN5Y-sN)b0S5=}7VwM`9dy0D9z`^?mvh%8Z`gL(3DMo-d?=)ADD@!Qn}gagx%8}v z)L`wC+wF=TX9XT`5v(8jk>?RvbsPKgHpK^}tgEZ6T_sb#4XwvmF=x=G4Kl^Rku~0l z3egHtt!y{n2nfUNYN(i^RxrHlcjw8bFdV??c39#_@!})r5 zRKTiR;de?81G2fBjoH}Yrunzb;s(5r&5v#45#Fn45f=lkL{^EbQ`H}@Q%k`zVnT{d z{O!(?BKIHtc1?fu2tA7`Q?;%rP26tAjcM_~JfY1jyO)${7*6!MozR*YKA+50$i)sC zJo7ohJYu$ae~1-mixDYdZIxnGDT>bNidDa&+@{_wdN2;JSoLD(wDk>{25S?IN(0|8 zi^3_@2S*~L+;w`zJ_=xO+s0L?P*%Ik9b`_-U_gevRg`(A)`w0#C}`j7+jUxqeXJ|%*K#0+b{J6%-xGiI8}G;1cn|xusm^VlSARdwu@9=v0R_PmlSi|Bre8fS zAP1lr;lMmYtQluI4y+m?CcRUMb&;U}`B)+|<+WN@cim5GZOx@{3%(|^`R(+s&~B?u zf|&ITh0g!fZY12rNB-BSR7W@MhGI`P>Bfch{ON8sKK6j)JF7wf?6I7Qc!1P!-_YY& z=cL2J0+?0$*-$z#8xR2nXNA-$n{MzQ5o!6s?$Q>~x6e4Y86l7XMuPN=BtAOZpL$L2 z>EVxl`@yi(&DlpV;s7YFzV;8z!#n>VPz*Q#ExTPtHXu%*ugnC+A_=UHeSh5v!D2rn zjoq#~e8^B5Ge9TkcdoxcSIx6c8G{Nj6e3+eF%g*e?-J9p?hdUPUbOx;XJY{|z%kG? z+n#*~oOs9X(%^HvLpf0UuWAc>2^?4j(YEI9V~hK#y(Xn|)-$cm>Hg*Kc}rpW0DzYu zalC@(BVr>S?c5QYYmNGH@T4JFNZ99NSLZL!Ube}}ZtPUILttU!AEg5|`jyx34G_eb zdSHLE@7_;9Id3w=Em>z&(oWN5hSARHC)H$Q{6)e1Tji&5G38aGtK#3_QlF)N;|@`Wp)7;qe2`_xc+&FjBHn zNr3KSX=XU~8>kB`M3+);(w3tl1>vpjRzw>8qm{3FCVskrLD8|)G4tvHRZgL?f92jJ z>!7?E^91FmaU9k6jo{&WPE&L%3-=rO)KOsWuGP!;pO{D>DDNZawuCPc-$ueUsJstY zOz~5%RpOp!iw+-Es4dV+hwf?8rAPe)FswNq0rbHVyt->c;C{vV7sSf+vyL?m{u`2O zY>o&$!}T$_6nS~wiGTp-vR)o_jaA4c=$l=3W-Sa^KEc92ozWLC%kXujhFndjzwSE& zOPI-T#{EA>69cuN4Z7k1PzL8u2`q=ScLWfB?PYXK-AkrTbn8tO;}RkjM!;NY!4hRl z>QJJ7fd;i#Rhq){VS1)JJPxzyRlRG+{;uC@4I1^@v*fgs)GYK<%(clAR##gh;QJAx z!+`_zKdCn_fGf9E)U!LJP{X~C-mR4!T)xa_fCfQI)Ga^gL0*5>yI*M@RWJj+XN0(j zJ9k9OFAC$Y5QcY|yx$aZTw-165QzglvOC~yz zK9R&3<&gr?h*?F>%l_Uv)fH}1y2KuKEiaxWNQ}5_cgnH3Fi1N2<+r#e{SC4ckOul= zbCQcvxS8HSge<}jPL&n@a_!&DpqP@Xj(w0pWn1je!lno5`6j?m#&+G$_jmFFeh{ub z4fiu@@dHmHKt@+sEXG}+?;(!@uxrYX1k;8aWYHQj4)OOcZNn)ev8uY%LCP^hD-1BR zFP1p^_*?4o7;7kA%cti=?r#-K1=+l!EEV@CxyJn45}FJ!_c$!CP*Q5aTBbntW&#h$ z{@!UDLYuUbs?yn#_3FS+#9u#eHYaBrV8Ke80{4n`+5%cw%~B+3#W|EjBFwq#dwv;Y z+3)efunUAC5GNR$^i0sr-}>zXg{Y{!EK6hnM)T)eIc@wMp!mctlLpP)Ls=C}FzPiK zJ7{1LmL&>A3U}f}jqdZB*H$gl8donP4(8V!93WN3`V9{#2sbdIALaG8J4`?JxiPwg zwK>7h=Yex#cmNwz#9h=6>(N5)up^WoLodEhxym)HGD>!trQ{=F80C#f|qFn7(nH2#1nY5iGBTo-Pj#I z&)pel6v4mad70KL9vGIyvkiLlt!EdnuClEr!O4Gw9|*>suV(Xu+~gO2qL?&3ubAJ& zrUb=>Zg#m=0pyw^DHdhAf2v?pM+D#WzVB$#J_U`YBXT5BVK z_Trhf-WemqleF0W+1T-~>+bC^7DeqAg(WQg3o;}R>b#Dtd7nUg!|P7{>ew8CpibMI zQ$7_SO(d3~E%3Cr*dRl}TdI3~COdKC1+}`y+yuuBhb#02J1dZP1S-(Z!S7zS$xh;j zh%z^5C>?r#3fbwU0(~h&)~sBA*j|DuVgc%-^kDS1{6@N1;oHU3bgP=o3ofu&!umRc zxcq$&0_?t9?EF_i4X<-5nSl!*cORa}?R9kw|S&!Aa?77xYGL)0a` zKd$H;TJZNxL`s4BiyDA2DlLLCO=uv-2P_WV>YNJl#6{tIQ{c7wtq+~MbY|=OEj@XY z=swMzcbY-f<`$lZp&u2=JPfubcVYej>8bFHaz3cU{&+2z+ z@Y?u{&-YZGXs!$G1k22GiP}d}RH7jgd_`sk=U%G$0t!V->BFpABlolHSmT1kFS zgeW!?FQht(B9T2$Kg*~3H{%JKneD-$Ff9SC*Mld$-RD+m+u!?vN513$kX|6(w^O_S z!ny~V;WUjlDrEqJ%^$T+jt62N$ZO}cPGUCm3ok78iPx+($Wi*_X;C+%o>UGp!9#%3 z)BQ*8?}*^w^$RLrT}CE7tJmUCDKt0jWl5QlL`r!7r^8+s8YL1ukSi~Qr5>ThAC(== zAgOotblmR3y)Dtb!QW8cI=2@0KTi#s-p6d*TL0t-*4`hOIBGNxd-YzDlFN7kiok=P z$55dq0Ke8p8>{#F=fs9el?($THJJzqZr0FJ-c1~4yPg$(V(?qNRyn-SXoK#qSYM0n#tj2z3t(3 zRJeclw!jMk`;BQAE7)}6eO-*JU6v;>@fN&TCln6pL^FcoO}KzE79meXB*Wm<(n7l7 z@{#;vQ#KQY@_H50qjL=@Zd5&Sy!3(ueq8pK%5; zy6vZ8@@}z*0=FXa-xHrux+p)jdIH-UPQPWTpfM&kqSJ6{uviKiqz_*{5T4>unu6<_ zjYgmO?z3Jn&^axg5BmuyO$;ryl`;FV3{Is{rWVSraJ`TQ%gEF|^zf{IXg@2Nn*HH} zn=kq#sdcXDoK%?JW56%#^t+&>mzq<&o}02o<-Fzdry4Bxkg{f@qtW%l-=4#0}O|fQ>_Sq;&FD(m8v|GQmcIjN#+}dAeX(PG{1IQKOQkC-^&u_X3!zShNa-50sbPqq8F)CI+wZ5_(> zC&H~JcHBpoRJh{7{fXanQh&r@I(K!Y9BM=LdKV)E^cL?|IVP>hn9zeI9&J8PRPE78 zl)O`L=@f5>Q#G!Y>z-f?tn1VeQyk97HHdD0te{S6n)aN1^N8U*IQG@3@SYo%*~2ig zPd>To@v;P`TuRA2d!_l54EWYxk<=a{MSq?P4|wj@vXt&3q{LsbS2gdZM>iy4<_Iy` z_>lkwgA`}0UOvS`BVRr`%*!Z+{`Lyr?x28ln4m-zT&NSWw)2r5$&gImJDRLRG95*U zD#?>Z;t@kx_=*T!S25R4e*wgZNlIybR+vLO3k~f7oRlJPqo>(8tibvl;r0zpH;`N6 zi&)qQGWZL!lIOW`AjE}L$QAw-SBKB9;E+rHo4KMu%}z3h1UPK#{rzS@*vprUlnx8* z(d{mvf)tz%!-o+Bq~QWaqtVU>zhRZ;k~Zo`VC56lf}{(J8nqTS3x6jg`}d*tURWnN zzVV52A%u03-k_Ns%-_}|1IA{-(eAuDMWyuw3ZU}Lt6i2Z^Xs@9IgdgG`#(oYi`i7z zmu?x$w35>U08Gig^!EV!ZyRF^d9o}a04C^y;R!YN#YuKQalYysM7I)Fs{i#%YnCxA9W{ zvuixJ#7r+w(fCxlL^wr-2PS&M>FvkCk)T&6x{(3|5dbVFy=Kaq66-nUdB?^`r-R@* zZ|t0;R&)6Q2|$r&%h-XX-8U)7WENX@Fr1?-UtORI<{!T81>Ni&d-gpoQ3aiV<_IP- zlVf#&M1&(40EPW~S}?k*?+_8QwX%^Ax_#GWHwt7oNP6Sn!XmWWL#LXaBeH{Y-*Ydn z?i}Yfl*UX|oCetQ?Qf+@=f5vIuW=!ZN+#%j@l!h5!jl3fU;$fV+cCPavkHf0Sk)gN zd-0r970Mc9^C*&Ahdzws?{T@xuedvE^e3zLW$C2Cu!V;!Il(lB_#8k^G_MW&34wRj zTTqrIC};zVEv`AW^XEXNEgX0R%)~1w*Pis8@cB*#e|He8eI~K5xj+&M0jW3Lbxh0<|#s{23xlp(uYq z$GFRXuUd5^mayooY8TaAvaSB4TG^kQ0X^O|542xGsivK3O3}93=jBzr`7_uY_)VOQ z{bXhc<)(EHB_`tSVtJC%sTr+=$ELO;!> zgwbC~CJr0Liu#q)eCd|ml%~(($mk8p3A=Vr#~m5$30-PZ&=!A?TLNVHq*HwQSV8jY z<}^GV)%>RE+O%2{iIi10OL?8MUY>vkA;+lLW8!g^6ajAFOY&+>+BN}1xlIi3Co34L zZl+~=oK}SCKs76^;%^GUjvH|}JOOR3`fmH>dMlLjs4q}1!&#p=^Y zg?}+RxZlh!ezZx}!~g_nnSXZ+zcXwZOhLHdjbqqlau#LXG*gtbpM35f4F|qIwz4oN zL>JqYditE$2+p;>*&>21`IIkO-1#x8@UapVKOJZP97ZJUR@BztaUC8Q;M$u%H8bI` z_4$FbICEX-*MVV3w@d*f!XnOilgI5efL>maaY4|Uk`HaR&8r<6Z;>7!zmZ`~9p+9f zqvS$8{+#zvpf6+q3#34H-&kvHr!>c{c*gnsjTRviU=&{wCyvor9;=HEOy}13!>=eN z*^%k82sK)oqe_{K^Q#%q;NYkrVbbSfMcsf>XQ{_5BtTK+L}Bva6@h)NaQ z3KeRgpWoVBpskY}RKVF$l7LcfJBK2ZXzmWN9l+aUpJ?49#U3bht)PMAU`I%J zmtPr@0Zy(B6o#~yzhvdx1ZvB#7T#qsgPV;fPB9WHW;JEU)rSb;{|0s6sCi##Prb?^ zPvzBQEwmoq=Md+?@4pYqLkS4ir^L^1pD#BFl(FKmjPAmsdbv+NMOBL;=8gixfx`E{ z`vK^53A12+=zs59E6n)x{^Rm?O1%W>VIS7z?o4u5fksv=drNk4;-gQjVWwOuugBx| zuUtM(4u14-zTSBt+A2Sev#VWbQ9>L^Idh{kO|JPIHa$U%qSGlAIuSLIVPR2P3Zf9E zj)r-FssGLO&i&!TJ6TI2DkT=cBpoyF)Nvao;dStimZMP2NX0>1={ku+9Zr2cwR|&v zP&gv93XnRMqr!l24cPL%odGweB^^*Xl8R& z5&@Rbb){RC99t1lDUq4}`$zV_R48&f4L1D!qvxAnAE%gm@_^zG+;jKEJwJ^&4UjTA zqIS+VipzbxoROoSHD2?x{4s)akJ$*@Bj`O!Am1@MmP&%DH&a4fUK$vh>z{I@{{W{&POf*m%>%#;yN#8(v-aEt|j5z9`3##sa z0laNjjk)7D^cUoX4FiWiUv;GCYIWG58h9rl1$WNX@>|Udg9QJ2DnK$RCB$sd2pdKW ze_k4c(6pT6= zHw141S7m>rw>p6OV4fL|9b<`8B!{{i*+T@_L3Y%ukgrCIdqSDGcfr&N52xb$Plerez4$Axq(kF9SIp9B?Twwh20#G@e9i;HO)v2 z;9^SL=O@G8(e3;~y%I`%kfY3j&#gUtCLZ0U#0Z~sIDk(8wTI1-RvQF&HYsBO-7p=K zbBi@4I%n;#ALE-t!^_tHX&X$iAYzElM-53jhZ^WSGhKltwO(Ri3yTqaJN@(YSu$(<*^hyLSPXnTM7S}v?yObf`Y8uqPiC>e^HlS}@4ZBn z*hB=F)q*HoJ@C54aYv_@_(EvP_#t-`N_0XeLW2Vp#WQ`;#*IT0!J{qI!##PBy*2|g zUV{w}Hg{ClDsoMoWp1BL?$Tk2`bvE9t`o>X6Se94^d$NAp5LdYt~&R8Hn`}1Hx5%b zYgE6uTS?0FsNnoG7NL!>@rDxeQ3=4s=a|pV+@j~Pv^1r6=pvo5em#1pm3yP8HsqLF zf&#>bqO6*|Ig-p}1;1#C_pE@ie(FGQ;=tvTMnN$Cjv)d7zqbFYGeAUf^*Q*8hgn1H z|Mx#xb15--(W-xM*MGruP5=NE|9=do1N{GW^N0FR=R-Aq%&%bjS1|o6nEn+^{|csm z1=GKR>0iP0uVDIDF#Ri-{uNCBpD~yYe)Hdh=~`ZjYGPPhu(}KbYCS{)AjDyC$qS-L zR3w8WWFe-`A-~RgON8yK)Wy13TIxoPJovIUmyx-ZwZE_u7#+s&py0G>+VB|ue>GJu*tjUak1jN<>g zyX{x)|1+~frzVvh2R_7B;r?C@Ps|r2x7n?A={{T|cR(_90r6K-Pkcb_OHg&xtTddS z7OMd?7B&{G^Yj>2<-knR>#TIoNYC;LI)iZoP()3v8Hny553=*2QQ4uV1i{J4rtCLh z3J2~g9A7V%IL>C$s;B;QO_og|WXiuOBW+a|!Z!g^c zCkY*RvHh~J5*sQQ1QSn`4eeh69V?1gF;LM(Llp#kN5^J3RvI|kHR^f~Z9y{J)Zai^ z`<@%4ZvJk2`qDY;|Av>5-n+*(>Ht;$%_2~i>;W*h9iqG-=o9FLWhnk@6`T(V=oIqc zpU&CoMJnOaD#RXQ=!rQ%(ZC1zQ51DYPB)$^IQvg01lLE8ee98L@&v%VtL_G3bfoAc z0z>To)G&nC62f)`@&S^nJ!ivH4PBpz0aFjvXn8boJdV;WRt;v`dO{;LWtBXg_k24O zEiIKMuaxx9eeF626hSp*7sLq1+a3jzF+8^9ZOy@;+w%nG-KS1BHdiN_IZvJl&O%O})U> zN^tP-{KR?_&(#7`7YM>oezX#xsyXUpb!s3coLL6&-B}iv5yC+)qIGjV?mdNxbO2CC zq@A;?T5p7nvnP2UxI9U|4_}IhmzfK6?;!t~BgSi{4U_RAiG~D0bIJ5=GxVx|ENL-U zAr}z)Z0JXk7dGDKwCEq-feqVkX33Bj3~>52dCbN$lw)_OK39vFtr8$O0J<|Bn@Loz z{3?mLt%|s1s!6&$323r2S}IT!-*>at0~gK_ef{;Z2^ZAuMl2{x_uN@fb~CAb_A!1aA%dToRprd1* zmWIcqt=zNW(bjlyu>6UdB7m5ZEx&VPP9PR<<6F@+B{ui5dK6#J1&BHsigY**J$j6N z5LSUvqu=^X;uI0yI~oJ!1QGlv%Q{JxAQw+NZzR)Cwl~tHuJi?HuEw{Lxy+0^_)O7a z?;N>pXER}vS)ZqUj&Q1H#U7(O3SJM5V}|+sEzn9a{ObgEM!If^kBq`RX& z{}6RVNa_FK4YmgY`~$S_QZU28z`*Z6^{)S|IL6EZ03hxE9~8#~|8*1mui{uNkl~-< z@TE3J%bfJcpB)Sq7 zzw3EXY3mQ|hy?UqM-^+Ox=>e=W8za{ZzQRRT&Vs~NufFd7;^3Kx}Maq1dIr%!iMNP zPboC^iAv!(y3W?(e82i%tY+z~n^kW5ho9Z&zqs8FCv=0596N$!{*H!{+5&F8Ys&us zvGfjfgz>%a9;t~fP^5ie1doiq5B@@VtD$Z1k=l^g3S=EjMj^oA;=ie{Ke#QB4X7pY z*Tdks+1_K$9Tt7B#fl682zBRL5LiSxdERre?^*ucfzTIiNAZ7e5TW{r8MFa3mTO^G z_J`z#SA==GW!J>W1Ct}b7lu@q($&H|&TDI;WwD--RMU#@IbpiBj${G%qf#9c^T*v?zey zN}0!Y%YO6&1?D>%TzT_oMrG;%Oh-1Xk6GOiqw#$NxgHWzJ!r)cX?Q=sBIrml)q1@i zQ~%~tzk5KlOarrzYWpNyAe!RliSG~G{N@y?{u>!VxuH7{c^x^t1UpoFRLBNi?$!c} zI71p|D9E$;c{t1Zfy^j>j-6K1sFZ}%2Q2T!D6z*J|L!~mqZPj~@;Uu zVZoY1&A%(+=)!_=Lu5*;PUK)gRxyp)Hcc?^KDi?tDmt(dTv2Akv7m#8r8>N#3&*H; zX3&54>BJ3ANJw^;0Dczdl9UZctb}n9@ISJnMSCGl47uERclffO0k@D9Ft^7^&?KgY z1DYi6CZB>=ks}9Z5bl9nvi1SS%cd#*2~f3lo@cO*u%x0pf%VP2aEUY zc0{U}I2oVjG9OJ`d2{{#x|Wy@s~QxTED)}|9fuc4|1y=TFa=k`S%7>h*po}YW>Cy< zJa^AiC7vmD!t{d?uQxdeTxTJH8X?owmDip8U%%O9ekL@$+HX$ABo4nhU+*yeS5C#? z{h!C2lVsy-O^(KIJ<2x+sTIe2u!p{npKUmMMhU$S16HgrQ$O_%A>5>hXjxAONmFck zw4PQ_Wg*BH+ly&pN7`T+6gx&))@82L(5=c=TI#U%fAqGzzV7&;2mV!z1mlY zG<8etL<|Z59*c*7v)eJEuq=8-Qo+r};#@CV9V7zNPL|OP;AV8pOkTC(ab@WIR%oQ| zrP?i`dSwk{BYmp-|E|Wt_9at($&_C*<(EwPB~yOMlwUICmrVI3Q+~;mUoz#FO!*~K ze#w;o-zGD^WXdm@@=K=tk}1Dr$}gGnOQ!siDZgaOFPZX7ru>pAzhufUnet1f{E{iZ zWXdm@@=K=tk}1Dr$}gGnOQ!siDZgaOFPZX7ru>pAzhufUnet1f{C~yW{6CW^t+{Nh z001zk&#%YV41CSN|8Hl&>A&s+ApgI=5B!sQxc~s1mH!bV0r~fP9uUBPVI&^dk8NKV z$(PjoCG~zuy4QzXxnasOZp~UIztfOVurpNQ>=&Yr# z;gov-U?(G_kM_|xHo(=zc%M~F!zxb+Qv#r5yJg|Mrd|XM+;HYO8wsUugBDK|^gQQh zkxh(onYY+IJZ$~1LwONbxbYcx6{+%*v{#2@onZM=ct*6>oSp0^T8vp48oMH`p8tk& zy4aNd-{725NGAQF|MWB72akgNuXFH6UI#P(J4Yl}UiaYB47&Ee zi{W>gHtJ}aFFwC#p_KfsZ-x^Iv2<3bDO^b9$vI?JCoD!Ucf^G|^a+=*C0uyM7j;eg znx^2Abm4bv5j=?-CIC<)`#e9I#Ycnvu#er z-`+Pi5A_?D*Mjacei3j&q3rD+6yaOZDl$IkoJDsvI#aD~jqicY!*+49mQ?I$u{<{y?Vn zV3CN)dH_L(4RzO%^+pxKZnW)8^voZp zBD;m2tCTbktPR?cAu&euo59^mis*ZY-C!ioyi6f}3S!>)-V%#6>gpTv|BeaRC5Wi}wYm=)=ouw&SFU z{Oj*%wY^`~8qdLeJSLd&2rKzGAjFBYTQ9o9yF-M1;SmU!v@pXFB;jNvvo(YCm@jXt zHpT($iudn6G-&56C3M~EnDbZ2VyH>}+PCIC|?+dXq*|QU@TsHOqg25Y?5^3 zr`-A>Myoap#X>(|XYkybox1*;my#Xs9ENp(7tp2B&eU+q{THhH^P1RgoXl5jcueeS zj#@~6R*V=sDCiYdWZ=YR9Vzz(Ckl8}^9=#Vrw4 zO-R%h;l0~n@c_Ixc(t9(RZQ1A1U^6*+A75Jq`fD~bPm(fnxlMi)$>rmZNb$`a1-j& zqf?KABI)%h@e~X|Wz!Fc{Y|DIcKkQ9l;KUW@P@S@*L})ma}o9k*cgC-=?e5$seI=s z@PaW>M$}==n6xVNQqR-$FsH97AJy8?&DA(FFYQQ;`BU`b_kcB~wYTHJ=Z*1ViHyS= zq@B(vE0H=NL$6`(08-L?&CvUN4hGn{>QFdNZZVr9=S%jLyX)EgH;)d(GE~oSQaf8^}ItkuG!id0yxvmsU zi}z+S<`Zm|Qw*8v%lcA-T$z^Wx#rj?;&qluP1WW`!E-;u+=>qAF@0O<)`u*Z(In6T zPX7eD75e68={ZglG}bNX^mHH}zTy7cy)S6X;)&gWLrv-|5;**W%iD53eyfiB2nATuxahiUFVzRBjWHAd_W{b7pYfFnVI}0 z?%NOnmuklNwEfY`%hFU#`_>Z&c%7_5v!-0H_ELOFDVyH(eHRUCS?DUs{x?k&MK+rO5Z_cfhF+b6zot$=wQ?+$_{@l3bkg7G?djfw z+%Z$Qc^G+p?~L0=DLQ|$V*Y@}I+B?*5jU@*wR6Yad(1BoH!^DAmeD>F;j!91kFnXk zXEVDFtZLB4Amge6fq11ZJlSrj=-rT;6u3~s-`$TMf;lPJ#^r`M2_~!B9SF4%wle6- zCTWLS#w%+&~_auu#hL*N>O@uwSQ zl|b`TgVp4-@J9zMNe;?)sb#>@O~L?{w%4dRtd*<{%g7tCLE^{=-}mX_B%$a=q5MDA zxsD6CLOj2JH>?|6jRIgz!RO|DDu?mDog4am^g{W^i+VbZ0v)W^^Gk{aNprz)0Vu7d zh3_t2DdF1HdYNRVK*?GV!|i0z?+(14VmEDK9}*TI#~5XGAVjf27O$Mb(ZNSSBeTo& zE8mAi#x8n?!bV4Y$U3)}g>L-!UAT4mdLcn}`ZysLiiHTZXEq0~CudO1@EF^(ZSVzB z{+D*HJe8*l6%I$_K-^khkO2+(wD4&b{wtHOIx?0mOB6a&; zyApSuO*kS!S)RzsFOzhLjj&!c%Y?XE>oz(SKvRmhqx|4?J zyNelL_Z}YTIia2HT|NIOw6&LQ7p3u}w$#O?$^4w&;<1k1hh;ena^gG;#gluZsw+#z zMcaMC84=m7uD$*gF%bua5@nqND>sYu@k-7@;GXrPCeCksL<$4b-UcmcJ)}9tKal*q z)OM-He1ub2V-=m|u9VxbSmtQuxlc{0t4H-lkn%Ib#CS?+7|*F$HX$N#pJnbQ(bHS+ zEgEyQi|1(K4sG_u+XWmYEyzLc=LUy1y*AorOXW>gEEpQrt)Kg zlFUP&K{KYucy8r$K_N@-gOq3HucZ5z{QS%ZuQAlT4?HmwPR-KYS$@XO>nAq-UGb=t zb*|brnE%pMo#{3THWKs7W&RiKqm^YIxtE@hNJ#cg(K9l2j#s{x^ZK{!u~3POIq8tQd5m27 zLS$dt?z6^o?4Do7oK+%CiyN56i!0jOT69ag*|l8?#4@PO)-QOez$>_%^o9;UhLJ2A zB`P@8?OCzuhDZK_e>Tm$D{>Vj z%*lrur>6mW{gcZ64h-1B6zDJoI!u8MQ=r2X=r9F3Oo0wlpu-gCFa(|v4t_VFvb?f*!o{&Y~kLoF}8F>ygfWJcje#= zQXWrSbjO+e>K4?mD#5T*V|ILMa43=nq zCHZKpO5IiXG}flBbe=vn4dFdk`B-OHm}@pAk*Ke+3}LcV8ZX{NsEjW;3hh6Xtk79E z(3~Wka%K@iegdgM6yXvTFCAQho$H&8^0VY?a_>pVDn91R=e|uJQf&Nokk^*H!Kz+6 zep^&ZtcX=6qRg#9)>a&m^xzMZ90^ZFeH(bwZIg_QzDO8nG8hJjKGciH(z^;EJT6~l zc$qT_4;djgTSsCgBot;-99LlTl#B&Kxi6S|H9@acH7}ruucNQLR!gUCWM9$3qFntk zj}Ns57xD?2M?%LNOyAI=Rt$uOH8rbN?t6L^BvpCi57T-#IF4tI>m~lJ`s50m?FNH@L1!ofz2DY(+_S8<4)g&3bzxK9_ zw8eX@mPR*^vGDYD+~T)jY?k%#2g9_MhBkecI$fCGu_TY6WsEc+z`(dd?`fR-_J8@p zlX8rTN|XqAoeJ;|4$;)?!6A487q_I8Gda|k7VJ-@IFqSfCw#p_f?dd-G;cqD)a^hH zp`H>2xcI!AJiH>JVvc0Wj{yNc^YIH1*9jl8w%0i68yBz`&erw^|A@m~ zSjgJ`fN!|3kI|puyp-PPK%-Ja=r~>xDMj_|+Ivkb?c98h`JD)$1cybkQm^OVu6f$D zwrq5%YmzK$m(2I#AGX;)UE7iQ>=-&s(@^mId%la7Yv_9>6+67C`R6ifn+t z3v2PHuNn>rX95DQ3x3;^2=GE@lLDB4Ao>x)OtdO91AEaqG-n~4(ntXKjpMKl4Z&sg z6NFERtiL;WuJ+o@L3mRDjdJ}MY(u9O7`zNZ=nz+}9tQ~AfeWxR9*CN*=mFe0G+<83 zE)fLq$sVnzLr@kEke}FGbbvehi4M6|`f97$90tS{lHvfOIPDaT+MKC)h;+IlXwyrr z+_8XPExc~v^Oxb5IR}a8KC6E=WC4PhqLLH=JqUbd3VJ{%>xt-M$vqnADzWp!&JTP4 z{$uZ-fACr!MT8JQ+k$2)xi|s?;3c + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/macOS/cubepp/cubepp.cmake b/demos/macOS/cubepp/cubepp.cmake new file mode 100644 index 0000000..874ed25 --- /dev/null +++ b/demos/macOS/cubepp/cubepp.cmake @@ -0,0 +1,104 @@ +# Cube Application Bundle + +set(cubepp_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/main.mm + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.mm + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.mm +) +set(cubepp_HDRS + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/AppDelegate.h + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/DemoViewController.h +) +set(cubepp_RESOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/lunarg.ppm + ${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/Resources/LunarGIcon.icns +) + +# Have Xcode handle the Storyboard +if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + set(cubepp_RESOURCES ${cubepp_RESOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/Resources/Main.storyboard + ) +endif() + +add_executable(cubepp MACOSX_BUNDLE + ${cubepp_SRCS} + ${cubepp_HDRS} + ${cubepp_RESOURCES} + cube.vert.inc cube.frag.inc +) + +# Handle the Storyboard ourselves +if(NOT ${CMAKE_GENERATOR} MATCHES "^Xcode.*") + # Compile the storyboard file with the ibtool. + add_custom_command(TARGET cubepp POST_BUILD + COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text + --compile ${CMAKE_CURRENT_BINARY_DIR}/cubepp.app/Contents/Resources/Main.storyboardc + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/Resources/Main.storyboard + COMMENT "Compiling storyboard" + ) +endif() + +add_dependencies(cubepp MoltenVK_icd-staging-json) + +# Include demo source code dir because the MacOS cubepp's Objective-C source includes +# the "original" cubepp application C++ source code. +# Also include the MoltenVK helper files. +target_include_directories(cubepp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${MOLTENVK_DIR}/MoltenVK/include +) + +target_link_libraries(cubepp ${LIBRARIES} "-framework Cocoa -framework QuartzCore") + +set_target_properties(cubepp PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macOS/cubepp/Info.plist +) + +# The RESOURCE target property cannot be used in conjunction with the MACOSX_PACKAGE_LOCATION +# property. We need fine-grained control over the Resource directory, so we have to specify +# the destination of all the resource files on a per-destination-directory basis. +# If all the files went into the top-level Resource directory, then we could simply set +# the RESOURCE property to a list of all the resource files. +set_source_files_properties(${cubepp_RESOURCES} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources" +) +set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/staging-json/MoltenVK_icd.json" PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources/vulkan/icd.d" +) + +# Direct the MoltenVK library to the right place. +install(FILES "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + DESTINATION "demos/cubepp.app/Contents/Frameworks" + COMPONENT Runtime +) + +# Copy the MoltenVK lib into the bundle. +if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + add_custom_command(TARGET cubepp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/$/cubepp.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +else() + add_custom_command(TARGET cubepp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/cubepp.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +endif() + +# Fix up the library search path in the executable to find (loader) libraries in the bundle. +install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_INSTALL_PREFIX}/demos/cubepp.app \"\" \"\") + " COMPONENT Runtime +) + +# Not sure this is needed. When activated, it makes a symlink from +# libvulkan.dylib to libvulkan.1.dylib (which in turn symlinks to libvulkan.1.0.xx.dylib.) +# install(FILES +# "${CMAKE_BINARY_DIR}/loader/libvulkan.dylib" +# DESTINATION "demos/cubepp.app/Contents/MacOS" +# COMPONENT Runtime) diff --git a/demos/macOS/cubepp/main.mm b/demos/macOS/cubepp/main.mm new file mode 100644 index 0000000..cf9e0d8 --- /dev/null +++ b/demos/macOS/cubepp/main.mm @@ -0,0 +1,21 @@ +/* + * main.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +int main(int argc, const char* argv[]) { return NSApplicationMain(argc, argv); } diff --git a/demos/macOS/vulkaninfo/Info.plist b/demos/macOS/vulkaninfo/Info.plist new file mode 100644 index 0000000..337223c --- /dev/null +++ b/demos/macOS/vulkaninfo/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + vulkaninfo.sh + CFBundleGetInfoString + VulkanInfo + CFBundleIconFile + LunarGIcon.icns + CFBundleIdentifier + com.lunarg.vulkaninfo + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + 1.0 + CFBundleName + VulkanInfo + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright (c) 2018 The Khronos Group Inc. LunarG Inc. All rights reserved. + + diff --git a/demos/macOS/vulkaninfo/Resources/LunarGIcon.icns b/demos/macOS/vulkaninfo/Resources/LunarGIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..640b756ff4157663d047c003c0e2181f490f3ef5 GIT binary patch literal 54120 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/vulkaninfo.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +else() + add_custom_command(TARGET vulkaninfo-bundle POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/vulkaninfo.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) +endif() + +# Fix up the library search path in the executable to find (loader) libraries in the bundle. +# When fixup_bundle() is passed a bundle in the first argument, it looks at the Info.plist file +# to determine the BundleExecutable. In this case, the executable is a script, which can't be fixed up. +# Instead pass it the explicit name of the executable. +install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_INSTALL_PREFIX}/demos/vulkaninfo.app/Contents/MacOS/vulkaninfo \"\" \"\") + " COMPONENT Runtime +) diff --git a/demos/macOS/vulkaninfo/vulkaninfo.sh b/demos/macOS/vulkaninfo/vulkaninfo.sh new file mode 100755 index 0000000..7387d88 --- /dev/null +++ b/demos/macOS/vulkaninfo/vulkaninfo.sh @@ -0,0 +1,3 @@ +#!/bin/bash +BASEDIR=`dirname $0` +open /Applications/Utilities/Terminal.app $BASEDIR/vulkaninfo diff --git a/demos/smoke/CMakeLists.txt b/demos/smoke/CMakeLists.txt index d87b392..92701ed 100644 --- a/demos/smoke/CMakeLists.txt +++ b/demos/smoke/CMakeLists.txt @@ -52,7 +52,12 @@ set(includes set(libraries PRIVATE ${CMAKE_THREAD_LIBS_INIT}) if(TARGET vulkan) - list(APPEND definitions PRIVATE -DUNINSTALLED_LOADER="$") + if(APPLE) + # For macOS, the "uninstalled" location is in the bundle + list(APPEND definitions PRIVATE -DUNINSTALLED_LOADER="./libvulkan.1.dylib") + else() + list(APPEND definitions PRIVATE -DUNINSTALLED_LOADER="$") + endif() endif() if(WIN32) @@ -60,7 +65,7 @@ if(WIN32) list(APPEND definitions PRIVATE -DWIN32_LEAN_AND_MEAN) list(APPEND sources ShellWin32.cpp ShellWin32.h) -else() +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") list(APPEND libraries PRIVATE -ldl -lrt) if(BUILD_WSI_XCB_SUPPORT AND DEMOS_WSI_SELECTION STREQUAL "XCB") @@ -78,15 +83,99 @@ else() list(APPEND includes PRIVATE ${WAYLAND_CLIENT_INCLUDE_DIR}) list(APPEND libraries PRIVATE ${WAYLAND_CLIENT_LIBRARIES}) endif() +elseif(APPLE) + set(LIBRARIES) + list(APPEND sources + macOS/AppDelegate.h macOS/AppDelegate.m + macOS/DemoViewController.h macOS/DemoViewController.mm + macOS/main.m macOS/ShellMVK.cpp macOS/ShellMVK.h + ) + list(APPEND includes + ${CMAKE_CURRENT_SOURCE_DIR} + ${MOLTENVK_DIR}/MoltenVK/include) + list(APPEND libraries + "-framework Cocoa -framework QuartzCore") + set(smoketest_RESOURCES + ${CMAKE_BINARY_DIR}/demos/staging-json/MoltenVK_icd.json + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/Resources/LunarGIcon.icns + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/Resources/Main.storyboard + ) + set_source_files_properties(${CMAKE_BINARY_DIR}/demos/staging-json/MoltenVK_icd.json PROPERTIES + GENERATED TRUE + ) + # Have Xcode handle the Storyboard + if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + list(APPEND sources + macOS/Resources/Main.storyboard + ) + endif() endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..) -add_executable(smoketest ${sources}) +add_executable(smoketest MACOSX_BUNDLE ${sources} ${smoketest_RESOURCES}) target_compile_definitions(smoketest ${definitions}) target_include_directories(smoketest ${includes}) target_link_libraries(smoketest ${libraries}) +if(APPLE) + # Handle the Storyboard ourselves + if(NOT ${CMAKE_GENERATOR} MATCHES "^Xcode.*") + # Compile the storyboard file with the ibtool. + add_custom_command(TARGET smoketest POST_BUILD + COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text + --compile ${CMAKE_CURRENT_BINARY_DIR}/../smoketest.app/Contents/Resources/Main.storyboardc + ${CMAKE_CURRENT_SOURCE_DIR}/macOS/Resources/Main.storyboard + COMMENT "Compiling storyboard" + ) + endif() + + add_dependencies(smoketest MoltenVK_icd-staging-json) + + set_target_properties(smoketest PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macOS/Info.plist + ) + set_source_files_properties(${smoketest_RESOURCES} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources" + ) + set_source_files_properties("${CMAKE_BINARY_DIR}/demos/staging-json/MoltenVK_icd.json" PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources/vulkan/icd.d" + ) + + # Direct the MoltenVK library to the right place. + install(FILES "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + DESTINATION "demos/smoketest.app/Contents/Frameworks" + COMPONENT Runtime + ) + + # Xcode projects need some extra help with what would be install steps. + # Vulkan lib needs to be copied manually since smoke does not link it. + if(${CMAKE_GENERATOR} MATCHES "^Xcode.*") + add_custom_command(TARGET smoketest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "$" + ${CMAKE_CURRENT_BINARY_DIR}/../$/smoketest.app/Contents/MacOS/libvulkan.1.dylib + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/../$/smoketest.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) + else() + add_custom_command(TARGET smoketest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy "$" + ${CMAKE_CURRENT_BINARY_DIR}/../smoketest.app/Contents/MacOS/libvulkan.1.dylib + COMMAND ${CMAKE_COMMAND} -E copy "${MOLTENVK_DIR}/MoltenVK/MacOS/libMoltenVK.dylib" + ${CMAKE_CURRENT_BINARY_DIR}/../smoketest.app/Contents/Frameworks/libMoltenVK.dylib + DEPENDS vulkan + ) + endif() + + # Fix up the library search path in the executable to find (loader) libraries in the bundle. + install(CODE " + include(BundleUtilities) + fixup_bundle(${CMAKE_INSTALL_PREFIX}/demos/smoketest.app \"\" \"\") + " COMPONENT Runtime + ) +endif() + if(UNIX) if(INSTALL_LVL_FILES) install(TARGETS smoketest DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/demos/smoke/Main.cpp b/demos/smoke/Main.cpp index f2fbbe2..39ba957 100644 --- a/demos/smoke/Main.cpp +++ b/demos/smoke/Main.cpp @@ -25,10 +25,12 @@ namespace { Game *create_game(const std::vector &args) { return new Smoke(args); } #endif +#if !defined(VK_USE_PLATFORM_MACOS_MVK) Game *create_game(int argc, char **argv) { std::vector args(argv, argv + argc); return new Smoke(args); } +#endif } // namespace diff --git a/demos/smoke/generate-dispatch-table.py b/demos/smoke/generate-dispatch-table.py index 8ef9260..c7fc22a 100755 --- a/demos/smoke/generate-dispatch-table.py +++ b/demos/smoke/generate-dispatch-table.py @@ -295,6 +295,14 @@ vk_ext_debug_report = Extension(name='VK_EXT_debug_report', version=1, guard=Non Command(name='DebugReportMessageEXT', dispatch='VkInstance'), ]) +vk_mvk_ios_surface = Extension(name='VK_MVK_ios_surface', version=1, guard='VK_USE_PLATFORM_IOS_MVK', commands=[ + Command(name='CreateIOSSurfaceMVK', dispatch='VkInstance'), +]) + +vk_mvk_macos_surface = Extension(name='VK_MVK_macos_surface', version=1, guard='VK_USE_PLATFORM_MACOS_MVK', commands=[ + Command(name='CreateMacOSSurfaceMVK', dispatch='VkInstance'), +]) + extensions = [ vk_core, vk_khr_surface, @@ -308,6 +316,8 @@ extensions = [ vk_khr_android_surface, vk_khr_win32_surface, vk_ext_debug_report, + vk_mvk_ios_surface, + vk_mvk_macos_surface, ] def generate_header(guard): diff --git a/demos/smoke/macOS/AppDelegate.h b/demos/smoke/macOS/AppDelegate.h new file mode 100644 index 0000000..0b1dfe4 --- /dev/null +++ b/demos/smoke/macOS/AppDelegate.h @@ -0,0 +1,23 @@ +/* + * AppDelegate.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface AppDelegate : NSObject + +@end diff --git a/demos/smoke/macOS/AppDelegate.m b/demos/smoke/macOS/AppDelegate.m new file mode 100644 index 0000000..2a7bcaf --- /dev/null +++ b/demos/smoke/macOS/AppDelegate.m @@ -0,0 +1,39 @@ +/* + * AppDelegate.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Insert code here to initialize your application +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender { + return YES; +} + +@end diff --git a/demos/smoke/macOS/DemoViewController.h b/demos/smoke/macOS/DemoViewController.h new file mode 100644 index 0000000..7f90cc7 --- /dev/null +++ b/demos/smoke/macOS/DemoViewController.h @@ -0,0 +1,33 @@ +/* + * DemoViewController.h + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#pragma mark - +#pragma mark DemoViewController + +/** The main view controller for the demo storyboard. */ +@interface DemoViewController : NSViewController +@end + +#pragma mark - +#pragma mark DemoView + +/** The Metal-compatibile view for the demo Storyboard. */ +@interface DemoView : NSView +@end diff --git a/demos/smoke/macOS/DemoViewController.mm b/demos/smoke/macOS/DemoViewController.mm new file mode 100644 index 0000000..9826dc9 --- /dev/null +++ b/demos/smoke/macOS/DemoViewController.mm @@ -0,0 +1,126 @@ +/* + * DemoViewController.mm + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "DemoViewController.h" +#import + +#include "ShellMVK.h" +#include "Smoke.h" + +#pragma mark - +#pragma mark DemoViewController + +@implementation DemoViewController { + CVDisplayLinkRef _displayLink; + ShellMVK* _shell; + Game* _game; +} + +- (void)dealloc { + delete _shell; + delete _game; + CVDisplayLinkRelease(_displayLink); + [super dealloc]; +} + +/** Since this is a single-view app, initialize Vulkan during view loading. */ +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.wantsLayer = YES; // Back the view with a layer created by the makeBackingLayer method. + + std::vector args; + // args.push_back("-p"); // Uncomment to use push constants + // args.push_back("-s"); // Uncomment to use a single thread + _game = new Smoke(args); + + _shell = new ShellMVK(*_game); + _shell->run(self.view); + + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, _shell); + CVDisplayLinkStart(_displayLink); +} + +#pragma mark Display loop callback function + +/** Rendering loop callback function for use with a CVDisplayLink. */ +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, + CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* target) { + ((ShellMVK*)target)->update_and_draw(); + return kCVReturnSuccess; +} + +- (void)viewDidAppear { + self.view.window.initialFirstResponder = self.view; +} + +// Delegated from the view as first responder. +- (void)keyDown:(NSEvent*)theEvent { + Game::Key key; + switch (theEvent.keyCode) { + case 53: + key = Game::KEY_ESC; + break; + case 126: + key = Game::KEY_UP; + break; + case 125: + key = Game::KEY_DOWN; + break; + case 49: + key = Game::KEY_SPACE; + break; + default: + key = Game::KEY_UNKNOWN; + break; + } + + _game->on_key(key); +} + +@end + +#pragma mark - +#pragma mark DemoView + +@implementation DemoView + +/** Indicates that the view wants to draw using the backing layer instead of using drawRect:. */ +- (BOOL)wantsUpdateLayer { + return YES; +} + +/** Returns a Metal-compatible layer. */ ++ (Class)layerClass { + return [CAMetalLayer class]; +} + +/** If the wantsLayer property is set to YES, this method will be invoked to return a layer instance. */ +- (CALayer*)makeBackingLayer { + CALayer* layer = [self.class.layerClass layer]; + CGSize viewScale = [self convertSizeToBacking:CGSizeMake(1.0, 1.0)]; + layer.contentsScale = MIN(viewScale.width, viewScale.height); + return layer; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +@end diff --git a/demos/smoke/macOS/Info.plist b/demos/smoke/macOS/Info.plist new file mode 100644 index 0000000..b553431 --- /dev/null +++ b/demos/smoke/macOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + LunarGIcon.icns + CFBundleIdentifier + com.lunarg.smoketest + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright (c) 2015-2017 The Brenwill Workshop Ltd. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/demos/smoke/macOS/Resources/LunarGIcon.icns b/demos/smoke/macOS/Resources/LunarGIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..640b756ff4157663d047c003c0e2181f490f3ef5 GIT binary patch literal 54120 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/smoke/macOS/ShellMVK.cpp b/demos/smoke/macOS/ShellMVK.cpp new file mode 100644 index 0000000..804a1bb --- /dev/null +++ b/demos/smoke/macOS/ShellMVK.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016-2018 The Brenwill Workshop Ltd. + * + * 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 "ShellMVK.h" +#include +#include +#include +#include +#include +#include "Helpers.h" +#include "Game.h" + +PosixTimer::PosixTimer() { + _tsBase = mach_absolute_time(); + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + _tsPeriod = (double)timebase.numer / (double)timebase.denom; +} + +double PosixTimer::get() { return (double)(mach_absolute_time() - _tsBase) * _tsPeriod / 1e9; } + +ShellMVK::ShellMVK(Game& game) : Shell(game) { + _timer = PosixTimer(); + _current_time = _timer.get(); + _profile_start_time = _current_time; + _profile_present_count = 0; + +#ifdef VK_USE_PLATFORM_IOS_MVK + instance_extensions_.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME); +#endif +#ifdef VK_USE_PLATFORM_MACOS_MVK + instance_extensions_.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); +#endif + + init_vk(); +} + +ShellMVK::~ShellMVK() { + destroy_context(); + cleanup_vk(); +} + +PFN_vkGetInstanceProcAddr ShellMVK::load_vk() { + const char filename[] = "libvulkan.1.dylib"; + void* handle = NULL; + void* symbol = NULL; + +#ifdef UNINSTALLED_LOADER + // Try to load the loader from the defined location. + handle = dlopen(UNINSTALLED_LOADER, RTLD_LAZY); +#endif + // If still no loader, try in the bundle executable directory. + if (!handle) { + unsigned int bufferSize = 512; + std::vector buffer(bufferSize + 1); + if (_NSGetExecutablePath(&buffer[0], &bufferSize)) { + buffer.resize(bufferSize); + _NSGetExecutablePath(&buffer[0], &bufferSize); + } + std::string s = &buffer[0]; + size_t i = s.rfind("smoketest"); + s.replace(i, std::string::npos, filename); + handle = dlopen(s.c_str(), RTLD_LAZY); + } + // If still no luck, try the default system libs with the default lib name. + if (!handle) handle = dlopen(filename, RTLD_LAZY); + + if (handle) symbol = dlsym(handle, "vkGetInstanceProcAddr"); + + if (!handle || !symbol) { + std::stringstream ss; + ss << "failed to load " << dlerror(); + + if (handle) dlclose(handle); + + throw std::runtime_error(ss.str()); + } + + return reinterpret_cast(symbol); +} + +bool ShellMVK::can_present(VkPhysicalDevice phy, uint32_t queue_family) { return true; } + +VkSurfaceKHR ShellMVK::create_surface(VkInstance instance) { + VkSurfaceKHR surface; + + VkResult err; +#ifdef VK_USE_PLATFORM_IOS_MVK + VkIOSSurfaceCreateInfoMVK surface_info; + surface_info.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; + surface_info.pNext = NULL; + surface_info.flags = 0; + surface_info.pView = _view; + err = vk::CreateIOSSurfaceMVK(instance, &surface_info, NULL, &surface); +#endif +#ifdef VK_USE_PLATFORM_MACOS_MVK + VkMacOSSurfaceCreateInfoMVK surface_info; + surface_info.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + surface_info.pNext = NULL; + surface_info.flags = 0; + surface_info.pView = _view; + err = vk::CreateMacOSSurfaceMVK(instance, &surface_info, NULL, &surface); +#endif + assert(!err); + + return surface; +} + +void ShellMVK::update_and_draw() { + acquire_back_buffer(); + + double t = _timer.get(); + add_game_time(static_cast(t - _current_time)); + + present_back_buffer(); + + _current_time = t; + + _profile_present_count++; + if (_current_time - _profile_start_time >= 5.0) { + const double fps = _profile_present_count / (_current_time - _profile_start_time); + std::stringstream ss; + ss << _profile_present_count << " presents in " << _current_time - _profile_start_time << " seconds " + << "(FPS: " << fps << ")"; + log(LOG_INFO, ss.str().c_str()); + + _profile_start_time = _current_time; + _profile_present_count = 0; + } +} + +void ShellMVK::run(void* view) { + _view = view; // not retained + create_context(); + resize_swapchain(settings_.initial_width, settings_.initial_height); +} diff --git a/demos/smoke/macOS/ShellMVK.h b/demos/smoke/macOS/ShellMVK.h new file mode 100644 index 0000000..2baad59 --- /dev/null +++ b/demos/smoke/macOS/ShellMVK.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2018 The Brenwill Workshop Ltd. + * + * 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. + */ + +#ifndef SHELL_MVK_H +#define SHELL_MVK_H + +#include +#include "Shell.h" +#include + +class PosixTimer { + public: + double get(); + PosixTimer(); + + protected: + uint64_t _tsBase; + double _tsPeriod; +}; + +class ShellMVK : public Shell { + public: + ShellMVK(Game& game); + ~ShellMVK(); + + void run(void* view); + void update_and_draw(); + + void run() { run(nullptr); }; + void quit() {} + + protected: + void* _view; + PosixTimer _timer; + double _current_time; + double _profile_start_time; + int _profile_present_count; + + PFN_vkGetInstanceProcAddr load_vk(); + bool can_present(VkPhysicalDevice phy, uint32_t queue_family); + + VkSurfaceKHR create_surface(VkInstance instance); +}; + +#endif // SHELL_MVK_H diff --git a/demos/smoke/macOS/main.m b/demos/smoke/macOS/main.m new file mode 100644 index 0000000..cf9e0d8 --- /dev/null +++ b/demos/smoke/macOS/main.m @@ -0,0 +1,21 @@ +/* + * main.m + * + * Copyright (c) 2014-2018 The Brenwill Workshop Ltd. (http://www.brenwill.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +int main(int argc, const char* argv[]) { return NSApplicationMain(argc, argv); } diff --git a/icd/CMakeLists.txt b/icd/CMakeLists.txt index fa48467..ff8a479 100644 --- a/icd/CMakeLists.txt +++ b/icd/CMakeLists.txt @@ -21,6 +21,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_definitions(-DVK_USE_PLATFORM_MIR_KHR -DVK_USE_PLATFORM_MIR_KHX) include_directories(${MIR_INCLUDE_DIR}) endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) else() message(FATAL_ERROR "Unsupported Platform!") endif() @@ -87,6 +89,14 @@ if (WIN32) #target_link_Libraries(VkICD_${target} VkICD_utils) #add_dependencies(VkICD_${target} generate_helper_files VkICD_utils) endmacro() +elseif(APPLE) + macro(add_vk_icd target) + add_library(VkICD_${target} SHARED ${ARGN}) + #target_link_Libraries(VkICD_${target} VkICD_utils) + add_dependencies(VkICD_${target} generate_icd_files) + set_target_properties(VkICD_${target} PROPERTIES LINK_FLAGS "-Wl") + install(TARGETS VkICD_${target} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + endmacro() else() macro(add_vk_icd target) add_library(VkICD_${target} SHARED ${ARGN}) diff --git a/include/vulkan/vk_icd.h b/include/vulkan/vk_icd.h index b8c7efc..35956a3 100644 --- a/include/vulkan/vk_icd.h +++ b/include/vulkan/vk_icd.h @@ -44,12 +44,12 @@ #define CURRENT_LOADER_ICD_INTERFACE_VERSION 5 #define MIN_SUPPORTED_LOADER_ICD_INTERFACE_VERSION 0 #define MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION 4 -typedef VkResult (VKAPI_PTR *PFN_vkNegotiateLoaderICDInterfaceVersion)(uint32_t *pVersion); +typedef VkResult(VKAPI_PTR *PFN_vkNegotiateLoaderICDInterfaceVersion)(uint32_t *pVersion); // This is defined in vk_layer.h which will be found by the loader, but if an ICD is building against this // file directly, it won't be found. #ifndef PFN_GetPhysicalDeviceProcAddr -typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance instance, const char* pName); +typedef PFN_vkVoidFunction(VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance instance, const char *pName); #endif /* @@ -85,6 +85,9 @@ typedef enum { VK_ICD_WSI_PLATFORM_WIN32, VK_ICD_WSI_PLATFORM_XCB, VK_ICD_WSI_PLATFORM_XLIB, + VK_ICD_WSI_PLATFORM_ANDROID, + VK_ICD_WSI_PLATFORM_MACOS, + VK_ICD_WSI_PLATFORM_IOS, VK_ICD_WSI_PLATFORM_DISPLAY } VkIcdWsiPlatform; @@ -98,7 +101,7 @@ typedef struct { MirConnection *connection; MirSurface *mirSurface; } VkIcdSurfaceMir; -#endif // VK_USE_PLATFORM_MIR_KHR +#endif // VK_USE_PLATFORM_MIR_KHR #ifdef VK_USE_PLATFORM_WAYLAND_KHR typedef struct { @@ -106,7 +109,7 @@ typedef struct { struct wl_display *display; struct wl_surface *surface; } VkIcdSurfaceWayland; -#endif // VK_USE_PLATFORM_WAYLAND_KHR +#endif // VK_USE_PLATFORM_WAYLAND_KHR #ifdef VK_USE_PLATFORM_WIN32_KHR typedef struct { @@ -114,7 +117,7 @@ typedef struct { HINSTANCE hinstance; HWND hwnd; } VkIcdSurfaceWin32; -#endif // VK_USE_PLATFORM_WIN32_KHR +#endif // VK_USE_PLATFORM_WIN32_KHR #ifdef VK_USE_PLATFORM_XCB_KHR typedef struct { @@ -122,7 +125,7 @@ typedef struct { xcb_connection_t *connection; xcb_window_t window; } VkIcdSurfaceXcb; -#endif // VK_USE_PLATFORM_XCB_KHR +#endif // VK_USE_PLATFORM_XCB_KHR #ifdef VK_USE_PLATFORM_XLIB_KHR typedef struct { @@ -130,13 +133,28 @@ typedef struct { Display *dpy; Window window; } VkIcdSurfaceXlib; -#endif // VK_USE_PLATFORM_XLIB_KHR +#endif // VK_USE_PLATFORM_XLIB_KHR #ifdef VK_USE_PLATFORM_ANDROID_KHR typedef struct { - ANativeWindow* window; + VkIcdSurfaceBase base; + ANativeWindow *window; } VkIcdSurfaceAndroid; -#endif //VK_USE_PLATFORM_ANDROID_KHR +#endif // VK_USE_PLATFORM_ANDROID_KHR + +#ifdef VK_USE_PLATFORM_MACOS_MVK +typedef struct { + VkIcdSurfaceBase base; + const void *pView; +} VkIcdSurfaceMacOS; +#endif // VK_USE_PLATFORM_MACOS_MVK + +#ifdef VK_USE_PLATFORM_IOS_MVK +typedef struct { + VkIcdSurfaceBase base; + const void *pView; +} VkIcdSurfaceIOS; +#endif // VK_USE_PLATFORM_IOS_MVK typedef struct { VkIcdSurfaceBase base; @@ -149,4 +167,4 @@ typedef struct { VkExtent2D imageExtent; } VkIcdSurfaceDisplay; -#endif // VKICD_H +#endif // VKICD_H diff --git a/layers/CMakeLists.txt b/layers/CMakeLists.txt index 08b0e20..7a1390a 100644 --- a/layers/CMakeLists.txt +++ b/layers/CMakeLists.txt @@ -21,6 +21,11 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_definitions(-DVK_USE_PLATFORM_MIR_KHR -DVK_USE_PLATFORM_MIR_KHX) include_directories(${MIR_INCLUDE_DIR}) endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) + if(CMAKE_GENERATOR MATCHES "^Xcode.*") + add_custom_target(mk_layer_config_dir ALL COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/$) + endif() else() message(FATAL_ERROR "Unsupported Platform!") endif() @@ -70,7 +75,7 @@ if (WIN32) ) set_target_properties(vk_validation_error_messages PROPERTIES FOLDER ${LVL_TARGET_FOLDER}) endif() -else() +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # extra setup for out-of-tree builds if (NOT (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)) foreach (config_file ${LAYER_JSON_FILES}) @@ -85,6 +90,31 @@ else() VERBATIM ) endif() +elseif(APPLE) + # extra setup for out-of-tree builds + if (NOT (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)) + if (CMAKE_GENERATOR MATCHES "^Xcode.*") + foreach (config_file ${LAYER_JSON_FILES}) + add_custom_target(${config_file}-json ALL + DEPENDS mk_layer_config_dir + COMMAND ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/macos/${config_file}.json $ + VERBATIM + ) + endforeach(config_file) + else() + foreach (config_file ${LAYER_JSON_FILES}) + add_custom_target(${config_file}-json ALL + COMMAND ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/macos/${config_file}.json + VERBATIM + ) + endforeach(config_file) + endif() + # Add link to vk_validation_error_messages.h in build dir for scripts to pick up + add_custom_target(vk_validation_error_messages ALL + COMMAND ln -sf ${CMAKE_CURRENT_SOURCE_DIR}/vk_validation_error_messages.h + VERBATIM + ) + endif() endif() # If a layer has a direct dependency on a project with the same name, use it. if ((Win32) OR (NOT (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR))) @@ -127,6 +157,17 @@ if (WIN32) target_link_Libraries(VkLayer_${target} VkLayer_utils) add_dependencies(VkLayer_${target} generate_helper_files VkLayer_utils) endmacro() +elseif(APPLE) + macro(add_vk_layer target) + add_library(VkLayer_${target} SHARED ${ARGN}) + target_link_Libraries(VkLayer_${target} VkLayer_utils) + add_dependencies(VkLayer_${target} generate_helper_files VkLayer_utils) + set_target_properties(VkLayer_${target} PROPERTIES + LINK_FLAGS "-Wl" + INSTALL_RPATH "@loader_path/" + ) + install(TARGETS VkLayer_${target} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + endmacro() else() macro(add_vk_layer target) add_library(VkLayer_${target} SHARED ${ARGN}) diff --git a/layers/core_validation.cpp b/layers/core_validation.cpp index c00c91f..74eae32 100644 --- a/layers/core_validation.cpp +++ b/layers/core_validation.cpp @@ -11076,6 +11076,20 @@ VKAPI_ATTR VkResult VKAPI_CALL CreateAndroidSurfaceKHR(VkInstance instance, cons } #endif // VK_USE_PLATFORM_ANDROID_KHR +#ifdef VK_USE_PLATFORM_IOS_MVK +VKAPI_ATTR VkResult VKAPI_CALL CreateIOSSurfaceMVK(VkInstance instance, const VkIOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + return CreateSurface(instance, pCreateInfo, pAllocator, pSurface, &VkLayerInstanceDispatchTable::CreateIOSSurfaceMVK); +} +#endif // VK_USE_PLATFORM_IOS_MVK + +#ifdef VK_USE_PLATFORM_MACOS_MVK +VKAPI_ATTR VkResult VKAPI_CALL CreateMacOSSurfaceMVK(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + return CreateSurface(instance, pCreateInfo, pAllocator, pSurface, &VkLayerInstanceDispatchTable::CreateMacOSSurfaceMVK); +} +#endif // VK_USE_PLATFORM_MACOS_MVK + #ifdef VK_USE_PLATFORM_MIR_KHR VKAPI_ATTR VkResult VKAPI_CALL CreateMirSurfaceKHR(VkInstance instance, const VkMirSurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { @@ -12031,6 +12045,12 @@ static const std::unordered_map name_to_funcptr_map = { {"vkCreateXlibSurfaceKHR", (void *)CreateXlibSurfaceKHR}, {"vkGetPhysicalDeviceXlibPresentationSupportKHR", (void *)GetPhysicalDeviceXlibPresentationSupportKHR}, #endif +#ifdef VK_USE_PLATFORM_IOS_MVK + {"vkCreateIOSSurfaceMVK", (void *)CreateIOSSurfaceMVK}, +#endif +#ifdef VK_USE_PLATFORM_MACOS_MVK + {"vkCreateMacOSSurfaceMVK", (void *)CreateMacOSSurfaceMVK}, +#endif {"vkCreateDisplayPlaneSurfaceKHR", (void *)CreateDisplayPlaneSurfaceKHR}, {"vkDestroySurfaceKHR", (void *)DestroySurfaceKHR}, {"vkGetPhysicalDeviceSurfaceCapabilitiesKHR", (void *)GetPhysicalDeviceSurfaceCapabilitiesKHR}, diff --git a/layers/macos/VkLayer_core_validation.json b/layers/macos/VkLayer_core_validation.json new file mode 100644 index 0000000..8c3d81b --- /dev/null +++ b/layers/macos/VkLayer_core_validation.json @@ -0,0 +1,29 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_LUNARG_core_validation", + "type": "GLOBAL", + "library_path": "./libVkLayer_core_validation.dylib", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "LunarG Validation Layer", + "instance_extensions": [ + { + "name": "VK_EXT_debug_report", + "spec_version": "6" + } + ], + "device_extensions": [ + { + "name": "VK_EXT_debug_marker", + "spec_version": "4", + "entrypoints": ["vkDebugMarkerSetObjectTagEXT", + "vkDebugMarkerSetObjectNameEXT", + "vkCmdDebugMarkerBeginEXT", + "vkCmdDebugMarkerEndEXT", + "vkCmdDebugMarkerInsertEXT" + ] + } + ] + } +} diff --git a/layers/macos/VkLayer_device_simulation.json b/layers/macos/VkLayer_device_simulation.json new file mode 100644 index 0000000..b2eada6 --- /dev/null +++ b/layers/macos/VkLayer_device_simulation.json @@ -0,0 +1,11 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_LUNARG_device_simulation", + "type": "GLOBAL", + "library_path": "./libVkLayer_device_simulation.dylib", + "api_version": "1.0.69", + "implementation_version": "1.2.0", + "description": "LunarG device simulation layer" + } +} diff --git a/layers/macos/VkLayer_object_tracker.json b/layers/macos/VkLayer_object_tracker.json new file mode 100644 index 0000000..ba84603 --- /dev/null +++ b/layers/macos/VkLayer_object_tracker.json @@ -0,0 +1,29 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_LUNARG_object_tracker", + "type": "GLOBAL", + "library_path": "./libVkLayer_object_tracker.dylib", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "LunarG Validation Layer", + "instance_extensions": [ + { + "name": "VK_EXT_debug_report", + "spec_version": "6" + } + ], + "device_extensions": [ + { + "name": "VK_EXT_debug_marker", + "spec_version": "4", + "entrypoints": ["vkDebugMarkerSetObjectTagEXT", + "vkDebugMarkerSetObjectNameEXT", + "vkCmdDebugMarkerBeginEXT", + "vkCmdDebugMarkerEndEXT", + "vkCmdDebugMarkerInsertEXT" + ] + } + ] + } +} diff --git a/layers/macos/VkLayer_parameter_validation.json b/layers/macos/VkLayer_parameter_validation.json new file mode 100644 index 0000000..579324a --- /dev/null +++ b/layers/macos/VkLayer_parameter_validation.json @@ -0,0 +1,29 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_LUNARG_parameter_validation", + "type": "GLOBAL", + "library_path": "./libVkLayer_parameter_validation.dylib", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "LunarG Validation Layer", + "instance_extensions": [ + { + "name": "VK_EXT_debug_report", + "spec_version": "6" + } + ], + "device_extensions": [ + { + "name": "VK_EXT_debug_marker", + "spec_version": "4", + "entrypoints": ["vkDebugMarkerSetObjectTagEXT", + "vkDebugMarkerSetObjectNameEXT", + "vkCmdDebugMarkerBeginEXT", + "vkCmdDebugMarkerEndEXT", + "vkCmdDebugMarkerInsertEXT" + ] + } + ] + } +} diff --git a/layers/macos/VkLayer_standard_validation.json b/layers/macos/VkLayer_standard_validation.json new file mode 100644 index 0000000..1be9fee --- /dev/null +++ b/layers/macos/VkLayer_standard_validation.json @@ -0,0 +1,17 @@ +{ + "file_format_version": "1.1.1", + "layer": { + "name": "VK_LAYER_LUNARG_standard_validation", + "type": "GLOBAL", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "LunarG Standard Validation", + "component_layers": [ + "VK_LAYER_GOOGLE_threading", + "VK_LAYER_LUNARG_parameter_validation", + "VK_LAYER_LUNARG_object_tracker", + "VK_LAYER_LUNARG_core_validation", + "VK_LAYER_GOOGLE_unique_objects" + ] + } +} diff --git a/layers/macos/VkLayer_threading.json b/layers/macos/VkLayer_threading.json new file mode 100644 index 0000000..d6af6c0 --- /dev/null +++ b/layers/macos/VkLayer_threading.json @@ -0,0 +1,17 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_GOOGLE_threading", + "type": "GLOBAL", + "library_path": "./libVkLayer_threading.dylib", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "Google Validation Layer", + "instance_extensions": [ + { + "name": "VK_EXT_debug_report", + "spec_version": "6" + } + ] + } +} diff --git a/layers/macos/VkLayer_unique_objects.json b/layers/macos/VkLayer_unique_objects.json new file mode 100644 index 0000000..63c5306 --- /dev/null +++ b/layers/macos/VkLayer_unique_objects.json @@ -0,0 +1,11 @@ +{ + "file_format_version" : "1.1.0", + "layer" : { + "name": "VK_LAYER_GOOGLE_unique_objects", + "type": "GLOBAL", + "library_path": "./libVkLayer_unique_objects.dylib", + "api_version": "1.0.69", + "implementation_version": "1", + "description": "Google Validation Layer" + } +} diff --git a/layers/threading.h b/layers/threading.h index 950d77c..e2301d0 100644 --- a/layers/threading.h +++ b/layers/threading.h @@ -96,8 +96,9 @@ class counter { if (use_data->thread != tid) { skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", - "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", - typeName, use_data->thread, tid); + "THREADING ERROR : object of type %s is simultaneously used in " + "thread 0x%" PRIx64 " and thread 0x%" PRIx64, + typeName, (uint64_t)use_data->thread, (uint64_t)tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { @@ -123,8 +124,9 @@ class counter { if (use_data->thread != tid) { skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", - "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", - typeName, use_data->thread, tid); + "THREADING ERROR : object of type %s is simultaneously used in " + "thread 0x%" PRIx64 " and thread 0x%" PRIx64, + typeName, (uint64_t)use_data->thread, (uint64_t)tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { @@ -181,8 +183,9 @@ class counter { // There is a writer of the object. skipCall |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), 0, THREADING_CHECKER_MULTIPLE_THREADS, "THREADING", - "THREADING ERROR : object of type %s is simultaneously used in thread %ld and thread %ld", typeName, - uses[object].thread, tid); + "THREADING ERROR : object of type %s is simultaneously used in " + "thread 0x%" PRIx64 " and thread 0x%" PRIx64, + typeName, (uint64_t)uses[object].thread, (uint64_t)tid); if (skipCall) { // Wait for thread-safe access to object instead of skipping call. while (uses.find(object) != uses.end()) { diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 85282d0..779ebbd 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -49,6 +49,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_definitions(-DVK_USE_PLATFORM_MIR_KHR) include_directories(${MIR_INCLUDE_DIR}) endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_definitions(-DVK_USE_PLATFORM_MACOS_MVK) else() message(FATAL_ERROR "Unsupported Platform!") endif() @@ -110,6 +112,11 @@ if (WIN32) set(OPT_LOADER_SRCS ${OPT_LOADER_SRCS} unknown_ext_chain.c) add_custom_target(loader_asm_gen_files) endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # For MacOS, use the C code and force the compiler's tail-call optimization instead of using assembly code. + set(OPT_LOADER_SRCS ${OPT_LOADER_SRCS} unknown_ext_chain.c) + set_source_files_properties(${OPT_LOADER_SRCS} PROPERTIES COMPILE_FLAGS -O) + add_custom_target(loader_asm_gen_files) # This causes no assembly files to be generated. else() enable_language(ASM-ATT) set(CMAKE_ASM-ATT_COMPILE_FLAGS "${CMAKE_ASM-ATT_COMPILE_FLAGS} $ENV{ASFLAGS}") @@ -211,6 +218,7 @@ if (WIN32) COMMAND xcopy /Y /I ${COPY_SRC_PATH} ${COPY_DST_TEST_PATH}) else() + # Linux and MacOS set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wpointer-arith") # Clang (and not gcc) warns about redefining a typedef with the same types, so disable that warning. @@ -219,27 +227,70 @@ else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-typedef-redefinition") endif() - add_library(${API_LOWERCASE} SHARED ${NORMAL_LOADER_SRCS} ${OPT_LOADER_SRCS}) + add_library(${API_LOWERCASE} SHARED ${NORMAL_LOADER_SRCS} ${OPT_LOADER_SRCS} ${FRAMEWORK_HEADERS}) add_dependencies(${API_LOWERCASE} generate_helper_files loader_gen_files loader_asm_gen_files) target_compile_definitions(${API_LOWERCASE} PUBLIC -DLOADER_DYNAMIC_LIB) set_target_properties(${API_LOWERCASE} PROPERTIES SOVERSION "1" VERSION "1.0.${vk_header_version}") target_link_libraries(${API_LOWERCASE} -ldl -lpthread -lm) + if(APPLE) + find_library(COREFOUNDATION_LIBRARY NAMES CoreFoundation) + target_link_libraries(${API_LOWERCASE} "-framework CoreFoundation") + + # Build vulkan.framework + set(FRAMEWORK_HEADERS + ${PROJECT_SOURCE_DIR}/include/vulkan/vk_icd.h + ${PROJECT_SOURCE_DIR}/include/vulkan/vk_layer.h + ${PROJECT_SOURCE_DIR}/include/vulkan/vk_platform.h + ${PROJECT_SOURCE_DIR}/include/vulkan/vk_sdk_platform.h + ${PROJECT_SOURCE_DIR}/include/vulkan/vulkan.h + ${PROJECT_SOURCE_DIR}/include/vulkan/vulkan.hpp + ) + add_library(vulkan-framework SHARED ${NORMAL_LOADER_SRCS} ${OPT_LOADER_SRCS} ${FRAMEWORK_HEADERS}) + add_dependencies(vulkan-framework generate_helper_files loader_gen_files loader_asm_gen_files) + target_compile_definitions(vulkan-framework PUBLIC -DLOADER_DYNAMIC_LIB) + target_link_libraries(vulkan-framework -ldl -lpthread -lm "-framework CoreFoundation") + + # The FRAMEWORK_VERSION needs to be "A" here so that Xcode code-signing works when + # a user adds their framework to an Xcode project and does "Sign on Copy". + # It would have been nicer to use "1" to denote Vulkan 1. + # Although Apple docs say that a framework version does not have to be "A", + # this part of the Apple toolchain expects it. + # https://forums.developer.apple.com/thread/65963 + set_target_properties(vulkan-framework PROPERTIES + OUTPUT_NAME vulkan + FRAMEWORK TRUE + FRAMEWORK_VERSION A + VERSION "1.0.${vk_header_version}" # "current version" + SOVERSION "1.0.0" # "compatibility version" + MACOSX_FRAMEWORK_IDENTIFIER com.lunarg.vulkanFramework + PUBLIC_HEADER "${FRAMEWORK_HEADERS}" + ) + install(TARGETS vulkan-framework + PUBLIC_HEADER DESTINATION vulkan + FRAMEWORK DESTINATION loader + ) + endif(APPLE) + if(INSTALL_LVL_FILES) install(TARGETS ${API_LOWERCASE} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() - # Generate pkg-config file. - include(FindPkgConfig QUIET) - if(PKG_CONFIG_FOUND) - set(VK_API_VERSION "1.0.${vk_header_version}") - foreach(LIB ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) - set(PRIVATE_LIBS "${PRIVATE_LIBS} -l${LIB}") - endforeach() - configure_file("vulkan.pc.in" "vulkan.pc" @ONLY) - if(INSTALL_LVL_FILES) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/vulkan.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + if(NOT APPLE) + # Generate pkg-config file. + include(FindPkgConfig QUIET) + if(PKG_CONFIG_FOUND) + set(VK_API_VERSION "1.0.${vk_header_version}") + foreach(LIB ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) + set(PRIVATE_LIBS "${PRIVATE_LIBS} -l${LIB}") + endforeach() + configure_file("vulkan.pc.in" "vulkan.pc" @ONLY) + if(INSTALL_LVL_FILES) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/vulkan.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + endif() endif() endif() endif() + + diff --git a/loader/LoaderAndLayerInterface.md b/loader/LoaderAndLayerInterface.md index e61619c..2e96ef6 100644 --- a/loader/LoaderAndLayerInterface.md +++ b/loader/LoaderAndLayerInterface.md @@ -54,7 +54,7 @@ Vulkan is a layered architecture, made up of the following elements: ![High Level View of Loader](./images/high_level_loader.png) The general concepts in this document are applicable to the loaders available -for Windows, Linux and Android based systems. +for Windows, Linux, Android and MacOS based systems. #### Who Should Read This Document @@ -292,7 +292,7 @@ loader. ##### Vulkan Direct Exports -The loader library on Windows, Linux and Android will export all core Vulkan +The loader library on Windows, Linux, Android and MacOS will export all core Vulkan and all appropriate Window System Interface (WSI) extensions. This is done to make it simpler to get started with Vulkan development. When an application links directly to the loader library in this way, the Vulkan calls are simple @@ -304,7 +304,8 @@ object they are given. ###### Dynamic Linking The loader is ordinarily distributed as a dynamic library (.dll on Windows or -.so on Linux) which gets installed to the system path for dynamic libraries. +.so on Linux or .dylib on MacOS) which gets installed to the system path +for dynamic libraries. Linking to the dynamic library is generally the preferred method of linking to the loader, as doing so allows the loader to be updated for bug fixes and improvements. Furthermore, the dynamic library is generally installed to Windows @@ -363,8 +364,8 @@ view of an Instance call chain with 3 enabled layers: ![Instance Call Chain](./images/loader_instance_chain.png) This is also how a Vulkan Device function call chain looks if you query it -using `vkGetInstanceProcAddr`. On the otherhand, a Device -function doesn't need to worry about the broadcast becuase it knows specifically +using `vkGetInstanceProcAddr`. On the other hand, a Device +function doesn't need to worry about the broadcast because it knows specifically which associated ICD and which associated Physical Device the call should terminate at. Because of this, the loader doesn't need to get involved between any enabled layers and the ICD. Thus, if you used a loader-exported Vulkan @@ -415,11 +416,12 @@ in the windows/system32 directory (on 64-bit Windows installs, the 32-bit version of the loader with the same name can be found in the windows/sysWOW64 directory). -For Linux, shared libraries are versioned based on a suffix. Thus, the ABI +For Linux and MacOS, shared libraries are versioned based on a suffix. Thus, the ABI number is not encoded in the base of the library filename as on Windows. On Linux an application wanting to link to the latest Vulkan ABI version would just link to the name vulkan (libvulkan.so). A specific Vulkan ABI version can also be linked to by applications (e.g. libvulkan.so.1). +On MacOS, the libraries are libvulkan.dylib abd libvulkan.1.dylib. #### Application Layer Usage @@ -519,8 +521,8 @@ Implicit layers have an additional requirement over explicit layers in that they require being able to be disabled by an environmental variable. This is due to the fact that they are not visible to the application and could cause issues. A good principle to keep in mind would be to define both an enable and disable -environment variable so the users can deterministicly enable the functionality. -On Desktop platforms (Windows and Linux), these enable/disable settings are +environment variable so the users can deterministically enable the functionality. +On Desktop platforms (Windows, Linux, and MacOS), these enable/disable settings are defined in the layer's JSON file. Discovery of system-installed implicit and explicit layers is described later in @@ -533,6 +535,7 @@ system, as shown in the table below. | Windows | Implicit Layers are located in a different Windows registry location than Explicit Layers. | | Linux | Implicit Layers are located in a different directory location than Explicit Layers. | | Android | There is **No Support For Implicit Layers** on Android. | +| MacOS | Implicit Layers are located in a different directory location than Explicit Layers. | ##### Forcing Layer Source Folders @@ -541,10 +544,10 @@ Developers may need to use special, pre-production layers, without modifying the system-installed layers. You can direct the loader to look for layers in a specific folder by defining the "VK\_LAYER\_PATH" environment variable. This will override the mechanism used for finding system-installed layers. Because -layers of interest may exist in several disinct folders on a system, this -environment variable can containis several paths seperated by the operating +layers of interest may exist in several distinct folders on a system, this +environment variable can contains several paths separated by the operating specific path separator. On Windows, each separate folder should be separated -in the list using a semi-colon. On Linux, each folder name should be separated +in the list using a semi-colon. On Linux and MacOS, each folder name should be separated using a colon. If "VK\_LAYER\_PATH" exists, **only** the folders listed in it will be scanned @@ -552,13 +555,13 @@ for layers. Each directory listed should be the full pathname of a folder containing layer manifest files. -##### Forcing Layers to be Enabled on Windows and Linux +##### Forcing Layers to be Enabled on Windows, Linux and MacOS Developers may want to enable layers that are not enabled by the given -application they are using. On Linux and Windows, the environment variable +application they are using. On desktop systems, the environment variable "VK\_INSTANCE\_LAYERS" can be used to enable additional layers which are not specified (enabled) by the application at `vkCreateInstance`. -"VK\_INSTANCE\_LAYERS" is a colon (Linux)/semi-colon (Windows) separated +"VK\_INSTANCE\_LAYERS" is a colon (Linux and MacOS)/semi-colon (Windows) separated list of layer names to enable. Order is relevant with the first layer in the list being the top-most layer (closest to the application) and the last layer in the list being the bottom-most layer (closest to the driver). @@ -571,7 +574,7 @@ layers. Layers specified via environment variable are top-most (closest to the application) while layers specified by the application are bottommost. An example of using these environment variables to activate the validation -layer `VK_LAYER_LUNARG_parameter_validation` on Windows or Linux is as follows: +layer `VK_LAYER_LUNARG_parameter_validation` on Windows, Linux or MacOS is as follows: ``` > $ export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_parameter_validation @@ -666,7 +669,7 @@ Khronos approved WSI extensions are available and provide Windows System Integration support for various execution environments. It is important to understand that some WSI extensions are valid for all targets, but others are particular to a given execution environment (and loader). This desktop loader -(currently targeting Windows and Linux) only enables and directly exports those +(currently targeting Windows, Linux, and MacOS) only enables and directly exports those WSI extensions that are appropriate to the current environment. For the most part, the selection is done in the loader using compile-time preprocessor flags. All versions of the desktop loader currently expose at least the following WSI @@ -684,6 +687,7 @@ specific extensions: | Linux (Default) | VK_KHR_xcb_surface and VK_KHR_xlib_surface | | Linux (Wayland) | VK_KHR_wayland_surface | | Linux (Mir) | VK_KHR_mir_surface | +| MacOS (MoltenVK) | VK_MVK_macos_surface | **NOTE:** Wayland and Mir targets are not fully supported at this time. Wayland support is present, but should be considered Beta quality. Mir support is not @@ -708,7 +712,7 @@ With the ability to expand Vulkan so easily, extensions will be created that the loader knows nothing about. If the extension is a device extension, the loader will pass the unknown entry-point down the device call chain ending with the appropriate ICD entry-points. The same thing will happen, if the extension is -an instance extension which takes a physical device paramater as it's first +an instance extension which takes a physical device parameter as it's first component. However, for all other instance extensions the loader will fail to load it. @@ -743,7 +747,7 @@ For the above reasons, the loader will filter out the names of these unknown ins extensions when an application calls `vkEnumerateInstanceExtensionProperties`. Additionally, this behavior will cause the loader to throw an error during `vkCreateInstance` if you still attempt to use one of these extensions. The intent is -to protect applications so that they don't inadvertantly use functionality +to protect applications so that they don't inadvertently use functionality which could lead to a crash. On the other-hand, if you know you can safely use the extension, you may disable @@ -762,6 +766,7 @@ In this section we'll discuss how the loader interacts with layers, including: * [Android Layer Discovery](#android-layer-discovery) * [Windows Layer Discovery](#windows-layer-discovery) * [Linux Layer Discovery](#linux-layer-discovery) + * [MacOS Layer Discovery](#macos-layer-discovery) * [Layer Version Negotiation](#layer-version-negotiation) * [Layer Call Chains and Distributed Dispatch](#layer-call-chains-and-distributed-dispatch) * [Layer Unknown Physical Device Extensions](#layer-unknown-physical-device-extensions) @@ -796,7 +801,7 @@ layers can be categorized into two categories: * Explicit Layers The main difference between the two is that Implicit Layers are automatically -enabled, unless overriden, and Explicit Layers must be enabled. Remember, +enabled, unless overridden, and Explicit Layers must be enabled. Remember, Implicit Layers are not present on all Operating Systems (like Android). On any system, the loader looks in specific areas for information on the @@ -815,7 +820,7 @@ follow, especially with regards to interacting with the loader and other layers. ##### Layer Manifest File Usage -On Windows and Linux systems, JSON formatted manifest files are used to store +On Windows, Linux, and MacOS systems, JSON formatted manifest files are used to store layer information. In order to find system-installed layers, the Vulkan loader will read the JSON files to identify the names and attributes of layers and their extensions. The use of manifest files allows the loader to avoid loading @@ -890,7 +895,7 @@ which case the value will be interpreted as a list of paths to JSON manifest fil In general, applications should install layers into the `SOFTWARE\Khrosos\Vulkan` paths. The PnP registry locations are intended specifically for layers that are -distrubuted as part of a driver installation. An application installer should not +distributed as part of a driver installation. An application installer should not modify the device-specific registries, while a device driver should not modify the system wide registries. @@ -919,7 +924,7 @@ directories: $HOME/.local/share/vulkan/explicit_layer.d $HOME/.local/share/vulkan/implicit_layer.d -Of course, ther are some things you have to know about the above folders: +Of course, there are some things you have to know about the above folders: 1. The "/usr/local/*" directories can be configured to be other directories at build time. 2. $HOME is the current home directory of the application's user id; this path @@ -937,6 +942,33 @@ environment variables are only used for non-suid programs. See [Forcing Layer Source Folders](#forcing-layer-source-folders) for more information on this. +##### MacOS Layer Discovery + +On MacOS, the Vulkan loader will scan the files in the following directories: + + /Contents/Resources/vulkan/explicit_layer.d + /Contents/Resources/vulkan/implicit_layer.d + /etc/vulkan/explicit_layer.d + /etc/vulkan/implicit_layer.d + /usr/local/share/vulkan/explicit_layer.d + /usr/local/share/vulkan/implicit_layer.d + /usr/share/vulkan/explicit_layer.d + /usr/share/vulkan/implicit_layer.d + $HOME/.local/share/vulkan/explicit_layer.d + $HOME/.local/share/vulkan/implicit_layer.d + +1. <bundle> is the directory containing a bundled application. It is scanned first. +1. The "/usr/local/*" directories can be configured to be other directories at +build time. +1. $HOME is the current home directory of the application's user id; this path +will be ignored for suid programs. + +As on Windows, if VK\_LAYER\_PATH is defined, then the +loader will instead look at the paths defined by that variable instead of using +the information provided by these default paths. However, these +environment variables are only used for non-suid programs. See +[Forcing Layer Source Folders](#forcing-layer-source-folders) for more +information on this. #### Layer Version Negotiation @@ -969,11 +1001,11 @@ provided for this interface in include/vulkan/vk_layer.h: You'll notice the `VkNegotiateLayerInterface` structure is similar to other Vulkan structures. The "sType" field, in this case takes a new enum defined just for internal loader/layer interfacing use. The valid values for "sType" -could grow in the future, but right only havs the one value +could grow in the future, but right now only has the one value "LAYER_NEGOTIATE_INTERFACE_STRUCT". This function (`vkNegotiateLoaderLayerInterfaceVersion`) should be exported by -the layer so that using "GetProcAddress" on Windows or "dlsym" on Linux, should +the layer so that using "GetProcAddress" on Windows or "dlsym" on Linux or MacOS, should return a valid function pointer to it. Once the loader has grabbed a valid address to the layers function, the loader will create a variable of type `VkNegotiateLayerInterface` and initialize it in the following ways: @@ -1015,7 +1047,7 @@ If the layer supports the new interface and reports version 2 or greater, then the loader will use the “fpGetInstanceProcAddr” and “fpGetDeviceProcAddr” functions from the “VkNegotiateLayerInterface” structure. Prior to these changes, the loader would query each of those functions using "GetProcAddress" -on Windows or "dlsym" on Linux. +on Windows or "dlsym" on Linux or MacOS. #### Layer Call Chains and Distributed Dispatch @@ -1423,7 +1455,7 @@ a meta-layer's component layers, and report them as the meta-layer's properties to the application when queried. Restrictions to defining and using a meta-layer are: - 1. A Meta-layer Manifest file **must** be a properly formated that contains one + 1. A Meta-layer Manifest file **must** be a properly formatted that contains one or more component layers. 3. All component layers **must be** present on a system for the meta-layer to be used. @@ -1676,7 +1708,7 @@ dispatch pointer from the `VkDevice` object, which is the parent of the #### Layer Manifest File Format -On Windows and Linux (desktop), the loader uses manifest files to discover +On Windows, Linux and MacOS (desktop), the loader uses manifest files to discover layer libraries and layers. The desktop loader doesn't directly query the layer library except during chaining. This is to reduce the likelihood of loading a malicious layer into memory. Instead, details are read from the @@ -1779,7 +1811,7 @@ Here's an example of a meta-layer manifest file: | "name" | The string used to uniquely identify this layer to applications. | vkEnumerateInstanceLayerProperties | | "type" | This field indicates the type of layer. The values can be: GLOBAL, or INSTANCE | vkEnumerate*LayerProperties | | | **NOTES:** Prior to deprecation, the "type" node was used to indicate which layer chain(s) to activate the layer upon: instance, device, or both. Distinct instance and device layers are deprecated; there are now just layers. Allowable values for type (both before and after deprecation) are "INSTANCE", "GLOBAL" and, "DEVICE." "DEVICE" layers are skipped over by the loader as if they were not found. | | -| "library\_path" | The "library\_path" specifies either a filename, a relative pathname, or a full pathname to a layer shared library file. If "library\_path" specifies a relative pathname, it is relative to the path of the JSON manifest file (e.g. for cases when an application provides a layer that is in the same folder hierarchy as the rest of the application files). If "library\_path" specifies a filename, the library must live in the system's shared object search path. There are no rules about the name of the layer shared library files other than it should end with the appropriate suffix (".DLL" on Windows, and ".so" on Linux). **This field must not be present if "component_layers" is defined** | N/A | +| "library\_path" | The "library\_path" specifies either a filename, a relative pathname, or a full pathname to a layer shared library file. If "library\_path" specifies a relative pathname, it is relative to the path of the JSON manifest file (e.g. for cases when an application provides a layer that is in the same folder hierarchy as the rest of the application files). If "library\_path" specifies a filename, the library must live in the system's shared object search path. There are no rules about the name of the layer shared library files other than it should end with the appropriate suffix (".DLL" on Windows, ".so" on Linux, and ".dylib" on MacOS). **This field must not be present if "component_layers" is defined** | N/A | | "api\_version" | The major.minor.patch version number of the Vulkan API that the shared library file for the library was built against. For example: 1.0.33. | vkEnumerateInstanceLayerProperties | | "implementation_version" | The version of the layer implemented. If the layer itself has any major changes, this number should change so the loader and/or application can identify it properly. | vkEnumerateInstanceLayerProperties | | "description" | A high-level description of the layer and it's intended use. | vkEnumerateInstanceLayerProperties | @@ -1868,7 +1900,7 @@ the concept of [Layer Unknown Physical Device Extensions](#layer-unknown-physical-device- extensions) and the associated `vk_layerGetPhysicalDeviceProcAddr` function. Finally, it -changed the manifest file defition to 1.1.0. +changed the manifest file definition to 1.1.0. ##### Layer Library API Version 1 @@ -1939,7 +1971,8 @@ ICD to properly hand-shake. * [ICD Manifest File Usage](#icd-manifest-file-usage) * [ICD Discovery on Windows](#icd-discovery-on-windows) * [ICD Discovery on Linux](#icd-discovery-on-linux) - * [Using Pre-Production ICDs on Windows and Linux](#using-pre-production-icds-on-windows-and-linux) + * [ICD Discovery on MacOS](#icd-discovery-on-macos) + * [Using Pre-Production ICDs on Windows, Linux and MacOS](#using-pre-production-icds-on-windows-and-linux) * [ICD Discovery on Android](#icd-discovery-on-android) * [ICD Manifest File Format](#icd-manifest-file-format) * [ICD Manifest File Versions](#icd-manifest-file-versions) @@ -1949,7 +1982,7 @@ ICD to properly hand-shake. * [ICD Dispatchable Object Creation](#icd-dispatchable-object-creation) * [Handling KHR Surface Objects in WSI Extensions](#handling-khr-surface-objects-in-wsi-extensions) * [Loader and ICD Interface Negotiation](#loader-and-icd-interface-negotiation) - * [Windows and Linux ICD Negotiation](#windows-and-linux-icd-negotiation) + * [Windows, Linux, and MacOS ICD Negotiation](#windows-and-linux-icd-negotiation) * [Version Negotiation Between Loader and ICDs](#version-negotiation-between-loader-and-icds) * [Interfacing With Legacy ICDs or Loader](#interfacing-with-legacy-icds-or-loader) * [Loader Version 5 Interface Requirements](#loader-version-5-interface-requirements) @@ -1968,7 +2001,7 @@ responsible for discovering available Vulkan ICDs on the system. Given a list of available ICDs, the loader can enumerate all the physical devices available for an application and return this information to the application. The process in which the loader discovers the available Installable Client Drivers (ICDs) -on a system is platform dependent. Windows, Linux and Android ICD discovery +on a system is platform dependent. Windows, Linux, Android, and MacOS ICD discovery details are listed below. #### Overriding the Default ICD Usage @@ -2001,10 +2034,18 @@ export VK_ICD_FILENAMES=/home/user/dev/mesa/share/vulkan/icd.d/intel_icd.x86_64. This is an example which is using the `VK_ICD_FILENAMES` override on Linux to point to the Intel Mesa driver's ICD Manifest file. +##### On MacOS + +``` +export VK_ICD_FILENAMES=/home/user/MoltenVK/Package/Latest/MoltenVK/macOS/MoltenVK_icd.json +``` + +This is an example which is using the `VK_ICD_FILENAMES` override on MacOS to point +to an installation and build of the MoltenVK GitHub repository that contains the MoltenVK ICD. #### ICD Manifest File Usage -As with layers, on Windows and Linux systems, JSON formatted manifest files are +As with layers, on Windows, Linux and MacOS systems, JSON formatted manifest files are used to store ICD information. In order to find system-installed drivers, the Vulkan loader will read the JSON files to identify the names and attributes of each driver. One thing you will notice is that ICD Manifest files are much @@ -2122,6 +2163,38 @@ pathname of an ICD shared library (".so") file. See the [ICD Manifest File Format](#icd-manifest-file-format) section for more details. +#### ICD Discovery on MacOS + +In order to find installed ICDs, the Vulkan loader will scan the files +in the following directories: + +``` + /Contents/Resources/vulkan/icd.d + /etc/vulkan/icd.d + /usr/local/share/vulkan/icd.d + /usr/share/vulkan/icd.d + $HOME/.local/share/vulkan/icd.d +``` + +The "/usr/local/*" directories can be configured to be other directories at +build time. + +The typical usage of the directories is indicated in the table below. + +| Location | Details | +|-------------------|------------------------| +| <bundle>/Contents/Resources/vulkan/icd.d | Directory for ICDs that are bundled with the application (searched first) | +| "/etc/vulkan/icd.d" | Location of ICDs installed manually | +| "/usr/local/share/vulkan/icd.d" | Directory for locally built ICDs | +| "/usr/share/vulkan/icd.d" | Location of ICDs installed from packages | +| $HOME/.local/share/vulkan/icd.d | $HOME is the current home directory of the application's user id; this path will be ignored for suid programs | + +The Vulkan loader will open each manifest file found to obtain the name or +pathname of an ICD shared library (".dylib") file. + +See the [ICD Manifest File Format](#icd-manifest-file-format) section for more +details. + ##### Additional Settings For ICD Debugging If you are seeing issues which may be related to the ICD. A possible option to debug is to enable the @@ -2130,7 +2203,7 @@ there is a problem with an ICD missing symbols on your system, this will expose to fail on loading the ICD. It is recommended that you enable `LD_BIND_NOW` along with `VK_LOADER_DEBUG=warn` to expose any issues. -#### Using Pre-Production ICDs on Windows and Linux +#### Using Pre-Production ICDs on Windows, Linux and MacOS Independent Hardware Vendor (IHV) pre-production ICDs. In some cases, a pre-production ICD may be in an installable package. In other cases, a @@ -2144,13 +2217,13 @@ other words, only the ICDs listed in "VK\_ICD\_FILENAMES" will be used. The "VK\_ICD\_FILENAMES" environment variable is a list of ICD manifest files, containing the full path to the ICD JSON Manifest file. This -list is colon-separated on Linux, and semi-colon separated on Windows. +list is colon-separated on Linux and MacOS, and semi-colon separated on Windows. Typically, "VK\_ICD\_FILENAMES" will only contain a full pathname to one info file for a developer-built ICD. A separator (colon or semi-colon) is only used if more than one ICD is listed. -**NOTE:** On Linux, this environment variable will be ignored for suid programs. +**NOTE:** On Linux and MacOS, this environment variable will be ignored for suid programs. #### ICD Discovery on Android @@ -2183,7 +2256,7 @@ Here is an example ICD JSON Manifest file: |----------------|--------------------| | "file\_format\_version" | The JSON format major.minor.patch version number of this file. Currently supported version is 1.0.0. | | "ICD" | The identifier used to group all ICD information together. | -| "library_path" | The "library\_path" specifies either a filename, a relative pathname, or a full pathname to a layer shared library file. If "library\_path" specifies a relative pathname, it is relative to the path of the JSON manifest file. If "library\_path" specifies a filename, the library must live in the system's shared object search path. There are no rules about the name of the ICD shared library files other than it should end with the appropriate suffix (".DLL" on Windows, and ".so" on Linux). | N/A | +| "library_path" | The "library\_path" specifies either a filename, a relative pathname, or a full pathname to a layer shared library file. If "library\_path" specifies a relative pathname, it is relative to the path of the JSON manifest file. If "library\_path" specifies a filename, the library must live in the system's shared object search path. There are no rules about the name of the ICD shared library files other than it should end with the appropriate suffix (".DLL" on Windows, ".so" on Linux and "*.dylib" on MacOS). | N/A | | "api_version" | The major.minor.patch version number of the Vulkan API that the shared library files for the ICD was built against. For example: 1.0.33. | **NOTE:** If the same ICD shared library supports multiple, incompatible @@ -2373,9 +2446,10 @@ vkObj alloc_icd_obj() ### Handling KHR Surface Objects in WSI Extensions Normally, ICDs handle object creation and destruction for various Vulkan -objects. The WSI surface extensions for Linux and Windows +objects. The WSI surface extensions for Linux, Windows, and MacOS ("VK\_KHR\_win32\_surface", "VK\_KHR\_xcb\_surface", "VK\_KHR\_xlib\_surface", -"VK\_KHR\_mir\_surface", "VK\_KHR\_wayland\_surface", and "VK\_KHR\_surface") +"VK\_KHR\_mir\_surface", "VK\_KHR\_wayland\_surface", "VK\_MVK\_macos\_surface" +and "VK\_KHR\_surface") are handled differently. For these extensions, the `VkSurfaceKHR` object creation and destruction may be handled by either the loader, or an ICD. @@ -2390,6 +2464,7 @@ If the loader handles the management of the `VkSurfaceKHR` objects: * Xlib * Windows * Android + * MacOS (`vkCreateMacOSSurfaceMVK`) 2. The loader creates a `VkIcdSurfaceXXX` object for the corresponding `vkCreateXXXSurfaceKHR` call. * The `VkIcdSurfaceXXX` structures are defined in `include/vulkan/vk_icd.h`. @@ -2418,7 +2493,7 @@ associated with multiple ICDs. Therefore, when the loader receives the object. This object acts as a container for each ICD's version of the `VkSurfaceKHR` object. If an ICD does not support the creation of its own `VkSurfaceKHR` object, the loader's container stores a NULL for that ICD. On -the otherhand, if the ICD does support `VkSurfaceKHR` creation, the loader will +the other hand, if the ICD does support `VkSurfaceKHR` creation, the loader will make the appropriate `vkCreateXXXSurfaceKHR` call to the ICD, and store the returned pointer in it's container object. The loader then returns the `VkSurfaceIcdXXX` as a `VkSurfaceKHR` object back up the call chain. Finally, @@ -2434,10 +2509,10 @@ viewed as a pass through. That is, the loader generally doesn't modify the functions or their parameters, but simply calls the ICDs entry-point for that function. There are specific additional interface requirements an ICD needs to comply with that are not part of any requirements from the Vulkan specification. -These addtional requirements are versioned to allow flexibility in the future. +These additional requirements are versioned to allow flexibility in the future. -#### Windows and Linux ICD Negotiation +#### Windows, Linux and MacOS ICD Negotiation ##### Version Negotiation Between Loader and ICDs @@ -2604,7 +2679,7 @@ The Android loader uses the same protocol for initializing the dispatch table as described above. The only difference is that the Android loader queries layer and extension information directly from the respective libraries and does not use the json manifest files used -by the Windows and Linux loaders. +by the Windows, Linux and MacOS loaders. ## Table of Debug Environment Variables @@ -2617,7 +2692,7 @@ of discovery. | VK_ICD_FILENAMES | Force the loader to use the specific ICD JSON files. The value should contain a list of delimited full path listings to ICD JSON Manifest files. **NOTE:** If you fail to use the global path to a JSON file, you may encounter issues. | `export VK_ICD_FILENAMES=\intel.json:\amd.json`

`set VK_ICD_FILENAMES=\nvidia.json;\mesa.json` | | VK_INSTANCE_LAYERS | Force the loader to add the given layers to the list of Enabled layers normally passed into `vkCreateInstance`. These layers are added first, and the loader will remove any duplicate layers that appear in both this list as well as that passed into `ppEnabledLayerNames`. | `export VK_INSTANCE_LAYERS=:`

`set VK_INSTANCE_LAYERS=;` | | VK_LAYER_PATH | Override the loader's standard Layer library search folders and use the provided delimited folders to search for layer Manifest files. | `export VK_LAYER_PATH=:`

`set VK_LAYER_PATH=;` | -| VK_LOADER_DISABLE_INST_EXT_FILTER | Disable the filtering out of instance extensions that the loader doesn't know about. This will allow applications to enable instance extensions exposed by ICDs but that the loader has no support for. **NOTE:** This may cause the loader or applciation to crash. | `export VK_LOADER_DISABLE_INST_EXT_FILTER=1`

`set VK_LOADER_DISABLE_INST_EXT_FILTER=1` | +| VK_LOADER_DISABLE_INST_EXT_FILTER | Disable the filtering out of instance extensions that the loader doesn't know about. This will allow applications to enable instance extensions exposed by ICDs but that the loader has no support for. **NOTE:** This may cause the loader or application to crash. | `export VK_LOADER_DISABLE_INST_EXT_FILTER=1`

`set VK_LOADER_DISABLE_INST_EXT_FILTER=1` | | VK_LOADER_DEBUG | Enable loader debug messages. Options are:
- error (only errors)
- warn (warnings and errors)
- info (info, warning, and errors)
- debug (debug + all before)
-all (report out all messages) | `export VK_LOADER_DEBUG=all`

`set VK_LOADER_DEBUG=warn` | ## Glossary of Terms @@ -2625,11 +2700,11 @@ of discovery. | Field Name | Field Value | |:---:|--------------------| | Android Loader | The loader designed to work primarily for the Android OS. This is generated from a different code-base than the desktop loader. But, in all important aspects, should be functionally equivalent. | -| Desktop Loader | The loader designed to work on both Windows and Linux. This is generated from a different [code-base](#https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers) than the Android loader. But in all important aspects, should be functionally equivalent. | +| Desktop Loader | The loader designed to work on Windows, Linux and MacOS. This is generated from a different [code-base](#https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers) than the Android loader. But in all important aspects, should be functionally equivalent. | | Core Function | A function that is already part of the Vulkan core specification and not an extension. For example, vkCreateDevice(). | | Device Call Chain | The call chain of functions followed for device functions. This call chain for a device function is usually as follows: first the application calls into a loader trampoline, then the loader trampoline calls enabled layers, the final layer calls into the ICD specific to the device. See the [Dispatch Tables and Call Chains](#dispatch-tables-and-call-chains) section for more information | | Device Function | A Device function is any Vulkan function which takes a `VkDevice`, `VkQueue`, `VkCommandBuffer`, or any child of these, as its first parameter. Some Vulkan Device functions are: `vkQueueSubmit`, `vkBeginCommandBuffer`, `vkCreateEvent`. See the [Instance Versus Device](#instance-versus-device) section for more information. | -| Discovery | The process of the loader searching for ICD and Layer files to setup the internal list of Vulkan objects available. On Windows/Linux, the discovery process typically focuses on searching for Manifest files. While on Android, the process focuses on searching for library files. | +| Discovery | The process of the loader searching for ICD and Layer files to setup the internal list of Vulkan objects available. On Windows/Linux/MacOS, the discovery process typically focuses on searching for Manifest files. While on Android, the process focuses on searching for library files. | | Dispatch Table | An array of function pointers (including core and possibly extension functions) used to step to the next entity in a call chain. The entity could be the loader, a layer or an ICD. See [Dispatch Tables and Call Chains](#dispatch-tables-and-call-chains) for more information. | | Extension | A concept of Vulkan used to expand the core Vulkan functionality. Extensions may be IHV-specific, platform-specific, or more broadly available. You should always query if an extension exists, and enable it during `vkCreateInstance` (if it is an instance extension) or during `vkCreateDevice` (if it is a device extension). | | ICD | Acronym for Installable Client Driver. These are drivers that are provided by IHVs to interact with the hardware they provide. See [Installable Client Drivers](#installable-client-drivers) section for more information. diff --git a/loader/loader.c b/loader/loader.c index 9fc764d..766711c 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -32,7 +32,10 @@ #include #include #include - +#if defined(__APPLE__) +#include +#include +#endif #include #if defined(_WIN32) #include "dirent_on_windows.h" @@ -205,7 +208,7 @@ void *loader_device_heap_realloc(const struct loader_device *device, void *pMemo } // Environment variables -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) static inline char *loader_getenv(const char *name, const struct loader_instance *inst) { // No allocation of memory necessary for Linux, but we should at least touch @@ -215,13 +218,22 @@ static inline char *loader_getenv(const char *name, const struct loader_instance } static inline char *loader_secure_getenv(const char *name, const struct loader_instance *inst) { - // No allocation of memory necessary for Linux, but we should at least touch - // the inst pointer to get rid of compiler warnings. - (void)inst; - +#if defined(__APPLE__) + // Apple does not appear to have a secure getenv implementation. + // The main difference between secure getenv and getenv is that secure getenv + // returns NULL if the process is being run with elevated privileges by a normal user. + // The idea is to prevent the reading of malicious environment variables by a process + // that can do damage. + // This algorithm is derived from glibc code that sets an internal + // variable (__libc_enable_secure) if the process is running under setuid or setgid. + return geteuid() != getuid() || getegid() != getgid() ? NULL : loader_getenv(name, inst); +#else +// Linux #ifdef HAVE_SECURE_GETENV + (void)inst; return secure_getenv(name); #elif defined(HAVE___SECURE_GETENV) + (void)inst; return __secure_getenv(name); #else #pragma message( \ @@ -229,6 +241,7 @@ static inline char *loader_secure_getenv(const char *name, const struct loader_i " updating to a different libc.") return loader_getenv(name, inst); #endif +#endif } static inline void loader_free_getenv(char *val, const struct loader_instance *inst) { @@ -2976,7 +2989,6 @@ static VkResult loader_get_manifest_files(const struct loader_instance *inst, co override = override_getenv = loader_secure_getenv(env_override, inst); } } - #if !defined(_WIN32) if (relative_location == NULL) { #else @@ -3016,6 +3028,10 @@ static VkResult loader_get_manifest_files(const struct loader_instance *inst, co #if defined(EXTRASYSCONFDIR) loc_size += strlen(EXTRASYSCONFDIR) + rel_size + 1; #endif +#if defined(__APPLE__) + // For bundle path + loc_size += MAXPATHLEN; +#endif #else loc_size += strlen(location) + 1; #endif @@ -3033,6 +3049,23 @@ static VkResult loader_get_manifest_files(const struct loader_instance *inst, co const char *loc_read; size_t start, stop; +#if defined(__APPLE__) + // Add the bundle's Resources dir to the beginning of the search path. + // Looks for manifests in the bundle first, before any system directories. + CFBundleRef main_bundle = CFBundleGetMainBundle(); + if (NULL != main_bundle) { + CFURLRef ref = CFBundleCopyResourcesDirectoryURL(main_bundle); + if (NULL != ref) { + if (CFURLGetFileSystemRepresentation(ref, TRUE, (UInt8 *)loc_write, loc_size)) { + loc_write += strlen(loc_write); + memcpy(loc_write, relative_location, rel_size); + loc_write += rel_size; + *loc_write++ = PATH_SEPARATOR; + } + CFRelease(ref); + } + } +#endif loc_read = &xdgconfdirs[0]; start = 0; while (loc_read[start] != '\0') { diff --git a/loader/loader.h b/loader/loader.h index 58dfefe..852417d 100644 --- a/loader/loader.h +++ b/loader/loader.h @@ -297,6 +297,12 @@ struct loader_instance { #ifdef VK_USE_PLATFORM_ANDROID_KHR bool wsi_android_surface_enabled; #endif +#ifdef VK_USE_PLATFORM_MACOS_MVK + bool wsi_macos_surface_enabled; +#endif +#ifdef VK_USE_PLATFORM_IOS_MVK + bool wsi_ios_surface_enabled; +#endif bool wsi_display_enabled; }; diff --git a/loader/vk_loader_platform.h b/loader/vk_loader_platform.h index 2b30c71..4162560 100644 --- a/loader/vk_loader_platform.h +++ b/loader/vk_loader_platform.h @@ -31,7 +31,7 @@ #include "vulkan/vk_platform.h" #include "vulkan/vk_sdk_platform.h" -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) /* Linux-specific common code: */ // Headers: diff --git a/loader/wsi.c b/loader/wsi.c index 131ca51..5de3345 100644 --- a/loader/wsi.c +++ b/loader/wsi.c @@ -55,6 +55,12 @@ void wsi_create_instance(struct loader_instance *ptr_instance, const VkInstanceC #ifdef VK_USE_PLATFORM_ANDROID_KHR ptr_instance->wsi_android_surface_enabled = false; #endif // VK_USE_PLATFORM_ANDROID_KHR +#ifdef VK_USE_PLATFORM_MACOS_MVK + ptr_instance->wsi_macos_surface_enabled = false; +#endif // VK_USE_PLATFORM_MACOS_MVK +#ifdef VK_USE_PLATFORM_IOS_MVK + ptr_instance->wsi_ios_surface_enabled = false; +#endif // VK_USE_PLATFORM_IOS_MVK ptr_instance->wsi_display_enabled = false; @@ -99,6 +105,18 @@ void wsi_create_instance(struct loader_instance *ptr_instance, const VkInstanceC continue; } #endif // VK_USE_PLATFORM_ANDROID_KHR +#ifdef VK_USE_PLATFORM_MACOS_MVK + if (strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_MVK_MACOS_SURFACE_EXTENSION_NAME) == 0) { + ptr_instance->wsi_macos_surface_enabled = true; + continue; + } +#endif // VK_USE_PLATFORM_MACOS_MVK +#ifdef VK_USE_PLATFORM_IOS_MVK + if (strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_MVK_IOS_SURFACE_EXTENSION_NAME) == 0) { + ptr_instance->wsi_ios_surface_enabled = true; + continue; + } +#endif // VK_USE_PLATFORM_IOS_MVK if (strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_KHR_DISPLAY_EXTENSION_NAME) == 0) { ptr_instance->wsi_display_enabled = true; continue; @@ -378,8 +396,7 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR(VkDevice devic } VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, - const VkAllocationCallbacks *pAllocator, - VkSwapchainKHR *pSwapchain) { + const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain) { uint32_t icd_index = 0; struct loader_device *dev; struct loader_icd_term *icd_term = loader_get_icd_and_device(device, &dev, &icd_index); @@ -1066,7 +1083,6 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateAndroidSurfaceKHR(VkInstance ins } pIcdSurface->base.platform = VK_ICD_WSI_PLATFORM_ANDROID; - pIcdSurface->dpy = dpy; pIcdSurface->window = window; *pSurface = (VkSurfaceKHR)pIcdSurface; @@ -1076,6 +1092,129 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateAndroidSurfaceKHR(VkInstance ins #endif // VK_USE_PLATFORM_ANDROID_KHR +#ifdef VK_USE_PLATFORM_MACOS_MVK + +// Functions for the VK_MVK_macos_surface extension: + +// This is the trampoline entrypoint for CreateMacOSSurfaceMVK +LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateMacOSSurfaceMVK(VkInstance instance, + const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) { + const VkLayerInstanceDispatchTable *disp; + disp = loader_get_instance_layer_dispatch(instance); + VkResult res; + + res = disp->CreateMacOSSurfaceMVK(instance, pCreateInfo, pAllocator, pSurface); + return res; +} + +// This is the instance chain terminator function for CreateMacOSSurfaceKHR +VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateMacOSSurfaceMVK(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + VkResult vkRes = VK_SUCCESS; + VkIcdSurface *pIcdSurface = NULL; + uint32_t i = 0; + + // First, check to ensure the appropriate extension was enabled: + struct loader_instance *ptr_instance = loader_get_instance(instance); + if (!ptr_instance->wsi_macos_surface_enabled) { + loader_log(ptr_instance, VK_DEBUG_REPORT_ERROR_BIT_EXT, 0, + "VK_MVK_macos_surface extension not enabled. vkCreateMacOSSurfaceMVK not executed!\n"); + vkRes = VK_ERROR_EXTENSION_NOT_PRESENT; + goto out; + } + + // Next, if so, proceed with the implementation of this function: + pIcdSurface = AllocateIcdSurfaceStruct(ptr_instance, sizeof(pIcdSurface->macos_surf.base), sizeof(pIcdSurface->macos_surf)); + if (pIcdSurface == NULL) { + vkRes = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + + pIcdSurface->macos_surf.base.platform = VK_ICD_WSI_PLATFORM_MACOS; + pIcdSurface->macos_surf.pView = pCreateInfo->pView; + + // Loop through each ICD and determine if they need to create a surface + for (struct loader_icd_term *icd_term = ptr_instance->icd_terms; icd_term != NULL; icd_term = icd_term->next, i++) { + if (icd_term->scanned_icd->interface_version >= ICD_VER_SUPPORTS_ICD_SURFACE_KHR) { + if (NULL != icd_term->dispatch.CreateMacOSSurfaceMVK) { + vkRes = icd_term->dispatch.CreateMacOSSurfaceMVK(icd_term->instance, pCreateInfo, pAllocator, + &pIcdSurface->real_icd_surfaces[i]); + if (VK_SUCCESS != vkRes) { + goto out; + } + } + } + } + + *pSurface = (VkSurfaceKHR)pIcdSurface; + +out: + + if (VK_SUCCESS != vkRes && NULL != pIcdSurface) { + if (NULL != pIcdSurface->real_icd_surfaces) { + i = 0; + for (struct loader_icd_term *icd_term = ptr_instance->icd_terms; icd_term != NULL; icd_term = icd_term->next, i++) { + if ((VkSurfaceKHR)NULL != pIcdSurface->real_icd_surfaces[i] && NULL != icd_term->dispatch.DestroySurfaceKHR) { + icd_term->dispatch.DestroySurfaceKHR(icd_term->instance, pIcdSurface->real_icd_surfaces[i], pAllocator); + } + } + loader_instance_heap_free(ptr_instance, pIcdSurface->real_icd_surfaces); + } + loader_instance_heap_free(ptr_instance, pIcdSurface); + } + + return vkRes; +} + +#endif // VK_USE_PLATFORM_MACOS_MVK + +#ifdef VK_USE_PLATFORM_IOS_MVK + +// Functions for the VK_MVK_ios_surface extension: + +// This is the trampoline entrypoint for CreateIOSSurfaceMVK +LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkCreateIOSSurfaceMVK(VkInstance instance, + const VkIOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkSurfaceKHR *pSurface) { + const VkLayerInstanceDispatchTable *disp; + disp = loader_get_instance_layer_dispatch(instance); + VkResult res; + + res = disp->CreateIOSSurfaceMVK(instance, pCreateInfo, pAllocator, pSurface); + return res; +} + +// This is the instance chain terminator function for CreateIOSSurfaceKHR +VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateIOSSurfaceMVK(VkInstance instance, const VkIOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { + // First, check to ensure the appropriate extension was enabled: + struct loader_instance *ptr_instance = loader_get_instance(instance); + if (!ptr_instance->wsi_ios_surface_enabled) { + loader_log(ptr_instance, VK_DEBUG_REPORT_ERROR_BIT_EXT, 0, + "VK_MVK_ios_surface extension not enabled. vkCreateIOSSurfaceMVK not executed!\n"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + // Next, if so, proceed with the implementation of this function: + VkIcdSurfaceIOS *pIcdSurface = + loader_instance_heap_alloc(ptr_instance, sizeof(VkIcdSurfaceIOS), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (pIcdSurface == NULL) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + pIcdSurface->base.platform = VK_ICD_WSI_PLATFORM_IOS; + pIcdSurface->pView = pCreateInfo->pView; + + *pSurface = (VkSurfaceKHR)pIcdSurface; + + return VK_SUCCESS; +} + +#endif // VK_USE_PLATFORM_IOS_MVK + // Functions for the VK_KHR_display instance extension: LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, @@ -1502,10 +1641,26 @@ bool wsi_swapchain_instance_gpa(struct loader_instance *ptr_instance, const char // Functions for the VK_KHR_android_surface extension: if (!strcmp("vkCreateAndroidSurfaceKHR", name)) { - *addr = ptr_instance->wsi_xlib_surface_enabled ? (void *)vkCreateAndroidSurfaceKHR : NULL; + *addr = ptr_instance->wsi_android_surface_enabled ? (void *)vkCreateAndroidSurfaceKHR : NULL; return true; } #endif // VK_USE_PLATFORM_ANDROID_KHR +#ifdef VK_USE_PLATFORM_MACOS_MVK + + // Functions for the VK_MVK_macos_surface extension: + if (!strcmp("vkCreateMacOSSurfaceMVK", name)) { + *addr = ptr_instance->wsi_macos_surface_enabled ? (void *)vkCreateMacOSSurfaceMVK : NULL; + return true; + } +#endif // VK_USE_PLATFORM_MACOS_MVK +#ifdef VK_USE_PLATFORM_IOS_MVK + + // Functions for the VK_MVK_ios_surface extension: + if (!strcmp("vkCreateIOSSurfaceMVK", name)) { + *addr = ptr_instance->wsi_ios_surface_enabled ? (void *)vkCreateIOSSurfaceMVK : NULL; + return true; + } +#endif // VK_USE_PLATFORM_IOS_MVK // Functions for VK_KHR_display extension: if (!strcmp("vkGetPhysicalDeviceDisplayPropertiesKHR", name)) { diff --git a/loader/wsi.h b/loader/wsi.h index 519a7aa..0347963 100644 --- a/loader/wsi.h +++ b/loader/wsi.h @@ -42,6 +42,9 @@ typedef struct { #ifdef VK_USE_PLATFORM_XLIB_KHR VkIcdSurfaceXlib xlib_surf; #endif // VK_USE_PLATFORM_XLIB_KHR +#ifdef VK_USE_PLATFORM_MACOS_MVK + VkIcdSurfaceMacOS macos_surf; +#endif // VK_USE_PLATFORM_MACOS_MVK VkIcdSurfaceDisplay display_surf; }; uint32_t base_size; // Size of VkIcdSurfaceBase @@ -115,6 +118,14 @@ VKAPI_ATTR VkBool32 VKAPI_CALL terminator_GetPhysicalDeviceXlibPresentationSuppo uint32_t queueFamilyIndex, Display *dpy, VisualID visualID); #endif +#ifdef VK_USE_PLATFORM_MACOS_MVK +VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateMacOSSurfaceMVK(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface); +#endif +#ifdef VK_USE_PLATFORM_IOS_MVK +VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateIOSSurfaceMVK(VkInstance instance, const VkIOSSurfaceCreateInfoMVK *pCreateInfo, + const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface); +#endif VKAPI_ATTR VkResult VKAPI_CALL terminator_GetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPropertiesKHR *pProperties); @@ -142,4 +153,4 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_CreateSharedSwapchainsKHR(VkDevice dev const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchains); -#endif // WSI_H +#endif // WSI_H diff --git a/scripts/loader_extension_generator.py b/scripts/loader_extension_generator.py index 370112d..9c2e34d 100644 --- a/scripts/loader_extension_generator.py +++ b/scripts/loader_extension_generator.py @@ -32,6 +32,8 @@ WSI_EXT_NAMES = ['VK_KHR_surface', 'VK_KHR_mir_surface', 'VK_KHR_win32_surface', 'VK_KHR_android_surface', + 'VK_MVK_macos_surface', + 'VK_MVK_ios_surface', 'VK_KHR_swapchain', 'VK_KHR_display_swapchain'] diff --git a/update_external_sources.sh b/update_external_sources.sh index ae16cec..374d632 100755 --- a/update_external_sources.sh +++ b/update_external_sources.sh @@ -53,7 +53,32 @@ function build_glslang () { make install } +function create_moltenvk () { + rm -rf "${BASEDIR}"/MoltenVK + echo "Creating local MoltenVK repository (${BASEDIR}/MoltenVK)." + mkdir -p "${BASEDIR}"/MoltenVK + cd "${BASEDIR}"/MoltenVK + git clone --recurse-submodules https://github.com/KhronosGroup/MoltenVK.git "${BASEDIR}"/MoltenVK +} + +function update_moltenvk () { + echo "Updating ${BASEDIR}/MoltenVK" + cd "${BASEDIR}"/MoltenVK + git pull +} + +function build_moltenvk () { + echo "Building ${BASEDIR}/MoltenVK" + cd "${BASEDIR}"/MoltenVK/External + ./makeAll + cd "${BASEDIR}"/MoltenVK + xcodebuild -project MoltenVKPackaging.xcodeproj \ + GCC_PREPROCESSOR_DEFINITIONS='$GCC_PREPROCESSOR_DEFINITIONS MVK_LOGGING_ENABLED=0' \ + -scheme "MoltenVK (Release)" build +} + INCLUDE_GLSLANG=false +INCLUDE_MOLTENVK=false NO_SYNC=false NO_BUILD=false USE_IMPLICIT_COMPONENT_LIST=true @@ -70,6 +95,14 @@ do USE_IMPLICIT_COMPONENT_LIST=false echo "Building glslang ($option)" ;; + # options to specify build of moltenvk components + -m|--moltenvk) + if [[ $(uname) == "Darwin" ]]; then + INCLUDE_MOLTENVK=true + USE_IMPLICIT_COMPONENT_LIST=false + echo "Building MoltenVK ($option)" + fi + ;; # options to specify build of spirv-tools components -s|--spirv-tools) echo "($option) is deprecated and is no longer necessary" @@ -89,6 +122,9 @@ do echo "Usage: update_external_sources.sh [options]" echo " Available options:" echo " -g | --glslang # enable glslang component" + if [[ $(uname) == "Darwin" ]]; then + echo " -m | --moltenvk # enable moltenvk component" + fi echo " --no-sync # skip sync from git" echo " --no-build # skip build" echo " If any component enables are provided, only those components are enabled." @@ -104,6 +140,10 @@ done if [ ${USE_IMPLICIT_COMPONENT_LIST} == "true" ]; then echo "Building glslang" INCLUDE_GLSLANG=true + if [[ $(uname) == "Darwin" ]]; then + echo "Building MoltenVK" + INCLUDE_MOLTENVK=true + fi fi if [ ${INCLUDE_GLSLANG} == "true" ]; then @@ -117,3 +157,17 @@ if [ ${INCLUDE_GLSLANG} == "true" ]; then build_glslang fi fi + +if [ ${INCLUDE_MOLTENVK} == "true" ]; then + if [ ${NO_SYNC} == "false" ]; then + if [ ! -d "${BASEDIR}/MoltenVK" -o ! -d "${BASEDIR}/MoltenVK/.git" ]; then + create_moltenvk + fi + update_moltenvk + fi + if [ ${NO_BUILD} == "false" ]; then + echo "Building moltenvk" + build_moltenvk + fi +fi + -- 2.7.4