feat(lang): L7 memory is safe unless it says unsafe

The typed buffers are slices: words/floats/fixeds/doubles/pointers(n) make
zeroed, bounds-checked []int/[]float/... and the type names mean them. buffer(n)
is a []byte, with text_of, Fs.read_bytes/write_bytes and view(xs, start, n).
bytes(), indexing a raw pointer or bytes, free, resize, Memory.*, raw file calls,
data_of and C externs are refused outside unsafe { } / unsafe function, and a
project's own files may write unsafe only with --unsafe; the runtime and packages
are the platform. A slice passed to an extern goes as its data.

What the change found: Sync's atomics on a slice header, words(n) uninitialised,
input's fixed axes in ints, truetype's fixed outlines as ints, skin matrices
typed int, gl_shader's source table made from raw bytes. render3d gets safe
entry points (safe_api.ludic). Rendering is byte-identical; a frame costs the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 12:53:27 +03:00
parent 9259808f80
commit b0b0b62bce
70 changed files with 69189 additions and 64569 deletions

View file

@ -17,7 +17,7 @@ var prefs_n: int = 0
namespace Prefs {
internal function init() -> void {
if prefs_names != null { return }
prefs_names = bytes(PREFS_MAX * 8)
prefs_names = pointers(PREFS_MAX)
prefs_vals = words(PREFS_MAX)
prefs_n = 0
}

View file

@ -19,6 +19,17 @@ const F_HALF: int = 0x3F000000
const PI: float = 3.1415927
const F_PI: int = 0x40490FDB
# the n 4x4 matrices laid end to end in `m`, a view of each. Views are made once, where their
# buffer is: a view is a small allocation, and one per matrix per frame is one nobody frees.
function m4_views(m: floats, n: int) -> [][]float {
let out = new [][]float
var i = 0
while i < n {
push(out, view(m, i * 16, 16))
i += 1
}
return out
}
function fl(x: fixed) -> float { return float(x) }
function fi(n: int) -> float { return float(n) }
function fr(n: int, d: int) -> float { return float(n) / float(d) }

View file

@ -241,19 +241,19 @@ function gpu_screenshot(path: string) -> bool { if gpu_kind == GPU_VK { return g
var gpu_u_tmp: words = null
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
# float bits (IEEE singles in an int), like every other number in the renderer
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, t, 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, t, 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, t, 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, t, 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 12, 1); return }; gl_uniform3fv(loc, 1, v) }
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 4, n); return }; gl_uniform1fv(loc, n, v) }
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, data_of(t), 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, data_of(t), 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, data_of(t), 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, data_of(t), 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 12, 1); return }; gl_uniform3fv(loc, 1, v) }
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 4, n); return }; gl_uniform1fv(loc, n, v) }
# n vec4s from 4n float bits. Not u_fv with 4n: on Vulkan an array element is copied at the size
# given and placed at the array's stride, so a vec4 array fed floats got one float per element -
# which drew the chunked grass with every tile at a nonsense corner and zero blades a cell.
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 16, n); return }; gl_uniform4fv(loc, n, v) }
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, t, 4, 1); return }; gl_uniform1i(loc, v) }
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 16, n); return }; gl_uniform4fv(loc, n, v) }
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, data_of(t), 4, 1); return }; gl_uniform1i(loc, v) }
# ---- what this machine can do ----------------------------------------------------
# The advanced graphics features are Windows features: the Vulkan renderer, ray tracing,

View file

