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:
parent
66940f9c0a
commit
1b7408c6a1
4 changed files with 254 additions and 19 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue