diff --git a/examples/rendering/smooth.ludic b/examples/rendering/smooth.ludic index 97449be4..a6204913 100644 --- a/examples/rendering/smooth.ludic +++ b/examples/rendering/smooth.ludic @@ -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) } } diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index fc07c7f6..915b3520 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -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 diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 49fea526..947b930a 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -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 } diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index 87f2b6d5..741869fe 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -298,6 +298,7 @@ function gvk_alloc(render3d_st: mut Render3dState, req: bytes, want: int) -> lon Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, Vk.get_i64(req, VkMemoryRequirements_size)) Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t) let out = bytes(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 @@ -338,6 +339,7 @@ function gvk_mem_raw(render3d_st: mut Render3dState, t: int, size: int, host: bo Vk.put_i64(mai, VkMemoryAllocateInfo_allocationSize, size_l) Vk.put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, t) let out = bytes(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 @@ -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 @@ -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) diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index b5bb3acb..e4049ffc 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -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 { - 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 + # 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 } } @@ -863,6 +873,7 @@ 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 } } @@ -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 diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index 4d25a78a..c9919531 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -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)) @@ -122,6 +122,7 @@ function gvk_staging(render3d_st: mut Render3dState, n: int, usage: int) -> poin Vk.put_i32(bci, VkBufferCreateInfo_usage, usage) Vk.put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE) let out = bytes(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) @@ -136,7 +137,9 @@ function gvk_staging(render3d_st: mut Render3dState, n: int, usage: int) -> poin 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,12 +288,11 @@ 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) + 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 = bytes(VkBufferImageCopy_sizeof) Vk.zero(bic, VkBufferImageCopy_sizeof) if depth { Vk.put_i32(bic, VkBufferImageCopy_imageSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_DEPTH_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, @@ -626,6 +666,7 @@ function gvk_buf_reserve(render3d_st: mut Render3dState, b: int, n: int) -> bool 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) + 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) diff --git a/packages/ludic.render3d/grass_gpu.ludic b/packages/ludic.render3d/grass_gpu.ludic index 845ea613..1c200b1f 100644 --- a/packages/ludic.render3d/grass_gpu.ludic +++ b/packages/ludic.render3d/grass_gpu.ludic @@ -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) diff --git a/packages/ludic.render3d/shaders/grass.vert b/packages/ludic.render3d/shaders/grass.vert index 32f75c16..456989cb 100644 --- a/packages/ludic.render3d/shaders/grass.vert +++ b/packages/ludic.render3d/shaders/grass.vert @@ -84,7 +84,7 @@ vec3 bladeField(vec2 xz, float y) { // dry and green patches a few metres across, and the tussocks' own shade of the same green float dryp = smoothstep(0.2, 0.8, gnoise(xz * 0.33 + 17.0) * 0.5 + 0.5); t *= mix(vec3(0.92, 1.0, 0.95), vec3(1.30, 1.10, 0.62), dryp); - return t * (0.72 + 0.56 * (gnoise(xz * 1.9 + 41.0) * 0.5 + 0.5)); + return t * (0.55 + 0.80 * (gnoise(xz * 1.9 + 41.0) * 0.5 + 0.5)); } void main() { diff --git a/packages/ludic.render3d/shaders/grass_cull.comp b/packages/ludic.render3d/shaders/grass_cull.comp index 4f4567ad..822abc24 100644 --- a/packages/ludic.render3d/shaders/grass_cull.comp +++ b/packages/ludic.render3d/shaders/grass_cull.comp @@ -61,7 +61,7 @@ vec3 bladeField(vec2 xz, float y) { t *= mix(vec3(0.7, 0.8, 0.55), vec3(1.1, 1.05, 0.85), patchy); float dryp = smoothstep(0.2, 0.8, gnoise(xz * 0.33 + 17.0) * 0.5 + 0.5); t *= mix(vec3(0.92, 1.0, 0.95), vec3(1.30, 1.10, 0.62), dryp); - return t * (0.72 + 0.56 * (gnoise(xz * 1.9 + 41.0) * 0.5 + 0.5)); + return t * (0.55 + 0.80 * (gnoise(xz * 1.9 + 41.0) * 0.5 + 0.5)); } void main() { @@ -118,6 +118,13 @@ void main() { uint o = (pr.base[b] + slot) * 4u; outv[o] = vec4(xz.x, h - 0.02, xz.y, seed); outv[o + 1u] = vec4(sin(ang), cos(ang), tall, bw); - outv[o + 2u] = vec4(bladeField(xz, h), 0.6 + 0.8 * h4); + // each blade its own: a hue a little toward olive or toward blue-green, and in a dry patch more + // of them gone to straw - a meadow is mixed blade by blade, not tinted as one + vec3 field = bladeField(xz, h); + float hj = bladeHash(ci, j, 7) - 0.5; + field *= vec3(1.0 + 0.30 * hj, 1.0, 1.0 - 0.30 * hj); + float dryp = smoothstep(0.2, 0.8, gnoise(xz * 0.33 + 17.0) * 0.5 + 0.5); + if (bladeHash(ci, j, 8) < 0.10 + 0.45 * dryp) field *= vec3(1.45, 1.18, 0.52); + outv[o + 2u] = vec4(field, 0.6 + 0.8 * h4); outv[o + 3u] = vec4(gn, dist); } diff --git a/packages/ludic.render3d/shaders/model.frag b/packages/ludic.render3d/shaders/model.frag index d6356ab3..e40149eb 100644 --- a/packages/ludic.render3d/shaders/model.frag +++ b/packages/ludic.render3d/shaders/model.frag @@ -142,7 +142,9 @@ void main() { // an occlusion term; grass albedo is capped near 0.5 and this was dividing it by five. // A blade is a thin thing standing in open air: shaded at the root by its neighbours, // not buried. - arm = vec3(mix(0.55, 1.0, t), 0.85, 0.0); + // the inside of a sward is its own shade: a blade's base sits among its neighbours (the old clumps + // got this from the shadow maps, at 4.5 ms; seen from above it is most of a meadow's texture) + arm = vec3(mix(0.30, 1.0, smoothstep(0.0, 0.8, t)), 0.85, 0.0); #elif defined(CARD) // thin grass is lit from either side: face the card toward the sun before shading if (dot(N, u_sun_dir) < 0.0) N = -N; diff --git a/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv b/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv index d8fef303..d8adce45 100644 Binary files a/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv and b/packages/ludic.render3d/shaders/spv/1f95f31b.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv b/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv index 2a3f5b41..19003a96 100644 Binary files a/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv and b/packages/ludic.render3d/shaders/spv/2e2908c8.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv b/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv index af676aef..4eb4d908 100644 Binary files a/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv and b/packages/ludic.render3d/shaders/spv/5e4589a1.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv b/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv index 3a316009..dc30dcd6 100644 Binary files a/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv and b/packages/ludic.render3d/shaders/spv/a0f2dbac.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b20fdcb5.frag.spv b/packages/ludic.render3d/shaders/spv/b20fdcb5.frag.spv index 35eaa795..b48636f1 100644 Binary files a/packages/ludic.render3d/shaders/spv/b20fdcb5.frag.spv and b/packages/ludic.render3d/shaders/spv/b20fdcb5.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv b/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv index a7862f95..f7b38d76 100644 Binary files a/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv and b/packages/ludic.render3d/shaders/spv/b8cff226.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv b/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv index 12eafb74..386b6ddd 100644 Binary files a/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv and b/packages/ludic.render3d/shaders/spv/b8cff226.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv b/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv index a7862f95..f7b38d76 100644 Binary files a/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv and b/packages/ludic.render3d/shaders/spv/cb13f3c6.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv b/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv index 4858c099..6c4bf083 100644 Binary files a/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv and b/packages/ludic.render3d/shaders/spv/cb13f3c6.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv b/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv index 39de29f8..e11d1872 100644 Binary files a/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv and b/packages/ludic.render3d/shaders/spv/d48d6a48.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/f2952361.frag.spv b/packages/ludic.render3d/shaders/spv/f2952361.frag.spv index 1baabb14..e31ae9e5 100644 Binary files a/packages/ludic.render3d/shaders/spv/f2952361.frag.spv and b/packages/ludic.render3d/shaders/spv/f2952361.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/f4382918.frag.spv b/packages/ludic.render3d/shaders/spv/f4382918.frag.spv index 9f268a77..1aef3e65 100644 Binary files a/packages/ludic.render3d/shaders/spv/f4382918.frag.spv and b/packages/ludic.render3d/shaders/spv/f4382918.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/f4a2b586.frag.spv b/packages/ludic.render3d/shaders/spv/f4a2b586.frag.spv index 21e807a8..708d4532 100644 Binary files a/packages/ludic.render3d/shaders/spv/f4a2b586.frag.spv and b/packages/ludic.render3d/shaders/spv/f4a2b586.frag.spv differ diff --git a/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv b/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv index 4b9c9260..c9656fde 100644 Binary files a/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv and b/packages/ludic.render3d/shaders/spv/grass_cull.comp.spv differ diff --git a/packages/ludic.render3d/shaders/spv/manifest.txt b/packages/ludic.render3d/shaders/spv/manifest.txt index 78000865..b494cca9 100644 --- a/packages/ludic.render3d/shaders/spv/manifest.txt +++ b/packages/ludic.render3d/shaders/spv/manifest.txt @@ -2996,10 +2996,11 @@ U 2e2908c8 frag u_half 828 float 1 0 U 2e2908c8 frag u_texel 832 float 1 0 U 2e2908c8 frag u_ortho_on 836 float 1 0 U 2e2908c8 frag u_carpet_on 840 float 1 0 -U 2e2908c8 frag u_snow_line 844 float 1 0 -U 2e2908c8 frag u_lake_level 848 float 1 0 -U 2e2908c8 frag u_wet 852 float 1 0 -U 2e2908c8 frag u_sea_level 856 float 1 0 +U 2e2908c8 frag u_grass_reach 844 float 1 0 +U 2e2908c8 frag u_snow_line 848 float 1 0 +U 2e2908c8 frag u_lake_level 852 float 1 0 +U 2e2908c8 frag u_wet 856 float 1 0 +U 2e2908c8 frag u_sea_level 860 float 1 0 U 2e2908c8 frag u_lake 864 vec4 1 0 U 2e2908c8 frag u_origin 880 vec2 1 0 U 2e2908c8 frag u_view 896 mat4 1 0 @@ -3073,10 +3074,11 @@ U 1f95f31b frag u_half 828 float 1 0 U 1f95f31b frag u_texel 832 float 1 0 U 1f95f31b frag u_ortho_on 836 float 1 0 U 1f95f31b frag u_carpet_on 840 float 1 0 -U 1f95f31b frag u_snow_line 844 float 1 0 -U 1f95f31b frag u_lake_level 848 float 1 0 -U 1f95f31b frag u_wet 852 float 1 0 -U 1f95f31b frag u_sea_level 856 float 1 0 +U 1f95f31b frag u_grass_reach 844 float 1 0 +U 1f95f31b frag u_snow_line 848 float 1 0 +U 1f95f31b frag u_lake_level 852 float 1 0 +U 1f95f31b frag u_wet 856 float 1 0 +U 1f95f31b frag u_sea_level 860 float 1 0 U 1f95f31b frag u_lake 864 vec4 1 0 U 1f95f31b frag u_origin 880 vec2 1 0 U 1f95f31b frag u_view 896 mat4 1 0 @@ -3150,10 +3152,11 @@ U f4382918 frag u_half 828 float 1 0 U f4382918 frag u_texel 832 float 1 0 U f4382918 frag u_ortho_on 836 float 1 0 U f4382918 frag u_carpet_on 840 float 1 0 -U f4382918 frag u_snow_line 844 float 1 0 -U f4382918 frag u_lake_level 848 float 1 0 -U f4382918 frag u_wet 852 float 1 0 -U f4382918 frag u_sea_level 856 float 1 0 +U f4382918 frag u_grass_reach 844 float 1 0 +U f4382918 frag u_snow_line 848 float 1 0 +U f4382918 frag u_lake_level 852 float 1 0 +U f4382918 frag u_wet 856 float 1 0 +U f4382918 frag u_sea_level 860 float 1 0 U f4382918 frag u_lake 864 vec4 1 0 U f4382918 frag u_origin 880 vec2 1 0 U f4382918 frag u_view 896 mat4 1 0 @@ -3227,10 +3230,11 @@ U f2952361 frag u_half 828 float 1 0 U f2952361 frag u_texel 832 float 1 0 U f2952361 frag u_ortho_on 836 float 1 0 U f2952361 frag u_carpet_on 840 float 1 0 -U f2952361 frag u_snow_line 844 float 1 0 -U f2952361 frag u_lake_level 848 float 1 0 -U f2952361 frag u_wet 852 float 1 0 -U f2952361 frag u_sea_level 856 float 1 0 +U f2952361 frag u_grass_reach 844 float 1 0 +U f2952361 frag u_snow_line 848 float 1 0 +U f2952361 frag u_lake_level 852 float 1 0 +U f2952361 frag u_wet 856 float 1 0 +U f2952361 frag u_sea_level 860 float 1 0 U f2952361 frag u_lake 864 vec4 1 0 U f2952361 frag u_origin 880 vec2 1 0 U f2952361 frag u_view 896 mat4 1 0 @@ -3304,10 +3308,11 @@ U a0f2dbac frag u_half 828 float 1 0 U a0f2dbac frag u_texel 832 float 1 0 U a0f2dbac frag u_ortho_on 836 float 1 0 U a0f2dbac frag u_carpet_on 840 float 1 0 -U a0f2dbac frag u_snow_line 844 float 1 0 -U a0f2dbac frag u_lake_level 848 float 1 0 -U a0f2dbac frag u_wet 852 float 1 0 -U a0f2dbac frag u_sea_level 856 float 1 0 +U a0f2dbac frag u_grass_reach 844 float 1 0 +U a0f2dbac frag u_snow_line 848 float 1 0 +U a0f2dbac frag u_lake_level 852 float 1 0 +U a0f2dbac frag u_wet 856 float 1 0 +U a0f2dbac frag u_sea_level 860 float 1 0 U a0f2dbac frag u_lake 864 vec4 1 0 U a0f2dbac frag u_origin 880 vec2 1 0 U a0f2dbac frag u_view 896 mat4 1 0 @@ -3381,10 +3386,11 @@ U 5e4589a1 frag u_half 828 float 1 0 U 5e4589a1 frag u_texel 832 float 1 0 U 5e4589a1 frag u_ortho_on 836 float 1 0 U 5e4589a1 frag u_carpet_on 840 float 1 0 -U 5e4589a1 frag u_snow_line 844 float 1 0 -U 5e4589a1 frag u_lake_level 848 float 1 0 -U 5e4589a1 frag u_wet 852 float 1 0 -U 5e4589a1 frag u_sea_level 856 float 1 0 +U 5e4589a1 frag u_grass_reach 844 float 1 0 +U 5e4589a1 frag u_snow_line 848 float 1 0 +U 5e4589a1 frag u_lake_level 852 float 1 0 +U 5e4589a1 frag u_wet 856 float 1 0 +U 5e4589a1 frag u_sea_level 860 float 1 0 U 5e4589a1 frag u_lake 864 vec4 1 0 U 5e4589a1 frag u_origin 880 vec2 1 0 U 5e4589a1 frag u_view 896 mat4 1 0 diff --git a/packages/ludic.render3d/shaders/terrain.frag b/packages/ludic.render3d/shaders/terrain.frag index a2d2cb2a..12877b55 100644 --- a/packages/ludic.render3d/shaders/terrain.frag +++ b/packages/ludic.render3d/shaders/terrain.frag @@ -16,6 +16,7 @@ uniform float u_ortho_on; uniform sampler2D u_rock_d; uniform sampler2D u_rock_n; uniform sampler2D u_rock_a; uniform sampler2D u_snow_d; uniform sampler2D u_carpet; // the clump cards baked straight down (alpha = coverage) uniform float u_carpet_on; +uniform float u_grass_reach; // where the GPU blades stop (0: none): the ground under them is in their shade uniform float u_snow_line; uniform float u_lake_level; // the ground just above the water is wet and dark // ---- RAIN THAT LEAVES SOMETHING BEHIND ----------------------------------------------------- @@ -339,6 +340,18 @@ vec3 groundShade(vec3 p, vec3 N, float slope, float dist, float viewDepth, bool vec3 cc = cp.rgb * vec3(0.5, 0.57, 0.45) * mix(vec3(0.85, 0.92, 0.9), vec3(1.1, 1.05, 0.85), smoothstep(-0.35, 0.35, N.z)) * (0.75 + 0.35 * lush); gA = mix(gA, cc, max(cp.a, 0.35) * carpetW * 0.97); } + // The blades' own patches (grass_cull.comp's bladeField): patchiness and dry patches, so where + // the blades thin out the ground they stand on carries the same meadow, not a lawn + { + float patchy = fbmC(cheap, p.xz * 0.045, 3) * 0.5 + 0.5; + float dryp = smoothstep(0.2, 0.8, gnoise(p.xz * 0.33 + 17.0) * 0.5 + 0.5); + vec3 pf = mix(vec3(0.7, 0.8, 0.55), vec3(1.1, 1.05, 0.85), patchy) * mix(vec3(0.92, 1.0, 0.95), vec3(1.30, 1.10, 0.62), dryp); + // and the tussocks' own shade, half a metre across: where blades stand a pixel wide the ground + // is most of what shows, and it has to carry the same light and dark as the blades on it + float tus = 0.55 + 0.80 * (gnoise(p.xz * 1.9 + 41.0) * 0.5 + 0.5); + gA *= mix(vec3(1.0), pf / vec3(0.95, 0.96, 0.72) * tus, grassW); + if (u_grass_reach > 0.0) gA *= mix(1.0, mix(0.50, 1.0, smoothstep(u_grass_reach * 0.25, u_grass_reach, dist)), grassW); + } // Subalpine forest floor: dark duff where the stands are dense. The photograph-driven // term marks duff only where the survey actually shows dense conifer, and is the same // at any distance — ground shading must not depend on where the viewer is. diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index ecf7866c..5cf87f24 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -418,6 +418,9 @@ function terrain_bind_prog(render3d_st: mut Render3dState, p: int) -> void { r3d_bind_2d(render3d_st, p, "u_snow_d", 10, render3d_st.ter_tex[9]) if render3d_st.ter_carpet != 0 { r3d_bind_2d(render3d_st, p, "u_carpet", 11, render3d_st.ter_carpet); u_f(render3d_st, gpu_uniform(render3d_st, p, "u_carpet_on"), 1.0) } else { r3d_bind_2d(render3d_st, p, "u_carpet", 11, render3d_st.ter_tex[0]); u_f(render3d_st, gpu_uniform(render3d_st, p, "u_carpet_on"), 0.0) } + var reach = 0.0 + if grass_live(render3d_st) { reach = render3d_st.grass_radius } + u_f(render3d_st, gpu_uniform(render3d_st, p, "u_grass_reach"), reach) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_half"), float(render3d_st.TERRAIN_HALF)) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_texel"), 1.0 / float(TERRAIN_RES)) u_f(render3d_st, gpu_uniform(render3d_st, p, "u_snow_line"), render3d_st.ter_snow_line) diff --git a/runtime/native/vk.ludic b/runtime/native/vk.ludic index 2a6985ac..24bca19b 100644 --- a/runtime/native/vk.ludic +++ b/runtime/native/vk.ludic @@ -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" diff --git a/runtime/native/vk_mac.ll b/runtime/native/vk_mac.ll index e34cd65b..9cd51b9c 100644 --- a/runtime/native/vk_mac.ll +++ b/runtime/native/vk_mac.ll @@ -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 +} diff --git a/runtime/native/vk_win.ll b/runtime/native/vk_win.ll index 5bca3814..2263e3e5 100644 --- a/runtime/native/vk_win.ll +++ b/runtime/native/vk_win.ll @@ -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 +}