@ -105,7 +105,7 @@ function grass_blade_mesh(rows: int) -> Mesh {
idx[q * 6] = b; idx[q * 6 + 1] = b + 1; idx[q * 6 + 2] = b + 2
idx[q * 6 + 3] = b + 1; idx[q * 6 + 4] = b + 3; idx[q * 6 + 5] = b + 2
}
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
free(idx)
m.count = nq * 6
gpu_mesh_done(m)
@ -270,7 +270,7 @@ function grass_flush() -> void {
if grass_n == 0 { return }
var p = grass_prog
let mesh = grass_mesh_live()
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, grass_rec, GPU_DYNAMIC) }
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, data_of(grass_rec), GPU_DYNAMIC) }
for b in 0 .. grass_band_n {
let s = grass_band_start[b]
var e = grass_n

View file

@ -52,7 +52,7 @@ function mesh_grid(n: int, half: float) -> Mesh {
k += 6
}
}
gpu_mesh_indices(m, idx, ni * 4, 4)
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
free(idx)
m.count = ni
gpu_mesh_done(m)
@ -81,7 +81,7 @@ function mesh_card() -> Mesh {
free(v)
let idx = words(6)
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
gpu_mesh_indices(m, idx, 24, 4)
gpu_mesh_indices(m, data_of(idx), 24, 4)
free(idx)
m.count = 6
gpu_mesh_done(m)

View file

@ -58,9 +58,17 @@ function q_rotate(o: floats, q: floats, v: floats) -> void {
}
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
var q_scratch: floats = null
var q_sy: floats = null # views of q_scratch's second, third and fourth
var q_sz: floats = null
var q_st: floats = null
function q_euler(o: floats, pitch: float, yaw: float, roll: float) -> void {
if q_scratch == null { q_scratch = floats(16) }
let qx = q_scratch; let qy = mem_off(q_scratch, 16); let qz = mem_off(q_scratch, 32); let t = mem_off(q_scratch, 48)
if q_scratch == null {
q_scratch = floats(16)
q_sy = view(q_scratch, 4, 4)
q_sz = view(q_scratch, 8, 4)
q_st = view(q_scratch, 12, 4)
}
let qx = q_scratch; let qy = q_sy; let qz = q_sz; let t = q_st
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)

View file

@ -35,3 +35,4 @@ import "grass.ludic"
import "water.ludic"
import "streamline.ludic"
import "render.ludic"
import "safe_api.ludic"

View file

@ -0,0 +1,41 @@
# safe_api.ludic — the renderer's raw buffers, as a program may hold them (L7). The renderer reads
# glTF accessors, the screen and PNG files into raw memory, which is its business; a game gets the
# same data as slices, bounds-checked, and never frees anything. Each of these copies or hands over
# at the boundary, so nothing raw crosses it.
# an accessor's float components (gltf_count elements of gltf_comps floats), as floats
function gltf_accessor_floats(idx: int) -> floats {
let p = gltf_accessor(idx)
let n = gltf_count * gltf_comps
let out = floats(n)
for i in 0 .. n { out[i] = float_from_bits(mem_get_f32_bits(p, i)) }
free(p)
return out
}
# how many elements an accessor holds, without reading them
function gltf_accessor_count(idx: int) -> int {
let acc = value_at(value_get(gltf_doc, "accessors"), idx)
return jint(acc, "count", 0)
}
# the presented frame as RGB8, bottom row first, into `out` (at least w * h * 3 bytes)
function gpu_read_screen_bytes(w: int, h: int, out: []byte) -> bool {
if out == null or len(out) < w * h * 3 { return false }
gpu_read_screen(w, h, data_of(out))
return true
}
# a PNG's samples (tex_w x tex_h x tex_channels, 8 or 16 bits): into `reuse` when it is large
# enough - a map swap decodes the same size again - else into a new buffer. null if unreadable.
function png_decode_bytes(path: string, reuse: []byte) -> []byte {
let p = png_decode(path)
if p == null { return null }
let n = tex_w * tex_h * tex_channels * (tex_depth / 8)
var out = reuse
if out == null or len(out) < n { out = buffer(n) }
for i in 0 .. n { out[i] = p[i] }
free(p)
return out
}
# upload samples laid out as the last decode left them (tex_w, tex_h, tex_channels, tex_depth)
function tex_upload_bytes(px: []byte, srgb: bool, mips: bool) -> int {
return tex_upload(data_of(px), srgb, mips)
}

View file

@ -116,7 +116,7 @@ function model_cross_card() -> Model {
let idx = words(12)
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
idx[6] = 4; idx[7] = 5; idx[8] = 6; idx[9] = 4; idx[10] = 6; idx[11] = 7
gpu_mesh_indices(m, idx, 48, 4)
gpu_mesh_indices(m, data_of(idx), 48, 4)
free(idx)
m.count = 12
gpu_mesh_done(m)
@ -194,7 +194,7 @@ function model_lupine() -> Model {
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
sc_model_layout(m)
free(v)
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
free(idx)
m.count = nq * 6
gpu_mesh_done(m)
@ -267,7 +267,7 @@ function model_lupine_dense() -> Model {
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
sc_model_layout(m)
free(v)
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
free(idx)
m.count = nq * 6
gpu_mesh_done(m)
@ -315,7 +315,7 @@ function model_blade() -> Model {
idx[k + 3] = a + 1; idx[k + 4] = a + 3; idx[k + 5] = a + 2
k += 6
}
gpu_mesh_indices(m, idx, ni * 4, 4)
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
free(idx)
m.count = ni
gpu_mesh_done(m)
@ -667,7 +667,7 @@ function layer_gpu_prepare(l: Layer) -> bool {
}
let n = l.n_lods
let cap = l.count
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, l.inst, GPU_STATIC)
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
gpu_buffer_upload(l.g_dst, (n + 1) * cap * INST_FLOATS * 4, null, GPU_DYNAMIC)
if l.g_arena == null { layer_arena_build(l) }
let n_mat = len(l.g_arena)
@ -690,14 +690,14 @@ function layer_gpu_prepare(l: Layer) -> bool {
}
}
}
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, rec, GPU_DYNAMIC)
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, data_of(rec), GPU_DYNAMIC)
let zeros = words(5)
for i in 0 .. 5 { zeros[i] = 0 }
gpu_buffer_upload(l.g_counts, 20, zeros, GPU_DYNAMIC)
gpu_buffer_upload(l.g_counts, 20, data_of(zeros), GPU_DYNAMIC)
free(rec); free(zeros)
# the card casts every instance, as on the CPU path (layer_grid_build uploads this there)
l.n_sh = l.count
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
l.g_n = l.count
l.g_on = true
return true
@ -715,7 +715,7 @@ function layer_gpu_cull(l: Layer) -> void {
pr[32] = float_bits(l.lods[0].height * 2.0); pr[33] = float_bits(4.0)
let bufs = words(4)
bufs[0] = l.g_src; bufs[1] = l.g_dst; bufs[2] = l.g_cmds; bufs[3] = l.g_counts
gpu_dispatch(sc_cull_prog, pr, 144, bufs, 1)
gpu_dispatch(sc_cull_prog, data_of(pr), 144, bufs, 1)
free(pr); free(bufs)
}
@ -762,7 +762,7 @@ function layer_grid_build(l: Layer, cell: float) -> void {
free(cellof); free(fill)
if l.vis == null { l.vis = floats(l.cap * INST_FLOATS) }
l.n_sh = l.count
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
}
# gather the instances of the cells the camera can see (and that are within cull)
@ -848,7 +848,7 @@ function layer_partition_lods(l: Layer, src: floats, total: int) -> void {
# casters: the whole (gathered) set from the shadow buffer, unless the impostor casts
if l.gcell == 0.0 {
l.n_sh = total
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, src, GPU_DYNAMIC) }
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, data_of(src), GPU_DYNAMIC) }
}
free(counts); free(start); free(fill)
}
@ -866,7 +866,7 @@ function layer_update(l: Layer) -> void {
# per-instance loop — one upload, and the same buffer casts its shadows.
if l.streamed and l.imp == null and l.near == 0.0 and l.n_lods <= 1 {
l.n_near = l.count; l.n_far = 0; l.n_sh = l.count
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4)
return
}
@ -916,10 +916,10 @@ function layer_update(l: Layer) -> void {
if l.gcell == 0.0 {
l.n_sh = l.count
if l.count > 0 {
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
}
}
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, tmp, GPU_DYNAMIC)
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, data_of(tmp), GPU_DYNAMIC)
if nf > 0 {
gpu_buffer_upload(l.imp_buf, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GPU_DYNAMIC)
}

