render3d: the GPU blades grow by a painted density (a layer of the ground densities, grass_density_layer) where a map has one - a 5x5-tile window round the camera in a storage buffer the cull samples - and by today's rules where not; grass_rule_at is the rules on the CPU for the migration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e6f4685bd0
commit
9186abbbe3
10 changed files with 219 additions and 21 deletions
100
packages/ludic.render3d/grass_density.ludic
Normal file
100
packages/ludic.render3d/grass_density.ludic
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# grass_density.ludic — the blades grow where the map's painted density says, when it has one: a layer of
|
||||
# the ground densities (ground_density.ludic; a PNG like any ground layer's, the same 2 m texel and 64 m
|
||||
# tiles), named by grass_density_layer. Without one grass_cull.comp keeps its rules (grass_rule_at here).
|
||||
#
|
||||
# The cull decides every blade on the GPU, so the density must be there too - but not all of it: 4096^2
|
||||
# bytes is 16 MB, and the blades stand within grass_reach (70 m at most). The GPU holds a window of
|
||||
# GB_TILES x GB_TILES density tiles round the camera's (5 x 64 m: 160^2 bytes, 25 KB, twice while a re-fill
|
||||
# swaps), re-filled from the CPU tile cache when the camera crosses into another tile. The camera stays in
|
||||
# the middle tile, so the window reaches at least 128 m every way. A host-visible storage buffer, not a
|
||||
# texture: filling a texture waits for the GPU (gpu_tex_fill), a buffer's re-fill swaps under the frame
|
||||
# still reading it (gvk_buf_reserve), and the cull does its own bilinear in four reads.
|
||||
|
||||
const GB_TILES: int = 5
|
||||
const GB_NONE: int = -1000000
|
||||
|
||||
# The layer of the ground densities the blades grow by (-1: none, the rules). Call it after
|
||||
# ground_density_open, with the index the blades' PNG had in its list; closing the densities forgets it.
|
||||
export function grass_density_layer(render3d_st: mut Render3dState, l: int) -> void {
|
||||
render3d_st.gb_layer = l
|
||||
render3d_st.gb_tx = GB_NONE
|
||||
}
|
||||
|
||||
# the side, in texels, a density PNG must have for this renderer (as every ground layer's)
|
||||
export function grass_density_texels() -> int { return TERRAIN_RES / TT_TEX * GD_SIDE }
|
||||
|
||||
# whether the blades read a density: a layer named, and the densities open with it
|
||||
function gb_on(render3d_st: Render3dState) -> bool {
|
||||
let l = render3d_st.gb_layer
|
||||
if l < 0 or render3d_st.gd_file == null or render3d_st.gb_win == null or l >= render3d_st.gd_layers { return false }
|
||||
return render3d_st.gd_ch[l] > 0
|
||||
}
|
||||
|
||||
# the densities' texel in metres
|
||||
function gb_texel(render3d_st: Render3dState) -> float { return 2.0 * float(render3d_st.TERRAIN_HALF) / float(render3d_st.gd_n * GD_SIDE) }
|
||||
|
||||
# once, with the cull's buffers: the window's bytes and its buffer, at its full size (zeros)
|
||||
@alloc_ok("start-up: the density window's bytes and buffer, once")
|
||||
function gb_init(render3d_st: mut Render3dState) -> void {
|
||||
let side = GB_TILES * GD_SIDE
|
||||
render3d_st.gb_win = words(side * side / 4)
|
||||
for i in 0 .. side * side / 4 { render3d_st.gb_win[i] = 0 }
|
||||
render3d_st.gb_buf = gpu_buffer_new(render3d_st)
|
||||
gpu_buffer_upload(render3d_st, render3d_st.gb_buf, side * side, data_of(render3d_st.gb_win), GPU_DYNAMIC)
|
||||
}
|
||||
|
||||
# before the cull: the window re-filled and sent when the camera is no longer in its middle tile
|
||||
function gb_frame(render3d_st: mut Render3dState) -> void {
|
||||
if not gb_on(render3d_st) { return }
|
||||
let texel = gb_texel(render3d_st)
|
||||
let tm = texel * float(GD_SIDE)
|
||||
let half = float(render3d_st.TERRAIN_HALF)
|
||||
let ctx = int(Math.floor((render3d_st.cam_pos[0] - render3d_st.ter_ox + half) / tm))
|
||||
let ctz = int(Math.floor((render3d_st.cam_pos[2] - render3d_st.ter_oz + half) / tm))
|
||||
let tx = (ctx - GB_TILES / 2) * GD_SIDE
|
||||
let tz = (ctz - GB_TILES / 2) * GD_SIDE
|
||||
if tx == render3d_st.gb_tx and tz == render3d_st.gb_tz { return }
|
||||
for j in 0 .. GB_TILES {
|
||||
for i in 0 .. GB_TILES { gb_fill_tile(render3d_st, i, j, ctx - GB_TILES / 2 + i, ctz - GB_TILES / 2 + j) }
|
||||
}
|
||||
let side = GB_TILES * GD_SIDE
|
||||
gpu_buffer_upload(render3d_st, render3d_st.gb_buf, side * side, data_of(render3d_st.gb_win), GPU_DYNAMIC)
|
||||
render3d_st.gb_tx = tx
|
||||
render3d_st.gb_tz = tz
|
||||
}
|
||||
|
||||
# window tile (i, j) from the map's tile (mx, mz): its density bytes, four to a word; zeros off the map
|
||||
function gb_fill_tile(render3d_st: mut Render3dState, i: int, j: int, mx: int, mz: int) -> void {
|
||||
let l = render3d_st.gb_layer
|
||||
let n = render3d_st.gd_n
|
||||
let wq = GB_TILES * GD_SIDE / 4
|
||||
let inside = mx >= 0 and mz >= 0 and mx < n and mz < n
|
||||
var at = 0
|
||||
var ch = 1
|
||||
if inside {
|
||||
at = gd_slot(render3d_st, l, mz * n + mx) * GD_SIDE * GD_SIDE * 2
|
||||
ch = render3d_st.gd_ch[l]
|
||||
}
|
||||
let pool = render3d_st.gd_pool
|
||||
for r in 0 .. GD_SIDE {
|
||||
for q in 0 .. GD_SIDE / 4 {
|
||||
var w = 0
|
||||
if inside {
|
||||
let o = at + (r * GD_SIDE + q * 4) * ch
|
||||
w = pool[o] | (pool[o + ch] << 8) | (pool[o + 2 * ch] << 16) | (pool[o + 3 * ch] << 24)
|
||||
}
|
||||
render3d_st.gb_win[(j * GD_SIDE + r) * wq + i * GD_SIDE / 4 + q] = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# grass_cull.comp's Params word 72: the window's world corner, its texel and side; a side of 0 is the rules
|
||||
function gb_params(render3d_st: Render3dState, pr: words) -> void {
|
||||
if not gb_on(render3d_st) or render3d_st.gb_tx == GB_NONE { return }
|
||||
let texel = gb_texel(render3d_st)
|
||||
let half = float(render3d_st.TERRAIN_HALF)
|
||||
pr[72] = float_bits(render3d_st.ter_ox - half + float(render3d_st.gb_tx) * texel)
|
||||
pr[73] = float_bits(render3d_st.ter_oz - half + float(render3d_st.gb_tz) * texel)
|
||||
pr[74] = float_bits(texel)
|
||||
pr[75] = float_bits(float(GB_TILES * GD_SIDE))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue