feat(render3d): Vulkan in the game's window on Windows

- gpu_select takes Vulkan for a window on Windows; gvk_init asks for VK_KHR_surface,
  VK_KHR_win32_surface and VK_KHR_swapchain when the renderer will present. A window elsewhere
  stays on OpenGL with the reason.
- gvk_open opens the window without a GL context and makes the screen images at its client area;
  gvk_swap_make builds the swapchain on the Win32 surface (FIFO with vsync, mailbox or immediate
  without) and rebuilds it when it is out of date, suboptimal or the window changes size.
- gvk_present blits the screen image into the acquired swapchain image, flipped (the screen keeps
  OpenGL's bottom-up rows), and presents it.
- Samplers pointed at a texture unit with u_i and then fed by binding units - the actors do this -
  read that unit's texture; on Vulkan they drew with the white stand-in (the tent, the log, the
  chair, the workbench).
- gl.ll carries a weak win_gl_drawable for headless macOS builds.

On the RTX 3070 Ti the windowed build runs the valley through Vulkan at 3840x2160 with the HUD and
the frame-rate readout (16 fps: every upload still waits on the frame, and buffers are
host-visible). The headless Vulkan frame is unchanged; OpenGL frames byte-identical at the five
viewpoints with 59 self-tests passing. The actor fix is compiled and OpenGL-verified, not yet
seen on the PC; blades at the grass's middle distance still draw black on Vulkan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 13:08:40 +03:00
parent 66940f9c0a
commit 1b7408c6a1
4 changed files with 254 additions and 19 deletions

View file

@ -17,6 +17,8 @@ var gvk_mp: bytes = null # VkPhysicalDeviceMemoryProperties
var gvk_device_name: string = ""
var gvk_why: string = "" # why the device did not come up, for the fallback notice
var gvk_max_aniso: int = 0x3F800000 # the device's anisotropy limit as float bits; 1.0 when it has none
var gvk_want_surface: bool = false # set before gvk_init when the renderer will present to a window
var gvk_has_surface: bool = false # the instance and device can make a surface and a swapchain
function gvk_fail(what: string, r: int) -> bool {
gvk_why = `{what} failed (VkResult {r})`
@ -52,15 +54,25 @@ function gvk_init() -> bool {
Vk.put_i32(app, VkApplicationInfo_sType, VK_STRUCTURE_TYPE_APPLICATION_INFO)
Vk.put_ptr(app, VkApplicationInfo_pApplicationName, "ludic.render3d")
Vk.put_i32(app, VkApplicationInfo_apiVersion, (1 << 22) | (3 << 12))
let iext_names = bytes(8)
let iext_names = bytes(32)
var n_iext = 0
let ici = bytes(VkInstanceCreateInfo_sizeof)
Vk.zero(ici, VkInstanceCreateInfo_sizeof)
Vk.put_i32(ici, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
Vk.put_ptr(ici, VkInstanceCreateInfo_pApplicationInfo, app)
if portability {
Vk.put_ptr(iext_names, 0, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); n_iext += 1
Vk.put_i32(ici, VkInstanceCreateInfo_flags, VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR)
Vk.put_i32(ici, VkInstanceCreateInfo_enabledExtensionCount, 1)
}
# a window's surface: only the Win32 one is built, so a window elsewhere stays on OpenGL
gvk_has_surface = gvk_want_surface and gvk_ext_in(iexts, nie, VK_KHR_SURFACE_EXTENSION_NAME) and gvk_ext_in(iexts, nie, VK_KHR_WIN32_SURFACE_EXTENSION_NAME)
if gvk_want_surface and not gvk_has_surface { gvk_why = "no Win32 surface in this Vulkan instance"; return false }
if gvk_has_surface {
Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_SURFACE_EXTENSION_NAME); n_iext += 1
Vk.put_ptr(iext_names, n_iext * 8, VK_KHR_WIN32_SURFACE_EXTENSION_NAME); n_iext += 1
}
if n_iext > 0 {
Vk.put_i32(ici, VkInstanceCreateInfo_enabledExtensionCount, n_iext)
Vk.put_ptr(ici, VkInstanceCreateInfo_ppEnabledExtensionNames, iext_names)
}
let out = bytes(8)
@ -147,16 +159,21 @@ function gvk_init() -> bool {
Vk.put_i32(qci, VkDeviceQueueCreateInfo_queueFamilyIndex, gvk_family)
Vk.put_i32(qci, VkDeviceQueueCreateInfo_queueCount, 1)
Vk.put_ptr(qci, VkDeviceQueueCreateInfo_pQueuePriorities, prio)
let dext_names = bytes(8)
let dext_names = bytes(32)
var n_dext = 0
let dci = bytes(VkDeviceCreateInfo_sizeof)
Vk.zero(dci, VkDeviceCreateInfo_sizeof)
Vk.put_i32(dci, VkDeviceCreateInfo_sType, VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO)
Vk.put_ptr(dci, VkDeviceCreateInfo_pNext, want2)
Vk.put_i32(dci, VkDeviceCreateInfo_queueCreateInfoCount, 1)
Vk.put_ptr(dci, VkDeviceCreateInfo_pQueueCreateInfos, qci)
if gvk_ext_in(dexts, nde, "VK_KHR_portability_subset") {
Vk.put_ptr(dext_names, 0, "VK_KHR_portability_subset")
Vk.put_i32(dci, VkDeviceCreateInfo_enabledExtensionCount, 1)
if gvk_ext_in(dexts, nde, "VK_KHR_portability_subset") { Vk.put_ptr(dext_names, n_dext * 8, "VK_KHR_portability_subset"); n_dext += 1 }
if gvk_has_surface {
if not gvk_ext_in(dexts, nde, VK_KHR_SWAPCHAIN_EXTENSION_NAME) { gvk_why = `{gvk_device_name} has no swapchain`; return false }
Vk.put_ptr(dext_names, n_dext * 8, VK_KHR_SWAPCHAIN_EXTENSION_NAME); n_dext += 1
}
if n_dext > 0 {
Vk.put_i32(dci, VkDeviceCreateInfo_enabledExtensionCount, n_dext)
Vk.put_ptr(dci, VkDeviceCreateInfo_ppEnabledExtensionNames, dext_names)
}
r = Vk.create_device(gvk_pd, dci, null, out)