View file

@ -11,7 +11,8 @@ const SHADOW_CASCADES: int = 5
var sh_tex: int = 0
var sh_fbo: int = 0
var sh_vp: floats = null # 4 x 16 float bits
var sh_vp: floats = null # the cascades' view-projections, 16 floats each
var sh_vp_v: [][]float = null # a view of each
var sh_split: floats = null # view-space far distance of each cascade
var sh_range: floats = null # 4 light-frustum depth extents (metres)
var sh_texel: floats = null # 4 shadow texel sizes (metres)
@ -29,6 +30,7 @@ function shadow_init() -> void {
gpu_fb_no_color()
gpu_fb_bind(0)
sh_vp = floats(16 * SHADOW_CASCADES)
sh_vp_v = m4_views(sh_vp, SHADOW_CASCADES)
sh_split = floats(SHADOW_CASCADES)
sh_range = floats(SHADOW_CASCADES)
sh_texel = floats(SHADOW_CASCADES)
@ -141,7 +143,7 @@ function shadow_fit(c: int, near: float, far: float) -> void {
free(out); free(eye); free(up); free(center); free(corners)
}
function shadow_cascade_vp(c: int) -> floats { return mem_off(sh_vp, c * 64) }
function shadow_cascade_vp(c: int) -> floats { return sh_vp_v[c] }
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
@ -211,7 +213,7 @@ function shadow_dump() -> void {
let buf = floats(n * SHADOW_CASCADES)
gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex)
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE)
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, buf)
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, data_of(buf))
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
if sh_probe_x != 0.0 {
let vp = shadow_cascade_vp(2)

View file

@ -26,11 +26,14 @@ property Skin {
names: []string,
pose_r: floats, # 4 per node: the pose rotation, model frame
pose_t: floats, # 3 per node: the pose offset, model frame (metres)
gmat: words, # 16 per node: global matrix this pose
gmat: floats, # 16 per node: global matrix this pose
gmat_v: [][]float, # a view of each node's matrix in gmat
n_joints: int = 0,
joints: words, # node index per joint
inv_bind: words, # 16 per joint
inv_bind: floats, # 16 per joint
inv_v: [][]float, # a view of each joint's inverse bind matrix
bones: floats, # 16 per joint: what the vertex shader skins with
bones_v: [][]float, # a view of each joint's matrix in bones
tmp_l: floats,
tmp_q: floats,
tmp_a: floats,
@ -76,7 +79,8 @@ function skin_load(idx: int) -> Skin {
sk.n_nodes = n
sk.par = words(n); sk.walk = words(n)
sk.rest_t = floats(n * 3); sk.rest_r = floats(n * 4); sk.rest_s = floats(n * 3); sk.rest_g = floats(n * 4)
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
sk.gmat_v = m4_views(sk.gmat, n)
sk.names = new []string
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
for i in 0 .. n { sk.par[i] = -1 }
@ -123,15 +127,17 @@ function skin_load(idx: int) -> Skin {
if nj > SKIN_MAX_JOINTS { print(`skin: {nj} joints, only the first {SKIN_MAX_JOINTS} are used`); nj = SKIN_MAX_JOINTS }
sk.n_joints = nj
sk.joints = words(nj)
sk.inv_bind = words(nj * 16)
sk.inv_bind = floats(nj * 16)
sk.inv_v = m4_views(sk.inv_bind, nj)
sk.bones = floats(nj * 16)
sk.bones_v = m4_views(sk.bones, nj)
for j in 0 .. nj { sk.joints[j] = value_as_int(value_at(jl, j)) }
if value_has(skv, "inverseBindMatrices") != 0 {
let ib = gltf_accessor(value_as_int(value_get(skv, "inverseBindMatrices")))
for i in 0 .. nj * 16 { sk.inv_bind[i] = mem_get_f32_bits(ib, i) }
for i in 0 .. nj * 16 { sk.inv_bind[i] = float_from_bits(mem_get_f32_bits(ib, i)) }
free(ib)
} else {
for j in 0 .. nj { m4_identity(mem_off(sk.inv_bind, j * 64)) }
for j in 0 .. nj { m4_identity(sk.inv_v[j]) }
}
skin_reset(sk)
skin_pose(sk)
@ -144,7 +150,7 @@ function skin_find(sk: Skin, name: string) -> int {
print(`skin: no node {name}`)
return -1
}
function skin_mat(sk: Skin, node: int) -> floats { return mem_off(sk.gmat, node * 64) }
function skin_mat(sk: Skin, node: int) -> floats { return sk.gmat_v[node] }
# back to the rest pose
function skin_reset(sk: Skin) -> void {
@ -192,7 +198,7 @@ function skin_pose(sk: Skin) -> void {
else { m4_copy(skin_mat(sk, i), sk.tmp_l) }
}
for j in 0 .. sk.n_joints {
m4_mul(mem_off(sk.bones, j * 64), skin_mat(sk, sk.joints[j]), mem_off(sk.inv_bind, j * 64))
m4_mul(sk.bones_v[j], skin_mat(sk, sk.joints[j]), sk.inv_v[j])
}
}
@ -209,10 +215,12 @@ function skin_clone(src: Skin) -> Skin {
sk.n_nodes = src.n_nodes; sk.par = src.par; sk.walk = src.walk
sk.rest_t = src.rest_t; sk.rest_r = src.rest_r; sk.rest_s = src.rest_s; sk.rest_g = src.rest_g
sk.names = src.names
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind; sk.inv_v = src.inv_v
let n = src.n_nodes
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
sk.gmat_v = m4_views(sk.gmat, n)
sk.bones = floats(src.n_joints * 16)
sk.bones_v = m4_views(sk.bones, src.n_joints)
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
skin_reset(sk)
skin_pose(sk)

View file

@ -261,7 +261,7 @@ function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
}
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(s, cx, cz, c) {
let tg = gl_now_us()
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), c.data, c.count * INST_FLOATS * 4)
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), data_of(c.data), c.count * INST_FLOATS * 4)
l.count += c.count
stream_us_gather = stream_us_gather + (gl_now_us() - tg)
}

