wip: uncommitted work gathered before the branch is merged into main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-30 17:18:48 +03:00
parent cc384efee0
commit 737d6e1859
30 changed files with 307 additions and 120 deletions

View file

@ -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)
}
}

View file

@ -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

View file

@ -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 }

View file

@ -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)

View file

@ -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 }
}
@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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() {

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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"

View file

@ -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
}

View file

@ -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
}