Jelajahi Sumber

started sdl/vulkan support (init + resize + clear ok)

Nicolas Cannasse 4 tahun lalu
induk
melakukan
d15f466b59
6 mengubah file dengan 454 tambahan dan 27 penghapusan
  1. 24 3
      libs/sdl/sdl.c
  2. 19 18
      libs/sdl/sdl.vcxproj
  3. 1 0
      libs/sdl/sdl.vcxproj.filters
  4. 27 0
      libs/sdl/sdl/Vulkan.hx
  5. 36 6
      libs/sdl/sdl/Window.hx
  6. 347 0
      libs/sdl/vulkan.c

+ 24 - 3
libs/sdl/sdl.c

@@ -5,7 +5,9 @@
 
 #if defined(_WIN32) || defined(__ANDROID__) || defined(HL_IOS) || defined(HL_TVOS)
 #	include <SDL.h>
+#	include <SDL_vulkan.h>
 #	include <SDL_syswm.h>
+#	define ALLOW_VULKAN
 #elif defined(HL_MAC)
 #	include <SDL.h>
 #else
@@ -425,12 +427,14 @@ DEFINE_PRIM(_BOOL, hint_value, _BYTES _BYTES);
 HL_PRIM SDL_Window *HL_NAME(win_create_ex)(int x, int y, int width, int height, int sdlFlags) {
 	SDL_Window *w;
 	// force window to match device resolution on mobile
+	if( !(sdlFlags & SDL_WINDOW_VULKAN) )
+		sdlFlags |= SDL_WINDOW_OPENGL;
 #ifdef	HL_MOBILE
 	SDL_DisplayMode displayMode;
 	SDL_GetDesktopDisplayMode(0, &displayMode);
-	w = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | sdlFlags);
+	w = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_BORDERLESS | sdlFlags);
 #else
-	w = SDL_CreateWindow("", x, y, width, height, SDL_WINDOW_OPENGL | sdlFlags);
+	w = SDL_CreateWindow("", x, y, width, height, sdlFlags);
 #endif
 #	ifdef HL_WIN
 	// force window to show even if the debugger force process windows to be hidden
@@ -450,6 +454,22 @@ HL_PRIM SDL_GLContext HL_NAME(win_get_glcontext)(SDL_Window *win) {
 	return SDL_GL_CreateContext(win);
 }
 