View file

@ -270,7 +270,7 @@ function terrain_generate() -> void {
ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
gpu_tex_bind(GPU_TEX2D, ter_height_tex)
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, ter_heights)
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, data_of(ter_heights))
gpu_fb_bind(0)
gpu_fb_free(fbo)
gpu_program_free(p)
@ -663,7 +663,7 @@ function cdlod_init() -> void {
k += 6
}
}
gpu_mesh_indices(m, idx, ni * 4, 4)
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
free(idx)
m.count = ni
gpu_mesh_done(m)

View file

@ -389,7 +389,7 @@ function tex_load_hdr(path: pointer) -> int {
let id = gpu_tex_new()
gpu_tex_bind(GPU_TEX2D, id)
gpu_pixel_store(GL_UNPACK_ALIGNMENT, 4)
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, px)
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, data_of(px))
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
@ -417,7 +417,7 @@ function tex_max(tex: int, w: int, h: int, tag: pointer) -> void {
let buf = floats(w * h * 4)
gpu_tex_bind(GPU_TEX2D, tex)
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, buf)
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, data_of(buf))
var best = 0.0; var bx = 0; var by = 0
var i = 0
while i < w * h {

View file

@ -21,6 +21,8 @@ var wt_wade_s: float = 0.0
var water_refl: Target = null # the world mirrored in the surface, half resolution
var water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
var water_saved: floats = null # the real camera's matrices, restored after the pass
var water_saved_vp: floats = null # views of its second and third matrices
var water_saved_ivp: floats = null
var water_dumped: bool = false
# Several still-water planes, each at its own level over its own bounds: the sea round an
# island and a lake a hundred metres above it cannot be one surface. Each draws the same way;
@ -49,11 +51,13 @@ function water_reflection_pass() -> void {
# rendered at the window's size would be paying for pixels the water never samples
water_refl = target_new(post_w / water_refl_div, post_h / water_refl_div, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
water_saved = floats(16 * 4 + 3)
water_saved_vp = view(water_saved, 16, 16)
water_saved_ivp = view(water_saved, 32, 16)
}
# save the camera
m4_copy(water_saved, cam_view)
m4_copy(mem_off(water_saved, 64), cam_vp)
m4_copy(mem_off(water_saved, 128), cam_inv_vp)
m4_copy(water_saved_vp, cam_vp)
m4_copy(water_saved_ivp, cam_inv_vp)
let sx = cam_pos[0]; let sy = cam_pos[1]; let sz = cam_pos[2]
# the mirrored camera: view' = view * R, R reflecting y about the surface (y' = 2L - y).
# R has determinant -1, so the winding flips (front faces culled below) and the image
@ -98,8 +102,8 @@ function water_reflection_pass() -> void {
# restore
r3d_clip_y = -2147483600.0
m4_copy(cam_view, water_saved)
m4_copy(cam_vp, mem_off(water_saved, 64))
m4_copy(cam_inv_vp, mem_off(water_saved, 128))
m4_copy(cam_vp, water_saved_vp)
m4_copy(cam_inv_vp, water_saved_ivp)
v3_set(cam_pos, sx, sy, sz)
free(eye); free(fwd); free(up); free(at)
gpu_fb_bind(0)

View file

@ -21,8 +21,8 @@ var ing_qty: words = null
function craft_ensure() -> void {
if recipe_reg == null {
recipe_reg = Dict.new()
recipe_out = bytes(CRAFT_MAX * 8); recipe_outqty = words(CRAFT_MAX)
ing_rid = words(CRAFT_ING_MAX); ing_name = bytes(CRAFT_ING_MAX * 8); ing_qty = words(CRAFT_ING_MAX)
recipe_out = pointers(CRAFT_MAX); recipe_outqty = words(CRAFT_MAX)
ing_rid = words(CRAFT_ING_MAX); ing_name = pointers(CRAFT_ING_MAX); ing_qty = words(CRAFT_ING_MAX)
}
}

View file

@ -23,8 +23,8 @@ function dlg_ensure() -> void {
if dlg_reg == null {
dlg_reg = Dict.new()
dlg_tree_start = words(DLG_TREE_MAX)
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = bytes(DLG_NODE_MAX * 8)
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = bytes(DLG_CHOICE_MAX * 8); dlg_choice_next = words(DLG_CHOICE_MAX)
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = pointers(DLG_NODE_MAX)
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = pointers(DLG_CHOICE_MAX); dlg_choice_next = words(DLG_CHOICE_MAX)
}
}