From 1b7408c6a1c590d6821a87c8a5050d6b77f734d2 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 13:08:40 +0300 Subject: [PATCH] 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 --- packages/ludic.render3d/gpu.ludic | 11 +- packages/ludic.render3d/gpu_vk.ludic | 31 ++- packages/ludic.render3d/gpu_vk_draw.ludic | 222 +++++++++++++++++++++- runtime/native/gl.ll | 9 + 4 files changed, 254 insertions(+), 19 deletions(-) diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 8bbda7b1..e75b0bb0 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -52,8 +52,9 @@ function gpu_select() -> int { if gpu_wanted == 0 { gpu_wanted = GPU_GL } gpu_kind = GPU_GL if gpu_wanted == GPU_VK { - # headless for now: the swapchain on the game's window is the next milestone - if is_windowed() { gpu_fallback_reason = "the Vulkan window is not built yet" } + # a window needs a surface, and only the Win32 one is built + gvk_want_surface = is_windowed() + if is_windowed() and Os.platform() != "windows" { gpu_fallback_reason = "the Vulkan window is built for Windows only" } else if not gvk_init() { gpu_fallback_reason = gvk_why } else if not gvk_manifest() { gpu_fallback_reason = "the renderer's SPIR-V manifest is missing" } else { gpu_kind = GPU_VK } @@ -228,10 +229,10 @@ function gpu_query_result(id: int, out: words) -> bool { } # ---- the context -------------------------------------------------------------------- -function gpu_open(w: int, h: int, title: string) -> bool { if gpu_kind == GPU_VK { return gvk_open(w, h) }; return gl_open(w, h, title) } -function gpu_vsync(on: int) -> void { if gpu_kind == GPU_VK { return }; gl_vsync(on) } +function gpu_open(w: int, h: int, title: string) -> bool { if gpu_kind == GPU_VK { return gvk_open(w, h, title) }; return gl_open(w, h, title) } +function gpu_vsync(on: int) -> void { if gpu_kind == GPU_VK { gvk_vsync = on != 0; if gvk_swap != 0 { gvk_swap_stale = true }; return }; gl_vsync(on) } function gpu_renderer_name() -> string { if gpu_kind == GPU_VK { return `{gvk_device_name} (Vulkan)` }; return gl_get_string(GL_RENDERER) } -function gpu_resize_check() -> bool { if gpu_kind == GPU_VK { return false }; let r = gl_resize_check(); gpu_glcheck_after("the resize check"); return r } +function gpu_resize_check() -> bool { if gpu_kind == GPU_VK { return gvk_resize_check() }; let r = gl_resize_check(); gpu_glcheck_after("the resize check"); return r } # the finished frame: presented to the window, or (headless) the GPU's work finished function gpu_present() -> void { if gpu_kind == GPU_VK { gvk_present(); return }; Gl.swap() } # the frame as it will be presented, to a binary PPM with the top row first; before gpu_present diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index 325b0ebd..cd146bfa 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -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) diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index 8c2a8351..28b4d332 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -370,18 +370,21 @@ function gvk_pipeline(p: int, m: Mesh, st: GvkState, n_color: int, color_fmt: in var gvk_ublk_v: []bytes = null # per program: the vertex stage's block as last set var gvk_ublk_f: []bytes = null var gvk_prog_tex: []words = null # per program: the texture bound to each manifest sampler +var gvk_prog_unit: []words = null # per program: the unit + 1 each sampler was pointed at with u_i function gvk_uniform_blocks(p: int) -> void { - if gvk_ublk_v == null { gvk_ublk_v = new []bytes; gvk_ublk_f = new []bytes; gvk_prog_tex = new []words } - while len(gvk_ublk_v) <= p { push(gvk_ublk_v, null); push(gvk_ublk_f, null); push(gvk_prog_tex, null) } + if gvk_ublk_v == null { gvk_ublk_v = new []bytes; gvk_ublk_f = new []bytes; gvk_prog_tex = new []words; gvk_prog_unit = new []words } + while len(gvk_ublk_v) <= p { push(gvk_ublk_v, null); push(gvk_ublk_f, null); push(gvk_prog_tex, null); push(gvk_prog_unit, null) } let v = gvk_prog_var[p] if v == null or gvk_ublk_v[p] != null or gvk_prog_tex[p] != null { return } if v.vblock > 0 { let b = bytes(v.vblock); Vk.zero(b, v.vblock); gvk_ublk_v[p] = b } if v.fblock > 0 { let b = bytes(v.fblock); Vk.zero(b, v.fblock); gvk_ublk_f[p] = b } let nt = len(v.t_name) + 1 let t = words(nt) - for i in 0 .. nt { t[i] = 0 } + let u = words(nt) + for i in 0 .. nt { t[i] = 0; u[i] = 0 } gvk_prog_tex[p] = t + gvk_prog_unit[p] = u } function gvk_uniform(p: int, name: string) -> int { @@ -389,6 +392,9 @@ function gvk_uniform(p: int, name: string) -> int { let v = gvk_prog_var[p] if v == null { return -1 } for i in 0 .. len(v.u_name) { if v.u_name[i] == name { return p * 4096 + i } } + # a sampler: OpenGL code points it at a texture unit once with u_i and then only binds units + # (the actors do), so its location is kept apart and u_i on it records the unit + for i in 0 .. len(v.t_name) { if v.t_name[i] == name { return p * 4096 + 2048 + i } } return -1 } @@ -399,6 +405,11 @@ function gvk_u_set(loc: int, src: pointer, size: int, n: int) -> void { let v = gvk_prog_var[p] if v == null { return } gvk_uniform_blocks(p) + if loc % 4096 >= 2048 { + let si = loc % 4096 - 2048 + if si < len(v.t_name) { gvk_prog_unit[p][si] = Vk.get_i32(src, 0) + 1 } + return + } let name = v.u_name[loc % 4096] for j in 0 .. len(v.u_name) { if v.u_name[j] != name { continue } @@ -530,6 +541,9 @@ function gvk_draw_set(p: int, tx: words, tx_w: int, tx_cap: int) -> long { let iw = VkDescriptorImageInfo_sizeof for t in 0 .. nt { var tex = gvk_prog_tex[p][t] + # nothing bound by name: the texture on the unit the sampler was pointed at, as OpenGL reads it + let unit1 = gvk_prog_unit[p][t] + if tex <= 0 and unit1 > 0 and unit1 <= 32 and gpu_unit_2d != null { tex = gpu_unit_2d[unit1 - 1] } if tex <= 0 or tex >= len(gvk_tex_image) or gvk_tex_image[tex] == 0 { tex = gvk_white } var smp: long = 0 let o = tex * tx_w @@ -871,8 +885,11 @@ function gvk_flush() -> void { gvk_cb = null } -# the finished frame: submitted, and waited for +# The finished frame: submitted and waited for. With a window, the screen image is blitted into +# the swapchain's next image first - flipped, since the screen image keeps OpenGL's bottom-up rows +# - and that image is presented. function gvk_present() -> void { + if gvk_swap != 0 { gvk_present_window(); return } if gvk_cb == null { return } gvk_pass_end() gvk_once_end(gvk_cb) @@ -918,12 +935,23 @@ function gvk_manifest() -> bool { return gpu_manifest_load(`{gvk_spv_dir}/manifest.txt`) > 0 and len(gpu_variants) > 0 } -# headless: the screen is a colour and a depth image of the asked-for size -function gvk_open(w: int, h: int) -> bool { +# The screen: a colour and a depth image. Headless they are the asked-for size; with a window +# they are its client area in pixels, and the swapchain is made on it. +function gvk_open(w: int, h: int, title: string) -> bool { gl_w = w gl_h = h + if is_windowed() { + win_open(w, h, 1, title) + win_gl_resize(w, h) + if gvk_size_buf == null { gvk_size_buf = words(4) } + win_gl_drawable(gvk_size_buf) + if gvk_size_buf[0] > 0 and gvk_size_buf[1] > 0 { gl_w = gvk_size_buf[0]; gl_h = gvk_size_buf[1] } + gl_scale = win_gl_scale() + } if not gvk_frame_init() { return false } - return gvk_screen_make(w, h) + if not gvk_screen_make(gl_w, gl_h) { return false } + if is_windowed() { return gvk_swap_make(gl_w, gl_h) } + return true } # a program handle for a variant; the key is gpu_program's, so gpu_program_key works on both @@ -1045,3 +1073,183 @@ function gvk_read_screen(w: int, h: int, out: pointer) -> void { } } } + +# ---- the window: surface and swapchain ---------------------------------------------------------- +# Windows only for now (the Win32 surface); gpu_select keeps a window elsewhere on OpenGL. +extern function win_native_window() -> pointer = "win_native_window" +extern function win_native_instance() -> pointer = "win_native_instance" + +var gvk_surface: long = 0 +var gvk_swap: long = 0 +var gvk_swap_n: int = 0 +var gvk_swap_images: bytes = null +var gvk_swap_fmt: int = 0 +var gvk_swap_w: int = 0 +var gvk_swap_h: int = 0 +var gvk_vsync: bool = true +var gvk_acq_fence: bytes = null +var gvk_swap_stale: bool = false + +function gvk_surface_make() -> bool { + if gvk_surface != 0 { return true } + let hwnd = win_native_window() + if hwnd == null { gvk_why = "no window to present to"; return false } + let sci = bytes(VkWin32SurfaceCreateInfoKHR_sizeof) + Vk.zero(sci, VkWin32SurfaceCreateInfoKHR_sizeof) + Vk.put_i32(sci, VkWin32SurfaceCreateInfoKHR_sType, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR) + Vk.put_ptr(sci, VkWin32SurfaceCreateInfoKHR_hinstance, win_native_instance()) + Vk.put_ptr(sci, VkWin32SurfaceCreateInfoKHR_hwnd, hwnd) + let out = bytes(8) + let r = Vk.create_win32_surface_khr(gvk_inst, sci, null, out) + if r != VK_SUCCESS { return gvk_fail("vkCreateWin32SurfaceKHR", r) } + gvk_surface = gvk_handle(out) + let ok = bytes(4) + Vk.put_i32(ok, 0, 0) + Vk.get_physical_device_surface_support_khr(gvk_pd, gvk_family, gvk_surface, ok) + if Vk.get_i32(ok, 0) != 1 { gvk_why = "the graphics queue cannot present to the window"; return false } + let fci = bytes(VkFenceCreateInfo_sizeof) + Vk.zero(fci, VkFenceCreateInfo_sizeof) + Vk.put_i32(fci, VkFenceCreateInfo_sType, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO) + gvk_acq_fence = bytes(8) + return Vk.create_fence(gvk_dev, fci, null, gvk_acq_fence) == VK_SUCCESS +} + +# (Re)make the swapchain for a w x h client area. The old one is handed over and then destroyed. +function gvk_swap_make(w: int, h: int) -> bool { + if not gvk_surface_make() { return false } + Vk.device_wait_idle(gvk_dev) + let caps = bytes(VkSurfaceCapabilitiesKHR_sizeof) + Vk.get_physical_device_surface_capabilities_khr(gvk_pd, gvk_surface, caps) + var ew = Vk.get_i32(caps, VkSurfaceCapabilitiesKHR_currentExtent + VkExtent2D_width) + var eh = Vk.get_i32(caps, VkSurfaceCapabilitiesKHR_currentExtent + VkExtent2D_height) + if ew == -1 or ew <= 0 { ew = w; eh = h } + if ew <= 0 or eh <= 0 { return false } # minimised: keep the old chain until it has a size + let cnt = bytes(4) + Vk.put_i32(cnt, 0, 0) + Vk.get_physical_device_surface_formats_khr(gvk_pd, gvk_surface, cnt, null) + let nf = Vk.get_i32(cnt, 0) + let fmts = bytes(nf * VkSurfaceFormatKHR_sizeof + 8) + Vk.get_physical_device_surface_formats_khr(gvk_pd, gvk_surface, cnt, fmts) + # the screen image holds display-ready 8-bit colour, so the swapchain takes it unconverted + var fmt = Vk.get_i32(fmts, VkSurfaceFormatKHR_format) + var cs = Vk.get_i32(fmts, VkSurfaceFormatKHR_colorSpace) + for i in 0 .. nf { + let f = Vk.get_i32(fmts, i * VkSurfaceFormatKHR_sizeof + VkSurfaceFormatKHR_format) + let c = Vk.get_i32(fmts, i * VkSurfaceFormatKHR_sizeof + VkSurfaceFormatKHR_colorSpace) + if (f == VK_FORMAT_B8G8R8A8_UNORM or f == VK_FORMAT_R8G8B8A8_UNORM) and c == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR { fmt = f; cs = c } + } + Vk.put_i32(cnt, 0, 0) + Vk.get_physical_device_surface_present_modes_khr(gvk_pd, gvk_surface, cnt, null) + let nm = Vk.get_i32(cnt, 0) + let modes = bytes(nm * 4 + 8) + Vk.get_physical_device_surface_present_modes_khr(gvk_pd, gvk_surface, cnt, modes) + # vsync: FIFO, which every device has. Off: mailbox where offered (no tearing), else immediate. + var mode = VK_PRESENT_MODE_FIFO_KHR + if not gvk_vsync { + for i in 0 .. nm { if Vk.get_i32(modes, i * 4) == VK_PRESENT_MODE_IMMEDIATE_KHR { mode = VK_PRESENT_MODE_IMMEDIATE_KHR } } + for i in 0 .. nm { if Vk.get_i32(modes, i * 4) == VK_PRESENT_MODE_MAILBOX_KHR { mode = VK_PRESENT_MODE_MAILBOX_KHR } } + } + var n = Vk.get_i32(caps, VkSurfaceCapabilitiesKHR_minImageCount) + 1 + let mx = Vk.get_i32(caps, VkSurfaceCapabilitiesKHR_maxImageCount) + if mx > 0 and n > mx { n = mx } + let sci = bytes(VkSwapchainCreateInfoKHR_sizeof) + Vk.zero(sci, VkSwapchainCreateInfoKHR_sizeof) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_sType, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR) + Vk.put_i64(sci, VkSwapchainCreateInfoKHR_surface, gvk_surface) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_minImageCount, n) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageFormat, fmt) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageColorSpace, cs) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageExtent + VkExtent2D_width, ew) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageExtent + VkExtent2D_height, eh) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageArrayLayers, 1) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageUsage, VK_IMAGE_USAGE_TRANSFER_DST_BIT) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_imageSharingMode, VK_SHARING_MODE_EXCLUSIVE) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_preTransform, Vk.get_i32(caps, VkSurfaceCapabilitiesKHR_currentTransform)) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_compositeAlpha, VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_presentMode, mode) + Vk.put_i32(sci, VkSwapchainCreateInfoKHR_clipped, 1) + Vk.put_i64(sci, VkSwapchainCreateInfoKHR_oldSwapchain, gvk_swap) + let out = bytes(8) + let r = Vk.create_swapchain_khr(gvk_dev, sci, null, out) + if r != VK_SUCCESS { return gvk_fail("vkCreateSwapchainKHR", r) } + if gvk_swap != 0 { Vk.destroy_swapchain_khr(gvk_dev, gvk_swap, null) } + gvk_swap = gvk_handle(out) + Vk.put_i32(cnt, 0, 0) + Vk.get_swapchain_images_khr(gvk_dev, gvk_swap, cnt, null) + gvk_swap_n = Vk.get_i32(cnt, 0) + gvk_swap_images = bytes(gvk_swap_n * 8 + 8) + Vk.get_swapchain_images_khr(gvk_dev, gvk_swap, cnt, gvk_swap_images) + gvk_swap_fmt = fmt + gvk_swap_w = ew + gvk_swap_h = eh + gvk_swap_stale = false + var mname = "fifo" + if mode == VK_PRESENT_MODE_MAILBOX_KHR { mname = "mailbox" } + if mode == VK_PRESENT_MODE_IMMEDIATE_KHR { mname = "immediate" } + print(`r3d: vulkan swapchain {ew}x{eh}, {gvk_swap_n} images, {mname}`) + return true +} + +function gvk_present_window() -> void { + let cb = gvk_frame_cb() + gvk_pass_end() + if gvk_swap_stale { gvk_once_end(cb); gvk_cb = null; gvk_swap_make(gvk_swap_w, gvk_swap_h); return } + let idx = bytes(4) + Vk.reset_fences(gvk_dev, 1, gvk_acq_fence) + let forever: long = -1 + let zero: long = 0 + var r = Vk.acquire_next_image_khr(gvk_dev, gvk_swap, forever, zero, Vk.get_i64(gvk_acq_fence, 0), idx) + if r == VK_ERROR_OUT_OF_DATE_KHR { gvk_once_end(cb); gvk_cb = null; gvk_swap_make(gvk_swap_w, gvk_swap_h); return } + Vk.wait_for_fences(gvk_dev, 1, gvk_acq_fence, 1, forever) + let i = Vk.get_i32(idx, 0) + let dst = Vk.get_i64(gvk_swap_images, i * 8) + let src = gvk_tex_image[gvk_screen_color] + gvk_barrier(cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) + gvk_barrier(cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + let blit = bytes(VkImageBlit_sizeof) + Vk.zero(blit, VkImageBlit_sizeof) + Vk.put_i32(blit, VkImageBlit_srcSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) + Vk.put_i32(blit, VkImageBlit_srcSubresource + VkImageSubresourceLayers_layerCount, 1) + # the screen image's row 0 is the bottom of the picture: read it from the top edge down + Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_y, gvk_screen_h) + Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_sizeof + VkOffset3D_x, gvk_screen_w) + Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1) + Vk.put_i32(blit, VkImageBlit_dstSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) + Vk.put_i32(blit, VkImageBlit_dstSubresource + VkImageSubresourceLayers_layerCount, 1) + Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_x, gvk_swap_w) + Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_y, gvk_swap_h) + Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1) + Vk.cmd_blit_image(cb, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR) + gvk_barrier(cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + gvk_barrier(cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) + gvk_once_end(cb) + gvk_cb = null + let pi = bytes(VkPresentInfoKHR_sizeof) + Vk.zero(pi, VkPresentInfoKHR_sizeof) + Vk.put_i32(pi, VkPresentInfoKHR_sType, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR) + let chains = bytes(8) + Vk.put_i64(chains, 0, gvk_swap) + Vk.put_i32(pi, VkPresentInfoKHR_swapchainCount, 1) + Vk.put_ptr(pi, VkPresentInfoKHR_pSwapchains, chains) + Vk.put_ptr(pi, VkPresentInfoKHR_pImageIndices, idx) + r = Vk.queue_present_khr(gvk_queue, pi) + if r == VK_ERROR_OUT_OF_DATE_KHR or r == VK_SUBOPTIMAL_KHR { gvk_swap_stale = true } +} + +# a window's client area changed: the screen images and the swapchain follow it +var gvk_size_buf: words = null +function gvk_resize_check() -> bool { + if gvk_swap == 0 { return false } + if gvk_size_buf == null { gvk_size_buf = words(4) } + win_gl_drawable(gvk_size_buf) + let w = gvk_size_buf[0] + let h = gvk_size_buf[1] + if w <= 0 or h <= 0 { return false } + if w == gl_w and h == gl_h and not gvk_swap_stale { return false } + gvk_flush() + gl_w = w + gl_h = h + gvk_screen_make(w, h) + gvk_swap_make(w, h) + return true +} diff --git a/runtime/native/gl.ll b/runtime/native/gl.ll index c8aeabe1..2683fe4a 100644 --- a/runtime/native/gl.ll +++ b/runtime/native/gl.ll @@ -376,3 +376,12 @@ define weak ptr @win_native_instance() { entry: ret ptr null } +; the drawable's size, for a graphics API that follows the window without a GL context (render3d's +; Vulkan resize check); headless there is no window, so 0 x 0. cocoa.ll's real one wins when linked. +define weak void @win_gl_drawable(ptr %out) { +entry: + store i32 0, ptr %out + %o1 = getelementptr i32, ptr %out, i32 1 + store i32 0, ptr %o1 + ret void +}