+#ifdef ALLOW_VULKAN
+extern VkInstance vk_get_instance(void);
+extern void *vk_init_context( VkSurfaceKHR surface );
+HL_PRIM void *HL_NAME(win_get_vulkan)( SDL_Window *win ) {
+	VkInstance inst = vk_get_instance();
+	VkSurfaceKHR surface = NULL;
+	if( !SDL_Vulkan_CreateSurface(win, inst, &surface) )
+		return NULL;
+	return vk_init_context(surface);
+}
+#else
+HL_PRIM void *HL_NAME(win_get_vulkan)( SDL_Window *win ) {
+	return NULL;
+}
+#endif
+
 HL_PRIM bool HL_NAME(win_set_fullscreen)(SDL_Window *win, int mode) {
 #	ifdef HL_WIN
 	wsave_pos *save = SDL_GetWindowData(win,"save");
@@ -589,7 +609,7 @@ HL_PRIM void HL_NAME(win_render_to)(SDL_Window *win, SDL_GLContext gl) {
 
 HL_PRIM void HL_NAME(win_destroy)(SDL_Window *win, SDL_GLContext gl) {
 	SDL_DestroyWindow(win);
-	SDL_GL_DeleteContext(gl);
+	if( gl ) SDL_GL_DeleteContext(gl);
 }
 
 #define TWIN _ABSTRACT(sdl_window)
@@ -597,6 +617,7 @@ HL_PRIM void HL_NAME(win_destroy)(SDL_Window *win, SDL_GLContext gl) {
 DEFINE_PRIM(TWIN, win_create_ex, _I32 _I32 _I32 _I32 _I32);
 DEFINE_PRIM(TWIN, win_create, _I32 _I32);
 DEFINE_PRIM(TGL, win_get_glcontext, TWIN);
+DEFINE_PRIM(_ABSTRACT(vk_context), win_get_vulkan, TWIN);
 DEFINE_PRIM(_BOOL, win_set_fullscreen, TWIN _I32);
 DEFINE_PRIM(_VOID, win_resize, TWIN _I32);
 DEFINE_PRIM(_VOID, win_set_title, TWIN _BYTES);

+ 19 - 18
libs/sdl/sdl.vcxproj

@@ -100,38 +100,38 @@
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LinkIncremental>true</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86;../../include/vulkan/Lib32</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <LinkIncremental>true</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64;../../include/vulkan/Lib</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <LinkIncremental>false</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86;../../include/vulkan/Lib32</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|Win32'">
     <LinkIncremental>false</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;../../$(Configuration);../../include/sdl/lib/x86;../../include/vulkan/Lib32</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64;../../include/vulkan/Lib</LibraryPath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|x64'">
     <LinkIncremental>false</LinkIncremental>
     <TargetExt>.hdll</TargetExt>
-    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl</IncludePath>
-    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64</LibraryPath>
+    <IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);../../include/sdl/include;../../src;../../include/gl;../../include/vulkan/Include</IncludePath>
+    <LibraryPath>$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;../../x64/$(Configuration);../../include/sdl/lib/x64;../../include/vulkan/Lib</LibraryPath>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
@@ -145,7 +145,7 @@
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -160,7 +160,7 @@
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -180,7 +180,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|Win32'">
@@ -200,7 +200,7 @@
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
       <OutputFile>../../$(Configuration)/$(TargetName).hdll</OutputFile>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -218,7 +218,7 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseVS2013|x64'">
@@ -236,12 +236,13 @@
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib</AdditionalDependencies>
+      <AdditionalDependencies>libhl.lib;winmm.lib;SDL2.lib;opengl32.lib;user32.lib;kernel32.lib;vulkan-1.lib</AdditionalDependencies>
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="gl.c" />
     <ClCompile Include="sdl.c" />
+    <ClCompile Include="vulkan.c" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

+ 1 - 0
libs/sdl/sdl.vcxproj.filters

@@ -3,5 +3,6 @@
   <ItemGroup>
     <ClCompile Include="sdl.c" />
     <ClCompile Include="gl.c" />
+    <ClCompile Include="vulkan.c" />
   </ItemGroup>
 </Project>

+ 27 - 0
libs/sdl/sdl/Vulkan.hx

@@ -0,0 +1,27 @@
+package sdl;
+
+@:hlNative("?sdl","vk_")
+class Vulkan {
+	public static function clearColorImage( r : Float, g : Float, b : Float, a : Float ) {
+	}
+}
+
+
+@:hlNative("?sdl","vk_")
+abstract VKContext(hl.Abstract<"vk_context">) {
+	
+	public function initSwapchain( width : Int, height : Int ) : Bool {
+		return false;
+	}
+	
+	public function beginFrame() : Bool {
+		return false;
+	}
+		
+	public function setCurrent() {
+	}
+
+	public function endFrame() {
+	}
+	
+}

+ 36 - 6
libs/sdl/sdl/Window.hx

@@ -45,6 +45,7 @@ class Window {
 
 	var win : WinPtr;
 	var glctx : GLContext;
+	var vkctx : Vulkan.VKContext;
 	var lastFrame : Float;
 	public var title(default, set) : String;
 	public var vsync(default, set) : Bool;
@@ -61,14 +62,30 @@ class Window {
 	public var opacity(get, set) : Float;
 
 	public function new( title : String, width : Int, height : Int, x : Int = SDL_WINDOWPOS_CENTERED, y : Int = SDL_WINDOWPOS_CENTERED, sdlFlags : Int = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE ) {
+	
+		var vk = (sdlFlags & SDL_WINDOW_VULKAN) != 0;
+		if( vk && !vkInit() )
+			throw "Failed to initialize Vulkan";
+	
 		while( true ) {
 			win = winCreateEx(x, y, width, height, sdlFlags);
 			if( win == null ) throw "Failed to create window";
-			glctx = winGetGLContext(win);
-			if( glctx == null || !GL.init() || !testGL() ) {
-				destroy();
-				if( Sdl.onGlContextRetry() ) continue;
-				Sdl.onGlContextError();
+			
+			if( vk ) {
+				vkctx = winGetVulkan(win);
+				if( vkctx == null ) {
+					destroy();
+					throw "Failed to create Vulkan Context";
+				}
+				if( !vkctx.initSwapchain(width, height) )
+					throw "Failed to init Vulkan Swapchain";
+			} else {
+				glctx = winGetGLContext(win);
+				if( glctx == null || !GL.init() || !testGL() ) {
+					destroy();
+					if( Sdl.onGlContextRetry() ) continue;
+					Sdl.onGlContextError();
+				}
 			}
 			break;
 		}
@@ -236,10 +253,12 @@ class Window {
 		Set the current window you will render to (in case of multiple windows)
 	**/
 	public function renderTo() {
-		winRenderTo(win, glctx);
+		if( glctx != null ) winRenderTo(win, glctx);
 	}
 
 	public function present() {
+		if( glctx == null )
+			return;
 		if( vsync && @:privateAccess Sdl.isWin32 ) {
 			// NVIDIA OpenGL windows driver does implement vsync as an infinite loop, causing high CPU usage
 			// make sure to sleep a bit here based on how much time we spent since the last frame
@@ -254,6 +273,7 @@ class Window {
 		try winDestroy(win, glctx) catch( e : Dynamic ) {};
 		win = null;
 		glctx = null;
+		vkctx = null;
 		windows.remove(this);
 	}
 
@@ -334,5 +354,15 @@ class Window {
 
 	static function setVsync( b : Bool ) {
 	}
+	
+	@:hlNative("?sdl","vk_init")
+	static function vkInit() : Bool {
+		return false;
+	}
 
+	@:hlNative("?sdl","win_get_vulkan")
+	static function winGetVulkan( win : WinPtr ) : Vulkan.VKContext {
+		return null;
+	}
+	
 }

+ 347 - 0
libs/sdl/vulkan.c

@@ -0,0 +1,347 @@
+#define HL_NAME(n) sdl_##n
+#include <hl.h>
+
+#define VK_USE_PLATFORM_WIN32_KHR
+#include <vulkan/vulkan.h>
+
+static VkInstance instance = NULL;
+
+VkInstance vk_get_instance() {
+	return instance;
+}
+
+HL_PRIM bool HL_NAME(vk_init)() {
+	if( instance )
+		return true;
+	VkApplicationInfo appInfo = {
+		.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+		.pApplicationName = "HashLink Vulkan",
+		.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
+		.pEngineName = "Heaps.io",
+		.engineVersion = VK_MAKE_VERSION(1, 0, 0),
+		.apiVersion = VK_API_VERSION_1_0,
+	};
+	VkInstanceCreateInfo info = {
+		.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+		.pApplicationInfo = &appInfo,
+		.enabledExtensionCount = 2,
+		.ppEnabledExtensionNames = (const char* const[]) { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME },
+	};
+	return vkCreateInstance(&info, NULL, &instance) == VK_SUCCESS;
+}
+
+// ------------------------------------------ CONTEXT INIT --------------------------------------------
+
+#define MAX_SWAPCHAIN_IMAGES 3
+#define FRAME_COUNT 2
+
+typedef struct {
+	VkCommandBuffer buffer;
+	VkFence fence;
+	VkSemaphore imageAvailable;
+	VkSemaphore renderFinished;
+} VkFrame;
+
+typedef struct _VkContext {
+	VkSurfaceKHR surface;
+	VkPhysicalDevice pdevice;
+	VkDevice device;
+	VkQueue queue;
+	int queueFamily;
+
+	VkSwapchainKHR swapchain;
+	int swapchainImageCount;
+	VkImage swapchainImages[MAX_SWAPCHAIN_IMAGES];
+
+	unsigned int currentFrame;
+	unsigned int currentImage;
+	VkCommandPool commandPool;
+	VkFrame frames[FRAME_COUNT];
+
+} *VkContext;
+
+
+
+void *vk_init_context( VkSurfaceKHR surface ) {
+	VkContext ctx = (VkContext)malloc(sizeof(struct _VkContext));
+	memset(ctx,0,sizeof(struct _VkContext));
+	ctx->surface = surface;
+
+	int physicalDeviceCount;
+#	define MAX_DEVICE_COUNT 16
+#	define MAX_QUEUE_COUNT 16
+	VkPhysicalDevice deviceHandles[MAX_DEVICE_COUNT];
+	VkQueueFamilyProperties queueFamilyProperties[MAX_QUEUE_COUNT];
+	VkPhysicalDeviceProperties deviceProperties;
+	VkPhysicalDeviceFeatures deviceFeatures;
+	VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+
+	vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, 0);
+	physicalDeviceCount = physicalDeviceCount > MAX_DEVICE_COUNT ? MAX_DEVICE_COUNT : physicalDeviceCount;
+	vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, deviceHandles);
+
+	for(int i=0; i<physicalDeviceCount; i++) {
+		int queueFamilyCount = 0;
+		vkGetPhysicalDeviceQueueFamilyProperties(deviceHandles[i], &queueFamilyCount, NULL);
+		queueFamilyCount = queueFamilyCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueFamilyCount;
+		vkGetPhysicalDeviceQueueFamilyProperties(deviceHandles[i], &queueFamilyCount, queueFamilyProperties);
+		vkGetPhysicalDeviceProperties(deviceHandles[i], &deviceProperties);
+		vkGetPhysicalDeviceFeatures(deviceHandles[i], &deviceFeatures);
+		vkGetPhysicalDeviceMemoryProperties(deviceHandles[i], &deviceMemoryProperties);
+		for(int j=0; j<queueFamilyCount; j++) {
+			VkBool32 supportsPresent = VK_FALSE;
+			vkGetPhysicalDeviceSurfaceSupportKHR(deviceHandles[i], j, ctx->surface, &supportsPresent);
+			if (supportsPresent && (queueFamilyProperties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
+				ctx->queueFamily = j;
+				ctx->pdevice = deviceHandles[i];
+				break;
+			}
+		}
+		if( ctx->pdevice ) break;
+	}
+
+	VkDeviceQueueCreateInfo qinf = {
+		.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+		.queueFamilyIndex = ctx->queueFamily,
+		.queueCount = 1,
+		.pQueuePriorities = (const float[]) { 1.0f }
+	};
+	VkDeviceCreateInfo dinf = {
+		.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+		.queueCreateInfoCount = 1,
+		.pQueueCreateInfos = 0,
+		.enabledLayerCount = 0,
+		.ppEnabledLayerNames = 0,
+		.enabledExtensionCount = 1,
+		.ppEnabledExtensionNames = (const char* const[]) { VK_KHR_SWAPCHAIN_EXTENSION_NAME },
+		.pEnabledFeatures = 0,
+		.pQueueCreateInfos = &qinf,
+	};
+
+	if( vkCreateDevice(ctx->pdevice, &dinf, NULL, &ctx->device) != VK_SUCCESS )
+		return NULL;
+
+	vkGetDeviceQueue(ctx->device, ctx->queueFamily, 0, &ctx->queue);
+
+	VkCommandPoolCreateInfo poolInf = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+		.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+		.queueFamilyIndex = ctx->queueFamily,
+	};
+	vkCreateCommandPool(ctx->device, &poolInf, 0, &ctx->commandPool);
+
+	VkCommandBuffer cbuffers[FRAME_COUNT];
+	VkCommandBufferAllocateInfo commandBufferAllocInfo = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+		.commandPool = ctx->commandPool,
+		.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+		.commandBufferCount = FRAME_COUNT,
+	};
+	vkAllocateCommandBuffers(ctx->device, &commandBufferAllocInfo, cbuffers);
+
+	VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+	VkFenceCreateInfo fenceCreateInfo = {
+		.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+		.flags = VK_FENCE_CREATE_SIGNALED_BIT
+	};
+	for(int i=0;i<FRAME_COUNT;i++) {
+		VkFrame *f = &ctx->frames[i];
+		f->buffer = cbuffers[i];
+		vkCreateSemaphore(ctx->device, &semaphoreCreateInfo, 0, &f->imageAvailable);
+		vkCreateSemaphore(ctx->device, &semaphoreCreateInfo, 0, &f->renderFinished);
+		vkCreateFence(ctx->device, &fenceCreateInfo, 0, &f->fence);
+	}
+	return ctx;
+}
+
+
+
+bool HL_NAME(vk_init_swapchain)( VkContext ctx, int width, int height ) {
+
+	vkDeviceWaitIdle(ctx->device);
+
+	if( ctx->swapchain ) {
+		vkDestroySwapchainKHR(ctx->device, ctx->swapchain, NULL);
+		ctx->swapchain = NULL;
+	}
+
+	int formatCount = 1;
+	VkSurfaceFormatKHR format;
+	vkGetPhysicalDeviceSurfaceFormatsKHR(ctx->pdevice, ctx->surface, &formatCount, 0); // suppress validation layer
+	formatCount = 1;
+	vkGetPhysicalDeviceSurfaceFormatsKHR(ctx->pdevice, ctx->surface, &formatCount, &format);
+	format.format = format.format == VK_FORMAT_UNDEFINED ? VK_FORMAT_B8G8R8A8_UNORM : format.format;
+
+	int modeCount = 0;
+#	define MAX_PRESENT_MODE_COUNT 16
+	VkPresentModeKHR modes[MAX_PRESENT_MODE_COUNT];
+
+	vkGetPhysicalDeviceSurfacePresentModesKHR(ctx->pdevice, ctx->surface, &modeCount, NULL);
+	modeCount = modeCount > MAX_PRESENT_MODE_COUNT ? MAX_PRESENT_MODE_COUNT : modeCount;
+	vkGetPhysicalDeviceSurfacePresentModesKHR(ctx->pdevice, ctx->surface, &modeCount, modes);
+
+	VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;   // always supported.
+	ctx->swapchainImageCount = 2;
+
+	// using MAILBOX will be same as no vsync
+	/*
+	for(int i=0; i<modeCount; i++) {
+		if( modes[i] == VK_PRESENT_MODE_MAILBOX_KHR ) {
+			presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+			ctx->swapchainImageCount = 3;
+			break;
+		}
+	}
+	*/
+
+	VkSurfaceCapabilitiesKHR scaps;
+	vkGetPhysicalDeviceSurfaceCapabilitiesKHR(ctx->pdevice, ctx->surface, &scaps);
+
+	VkExtent2D swapchainExtent = scaps.currentExtent;
+#	define clamp(v,min,max) ( ((v) < (min)) ? (min) : ((v) < (max)) ? (max) : (v) )
+	if( swapchainExtent.width == UINT32_MAX ) {
+		swapchainExtent.width = clamp((unsigned)width, scaps.minImageExtent.width, scaps.maxImageExtent.width);
+		swapchainExtent.height = clamp((unsigned)height, scaps.minImageExtent.height, scaps.maxImageExtent.height);
+	}
+
+	VkSwapchainCreateInfoKHR swapInfo = {
+		.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+		.surface = ctx->surface,
+		.minImageCount = ctx->swapchainImageCount,
+		.imageFormat = format.format,
+		.imageColorSpace = format.colorSpace,
+		.imageExtent = swapchainExtent,
+		.imageArrayLayers = 1,
+		.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
+		.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
+		.preTransform = scaps.currentTransform,
+		.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+		.presentMode = presentMode,
+		.clipped = VK_TRUE,
+	};
+
+	if( vkCreateSwapchainKHR(ctx->device, &swapInfo, 0, &ctx->swapchain) != VK_SUCCESS )
+		return false;
+
+	vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->swapchainImageCount, NULL);
+	vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->swapchainImageCount, ctx->swapchainImages);
+
+	return true;
+}
+
+const VkImageSubresourceRange RANGE_ALL = {
+	.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+	.baseMipLevel = 0,
+	.levelCount = VK_REMAINING_MIP_LEVELS,
+	.baseArrayLayer = 0,
+	.layerCount = VK_REMAINING_ARRAY_LAYERS,
+};
+
+static VkContext current_context = NULL;
+static VkCommandBuffer current_buffer = NULL;
+
+HL_PRIM void HL_NAME(vk_set_current)( VkContext ctx ) {
+	current_context = ctx;
+	current_buffer = ctx->frames[ctx->currentFrame].buffer;
+}
+
+HL_PRIM bool HL_NAME(vk_begin_frame)( VkContext ctx ) {
+	VkFrame *frame = &ctx->frames[ctx->currentFrame];
+	VkCommandBuffer buffer = frame->buffer;
+
+	vkWaitForFences(ctx->device, 1, &frame->fence, VK_TRUE, UINT64_MAX);
+
+	if( vkAcquireNextImageKHR(ctx->device, ctx->swapchain, UINT64_MAX, frame->imageAvailable, VK_NULL_HANDLE, &ctx->currentImage) != VK_SUCCESS )
+		return false;
+
+	vkResetFences(ctx->device, 1, &frame->fence);
+
+	VkCommandBufferBeginInfo beginInfo = {
+		.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+		.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+	};
+	vkBeginCommandBuffer(buffer, &beginInfo);
+
+	VkImageMemoryBarrier barrier = {
+		.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+		  .srcAccessMask = 0,
+			.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+			.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+			.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			.srcQueueFamilyIndex = ctx->queueFamily,
+			.dstQueueFamilyIndex = ctx->queueFamily,
+			.image = ctx->swapchainImages[ctx->currentImage],
+			.subresourceRange = RANGE_ALL,
+	};
+	vkCmdPipelineBarrier(buffer,
+		VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+		0, 0, NULL, 0, NULL, 1, &barrier
+	);
+
+	sdl_vk_set_current(ctx);
+	return true;
+}
+
+HL_PRIM void HL_NAME(vk_clear_color_image)( double r, double g, double b, double a ) {
+	VkClearColorValue color = { (float)r, (float)g, (float)b, (float)a };
+	vkCmdClearColorImage(current_buffer,
+		current_context->swapchainImages[current_context->currentImage], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+		&color, 1, &RANGE_ALL
+	);
+}
+
+HL_PRIM void HL_NAME(vk_end_frame)( VkContext ctx ) {
+	VkFrame *frame = &ctx->frames[ctx->currentFrame];
+
+	VkImageMemoryBarrier barrier2 = {
+		.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+		.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+		.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
+		.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+		.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+		.srcQueueFamilyIndex = ctx->queueFamily,
+		.dstQueueFamilyIndex = ctx->queueFamily,
+		.image = ctx->swapchainImages[ctx->currentImage],
+		.subresourceRange = RANGE_ALL,
+	};
+	vkCmdPipelineBarrier(frame->buffer,
+		VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+		0, 0, NULL, 0, NULL, 1,
+		&barrier2
+	);
+
+	vkEndCommandBuffer(frame->buffer);
+
+	VkSubmitInfo submitInfo = {
+		.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+		.waitSemaphoreCount = 1,
+		.pWaitSemaphores = &frame->imageAvailable,
+		.pWaitDstStageMask = (VkPipelineStageFlags[]) { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT },
+		.commandBufferCount = 1,
+		.pCommandBuffers = &frame->buffer,
+		.signalSemaphoreCount = 1,
+		.pSignalSemaphores = &frame->renderFinished,
+	};
+	vkQueueSubmit(ctx->queue, 1, &submitInfo, frame->fence);
+
+	VkPresentInfoKHR presentInfo = {
+		.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+		.waitSemaphoreCount = 1,
+		.pWaitSemaphores = &frame->renderFinished,
+		.swapchainCount = 1,
+		.pSwapchains = &ctx->swapchain,
+		.pImageIndices = &ctx->currentImage,
+	};
+	vkQueuePresentKHR(ctx->queue, &presentInfo);
+
+	ctx->currentFrame++;
+	ctx->currentFrame %= FRAME_COUNT;
+}
+
+#define _VCTX _ABSTRACT(vk_context)
+DEFINE_PRIM(_BOOL, vk_init, _NO_ARG);
+DEFINE_PRIM(_BOOL, vk_init_swapchain, _VCTX _I32 _I32);
+DEFINE_PRIM(_BOOL, vk_begin_frame, _VCTX);
+DEFINE_PRIM(_VOID, vk_set_current, _VCTX);
+DEFINE_PRIM(_VOID, vk_clear_color_image, _F64 _F64 _F64 _F64);
+DEFINE_PRIM(_VOID, vk_end_frame, _VCTX);