Merge branch 'vk/leaks' into lang/foundations
This commit is contained in:
commit
8808dda605
11 changed files with 284 additions and 113 deletions
15
changes/vulkan-leaks.md
Normal file
15
changes/vulkan-leaks.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**The Vulkan renderer no longer leaks memory as it loads and draws.** Every texture upload
|
||||
malloc'd a CPU copy of its pixels and never freed it (236 MB over the valley's boot, and again on
|
||||
every model loaded later); the pixels are now converted straight into the mapped staging buffer.
|
||||
The per-level and per-layer views made for mipmapped and layered targets outlived their texture,
|
||||
and on MoltenVK a view keeps its Metal texture alive, so every rebuild of the screen-sized targets
|
||||
left the last set in memory; a released texture now takes its views with it, and the cache is
|
||||
keyed by numbers rather than a string built on every call. The Vulkan structs filled for a draw, a
|
||||
pass, a barrier, a descriptor set or a buffer (about sixty call sites, several per draw) come from
|
||||
a reused 1 MB scratch ring instead of a malloc each; the descriptor-set cache and the retired
|
||||
buffer list are emptied and refilled rather than replaced; and on macOS the frame drains an
|
||||
autorelease pool (`Vk.frame_pool()`), which MoltenVK and the layer rely on. `R3D_VK_PROF` now
|
||||
also prints, every 120 frames, the Vulkan objects made and destroyed by kind and the length of
|
||||
every cache, so a kind made each frame and never destroyed shows at once.
|
||||
|
|
@ -192,7 +192,7 @@ program Smooth {
|
|||
handler Draw(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Render {
|
||||
r3d_frame(render3d_st, float(Time.elapsed()))
|
||||
smooth_st.frame += 1
|
||||
if smooth_st.frame == smooth_st.shot_at { Gl.screenshot(path: "build/smooth.ppm") }
|
||||
Gl.swap()
|
||||
if smooth_st.frame == smooth_st.shot_at { r3d_screenshot(render3d_st, "build/smooth.ppm") }
|
||||
r3d_present(render3d_st)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,6 +189,9 @@ export state Render3dState {
|
|||
gvk_pool: long = 0
|
||||
gvk_fence: bytes = null
|
||||
gvk_frame_fence: bytes = null # the presented frame still on the GPU (one frame in flight)
|
||||
gvk_tmp_buf: pointer = null # the per-call scratch ring (gvk_tmp)
|
||||
gvk_tmp_off: int = 0
|
||||
gvk_seen: words = null # gvk_draw's vertex buffers already bound, reused
|
||||
gvk_frame_pending: bool = false
|
||||
gvk_frame_pending_cb: pointer = null
|
||||
gvk_frame_pending_no: int = -1 # which frame it is (gvk_frame_no when it was submitted)
|
||||
|
|
@ -197,6 +200,20 @@ export state Render3dState {
|
|||
gvk_retired_frame: []int = null # the last frame that read each retired buffer
|
||||
gvk_labels: bool = false # R3D_VK_LABELS: each profiled pass is a debug label (Metal System Trace)
|
||||
gvk_inflight: int = -1
|
||||
gvk_nopool: bool = false
|
||||
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
|
||||
gvk_mk_img: int = 0
|
||||
gvk_mk_view: int = 0
|
||||
gvk_mk_buf: int = 0
|
||||
gvk_mk_mem: int = 0
|
||||
gvk_mk_smp: int = 0
|
||||
gvk_mk_set: int = 0
|
||||
gvk_mk_cmd: int = 0
|
||||
gvk_mk_dpool: int = 0
|
||||
gvk_mk_x_img: int = 0
|
||||
gvk_mk_x_view: int = 0
|
||||
gvk_mk_x_buf: int = 0
|
||||
gvk_mk_x_mem: int = 0
|
||||
gvk_label_depth: int = 0 # -1 until asked: 1 leaves the presented frame in flight
|
||||
gvk_has_hqr: bool = false
|
||||
gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick
|
||||
|
|
@ -269,8 +286,9 @@ export state Render3dState {
|
|||
gvk_screen_depth: int = 0
|
||||
gvk_screen_w: int = 0
|
||||
gvk_screen_h: int = 0
|
||||
gvk_layer_views: []string = null # "tex:layer" -> index into gvk_layer_view
|
||||
gvk_layer_views: []int = null # generation * 4096 + layer, beside gvk_layer_view_tex
|
||||
gvk_layer_view: []long = null
|
||||
gvk_layer_view_tex: []int = null # the texture each cached view is of, so a release drops them
|
||||
gvk_fb_ncolor: words = null # per framebuffer: colour slots drawn (draw buffers; 0 = none)
|
||||
gvk_hdr_want: bool = false # r3d_hdr: the setting
|
||||
gvk_hdr_on: bool = false # the swapchain is HDR10 now
|
||||
|
|
@ -377,6 +395,11 @@ export state Render3dState {
|
|||
gg_mesh: []Mesh = null
|
||||
gg_n: int = 0
|
||||
gg_max: int = 0
|
||||
gg_rb: words = null # the per-frame dispatch arguments, made once (grass_gpu.ludic)
|
||||
gg_cb: words = null
|
||||
gg_bufs: words = null
|
||||
gg_texs: words = null
|
||||
gg_pr: words = null
|
||||
grass_band_mesh: []Mesh = null # per band: the blade it draws (rows fall with distance)
|
||||
grass_mesh3: Mesh = null # three rows: the middle distance
|
||||
grass_mesh2: Mesh = null # two rows, one quad: far enough that the arch is under a pixel
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ function gpu_query_new(render3d_st: mut Render3dState, n: int, ids: words) -> vo
|
|||
function gpu_query_begin(render3d_st: mut Render3dState, id: int) -> void { if render3d_st.gpu_kind == GPU_VK { gvk_query_begin(render3d_st, id); return }; gl_begin_query(GL_TIME_ELAPSED, id) }
|
||||
function gpu_query_end(render3d_st: mut Render3dState) -> void { if render3d_st.gpu_kind == GPU_VK { gvk_query_end(render3d_st); return }; gl_end_query(GL_TIME_ELAPSED) }
|
||||
# true once the query has its result; the nanoseconds (low 32 bits) are then in out[0]
|
||||
function gpu_query_result(render3d_st: Render3dState, id: int, out: words) -> bool {
|
||||
function gpu_query_result(render3d_st: mut Render3dState, id: int, out: words) -> bool {
|
||||
if render3d_st.gpu_kind == GPU_VK { return gvk_query_result(render3d_st, id, out) }
|
||||
gl_get_query_objectiv(id, GL_QUERY_RESULT_AVAILABLE, out)
|
||||
if out[0] == 0 { return false }
|
||||
|
|
|
|||
|
|
@ -292,12 +292,13 @@ function gvk_alloc(render3d_st: mut Render3dState, req: bytes, want: int) -> lon
|
|||
if t < 0 and want == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT { t = gvk_mem_type(render3d_st, allowed, 0) }
|
||||
let zero: long = 0
|
||||
if t < 0 { print(`r3d: vulkan: no memory type for properties {want}`); return zero }
|
||||
let mai = bytes(VkMemoryAllocateInfo_sizeof)
|
||||
let mai = gvk_tmp(render3d_st, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.zero(mai, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, Vk.get_i64(req, VkMemoryRequirements_size))
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_mem += 1
|
||||
let r = Vk.allocate_memory(render3d_st.gvk_dev, mai, null, out)
|
||||
if r != VK_SUCCESS { print(`r3d: vulkan: vkAllocateMemory failed (VkResult {r})`); return zero }
|
||||
render3d_st.gvk_n_allocs += 1
|
||||
|
|
@ -331,20 +332,21 @@ function gvk_mem_init(render3d_st: mut Render3dState) -> void {
|
|||
# one vkAllocateMemory of `size` bytes of memory type t, mapped when host-visible; 0 on failure
|
||||
function gvk_mem_raw(render3d_st: mut Render3dState, t: int, size: int, host: bool, out_map: []pointer) -> long {
|
||||
let zero: long = 0
|
||||
let mai = bytes(VkMemoryAllocateInfo_sizeof)
|
||||
let mai = gvk_tmp(render3d_st, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.zero(mai, VkMemoryAllocateInfo_sizeof)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
let size_l: long = size
|
||||
Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, size_l)
|
||||
Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_mem += 1
|
||||
let r = Vk.allocate_memory(render3d_st.gvk_dev, mai, null, out)
|
||||
if r != VK_SUCCESS { gvk_note(render3d_st, `r3d: vulkan: vkAllocateMemory of {size} bytes failed (VkResult {r}, {render3d_st.gvk_n_allocs} allocations live)`); return zero }
|
||||
render3d_st.gvk_n_allocs += 1
|
||||
let mem = gvk_handle(out)
|
||||
out_map[0] = null
|
||||
if host {
|
||||
let pp = bytes(8)
|
||||
let pp = gvk_tmp(render3d_st, 8)
|
||||
if Vk.map_memory(render3d_st.gvk_dev, mem, zero, size_l, 0, pp) != VK_SUCCESS { gvk_note(render3d_st, "r3d: vulkan: vkMapMemory failed"); return zero }
|
||||
out_map[0] = Vk.get_ptr(pp, 0)
|
||||
}
|
||||
|
|
@ -447,6 +449,7 @@ function gvk_mem_free(render3d_st: mut Render3dState, a: int) -> void {
|
|||
let zero: long = 0
|
||||
if b < 0 {
|
||||
if render3d_st.gvk_al_map[a] != null { Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_al_mem[a]) }
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_al_mem[a], null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
} else {
|
||||
|
|
@ -466,6 +469,7 @@ function gvk_mem_free(render3d_st: mut Render3dState, a: int) -> void {
|
|||
if off == 0 and n == render3d_st.gvk_blk_size[b] {
|
||||
# the block is empty again: give it back, or a world swapped out keeps its memory for good
|
||||
if render3d_st.gvk_blk_map[b] != null { Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_blk_mem[b]) }
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_blk_mem[b], null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
render3d_st.gvk_blk_mem[b] = zero; render3d_st.gvk_blk_map[b] = null; render3d_st.gvk_blk_kind[b] = -1; render3d_st.gvk_blk_size[b] = 0
|
||||
|
|
@ -503,17 +507,35 @@ function gvk_cmd_init(render3d_st: mut Render3dState) -> bool {
|
|||
return true
|
||||
}
|
||||
# a command buffer, begun
|
||||
function gvk_once_begin(render3d_st: Render3dState) -> pointer {
|
||||
let cbai = bytes(VkCommandBufferAllocateInfo_sizeof)
|
||||
# ---- scratch -------------------------------------------------------------------------------
|
||||
# The structs a Vulkan call reads are copied before it returns, so the ones built for a draw, a
|
||||
# pass, a barrier or a submit need not outlive it. They were each a bytes() - a malloc nothing
|
||||
# freed, several per draw: about 0.5 MB a frame, and one windowed run grew past 150 GB. They
|
||||
# come from this ring instead: one block, handed out in order and wrapped when it is used up.
|
||||
const GVK_TMP_BYTES: int = 1048576
|
||||
|
||||
function gvk_tmp(render3d_st: mut Render3dState, n: int) -> pointer {
|
||||
if render3d_st.gvk_tmp_buf == null { render3d_st.gvk_tmp_buf = bytes(GVK_TMP_BYTES) }
|
||||
let sz = (n + 15) / 16 * 16
|
||||
if sz > GVK_TMP_BYTES { print(`r3d: vulkan: {n} bytes of scratch asked for at once`); return null }
|
||||
if render3d_st.gvk_tmp_off + sz > GVK_TMP_BYTES { render3d_st.gvk_tmp_off = 0 }
|
||||
let p = mem_off(render3d_st.gvk_tmp_buf, render3d_st.gvk_tmp_off)
|
||||
render3d_st.gvk_tmp_off += sz
|
||||
return p
|
||||
}
|
||||
|
||||
function gvk_once_begin(render3d_st: mut Render3dState) -> pointer {
|
||||
let cbai = gvk_tmp(render3d_st, VkCommandBufferAllocateInfo_sizeof)
|
||||
Vk.zero(cbai, VkCommandBufferAllocateInfo_sizeof)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO)
|
||||
Vk.put_i64(cbai, VkCommandBufferAllocateInfo_commandPool, render3d_st.gvk_pool)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY)
|
||||
Vk.put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1)
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_cmd += 1
|
||||
if Vk.allocate_command_buffers(render3d_st.gvk_dev, cbai, cbs) != VK_SUCCESS { return null }
|
||||
let cb = Vk.get_ptr(cbs, 0)
|
||||
let cbbi = bytes(VkCommandBufferBeginInfo_sizeof)
|
||||
let cbbi = gvk_tmp(render3d_st, VkCommandBufferBeginInfo_sizeof)
|
||||
Vk.zero(cbbi, VkCommandBufferBeginInfo_sizeof)
|
||||
Vk.put_i32(cbbi, VkCommandBufferBeginInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO)
|
||||
Vk.put_i32(cbbi, VkCommandBufferBeginInfo_flags, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)
|
||||
|
|
@ -525,10 +547,10 @@ function gvk_once_end(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
gvk_frame_wait(render3d_st)
|
||||
var r = Vk.end_command_buffer(cb)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) }
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, cb)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_fence)
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
let si = gvk_tmp(render3d_st, VkSubmitInfo_sizeof)
|
||||
Vk.zero(si, VkSubmitInfo_sizeof)
|
||||
Vk.put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO)
|
||||
Vk.put_i32(si, VkSubmitInfo_commandBufferCount, 1)
|
||||
|
|
@ -556,6 +578,7 @@ function gvk_inflight_on(render3d_st: mut Render3dState) -> bool {
|
|||
if render3d_st.gvk_inflight < 0 {
|
||||
render3d_st.gvk_inflight = 0
|
||||
if Os.platform() == "macos" { render3d_st.gvk_inflight = 1 }
|
||||
render3d_st.gvk_nopool = r3d_env_has(render3d_st, "R3D_VK_NOPOOL")
|
||||
if r3d_env_has(render3d_st, "R3D_VK_INFLIGHT") { render3d_st.gvk_inflight = Text.to_int(r3d_env(render3d_st, "R3D_VK_INFLIGHT")) }
|
||||
}
|
||||
return render3d_st.gvk_inflight == 1
|
||||
|
|
@ -565,10 +588,10 @@ function gvk_frame_submit(render3d_st: mut Render3dState, cb: pointer) -> bool {
|
|||
gvk_frame_wait(render3d_st)
|
||||
var r = Vk.end_command_buffer(cb)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkEndCommandBuffer", r) }
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, cb)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence)
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
let si = gvk_tmp(render3d_st, VkSubmitInfo_sizeof)
|
||||
Vk.zero(si, VkSubmitInfo_sizeof)
|
||||
Vk.put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO)
|
||||
Vk.put_i32(si, VkSubmitInfo_commandBufferCount, 1)
|
||||
|
|
@ -585,7 +608,7 @@ function gvk_frame_wait(render3d_st: mut Render3dState) -> void {
|
|||
if not render3d_st.gvk_frame_pending { return }
|
||||
let forever: long = -1
|
||||
Vk.wait_for_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_frame_fence, 1, forever)
|
||||
let cbs = bytes(8)
|
||||
let cbs = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_ptr(cbs, 0, render3d_st.gvk_frame_pending_cb)
|
||||
Vk.free_command_buffers(render3d_st.gvk_dev, render3d_st.gvk_pool, 1, cbs)
|
||||
render3d_st.gvk_frame_pending = false
|
||||
|
|
@ -615,12 +638,12 @@ function gvk_shutdown(render3d_st: mut Render3dState) -> void {
|
|||
function gvk_query_new(render3d_st: mut Render3dState, n: int, ids: words) -> void {
|
||||
for i in 0 .. n { ids[i] = i }
|
||||
if not render3d_st.gvk_has_hqr or render3d_st.gvk_dev == null { return }
|
||||
let qci = bytes(VkQueryPoolCreateInfo_sizeof)
|
||||
let qci = gvk_tmp(render3d_st, VkQueryPoolCreateInfo_sizeof)
|
||||
Vk.zero(qci, VkQueryPoolCreateInfo_sizeof)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_sType, VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_queryType, VK_QUERY_TYPE_TIMESTAMP)
|
||||
Vk.put_i32(qci, VkQueryPoolCreateInfo_queryCount, n * 2)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
if Vk.create_query_pool(render3d_st.gvk_dev, qci, null, out) != VK_SUCCESS { return }
|
||||
render3d_st.gvk_qpool = gvk_handle(out)
|
||||
Vk.reset_query_pool(render3d_st.gvk_dev, render3d_st.gvk_qpool, 0, n * 2)
|
||||
|
|
@ -636,9 +659,9 @@ function gvk_query_end(render3d_st: mut Render3dState) -> void {
|
|||
Vk.cmd_write_timestamp(gvk_frame_cb(render3d_st), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, render3d_st.gvk_qpool, render3d_st.gvk_q_active * 2 + 1)
|
||||
render3d_st.gvk_q_active = -1
|
||||
}
|
||||
function gvk_query_result(render3d_st: Render3dState, id: int, out: words) -> bool {
|
||||
function gvk_query_result(render3d_st: mut Render3dState, id: int, out: words) -> bool {
|
||||
if render3d_st.gvk_qpool == 0 { return false }
|
||||
let data = bytes(16)
|
||||
let data = gvk_tmp(render3d_st, 16)
|
||||
let size: long = 16
|
||||
let stride: long = 8
|
||||
if Vk.get_query_pool_results(render3d_st.gvk_dev, render3d_st.gvk_qpool, id * 2, 2, size, data, stride, VK_QUERY_RESULT_64_BIT) != VK_SUCCESS { return false }
|
||||
|
|
@ -653,7 +676,7 @@ function gvk_label_begin(render3d_st: mut Render3dState, name: pointer) -> void
|
|||
# only inside a frame: a pass the load profiles comes before the frame's pools exist
|
||||
if render3d_st.gpu_kind != GPU_VK or render3d_st.gvk_cb == null { return }
|
||||
let cb = render3d_st.gvk_cb
|
||||
let li = bytes(VkDebugUtilsLabelEXT_sizeof)
|
||||
let li = gvk_tmp(render3d_st, VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.zero(li, VkDebugUtilsLabelEXT_sizeof)
|
||||
Vk.put_i32(li, VkDebugUtilsLabelEXT_sType, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT)
|
||||
Vk.put_ptr(li, VkDebugUtilsLabelEXT_pLabelName, name)
|
||||
|
|
|
|||
|
|
@ -195,15 +195,16 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
let nb = render3d_st.gvk_cp_nbuf[c]
|
||||
var nt = 0
|
||||
if render3d_st.gvk_cp_ntex != null and c < len(render3d_st.gvk_cp_ntex) { nt = render3d_st.gvk_cp_ntex[c] }
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
let dsai = gvk_tmp(render3d_st, VkDescriptorSetAllocateInfo_sizeof)
|
||||
Vk.zero(dsai, VkDescriptorSetAllocateInfo_sizeof)
|
||||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO)
|
||||
Vk.put_i64(dsai, VkDescriptorSetAllocateInfo_descriptorPool, render3d_st.gvk_dpool)
|
||||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
let layouts = bytes(8)
|
||||
let layouts = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(layouts, 0, render3d_st.gvk_cp_dsl[c])
|
||||
Vk.put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, layouts)
|
||||
let sets = bytes(8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_set += 1
|
||||
let r = Vk.allocate_descriptor_sets(render3d_st.gvk_dev, dsai, sets)
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkAllocateDescriptorSets (compute)", r); return }
|
||||
let set = Vk.get_i64(sets, 0)
|
||||
|
|
@ -212,11 +213,11 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
let ww = VkWriteDescriptorSet_sizeof
|
||||
let bw = VkDescriptorBufferInfo_sizeof
|
||||
let iw = VkDescriptorImageInfo_sizeof
|
||||
let writes = bytes(ww * (nb + 1 + nt))
|
||||
let writes = gvk_tmp(render3d_st, ww * (nb + 1 + nt))
|
||||
Vk.zero(writes, ww * (nb + 1 + nt))
|
||||
let infos = bytes(bw * (nb + 1))
|
||||
let infos = gvk_tmp(render3d_st, bw * (nb + 1))
|
||||
Vk.zero(infos, bw * (nb + 1))
|
||||
let iis = bytes(iw * (nt + 1))
|
||||
let iis = gvk_tmp(render3d_st, iw * (nt + 1))
|
||||
Vk.zero(iis, iw * (nt + 1))
|
||||
for k in 0 .. nb + 1 {
|
||||
var buf = render3d_st.gvk_ring_buf
|
||||
|
|
@ -254,7 +255,7 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
|
|||
Vk.cmd_bind_pipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, render3d_st.gvk_cp_pipe[c])
|
||||
Vk.cmd_bind_descriptor_sets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, render3d_st.gvk_cp_layout[c], 0, 1, sets, 0, null)
|
||||
Vk.cmd_dispatch(cb, gx, gy, gz)
|
||||
let mb = bytes(VkMemoryBarrier_sizeof)
|
||||
let mb = gvk_tmp(render3d_st, VkMemoryBarrier_sizeof)
|
||||
Vk.zero(mb, VkMemoryBarrier_sizeof)
|
||||
Vk.put_i32(mb, VkMemoryBarrier_sType, VK_STRUCTURE_TYPE_MEMORY_BARRIER)
|
||||
Vk.put_i32(mb, VkMemoryBarrier_srcAccessMask, VK_ACCESS_SHADER_WRITE_BIT)
|
||||
|
|
@ -704,6 +705,7 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
let out = bytes(8)
|
||||
render3d_st.gvk_dpools = new []long
|
||||
for k in 0 .. 2 {
|
||||
render3d_st.gvk_mk_dpool += 1
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool", r) }
|
||||
push(render3d_st.gvk_dpools, gvk_handle(out))
|
||||
|
|
@ -717,6 +719,8 @@ function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
|
|||
}
|
||||
|
||||
function gvk_frame_reset(render3d_st: mut Render3dState) -> void {
|
||||
# what MoltenVK autoreleased last frame goes back here (a no-op off macOS); R3D_VK_NOPOOL=1 for an A/B
|
||||
if not render3d_st.gvk_nopool { Vk.frame_pool() }
|
||||
let slot = render3d_st.gvk_frame_no & 1
|
||||
render3d_st.gvk_ring_off = slot * GVK_RING_BYTES
|
||||
render3d_st.gvk_ring_end = (slot + 1) * GVK_RING_BYTES
|
||||
|
|
@ -756,6 +760,7 @@ function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
|
|||
Vk.put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 2)
|
||||
Vk.put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, sizes)
|
||||
let out = bytes(8)
|
||||
render3d_st.gvk_mk_dpool += 1
|
||||
let r = Vk.create_descriptor_pool(render3d_st.gvk_dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateDescriptorPool (kept sets)", r) }
|
||||
render3d_st.gvk_kpool = gvk_handle(out)
|
||||
|
|
@ -764,8 +769,13 @@ function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
|
|||
}
|
||||
|
||||
function gvk_sc_clear(render3d_st: mut Render3dState) -> void {
|
||||
# emptied, not replaced: five fresh lists each time the pool filled were never given back
|
||||
if render3d_st.gvk_sc_prog == null {
|
||||
render3d_st.gvk_sc_prog = new []int; render3d_st.gvk_sc_koff = new []int; render3d_st.gvk_sc_set = new []long; render3d_st.gvk_sc_next = new []int
|
||||
render3d_st.gvk_sc_keys = new []long
|
||||
}
|
||||
List.clear(render3d_st.gvk_sc_prog); List.clear(render3d_st.gvk_sc_koff); List.clear(render3d_st.gvk_sc_set); List.clear(render3d_st.gvk_sc_next)
|
||||
List.clear(render3d_st.gvk_sc_keys)
|
||||
if render3d_st.gvk_sc_head == null { render3d_st.gvk_sc_head = words(4096) }
|
||||
for i in 0 .. 4096 { render3d_st.gvk_sc_head[i] = -1 }
|
||||
}
|
||||
|
|
@ -851,9 +861,9 @@ function gvk_draw_set(render3d_st: mut Render3dState, p: int, tx: words, tx_w: i
|
|||
function gvk_sc_make(render3d_st: mut Render3dState, p: int, nt: int) -> long {
|
||||
let zero: long = 0
|
||||
let v = render3d_st.gvk_prog_var[p]
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
let layouts = bytes(8)
|
||||
let sets = bytes(8)
|
||||
let dsai = gvk_tmp(render3d_st, VkDescriptorSetAllocateInfo_sizeof)
|
||||
let layouts = gvk_tmp(render3d_st, 8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
var r = 0
|
||||
var tries = 0
|
||||
while tries < 2 {
|
||||
|
|
@ -863,16 +873,17 @@ function gvk_sc_make(render3d_st: mut Render3dState, p: int, nt: int) -> long {
|
|||
Vk.put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
Vk.put_i64(layouts, 0, render3d_st.gvk_prog_dsl[p])
|
||||
Vk.put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, layouts)
|
||||
render3d_st.gvk_mk_set += 1
|
||||
r = Vk.allocate_descriptor_sets(render3d_st.gvk_dev, dsai, sets)
|
||||
if r == VK_SUCCESS { tries = 2 } else { gvk_kpool_reset(render3d_st); tries += 1 }
|
||||
}
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkAllocateDescriptorSets (kept sets)", r); return zero }
|
||||
let set = Vk.get_i64(sets, 0)
|
||||
let ww = VkWriteDescriptorSet_sizeof
|
||||
let writes = bytes(ww * (nt + 3))
|
||||
let writes = gvk_tmp(render3d_st, ww * (nt + 3))
|
||||
Vk.zero(writes, ww * (nt + 3))
|
||||
let bis = bytes(VkDescriptorBufferInfo_sizeof * 2)
|
||||
let iis = bytes(VkDescriptorImageInfo_sizeof * (nt + 1))
|
||||
let bis = gvk_tmp(render3d_st, VkDescriptorBufferInfo_sizeof * 2)
|
||||
let iis = gvk_tmp(render3d_st, VkDescriptorImageInfo_sizeof * (nt + 1))
|
||||
var nw = 0
|
||||
var bi = 0
|
||||
for stage in 0 .. 2 {
|
||||
|
|
@ -1011,9 +1022,10 @@ function gvk_frame_cb(render3d_st: mut Render3dState) -> pointer {
|
|||
# on its own. Keyed by the image's generation, so a replaced image never reuses a stale view.
|
||||
function gvk_view_of(render3d_st: mut Render3dState, tex: int, layer1: int) -> long {
|
||||
if layer1 == 0 and render3d_st.gvk_tex_levels[tex] <= 1 and render3d_st.gvk_tex_layers[tex] <= 1 { return render3d_st.gvk_tex_view[tex] }
|
||||
let key = `{tex}:{render3d_st.gvk_tex_gen[tex]}:{layer1}`
|
||||
if render3d_st.gvk_layer_views == null { render3d_st.gvk_layer_views = new []string; render3d_st.gvk_layer_view = new []long }
|
||||
for i in 0 .. len(render3d_st.gvk_layer_views) { if render3d_st.gvk_layer_views[i] == key { return render3d_st.gvk_layer_view[i] } }
|
||||
# keyed by numbers, not a string built on every call: the texture, its generation and the layer
|
||||
let key = render3d_st.gvk_tex_gen[tex] * 4096 + layer1
|
||||
if render3d_st.gvk_layer_views == null { render3d_st.gvk_layer_views = new []int; render3d_st.gvk_layer_view = new []long; render3d_st.gvk_layer_view_tex = new []int }
|
||||
for i in 0 .. len(render3d_st.gvk_layer_views) { if render3d_st.gvk_layer_view_tex[i] == tex and render3d_st.gvk_layer_views[i] == key { return render3d_st.gvk_layer_view[i] } }
|
||||
let depth = render3d_st.gvk_tex_vkfmt[tex] == VK_FORMAT_D32_SFLOAT
|
||||
let vci = bytes(VkImageViewCreateInfo_sizeof)
|
||||
Vk.zero(vci, VkImageViewCreateInfo_sizeof)
|
||||
|
|
@ -1028,16 +1040,18 @@ function gvk_view_of(render3d_st: mut Render3dState, tex: int, layer1: int) -> l
|
|||
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, 1)
|
||||
let out = bytes(8)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_view += 1
|
||||
if Vk.create_image_view(render3d_st.gvk_dev, vci, null, out) != VK_SUCCESS { return zero }
|
||||
push(render3d_st.gvk_layer_views, key)
|
||||
push(render3d_st.gvk_layer_view, gvk_handle(out))
|
||||
push(render3d_st.gvk_layer_view_tex, tex)
|
||||
return gvk_handle(out)
|
||||
}
|
||||
# the layer range a barrier for an attachment covers: the whole image unless one layer is drawn
|
||||
function gvk_att_barrier(render3d_st: Render3dState, cb: pointer, tex: int, layer1: int, depth: bool, old_layout: int, new_layout: int) -> void {
|
||||
function gvk_att_barrier(render3d_st: mut Render3dState, cb: pointer, tex: int, layer1: int, depth: bool, old_layout: int, new_layout: int) -> void {
|
||||
# an attachment whose image was never made (no memory for it) has nothing to transition
|
||||
if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) or render3d_st.gvk_tex_image[tex] == 0 { return }
|
||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||
let b = gvk_tmp(render3d_st, VkImageMemoryBarrier_sizeof)
|
||||
Vk.zero(b, VkImageMemoryBarrier_sizeof)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_srcAccessMask, gvk_layout_access(old_layout))
|
||||
|
|
@ -1085,7 +1099,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
let cb = gvk_frame_cb(render3d_st)
|
||||
gvk_pass_collect(render3d_st, render3d_st.gvk_fb_cur, rec, rec_o)
|
||||
let aw = VkRenderingAttachmentInfo_sizeof
|
||||
let catt = bytes(aw * 3)
|
||||
let catt = gvk_tmp(render3d_st, aw * 3)
|
||||
Vk.zero(catt, aw * 3)
|
||||
render3d_st.gvk_pass_w = 0
|
||||
render3d_st.gvk_pass_h = 0
|
||||
|
|
@ -1108,7 +1122,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
render3d_st.gvk_pass_samples = gvk_tex_samples_of(render3d_st, tex)
|
||||
if render3d_st.gvk_pass_w == 0 { render3d_st.gvk_pass_w = gvk_tex_w(render3d_st, tex); render3d_st.gvk_pass_h = gvk_tex_h(render3d_st, tex) }
|
||||
}
|
||||
let datt = bytes(aw)
|
||||
let datt = gvk_tmp(render3d_st, aw)
|
||||
Vk.zero(datt, aw)
|
||||
let dtex = render3d_st.gvk_pass_dep[0]
|
||||
if dtex > 0 {
|
||||
|
|
@ -1126,7 +1140,7 @@ function gvk_pass_begin(render3d_st: mut Render3dState, rec: words, rec_o: int)
|
|||
if render3d_st.gvk_pass_w == 0 { render3d_st.gvk_pass_w = gvk_tex_w(render3d_st, dtex); render3d_st.gvk_pass_h = gvk_tex_h(render3d_st, dtex) }
|
||||
}
|
||||
render3d_st.gvk_clear_bits = 0
|
||||
let ri = bytes(VkRenderingInfo_sizeof)
|
||||
let ri = gvk_tmp(render3d_st, VkRenderingInfo_sizeof)
|
||||
Vk.zero(ri, VkRenderingInfo_sizeof)
|
||||
Vk.put_i32(ri, VkRenderingInfo_sType, VK_STRUCTURE_TYPE_RENDERING_INFO)
|
||||
Vk.put_i32(ri, VkRenderingInfo_renderArea + VkRect2D_extent + VkExtent2D_width, render3d_st.gvk_pass_w)
|
||||
|
|
@ -1157,7 +1171,7 @@ function gvk_clear(render3d_st: mut Render3dState, mask: int, rec: words, rec_o:
|
|||
if not render3d_st.gvk_in_pass { render3d_st.gvk_clear_bits = render3d_st.gvk_clear_bits | mask; return }
|
||||
let cb = render3d_st.gvk_cb
|
||||
let caw = VkClearAttachment_sizeof
|
||||
let atts = bytes(caw * 4)
|
||||
let atts = gvk_tmp(render3d_st, caw * 4)
|
||||
Vk.zero(atts, caw * 4)
|
||||
var n = 0
|
||||
if (mask & GL_COLOR_BUFFER_BIT) != 0 {
|
||||
|
|
@ -1174,7 +1188,7 @@ function gvk_clear(render3d_st: mut Render3dState, mask: int, rec: words, rec_o:
|
|||
n += 1
|
||||
}
|
||||
if n == 0 { return }
|
||||
let rect = bytes(VkClearRect_sizeof)
|
||||
let rect = gvk_tmp(render3d_st, VkClearRect_sizeof)
|
||||
Vk.zero(rect, VkClearRect_sizeof)
|
||||
Vk.put_i32(rect, VkClearRect_rect + VkRect2D_extent + VkExtent2D_width, render3d_st.gvk_pass_w)
|
||||
Vk.put_i32(rect, VkClearRect_rect + VkRect2D_extent + VkExtent2D_height, render3d_st.gvk_pass_h)
|
||||
|
|
@ -1187,8 +1201,8 @@ function gvk_tex_h(render3d_st: Render3dState, tex: int) -> int { return render3
|
|||
|
||||
# viewport and scissor for the draw; OpenGL's rows count from the bottom and so do a Vulkan
|
||||
# target's here (no y flip), so both pass straight through
|
||||
function gvk_set_view(render3d_st: Render3dState, cb: pointer) -> void {
|
||||
let vp = bytes(VkViewport_sizeof)
|
||||
function gvk_set_view(render3d_st: mut Render3dState, cb: pointer) -> void {
|
||||
let vp = gvk_tmp(render3d_st, VkViewport_sizeof)
|
||||
Vk.zero(vp, VkViewport_sizeof)
|
||||
Vk.put_i32(vp, VkViewport_x, float_bits(float(render3d_st.gvk_vp[0])))
|
||||
Vk.put_i32(vp, VkViewport_y, float_bits(float(render3d_st.gvk_vp[1])))
|
||||
|
|
@ -1196,7 +1210,7 @@ function gvk_set_view(render3d_st: Render3dState, cb: pointer) -> void {
|
|||
Vk.put_i32(vp, VkViewport_height, float_bits(float(render3d_st.gvk_vp[3])))
|
||||
Vk.put_i32(vp, VkViewport_maxDepth, 0x3F800000)
|
||||
Vk.cmd_set_viewport(cb, 0, 1, vp)
|
||||
let sc = bytes(VkRect2D_sizeof)
|
||||
let sc = gvk_tmp(render3d_st, VkRect2D_sizeof)
|
||||
Vk.zero(sc, VkRect2D_sizeof)
|
||||
if render3d_st.gvk_sc[0] == 1 {
|
||||
Vk.put_i32(sc, VkRect2D_offset + VkOffset2D_x, render3d_st.gvk_sc[1])
|
||||
|
|
@ -1238,16 +1252,17 @@ function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState,
|
|||
let set = gvk_draw_set(render3d_st, p, tx, tx_w, tx_cap)
|
||||
if set == 0 { return }
|
||||
if prof { render3d_st.gvk_us_set = render3d_st.gvk_us_set + (gl_now_us() - t1) }
|
||||
let sets = bytes(8)
|
||||
let sets = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(sets, 0, set)
|
||||
Vk.cmd_bind_descriptor_sets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, render3d_st.gvk_prog_layout[p], 0, 1, sets, render3d_st.gvk_set_ndyn, render3d_st.gvk_set_offs)
|
||||
let v = render3d_st.gvk_prog_var[p]
|
||||
if m != null and m.attrs != null {
|
||||
# the same bindings, in the same order, as gvk_pipeline gave the layout
|
||||
let bufs = bytes(8 * (GPU_MAX_VBUFS + 1))
|
||||
let offs = bytes(8 * (GPU_MAX_VBUFS + 1))
|
||||
let bufs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
|
||||
let offs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
|
||||
Vk.zero(offs, 8 * (GPU_MAX_VBUFS + 1))
|
||||
let seen = words(GPU_MAX_VBUFS)
|
||||
if render3d_st.gvk_seen == null { render3d_st.gvk_seen = words(GPU_MAX_VBUFS) }
|
||||
let seen = render3d_st.gvk_seen
|
||||
var nbd = 0
|
||||
for i in 0 .. m.n_attrs {
|
||||
let o = i * GPU_ATTR_W
|
||||
|
|
@ -1510,7 +1525,7 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
gvk_att_barrier(render3d_st, cb, src, 0, depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layout)
|
||||
gvk_att_barrier(render3d_st, cb, dst, 0, depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, layout)
|
||||
let aw = VkRenderingAttachmentInfo_sizeof
|
||||
let att = bytes(aw)
|
||||
let att = gvk_tmp(render3d_st, aw)
|
||||
Vk.zero(att, aw)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_sType, VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO)
|
||||
Vk.put_i64(att, VkRenderingAttachmentInfo_imageView, gvk_view_of(render3d_st, src, 0))
|
||||
|
|
@ -1520,7 +1535,7 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
Vk.put_i32(att, VkRenderingAttachmentInfo_resolveImageLayout, layout)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_loadOp, VK_ATTACHMENT_LOAD_OP_LOAD)
|
||||
Vk.put_i32(att, VkRenderingAttachmentInfo_storeOp, VK_ATTACHMENT_STORE_OP_STORE)
|
||||
let ri = bytes(VkRenderingInfo_sizeof)
|
||||
let ri = gvk_tmp(render3d_st, VkRenderingInfo_sizeof)
|
||||
Vk.zero(ri, VkRenderingInfo_sizeof)
|
||||
Vk.put_i32(ri, VkRenderingInfo_sType, VK_STRUCTURE_TYPE_RENDERING_INFO)
|
||||
Vk.put_i32(ri, VkRenderingInfo_renderArea + VkRect2D_extent + VkExtent2D_width, w)
|
||||
|
|
@ -1536,9 +1551,9 @@ function gvk_resolve(render3d_st: mut Render3dState, cb: pointer, src: int, dst:
|
|||
function gvk_copy(render3d_st: mut Render3dState, cb: pointer, src: int, dst: int, depth: bool, w: int, h: int) -> void {
|
||||
if src <= 0 or dst <= 0 or render3d_st.gvk_tex_image[src] == 0 or render3d_st.gvk_tex_image[dst] == 0 { return }
|
||||
if gvk_tex_samples_of(render3d_st, src) > 1 and gvk_tex_samples_of(render3d_st, dst) == 1 { gvk_resolve(render3d_st, cb, src, dst, depth, w, h); return }
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = bytes(VkImageCopy_sizeof)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = gvk_tmp(render3d_st, VkImageCopy_sizeof)
|
||||
Vk.zero(ic, VkImageCopy_sizeof)
|
||||
var aspect = VK_IMAGE_ASPECT_COLOR_BIT
|
||||
if depth { aspect = VK_IMAGE_ASPECT_DEPTH_BIT }
|
||||
|
|
@ -1550,8 +1565,8 @@ function gvk_copy(render3d_st: mut Render3dState, cb: pointer, src: int, dst: in
|
|||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_height, h)
|
||||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image(cb, render3d_st.gvk_tex_image[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_tex_image[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ic)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[src], depth, 0, 1, render3d_st.gvk_tex_layers[src], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[dst], depth, 0, 1, render3d_st.gvk_tex_layers[dst], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
}
|
||||
function gvk_blit(render3d_st: mut Render3dState, w: int, h: int, mask: int) -> void {
|
||||
gvk_pass_end(render3d_st)
|
||||
|
|
@ -1725,20 +1740,20 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void {
|
|||
Time.sleep_us(16000)
|
||||
return
|
||||
}
|
||||
if render3d_st.gvk_swap_stale { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
let idx = bytes(4)
|
||||
if render3d_st.gvk_swap_stale { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; print("r3d: vulkan: swapchain remade at acquire"); gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
let idx = gvk_tmp(render3d_st, 4)
|
||||
Vk.reset_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_acq_fence)
|
||||
let forever: long = -1
|
||||
let zero: long = 0
|
||||
var r = Vk.acquire_next_image_khr(render3d_st.gvk_dev, render3d_st.gvk_swap, forever, zero, Vk.get_i64(render3d_st.gvk_acq_fence, 0), idx)
|
||||
if r == VK_ERROR_OUT_OF_DATE_KHR { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
if r == VK_ERROR_OUT_OF_DATE_KHR { gvk_once_end(render3d_st, cb); render3d_st.gvk_cb = null; print("r3d: vulkan: swapchain remade at acquire"); gvk_swap_make(render3d_st, render3d_st.gvk_swap_w, render3d_st.gvk_swap_h); return }
|
||||
Vk.wait_for_fences(render3d_st.gvk_dev, 1, render3d_st.gvk_acq_fence, 1, forever)
|
||||
let i = Vk.get_i32(idx, 0)
|
||||
let dst = Vk.get_i64(render3d_st.gvk_swap_images, i * 8)
|
||||
let src = render3d_st.gvk_tex_image[render3d_st.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)
|
||||
gvk_barrier(render3d_st, cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let blit = gvk_tmp(render3d_st, 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)
|
||||
|
|
@ -1752,14 +1767,14 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void {
|
|||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_y, render3d_st.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_barrier(render3d_st, cb, src, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, dst, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
|
||||
if gvk_inflight_on(render3d_st) { gvk_frame_submit(render3d_st, cb) } else { gvk_once_end(render3d_st, cb) }
|
||||
render3d_st.gvk_cb = null
|
||||
let pi = bytes(VkPresentInfoKHR_sizeof)
|
||||
let pi = gvk_tmp(render3d_st, VkPresentInfoKHR_sizeof)
|
||||
Vk.zero(pi, VkPresentInfoKHR_sizeof)
|
||||
Vk.put_i32(pi, VkPresentInfoKHR_sType, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR)
|
||||
let chains = bytes(8)
|
||||
let chains = gvk_tmp(render3d_st, 8)
|
||||
Vk.put_i64(chains, 0, render3d_st.gvk_swap)
|
||||
Vk.put_i32(pi, VkPresentInfoKHR_swapchainCount, 1)
|
||||
Vk.put_ptr(pi, VkPresentInfoKHR_pSwapchains, chains)
|
||||
|
|
@ -1779,6 +1794,8 @@ function gvk_resize_check(render3d_st: mut Render3dState) -> bool {
|
|||
let h = render3d_st.gvk_size_buf[1]
|
||||
if w <= 0 or h <= 0 { return false }
|
||||
if w == gl_width() and h == gl_height() and not render3d_st.gvk_swap_stale { return false }
|
||||
# a remake is rare (a resize); said, so one on every frame shows in the log
|
||||
print(`r3d: vulkan: swapchain remade {w}x{h} (was {gl_width()}x{gl_height()}, stale {render3d_st.gvk_swap_stale})`)
|
||||
gvk_flush(render3d_st)
|
||||
gl_set_drawable(w, h)
|
||||
gvk_screen_make(render3d_st, w, h)
|
||||
|
|
@ -1816,11 +1833,26 @@ function gvk_prof_frame(render3d_st: mut Render3dState) -> void {
|
|||
if render3d_st.gvk_n_asked != render3d_st.gvk_n_draws {
|
||||
print(`r3d: vulkan: {(render3d_st.gvk_n_asked - render3d_st.gvk_n_draws) / f} draws a frame were asked for and not made ({render3d_st.gvk_n_asked / f} asked, {render3d_st.gvk_n_draws / f} made)`)
|
||||
}
|
||||
gvk_prof_made(render3d_st)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_us_pipe = zero; render3d_st.gvk_us_set = zero; render3d_st.gvk_us_draw = zero
|
||||
render3d_st.gvk_n_draws = 0; render3d_st.gvk_n_asked = 0; render3d_st.gvk_n_flush = 0; render3d_st.gvk_prof_frames = 0; render3d_st.gvk_n_pipe_new = 0
|
||||
}
|
||||
|
||||
# what was made and destroyed over those frames, and how long the caches are: a kind made every
|
||||
# frame and never destroyed, or a cache that only grows, is a leak
|
||||
function gvk_prof_made(render3d_st: mut Render3dState) -> void {
|
||||
let r = render3d_st
|
||||
print(`r3d: vulkan made/destroyed: images {r.gvk_mk_img}/{r.gvk_mk_x_img}, views {r.gvk_mk_view}/{r.gvk_mk_x_view}, buffers {r.gvk_mk_buf}/{r.gvk_mk_x_buf}, memory {r.gvk_mk_mem}/{r.gvk_mk_x_mem}, samplers {r.gvk_mk_smp}, sets {r.gvk_mk_set}, pools {r.gvk_mk_dpool}, cmds {r.gvk_mk_cmd}`)
|
||||
print(`r3d: vulkan caches: layer views {gvk_len_i(r.gvk_layer_views)}, pipelines {gvk_len_s(r.gvk_pipe_keys)}, layouts {gvk_len_s(r.gvk_layout_keys)}, pipe cache {gvk_len_i(r.gvk_pc_prog)}, retired {gvk_len_l(r.gvk_retired_buf)}, buffers {gvk_len_l(r.gvk_buf)}, allocs {r.gvk_n_allocs}`)
|
||||
render3d_st.gvk_mk_img = 0; render3d_st.gvk_mk_x_img = 0; render3d_st.gvk_mk_view = 0; render3d_st.gvk_mk_x_view = 0
|
||||
render3d_st.gvk_mk_buf = 0; render3d_st.gvk_mk_x_buf = 0; render3d_st.gvk_mk_mem = 0; render3d_st.gvk_mk_x_mem = 0
|
||||
render3d_st.gvk_mk_smp = 0; render3d_st.gvk_mk_set = 0; render3d_st.gvk_mk_dpool = 0; render3d_st.gvk_mk_cmd = 0
|
||||
}
|
||||
function gvk_len_s(xs: []string) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
function gvk_len_i(xs: []int) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
function gvk_len_l(xs: []long) -> int { if xs == null { return 0 }; return len(xs) }
|
||||
|
||||
# ---- the pipeline cache, by integers -----------------------------------------------------------
|
||||
# gvk_pipeline builds and keys pipelines by a string; a draw only needs to find one it already has.
|
||||
# A mesh carries an interned layout id (its recorded layout, buffers named by order), the render
|
||||
|
|
|
|||
|
|
@ -88,10 +88,10 @@ function gvk_layout_access(layout: int) -> int {
|
|||
return 0
|
||||
}
|
||||
# levels [base, base + n) of every layer of an image, from one layout to another
|
||||
function gvk_barrier(cb: pointer, image: long, depth: bool, base: int, n: int, layers: int, old_layout: int, new_layout: int) -> void {
|
||||
function gvk_barrier(render3d_st: mut Render3dState, cb: pointer, image: long, depth: bool, base: int, n: int, layers: int, old_layout: int, new_layout: int) -> void {
|
||||
# an image that was never made (its memory could not be had) has nothing to transition
|
||||
if image == 0 { return }
|
||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||
let b = gvk_tmp(render3d_st, VkImageMemoryBarrier_sizeof)
|
||||
Vk.zero(b, VkImageMemoryBarrier_sizeof)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||
Vk.put_i32(b, VkImageMemoryBarrier_srcAccessMask, gvk_layout_access(old_layout))
|
||||
|
|
@ -115,28 +115,31 @@ function gvk_barrier(cb: pointer, image: long, depth: bool, base: int, n: int, l
|
|||
# a host-visible buffer of n bytes, mapped; gvk_staging_free unmaps and frees it
|
||||
function gvk_staging(render3d_st: mut Render3dState, n: int, usage: int) -> pointer {
|
||||
let size: long = n
|
||||
let bci = bytes(VkBufferCreateInfo_sizeof)
|
||||
let bci = gvk_tmp(render3d_st, VkBufferCreateInfo_sizeof)
|
||||
Vk.zero(bci, VkBufferCreateInfo_sizeof)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sType, VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
|
||||
Vk.put_i64(bci, VkBufferCreateInfo_size, size)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_usage, usage)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_buf += 1
|
||||
if Vk.create_buffer(render3d_st.gvk_dev, bci, null, out) != VK_SUCCESS { return null }
|
||||
render3d_st.gvk_st_buf = gvk_handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
let req = gvk_tmp(render3d_st, VkMemoryRequirements_sizeof)
|
||||
Vk.get_buffer_memory_requirements(render3d_st.gvk_dev, render3d_st.gvk_st_buf, req)
|
||||
render3d_st.gvk_st_mem = gvk_alloc(render3d_st, req, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
let zero: long = 0
|
||||
Vk.bind_buffer_memory(render3d_st.gvk_dev, render3d_st.gvk_st_buf, render3d_st.gvk_st_mem, zero)
|
||||
let pp = bytes(8)
|
||||
let pp = gvk_tmp(render3d_st, 8)
|
||||
if Vk.map_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem, zero, size, 0, pp) != VK_SUCCESS { return null }
|
||||
return Vk.get_ptr(pp, 0)
|
||||
}
|
||||
function gvk_staging_free(render3d_st: mut Render3dState) -> void {
|
||||
gvk_frame_wait(render3d_st) # the frame in flight may still read it
|
||||
Vk.unmap_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem)
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_st_buf, null)
|
||||
render3d_st.gvk_mk_x_mem += 1
|
||||
Vk.free_memory(render3d_st.gvk_dev, render3d_st.gvk_st_mem, null)
|
||||
render3d_st.gvk_n_allocs -= 1
|
||||
}
|
||||
|
|
@ -175,6 +178,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
Vk.put_i32(ici, VkImageCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
Vk.put_i32(ici, VkImageCreateInfo_initialLayout, VK_IMAGE_LAYOUT_UNDEFINED)
|
||||
let out = bytes(8)
|
||||
render3d_st.gvk_mk_img += 1
|
||||
var r = Vk.create_image(render3d_st.gvk_dev, ici, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateImage {w}x{h}x{layers} format {vkfmt}`, r) }
|
||||
let image = gvk_handle(out)
|
||||
|
|
@ -186,6 +190,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
# no memory for it (one allocation per resource meets the driver's allocation limit long before
|
||||
# the card is full): say so instead of binding a null allocation, which the driver may accept
|
||||
if mem == 0 {
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, image, null)
|
||||
return gvk_fail(render3d_st, `no device memory for a {w}x{h}x{layers} image ({render3d_st.gvk_n_allocs} allocations live)`, VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||||
}
|
||||
|
|
@ -201,6 +206,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
if depth { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) } else { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
||||
Vk.put_i32(vci, sr + VkImageSubresourceRange_levelCount, levels)
|
||||
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, layers)
|
||||
render3d_st.gvk_mk_view += 1
|
||||
r = Vk.create_image_view(render3d_st.gvk_dev, vci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateImageView", r) }
|
||||
render3d_st.gvk_tex_image[tex] = image; render3d_st.gvk_tex_view[tex] = gvk_handle(out); render3d_st.gvk_tex_mem[tex] = mem
|
||||
|
|
@ -211,7 +217,7 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
|||
if array { render3d_st.gvk_tex_array[tex] = 1 } else { render3d_st.gvk_tex_array[tex] = 0 }
|
||||
# a target starts cleared-to-nothing but readable: every level in the layout samplers expect
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
return gvk_once_end(render3d_st, cb)
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +254,8 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
let n = texels * cout * bout
|
||||
let dst = gvk_staging(render3d_st, n, VK_BUFFER_USAGE_TRANSFER_SRC_BIT)
|
||||
if dst == null { print("r3d: vulkan: no staging buffer for an upload"); return false }
|
||||
let buf = bytes(n)
|
||||
# written straight into the mapped staging memory: a copy of its own was never freed
|
||||
let buf: pointer = dst
|
||||
if cin == cout and bin == bout and not (bin == 2 and render3d_st.gvk_unpack_swap) {
|
||||
mem_copy(buf, data, n)
|
||||
} else {
|
||||
|
|
@ -281,13 +288,12 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
}
|
||||
}
|
||||
}
|
||||
mem_copy(dst, buf, n)
|
||||
let image = render3d_st.gvk_tex_image[tex]
|
||||
let depth = gvk_is_depth(ifmt)
|
||||
let levels = render3d_st.gvk_tex_levels[tex]
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let bic = bytes(VkBufferImageCopy_sizeof)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let bic = gvk_tmp(render3d_st, VkBufferImageCopy_sizeof)
|
||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
||||
else { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
|
||||
|
|
@ -296,7 +302,7 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
|
|||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_buffer_to_image(cb, render3d_st.gvk_st_buf, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, bic)
|
||||
gvk_barrier(cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, levels, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
gvk_staging_free(render3d_st)
|
||||
return ok
|
||||
|
|
@ -315,8 +321,8 @@ function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h:
|
|||
render3d_st.gvk_tex_image[tex] = zero
|
||||
if not gvk_tex_storage(render3d_st, tex, render3d_st.gvk_tex_array[tex] == 1, render3d_st.gvk_tex_glfmt[tex], w, h, layers, true) { return false }
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, old_image, false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, old_image, false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
let ic = bytes(VkImageCopy_sizeof)
|
||||
Vk.zero(ic, VkImageCopy_sizeof)
|
||||
Vk.put_i32(ic, VkImageCopy_srcSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
|
|
@ -327,9 +333,11 @@ function gvk_tex_grow_mips(render3d_st: mut Render3dState, tex: int, w: int, h:
|
|||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_height, h)
|
||||
Vk.put_i32(ic, VkImageCopy_extent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image(cb, old_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_tex_image[tex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ic)
|
||||
gvk_barrier(cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, render3d_st.gvk_tex_image[tex], false, 0, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, old_view, null)
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, old_image, null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(old_mem))
|
||||
return ok
|
||||
|
|
@ -346,7 +354,7 @@ function gvk_tex_mips(render3d_st: mut Render3dState, tex: int, w: int, h: int)
|
|||
return gvk_once_end(render3d_st, cb)
|
||||
}
|
||||
# the chain's blits and barriers recorded into cb (the frame's own, when one is open)
|
||||
function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w: int, h: int) -> void {
|
||||
function gvk_tex_mips_into(render3d_st: mut Render3dState, cb: pointer, tex: int, w: int, h: int) -> void {
|
||||
let levels = render3d_st.gvk_tex_levels[tex]
|
||||
if levels <= 1 { return }
|
||||
let image = render3d_st.gvk_tex_image[tex]
|
||||
|
|
@ -358,8 +366,8 @@ function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w:
|
|||
if dw < 1 { dw = 1 }
|
||||
var dh = sh / 2
|
||||
if dh < 1 { dh = 1 }
|
||||
gvk_barrier(cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 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)
|
||||
|
|
@ -375,8 +383,8 @@ function gvk_tex_mips_into(render3d_st: Render3dState, cb: pointer, tex: int, w:
|
|||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_y, dh)
|
||||
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1)
|
||||
Vk.cmd_blit_image(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR)
|
||||
gvk_barrier(cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv - 1, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, false, lv, 1, layers, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
sw = dw
|
||||
sh = dh
|
||||
}
|
||||
|
|
@ -400,7 +408,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
|
|||
let image = render3d_st.gvk_tex_image[tex]
|
||||
let depth = gvk_is_depth(ifmt)
|
||||
let cb = gvk_once_begin(render3d_st)
|
||||
gvk_barrier(cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||||
let bic = bytes(VkBufferImageCopy_sizeof)
|
||||
Vk.zero(bic, VkBufferImageCopy_sizeof)
|
||||
if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) }
|
||||
|
|
@ -410,7 +418,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
|
|||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
|
||||
Vk.put_i32(bic, VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
|
||||
Vk.cmd_copy_image_to_buffer(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, render3d_st.gvk_st_buf, 1, bic)
|
||||
gvk_barrier(cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
gvk_barrier(render3d_st, cb, image, depth, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
let ok = gvk_once_end(render3d_st, cb)
|
||||
if ok and cwant == cout { mem_copy(out, src, n) }
|
||||
if ok and cwant < cout {
|
||||
|
|
@ -426,14 +434,39 @@ function gvk_tex_release(render3d_st: mut Render3dState, tex: int) -> void {
|
|||
gvk_frame_wait(render3d_st) # the frame in flight may still read it
|
||||
if render3d_st.gvk_tex_image[tex] == 0 { return }
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, render3d_st.gvk_tex_view[tex], null)
|
||||
render3d_st.gvk_mk_x_img += 1
|
||||
Vk.destroy_image(render3d_st.gvk_dev, render3d_st.gvk_tex_image[tex], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_tex_mem[tex]))
|
||||
gvk_layer_views_drop(render3d_st, tex)
|
||||
render3d_st.gvk_tex_image[tex] = zero; render3d_st.gvk_tex_view[tex] = zero; render3d_st.gvk_tex_mem[tex] = zero
|
||||
render3d_st.gvk_tex_levels[tex] = 0; render3d_st.gvk_tex_layers[tex] = 0; render3d_st.gvk_tex_vkfmt[tex] = 0
|
||||
render3d_st.gvk_tex_gen[tex] = render3d_st.gvk_tex_gen[tex] + 1
|
||||
}
|
||||
|
||||
# the per-level and per-layer views gvk_view_of made of a texture go with it: kept, each held its
|
||||
# old Metal texture alive, so every rebuild of the screen targets left the last set's memory behind
|
||||
function gvk_layer_views_drop(render3d_st: mut Render3dState, tex: int) -> void {
|
||||
if render3d_st.gvk_layer_views == null { return }
|
||||
let keys = render3d_st.gvk_layer_views
|
||||
let views = render3d_st.gvk_layer_view
|
||||
let texs = render3d_st.gvk_layer_view_tex
|
||||
var w = 0
|
||||
for i in 0 .. len(keys) {
|
||||
if texs[i] == tex {
|
||||
render3d_st.gvk_mk_x_view += 1
|
||||
Vk.destroy_image_view(render3d_st.gvk_dev, views[i], null)
|
||||
} else {
|
||||
keys[w] = keys[i]; views[w] = views[i]; texs[w] = texs[i]
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
while len(keys) > w { List.pop(keys) }
|
||||
while len(views) > w { List.pop(views) }
|
||||
while len(texs) > w { List.pop(texs) }
|
||||
}
|
||||
|
||||
# ---- samplers -----------------------------------------------------------------------------
|
||||
# One VkSampler per distinct way of reading a texture, made the first time it is asked for.
|
||||
# The arguments are what the renderer set through gpu_tex_param (0 where it set nothing, which
|
||||
|
|
@ -526,6 +559,7 @@ function gvk_sampler(render3d_st: mut Render3dState, min_f: int, mag_f: int, wra
|
|||
Vk.put_i32(sci, VkSamplerCreateInfo_borderColor, VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE)
|
||||
let out = bytes(8)
|
||||
let zero: long = 0
|
||||
render3d_st.gvk_mk_smp += 1
|
||||
let r = Vk.create_sampler(render3d_st.gvk_dev, sci, null, out)
|
||||
if r != VK_SUCCESS { gvk_fail(render3d_st, "vkCreateSampler", r); return zero }
|
||||
let s = gvk_handle(out)
|
||||
|
|
@ -564,6 +598,7 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
|
|||
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) - 1 { push(render3d_st.gvk_retired_frame, 0) }
|
||||
push(render3d_st.gvk_retired_frame, render3d_st.gvk_buf_used[b])
|
||||
} else {
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_buf[b], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_buf_mem[b]))
|
||||
}
|
||||
|
|
@ -582,22 +617,27 @@ function gvk_retire_flush(render3d_st: mut Render3dState) -> void { gvk_retire_u
|
|||
# the frame being recorded read stays
|
||||
function gvk_retire_upto(render3d_st: mut Render3dState, done: int) -> void {
|
||||
if render3d_st.gvk_retired_buf == null { return }
|
||||
let keep_buf = new []long
|
||||
let keep_mem = new []long
|
||||
let keep_frame = new []int
|
||||
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
|
||||
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) { push(render3d_st.gvk_retired_frame, 0) }
|
||||
# compacted in place: three fresh lists a frame were never given back
|
||||
var w = 0
|
||||
for i in 0 .. len(render3d_st.gvk_retired_buf) {
|
||||
var f = 0
|
||||
if render3d_st.gvk_retired_frame != null and i < len(render3d_st.gvk_retired_frame) { f = render3d_st.gvk_retired_frame[i] }
|
||||
let f = render3d_st.gvk_retired_frame[i]
|
||||
if f > done {
|
||||
push(keep_buf, render3d_st.gvk_retired_buf[i]); push(keep_mem, render3d_st.gvk_retired_mem[i]); push(keep_frame, f)
|
||||
render3d_st.gvk_retired_buf[w] = render3d_st.gvk_retired_buf[i]; render3d_st.gvk_retired_mem[w] = render3d_st.gvk_retired_mem[i]; render3d_st.gvk_retired_frame[w] = f
|
||||
w += 1
|
||||
} else {
|
||||
render3d_st.gvk_mk_x_buf += 1
|
||||
Vk.destroy_buffer(render3d_st.gvk_dev, render3d_st.gvk_retired_buf[i], null)
|
||||
gvk_mem_free(render3d_st, gvk_mem_id(render3d_st.gvk_retired_mem[i]))
|
||||
}
|
||||
}
|
||||
render3d_st.gvk_retired_buf = keep_buf
|
||||
render3d_st.gvk_retired_mem = keep_mem
|
||||
render3d_st.gvk_retired_frame = keep_frame
|
||||
let rb = render3d_st.gvk_retired_buf
|
||||
let rm = render3d_st.gvk_retired_mem
|
||||
let rf = render3d_st.gvk_retired_frame
|
||||
while len(rb) > w { List.pop(rb) }
|
||||
while len(rm) > w { List.pop(rm) }
|
||||
while len(rf) > w { List.pop(rf) }
|
||||
}
|
||||
|
||||
# A buffer a compute pass writes: a draw later in the same frame reads what the GPU put there,
|
||||
|
|
@ -619,17 +659,18 @@ function gvk_buf_reserve(render3d_st: mut Render3dState, b: int, n: int) -> bool
|
|||
var size = n
|
||||
if size < 64 { size = 64 }
|
||||
let size_l: long = size
|
||||
let bci = bytes(VkBufferCreateInfo_sizeof)
|
||||
let bci = gvk_tmp(render3d_st, VkBufferCreateInfo_sizeof)
|
||||
Vk.zero(bci, VkBufferCreateInfo_sizeof)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sType, VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
|
||||
Vk.put_i64(bci, VkBufferCreateInfo_size, size_l)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)
|
||||
Vk.put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
let out = bytes(8)
|
||||
let out = gvk_tmp(render3d_st, 8)
|
||||
render3d_st.gvk_mk_buf += 1
|
||||
var r = Vk.create_buffer(render3d_st.gvk_dev, bci, null, out)
|
||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateBuffer ({size} bytes)`, r) }
|
||||
let buf = gvk_handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
let req = gvk_tmp(render3d_st, VkMemoryRequirements_sizeof)
|
||||
Vk.get_buffer_memory_requirements(render3d_st.gvk_dev, buf, req)
|
||||
let ma = gvk_mem_new(render3d_st, req, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, false)
|
||||
let zero: long = 0
|
||||
|
|
|
|||
|
|
@ -102,9 +102,14 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
|
|||
gg_tiles(render3d_st, 4, 0.0, 16.0)
|
||||
gg_tiles(render3d_st, 8, 16.0, 40.0)
|
||||
gg_tiles(render3d_st, 16, 40.0, render3d_st.grass_radius)
|
||||
let rb = words(4)
|
||||
# made once: a words() a frame is a calloc nothing frees
|
||||
if render3d_st.gg_rb == null {
|
||||
render3d_st.gg_rb = words(4); render3d_st.gg_cb = words(1); render3d_st.gg_bufs = words(3)
|
||||
render3d_st.gg_texs = words(2); render3d_st.gg_pr = words(48)
|
||||
}
|
||||
let rb = render3d_st.gg_rb
|
||||
rb[0] = GG_BANDS; rb[1] = 0; rb[2] = 0; rb[3] = 0
|
||||
let cb = words(1)
|
||||
let cb = render3d_st.gg_cb
|
||||
cb[0] = render3d_st.gg_cmds
|
||||
gpu_dispatch(render3d_st, render3d_st.gg_reset, data_of(rb), 16, cb, 1)
|
||||
if render3d_st.gg_n == 0 { return }
|
||||
|
|
@ -112,16 +117,16 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
|
|||
let tb = render3d_st.gg_tiles_buf[render3d_st.r3d_test_frame % 2]
|
||||
gpu_buffer_upload(render3d_st, tb, render3d_st.gg_n * 16, data_of(render3d_st.gg_tv), GPU_DYNAMIC)
|
||||
let pr = gg_params(render3d_st)
|
||||
let bufs = words(3)
|
||||
let bufs = render3d_st.gg_bufs
|
||||
bufs[0] = tb; bufs[1] = render3d_st.gg_out; bufs[2] = render3d_st.gg_cmds
|
||||
let texs = words(2)
|
||||
let texs = render3d_st.gg_texs
|
||||
texs[0] = render3d_st.ter_height_tex; texs[1] = render3d_st.ter_ortho_tex
|
||||
gpu_dispatch_tex(render3d_st, render3d_st.gg_cull, data_of(pr), 192, bufs, texs, (render3d_st.gg_max + 63) / 64, render3d_st.gg_n)
|
||||
}
|
||||
|
||||
# grass_cull.comp's Params, std140: 12 vec4s
|
||||
function gg_params(render3d_st: Render3dState) -> words {
|
||||
let pr = words(48)
|
||||
# grass_cull.comp's Params, std140: 12 vec4s, into the block made once in gg_cull_frame
|
||||
function gg_params(render3d_st: mut Render3dState) -> words {
|
||||
let pr = render3d_st.gg_pr
|
||||
for i in 0 .. 48 { pr[i] = 0 }
|
||||
if render3d_st.cam_planes != null { for i in 0 .. 16 { pr[i] = float_bits(render3d_st.cam_planes[i]) } }
|
||||
pr[16] = float_bits(render3d_st.cam_pos[0]); pr[17] = float_bits(render3d_st.cam_pos[1]); pr[18] = float_bits(render3d_st.cam_pos[2]); pr[19] = float_bits(render3d_st.grass_radius)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import "vk_api.ludic"
|
|||
# Vk.sl_active() says whether it was. Each Vk.sl_* is the sl* export of the same name and returns
|
||||
# its sl::Result (0 is eOk); -1 means there is no Streamline (macOS, or the DLL missing). The
|
||||
# structs are plain memory, laid out as the SDK headers (include/sl_*.h, SDK 2.14.1) declare them.
|
||||
# Once a frame: on macOS drains what MoltenVK and the layer autoreleased during the last one (a
|
||||
# program pumping its own events has no other pool); nothing elsewhere.
|
||||
extern function vk_frame_pool() = "lvk_frame_pool"
|
||||
extern function vk_sl_prefer(on: int) = "lvk_sl_prefer"
|
||||
extern function vk_sl_active() -> int = "lvk_sl_active"
|
||||
extern function vk_sl_init(pref: pointer, sdk_version: long) -> int = "lsl_slInit"
|
||||
|
|
|
|||
|
|
@ -225,3 +225,26 @@ entry:
|
|||
%r = call i64 %fn(ptr %a0, ptr %a1)
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
; ---- the frame's autorelease pool --------------------------------------------------------------
|
||||
; MoltenVK and CAMetalLayer autorelease objects on every frame (the drawable, each render pass's
|
||||
; descriptor), and a program that pumps its own events never drains a pool: without this they are
|
||||
; kept for the life of the process - 160 MB/s of small allocations in a windowed valley.
|
||||
; lvk_frame_pool() pops the pool the last frame pushed and pushes the next one.
|
||||
declare ptr @objc_autoreleasePoolPush()
|
||||
declare void @objc_autoreleasePoolPop(ptr)
|
||||
@lvk_pool = internal global ptr null
|
||||
|
||||
define void @lvk_frame_pool() {
|
||||
entry:
|
||||
%old = load ptr, ptr @lvk_pool
|
||||
%have = icmp ne ptr %old, null
|
||||
br i1 %have, label %pop, label %push
|
||||
pop:
|
||||
call void @objc_autoreleasePoolPop(ptr %old)
|
||||
br label %push
|
||||
push:
|
||||
%p = call ptr @objc_autoreleasePoolPush()
|
||||
store ptr %p, ptr @lvk_pool
|
||||
ret void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -363,3 +363,9 @@ entry:
|
|||
%r = call i64 %fn(ptr %a0, ptr %a1)
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
; the frame's autorelease pool is a macOS matter (vk_mac.ll); Windows has nothing to drain
|
||||
define void @lvk_frame_pool() {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue