aboutsummaryrefslogtreecommitdiffstats
path: root/include/pugl
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2020-10-26 20:48:49 +0100
committerDavid Robillard <d@drobilla.net>2020-10-26 20:48:49 +0100
commit1bcc437a37d80e3ad0e6f1dbd5ba858f933a0846 (patch)
tree52b03df6250be1c5af214ed0b1b043301b9dd0fe /include/pugl
parent472d42f2678cfb60eef9ea181976c4389d4661df (diff)
downloadpugl-1bcc437a37d80e3ad0e6f1dbd5ba858f933a0846.tar.gz
pugl-1bcc437a37d80e3ad0e6f1dbd5ba858f933a0846.tar.bz2
pugl-1bcc437a37d80e3ad0e6f1dbd5ba858f933a0846.zip
Add Vulkan backends
Diffstat (limited to 'include/pugl')
-rw-r--r--include/pugl/detail/mac_vulkan.m216
-rw-r--r--include/pugl/detail/win_vulkan.c130
-rw-r--r--include/pugl/detail/x11_vulkan.c134
-rw-r--r--include/pugl/pugl_vulkan.h148
-rw-r--r--include/pugl/pugl_vulkan.hpp170
5 files changed, 798 insertions, 0 deletions
diff --git a/include/pugl/detail/mac_vulkan.m b/include/pugl/detail/mac_vulkan.m
new file mode 100644
index 0000000..813e0ae
--- /dev/null
+++ b/include/pugl/detail/mac_vulkan.m
@@ -0,0 +1,216 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file mac_vulkan.m Vulkan graphics backend for MacOS.
+*/
+
+#define VK_NO_PROTOTYPES 1
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/detail/stub.h"
+#include "pugl/detail/types.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+#include "pugl/pugl_vulkan.h"
+
+#include <vulkan/vulkan_core.h>
+#include <vulkan/vulkan_macos.h>
+
+#import <Cocoa/Cocoa.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+#include <dlfcn.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+@interface PuglVulkanView : NSView<CALayerDelegate>
+
+@end
+
+@implementation PuglVulkanView
+{
+@public
+ PuglView* puglview;
+}
+
+- (id)initWithFrame:(NSRect)frame
+{
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ self.wantsLayer = YES;
+ self.layerContentsRedrawPolicy =
+ NSViewLayerContentsRedrawOnSetNeedsDisplay;
+ }
+
+ return self;
+}
+
+- (CALayer*)makeBackingLayer
+{
+ CAMetalLayer* layer = [CAMetalLayer layer];
+ [layer setDelegate:self];
+ return layer;
+}
+
+- (void)setFrameSize:(NSSize)newSize
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [super setFrameSize:newSize];
+ [wrapper setReshaped];
+
+ self.layer.frame = self.bounds;
+}
+
+- (void)displayLayer:(CALayer*)layer
+{
+ (void)layer;
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+ [wrapper dispatchExpose:[self bounds]];
+}
+
+@end
+
+static PuglStatus
+puglMacVulkanCreate(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+ PuglVulkanView* drawView = [PuglVulkanView alloc];
+ const NSRect rect = NSMakeRect(0, 0, view->frame.width, view->frame.height);
+
+ drawView->puglview = view;
+ [drawView initWithFrame:rect];
+ if (view->hints[PUGL_RESIZABLE]) {
+ [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ } else {
+ [drawView setAutoresizingMask:NSViewNotSizable];
+ }
+
+ impl->drawView = drawView;
+ return PUGL_SUCCESS;
+}
+
+static PuglStatus
+puglMacVulkanDestroy(PuglView* view)
+{
+ PuglVulkanView* const drawView = (PuglVulkanView*)view->impl->drawView;
+
+ [drawView removeFromSuperview];
+ [drawView release];
+
+ view->impl->drawView = nil;
+ return PUGL_SUCCESS;
+}
+
+struct PuglVulkanLoaderImpl {
+ void* libvulkan;
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+ PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
+};
+
+PuglVulkanLoader*
+puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
+{
+ PuglVulkanLoader* loader = (PuglVulkanLoader*)
+ calloc(1, sizeof(PuglVulkanLoader));
+ if (!loader) {
+ return NULL;
+ }
+
+ if (!(loader->libvulkan = dlopen("libvulkan.dylib", RTLD_LAZY))) {
+ free(loader);
+ return NULL;
+ }
+
+ loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)
+ dlsym(loader->libvulkan, "vkGetInstanceProcAddr");
+
+ loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)
+ dlsym(loader->libvulkan, "vkGetDeviceProcAddr");
+
+ return loader;
+}
+
+void
+puglFreeVulkanLoader(PuglVulkanLoader* loader)
+{
+ if (loader) {
+ dlclose(loader->libvulkan);
+ free(loader);
+ }
+}
+
+PFN_vkGetInstanceProcAddr
+puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetInstanceProcAddr;
+}
+
+PFN_vkGetDeviceProcAddr
+puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetDeviceProcAddr;
+}
+
+const PuglBackend*
+puglVulkanBackend(void)
+{
+ static const PuglBackend backend = {puglStubConfigure,
+ puglMacVulkanCreate,
+ puglMacVulkanDestroy,
+ puglStubEnter,
+ puglStubLeave,
+ puglStubGetContext};
+
+ return &backend;
+}
+
+const char* const*
+puglGetInstanceExtensions(uint32_t* const count)
+{
+ static const char* const extensions[] = {"VK_KHR_surface",
+ "VK_MVK_macos_surface"};
+
+ *count = 2;
+ return extensions;
+}
+
+VkResult
+puglCreateSurface(const PuglVulkanLoader* const loader,
+ PuglView* const view,
+ VkInstance instance,
+ const VkAllocationCallbacks* const allocator,
+ VkSurfaceKHR* const surface)
+{
+ PuglInternals* const impl = view->impl;
+
+ PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK =
+ (PFN_vkCreateMacOSSurfaceMVK)puglGetInstanceProcAddrFunc(
+ loader)(instance, "vkCreateMacOSSurfaceMVK");
+
+ const VkMacOSSurfaceCreateInfoMVK info = {
+ VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK,
+ NULL,
+ 0,
+ impl->drawView,
+ };
+
+ return vkCreateMacOSSurfaceMVK(instance, &info, allocator, surface);
+}
diff --git a/include/pugl/detail/win_vulkan.c b/include/pugl/detail/win_vulkan.c
new file mode 100644
index 0000000..cfe3613
--- /dev/null
+++ b/include/pugl/detail/win_vulkan.c
@@ -0,0 +1,130 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win_vulkan.c Vulkan graphics backend for Windows.
+*/
+
+#define VK_NO_PROTOTYPES 1
+
+#include "pugl/detail/stub.h"
+#include "pugl/detail/types.h"
+#include "pugl/detail/win.h"
+#include "pugl/pugl_stub.h"
+#include "pugl/pugl_vulkan.h"
+
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_win32.h>
+
+#include <stdlib.h>
+
+struct PuglVulkanLoaderImpl
+{
+ HMODULE libvulkan;
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+ PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
+};
+
+PuglVulkanLoader*
+puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
+{
+ PuglVulkanLoader* loader =
+ (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader));
+ if (!loader) {
+ return NULL;
+ }
+
+ if (!(loader->libvulkan = LoadLibrary("vulkan-1.dll"))) {
+ free(loader);
+ return NULL;
+ }
+
+ loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)GetProcAddress(
+ loader->libvulkan, "vkGetInstanceProcAddr");
+
+ loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)GetProcAddress(
+ loader->libvulkan, "vkGetDeviceProcAddr");
+
+ return loader;
+}
+
+void
+puglFreeVulkanLoader(PuglVulkanLoader* loader)
+{
+ if (loader) {
+ FreeLibrary(loader->libvulkan);
+ free(loader);
+ }
+}
+
+PFN_vkGetInstanceProcAddr
+puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetInstanceProcAddr;
+}
+
+PFN_vkGetDeviceProcAddr
+puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetDeviceProcAddr;
+}
+
+const PuglBackend*
+puglVulkanBackend()
+{
+ static const PuglBackend backend = {puglWinStubConfigure,
+ puglStubCreate,
+ puglStubDestroy,
+ puglWinStubEnter,
+ puglWinStubLeave,
+ puglStubGetContext};
+
+ return &backend;
+}
+
+const char* const*
+puglGetInstanceExtensions(uint32_t* const count)
+{
+ static const char* const extensions[] = {"VK_KHR_surface",
+ "VK_KHR_win32_surface"};
+
+ *count = 2;
+ return extensions;
+}
+
+VkResult
+puglCreateSurface(const PuglVulkanLoader* const loader,
+ PuglView* const view,
+ VkInstance instance,
+ const VkAllocationCallbacks* const pAllocator,
+ VkSurfaceKHR* const pSurface)
+{
+ PuglInternals* const impl = view->impl;
+
+ PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR =
+ (PFN_vkCreateWin32SurfaceKHR)puglGetInstanceProcAddrFunc(loader)(
+ instance, "vkCreateWin32SurfaceKHR");
+
+ const VkWin32SurfaceCreateInfoKHR createInfo = {
+ VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+ NULL,
+ 0,
+ GetModuleHandle(NULL),
+ impl->hwnd,
+ };
+
+ return vkCreateWin32SurfaceKHR(instance, &createInfo, pAllocator, pSurface);
+}
diff --git a/include/pugl/detail/x11_vulkan.c b/include/pugl/detail/x11_vulkan.c
new file mode 100644
index 0000000..0bd1532
--- /dev/null
+++ b/include/pugl/detail/x11_vulkan.c
@@ -0,0 +1,134 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file x11_vulkan.c Vulkan graphics backend for X11.
+*/
+
+#define VK_NO_PROTOTYPES 1
+
+#include "pugl/detail/stub.h"
+#include "pugl/detail/types.h"
+#include "pugl/detail/x11.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_vulkan.h"
+
+#include <vulkan/vulkan_core.h>
+#include <vulkan/vulkan_xlib.h>
+
+#include <dlfcn.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+struct PuglVulkanLoaderImpl
+{
+ void* libvulkan;
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+ PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
+};
+
+PuglVulkanLoader*
+puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world))
+{
+ PuglVulkanLoader* loader =
+ (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader));
+ if (!loader) {
+ return NULL;
+ }
+
+ if (!(loader->libvulkan = dlopen("libvulkan.so", RTLD_LAZY))) {
+ free(loader);
+ return NULL;
+ }
+
+ loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(
+ loader->libvulkan, "vkGetInstanceProcAddr");
+
+ loader->vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)dlsym(
+ loader->libvulkan, "vkGetDeviceProcAddr");
+
+ return loader;
+}
+
+void
+puglFreeVulkanLoader(PuglVulkanLoader* loader)
+{
+ if (loader) {
+ dlclose(loader->libvulkan);
+ free(loader);
+ }
+}
+
+PFN_vkGetInstanceProcAddr
+puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetInstanceProcAddr;
+}
+
+PFN_vkGetDeviceProcAddr
+puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader)
+{
+ return loader->vkGetDeviceProcAddr;
+}
+
+const PuglBackend*
+puglVulkanBackend(void)
+{
+ static const PuglBackend backend = {puglX11StubConfigure,
+ puglStubCreate,
+ puglStubDestroy,
+ puglStubEnter,
+ puglStubLeave,
+ puglStubGetContext};
+
+ return &backend;
+}
+
+const char* const*
+puglGetInstanceExtensions(uint32_t* const count)
+{
+ static const char* const extensions[] = {"VK_KHR_surface",
+ "VK_KHR_xlib_surface"};
+
+ *count = 2;
+ return extensions;
+}
+
+VkResult
+puglCreateSurface(const PuglVulkanLoader* const loader,
+ PuglView* const view,
+ VkInstance instance,
+ const VkAllocationCallbacks* const allocator,
+ VkSurfaceKHR* const surface)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWorldInternals* world_impl = view->world->impl;
+
+ PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR =
+ (PFN_vkCreateXlibSurfaceKHR)puglGetInstanceProcAddrFunc(loader)(
+ instance, "vkCreateXlibSurfaceKHR");
+
+ const VkXlibSurfaceCreateInfoKHR info = {
+ VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
+ NULL,
+ 0,
+ world_impl->display,
+ impl->win,
+ };
+
+ return vkCreateXlibSurfaceKHR(instance, &info, allocator, surface);
+}
diff --git a/include/pugl/pugl_vulkan.h b/include/pugl/pugl_vulkan.h
new file mode 100644
index 0000000..451c28f
--- /dev/null
+++ b/include/pugl/pugl_vulkan.h
@@ -0,0 +1,148 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_vulkan.h Vulkan-specific API.
+
+ Note that this header includes Vulkan headers, so if you are writing a
+ program or plugin that dynamically loads vulkan, you should first define
+ `VK_NO_PROTOTYPES` before including it.
+*/
+
+#ifndef PUGL_PUGL_VULKAN_H
+#define PUGL_PUGL_VULKAN_H
+
+#include "pugl/pugl.h"
+
+#include <vulkan/vulkan.h>
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup vulkan Vulkan
+ Vulkan graphics support.
+
+ Vulkan support differs from OpenGL because almost all most configuration is
+ done using the Vulkan API itself, rather than by setting view hints to
+ configure the context. Pugl only provides a minimal loader for loading the
+ Vulkan library, and a portable function to create a Vulkan surface for a
+ view, which hides the platform-specific implementation details.
+
+ @ingroup pugl_c
+ @{
+*/
+
+/**
+ Dynamic Vulkan loader.
+
+ This can be used to dynamically load the Vulkan library. Applications or
+ plugins should not link against the Vulkan library, but instead use this at
+ runtime. This ensures that things will work on as many systems as possible,
+ and allows errors to be handled gracefully.
+
+ This is not a "loader" in the sense of loading all the required Vulkan
+ functions (which is the application's responsibility), but just a minimal
+ implementation to portably load the Vulkan library and get the two functions
+ that are used to load everything else.
+
+ Note that this owns the loaded Vulkan library, so it must outlive all use of
+ the Vulkan API.
+
+ @see https://www.khronos.org/registry/vulkan/specs/1.0/html/chap4.html
+*/
+typedef struct PuglVulkanLoaderImpl PuglVulkanLoader;
+
+/**
+ Create a new dynamic loader for Vulkan functions.
+
+ This dynamically loads the Vulkan library and gets the load functions from it.
+
+ @return A new Vulkan loader, or null on failure.
+*/
+PUGL_API PuglVulkanLoader*
+puglNewVulkanLoader(PuglWorld* world);
+
+/**
+ Free a loader created with puglNewVulkanLoader().
+
+ Note that this closes the Vulkan library, so no Vulkan objects or API may be
+ used after this is called.
+*/
+PUGL_API void
+puglFreeVulkanLoader(PuglVulkanLoader* loader);
+
+/**
+ Return the `vkGetInstanceProcAddr` function.
+
+ @return Null if the Vulkan library does not contain this function (which is
+ unlikely and indicates a broken system).
+*/
+PUGL_API PFN_vkGetInstanceProcAddr
+puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader);
+
+/**
+ Return the `vkGetDeviceProcAddr` function.
+
+ @return Null if the Vulkan library does not contain this function (which is
+ unlikely and indicates a broken system).
+*/
+PUGL_API PFN_vkGetDeviceProcAddr
+puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader);
+
+/**
+ Return the Vulkan instance extensions required to draw to a PuglView.
+
+ This simply returns static strings, it does not access Vulkan or the window
+ system. The returned array always contains at least "VK_KHR_surface".
+
+ @param[out] count The number of extensions in the returned array.
+ @return An array of extension name strings.
+*/
+PUGL_API const char* const*
+puglGetInstanceExtensions(uint32_t* count);
+
+/**
+ Create a Vulkan surface for a Pugl view.
+
+ @param loader The loader for Vulkan functions.
+ @param view The view the surface is to be displayed on.
+ @param instance The Vulkan instance.
+ @param allocator Vulkan allocation callbacks, may be NULL.
+ @param[out] surface Pointed to a newly created Vulkan surface.
+ @return `VK_SUCCESS` on success, or a Vulkan error code.
+*/
+PUGL_API VkResult
+puglCreateSurface(const PuglVulkanLoader* loader,
+ PuglView* view,
+ VkInstance instance,
+ const VkAllocationCallbacks* allocator,
+ VkSurfaceKHR* surface);
+
+/**
+ Vulkan graphics backend.
+
+ Pass the returned value to puglSetBackend() to draw to a view with Vulkan.
+*/
+PUGL_API PUGL_CONST_FUNC const PuglBackend*
+puglVulkanBackend(void);
+
+/**
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif // PUGL_PUGL_VULKAN_H
diff --git a/include/pugl/pugl_vulkan.hpp b/include/pugl/pugl_vulkan.hpp
new file mode 100644
index 0000000..9241aba
--- /dev/null
+++ b/include/pugl/pugl_vulkan.hpp
@@ -0,0 +1,170 @@
+/*
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_vulkan.hpp Vulkan-specific C++ API.
+
+ Note that this header includes Vulkan headers, so if you are writing a
+ program or plugin that dynamically loads vulkan, you should first define
+ `VK_NO_PROTOTYPES` before including it.
+*/
+
+#ifndef PUGL_PUGL_VULKAN_HPP
+#define PUGL_PUGL_VULKAN_HPP
+
+#include "pugl/pugl.h"
+#include "pugl/pugl.hpp"
+#include "pugl/pugl_vulkan.h"
+
+#include <vulkan/vulkan_core.h>
+
+#include <cstdint>
+
+namespace pugl {
+
+/**
+ @defgroup vulkanxx Vulkan
+ Vulkan graphics support.
+
+ Note that the Pugl C++ wrapper does not use vulkan.hpp because it is a
+ heavyweight dependency which not everyone uses, and its design is not very
+ friendly to dynamic loading in plugins anyway. However, if you do use
+ vulkan.hpp smart handles, it is relatively straightforward to wrap the
+ result of createSurface() manually.
+
+ @ingroup pugl_cxx
+ @{
+*/
+
+/// @copydoc PuglVulkanLoader
+class PUGL_API VulkanLoader final
+ : public detail::Wrapper<PuglVulkanLoader, puglFreeVulkanLoader>
+{
+public:
+ /**
+ Create a new dynamic loader for Vulkan functions.
+
+ This dynamically loads the Vulkan library and gets the load functions
+ from it.
+
+ Note that this constructor does not throw exceptions, though failure is
+ possible. To check if the Vulkan library failed to load, test this
+ loader, which is explicitly convertible to `bool`. It is safe to use a
+ failed loader, but the accessors will always return null.
+ */
+ explicit VulkanLoader(World& world) noexcept
+ : Wrapper{puglNewVulkanLoader(world.cobj())}
+ {}
+
+ /**
+ Return the `vkGetInstanceProcAddr` function.
+
+ @return Null if the Vulkan library failed to load, or does not contain
+ this function (which is unlikely and indicates a broken system).
+ */
+ PFN_vkGetInstanceProcAddr getInstanceProcAddrFunc() const noexcept
+ {
+ return cobj() ? puglGetInstanceProcAddrFunc(cobj()) : nullptr;
+ }
+
+ /**
+ Return the `vkGetDeviceProcAddr` function.
+
+ @return Null if the Vulkan library failed to load, or does not contain
+ this function (which is unlikely and indicates a broken system).
+ */
+ PFN_vkGetDeviceProcAddr getDeviceProcAddrFunc() const noexcept
+ {
+ return cobj() ? puglGetDeviceProcAddrFunc(cobj()) : nullptr;
+ }
+
+ /// Return true if this loader is valid to use
+ explicit operator bool() const noexcept { return cobj(); }
+};
+
+/**
+ A simple wrapper for an array of static C strings.
+
+ This provides a minimal API that supports iteration, like `std::vector`, but
+ avoids allocation, exceptions, and a dependency on the C++ standard library.
+*/
+class PUGL_API StaticStringArray final
+{
+public:
+ using value_type = const char*;
+ using const_iterator = const char* const*;
+ using size_type = uint32_t;
+
+ StaticStringArray(const char* const* strings, const uint32_t size) noexcept
+ : _strings{strings}
+ , _size{size}
+ {}
+
+ const char* const* begin() const noexcept { return _strings; }
+ const char* const* end() const noexcept { return _strings + _size; }
+ const char* const* data() const noexcept { return _strings; }
+ uint32_t size() const noexcept { return _size; }
+
+private:
+ const char* const* _strings;
+ uint32_t _size;
+};
+
+/**
+ Return the Vulkan instance extensions required to draw to a PuglView.
+
+ If successful, the returned array always contains "VK_KHR_surface", along
+ with whatever other platform-specific extensions are required.
+
+ @return An array of extension name strings.
+*/
+inline StaticStringArray
+getInstanceExtensions() noexcept
+{
+ uint32_t count = 0;
+ const char* const* const extensions = puglGetInstanceExtensions(&count);
+
+ return StaticStringArray{extensions, count};
+}
+
+/// @copydoc puglCreateSurface
+inline VkResult
+createSurface(const VulkanLoader& loader,
+ View& view,
+ VkInstance instance,
+ const VkAllocationCallbacks* const allocator,
+ VkSurfaceKHR* const surface) noexcept
+{
+ const VkResult r = puglCreateSurface(
+ loader.cobj(), view.cobj(), instance, allocator, surface);
+
+ return (!r && !surface) ? VK_ERROR_INITIALIZATION_FAILED : r;
+}
+
+/// @copydoc puglVulkanBackend
+static inline const PuglBackend*
+vulkanBackend() noexcept
+{
+ return puglVulkanBackend();
+}
+
+/**
+ @}
+*/
+
+} // namespace pugl
+
+#endif // PUGL_PUGL_VULKAN_HPP