ludic/packages/ludic.render3d/gpu_vk_layers.ludic
Orkuncakilkaya 617ac10c5a render3d: the terrain's GPU maps paged round the camera while the tiles are on
At the cut the whole 4096 height, normal and photograph textures go, replaced by a coarse
2048 level (the CPU's tt_coarse uploaded; the normal and photograph blitted down on the GPU,
the photograph mipmapped) and a pool of fine tiles in three array textures - heights, normals,
photograph, each layer a tile with a one-texel border - addressed through a tt_n^2 page table
(u_tp_page: slot + 1, 0 = the coarse level). The pool holds the tiles within the reach (900 m,
or the fog wall's when nearer) and a ring, and is made again when the fog wall changes its size
(terrain_pages_fog). Once a frame (tp_frame, beside tt_frame) tiles past the reach and two tiles
go and the wanted ones come in nearest first, 8 a frame, written into a staging buffer kept for
the process (two halves, one per frame in flight) and copied into their layers inside the frame's
own command buffer - no submit of their own - with the page table re-uploaded only when it
changed. tp_bind binds the pool (or 1-layer stand-in arrays while paging is off, u_tp_on = 0) for
every program that reads the ground: terrain_bind_height (models, scatter, shadow_bind, grass),
terrain_bind_prog, the sun pass and the shadow bake. grass_cull.comp reads through the same page
table. R3D_VKMEM prints the pool's line. Tiles off, nothing changes. Compile-only: not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 17:13:11 +03:00

94 lines
5.7 KiB
Text

# gpu_vk_layers.ludic — what the terrain's page pool asks of Vulkan (terrain_pages.ludic): a staging
# buffer kept for the process, regions of it copied into chosen layers of an array texture inside the
# frame's own command buffer (no submit of their own), and a one-off linear blit for a coarse level.
# a host-visible staging buffer of n bytes kept for good (the pool's uploads reuse it every frame)
@alloc_ok("made once per resource and kept for its life: the page pool's staging buffer")
function gpu_staging_keep(render3d_st: mut Render3dState, n: int) -> pointer {
gvk_flush(render3d_st)
let p = gvk_staging(render3d_st, n, VK_BUFFER_USAGE_TRANSFER_SRC_BIT)
if p == null { return null }
render3d_st.tp_st_buf = render3d_st.gvk_st_buf
render3d_st.tp_st_mem = render3d_st.gvk_st_mem
render3d_st.tp_st_bytes = n
return p
}
# ... and let go (a map whose tiles need a larger one)
function gpu_staging_drop(render3d_st: mut Render3dState) -> void {
if render3d_st.tp_st_buf == 0 { return }
gvk_flush(render3d_st)
let buf = render3d_st.gvk_st_buf
let mem = render3d_st.gvk_st_mem
render3d_st.gvk_st_buf = render3d_st.tp_st_buf
render3d_st.gvk_st_mem = render3d_st.tp_st_mem
gvk_staging_free(render3d_st)
render3d_st.gvk_st_buf = buf
render3d_st.gvk_st_mem = mem
let zero: long = 0
render3d_st.tp_st_buf = zero; render3d_st.tp_st_mem = zero; render3d_st.tp_st_ptr = null; render3d_st.tp_st_bytes = 0
}
# the frame's command buffer, outside any pass, for copies recorded before the frame's draws
function gpu_upload_cb(render3d_st: mut Render3dState) -> pointer {
gvk_pass_end(render3d_st)
return gvk_frame_cb(render3d_st)
}
# n regions of the kept staging buffer, each w x h texels starting at base + k * stride, into layer
# layers[k] of tex's level 0: one barrier each way around them all, recorded into cb
function gpu_layers_copy(render3d_st: mut Render3dState, cb: pointer, tex: int, w: int, h: int, base: int, stride: int, layers: words, n: int) -> void {
if n <= 0 or tex <= 0 or cb == null { return }
let image = render3d_st.gvk_tex_image[tex]
let all = render3d_st.gvk_tex_layers[tex]
let levels = render3d_st.gvk_tex_levels[tex]
gvk_barrier(render3d_st, cb, image, false, 0, levels, all, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
let sz = VkBufferImageCopy_sizeof
let bic = gvk_tmp(render3d_st, sz * n)
Vk.zero(bic, sz * n)
let sub = VkBufferImageCopy_imageSubresource
for k in 0 .. n {
let at: long = base + k * stride
Vk.put_i64(bic, k * sz + VkBufferImageCopy_bufferOffset, at)
Vk.put_i32(bic, k * sz + sub + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
Vk.put_i32(bic, k * sz + sub + VkImageSubresourceLayers_baseArrayLayer, layers[k])
Vk.put_i32(bic, k * sz + sub + VkImageSubresourceLayers_layerCount, 1)
Vk.put_i32(bic, k * sz + VkBufferImageCopy_imageExtent + VkExtent3D_width, w)
Vk.put_i32(bic, k * sz + VkBufferImageCopy_imageExtent + VkExtent3D_height, h)
Vk.put_i32(bic, k * sz + VkBufferImageCopy_imageExtent + VkExtent3D_depth, 1)
}
Vk.cmd_copy_buffer_to_image(cb, render3d_st.tp_st_buf, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, n, bic)
gvk_barrier(render3d_st, cb, image, false, 0, levels, all, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
}
# level 0 of src (sw x sh) filtered into level 0 of dst (dw x dh), once: a coarse level made on the GPU
function gpu_tex_blit(render3d_st: mut Render3dState, src: int, sw: int, sh: int, dst: int, dw: int, dh: int) -> bool {
gvk_flush(render3d_st)
let si = render3d_st.gvk_tex_image[src]
let di = render3d_st.gvk_tex_image[dst]
if si == 0 or di == 0 { return false }
let cb = gvk_once_begin(render3d_st)
gvk_barrier(render3d_st, cb, si, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
gvk_barrier(render3d_st, cb, di, false, 0, 1, 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 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)
Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_sizeof + VkOffset3D_x, sw)
Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_sizeof + VkOffset3D_y, sh)
Vk.put_i32(blit, VkImageBlit_srcOffsets + VkOffset3D_sizeof + VkOffset3D_z, 1)
Vk.put_i32(blit, VkImageBlit_dstSubresource + VkImageSubresourceLayers_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
Vk.put_i32(blit, VkImageBlit_dstSubresource + VkImageSubresourceLayers_layerCount, 1)
Vk.put_i32(blit, VkImageBlit_dstOffsets + VkOffset3D_sizeof + VkOffset3D_x, dw)
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, si, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, di, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blit, VK_FILTER_LINEAR)
gvk_barrier(render3d_st, cb, si, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
gvk_barrier(render3d_st, cb, di, false, 0, 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
return gvk_once_end(render3d_st, cb)
}
# pixels into level 0 of a texture made without them (tex_target), laid out as gvk_tex_upload takes them
function gpu_tex_fill(render3d_st: mut Render3dState, tex: int, ifmt: int, w: int, h: int, fmt: int, ty: int, data: pointer) -> bool {
gvk_flush(render3d_st)
return gvk_tex_upload(render3d_st, tex, ifmt, w, h, 1, fmt, ty, data)
}