From 43c379aa0f8687a7a5b637408f05be1cedb39027 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 10:21:55 +0300 Subject: [PATCH] refactor(render3d): vertex data and draws behind gpu.ludic Every vertex array, vertex and index buffer, attribute pointer, instance divisor, stream upload and draw call now goes through gpu_mesh_* / gpu_buffer_* / gpu_draw_*, and no other file in the package names them. A Mesh records its layout as it is built - which buffer feeds which attribute at what stride and offset, per vertex or per instance - so a backend that bakes vertex input into a pipeline can read it back. On OpenGL each call is the GL it replaces, in the same order: the five fixed viewpoints render bit-identically and the game's self-tests report exactly what they did before. scatter_attach takes the mesh rather than its vertex array; a mesh now frees every vertex buffer it owns (glTF meshes used to keep all but the first); the two helpers nothing called, mesh_grid_patches and mesh_instance_buffer, are gone. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/gltf.ludic | 20 +--- packages/ludic.render3d/gpu.ludic | 136 +++++++++++++++++++++ packages/ludic.render3d/grass.ludic | 19 ++- packages/ludic.render3d/mesh.ludic | 126 +++++--------------- packages/ludic.render3d/overlay.ludic | 26 ++--- packages/ludic.render3d/scatter.ludic | 162 ++++++++++---------------- packages/ludic.render3d/skin.ludic | 26 ++--- packages/ludic.render3d/terrain.ludic | 23 ++-- 8 files changed, 271 insertions(+), 267 deletions(-) diff --git a/packages/ludic.render3d/gltf.ludic b/packages/ludic.render3d/gltf.ludic index 98ec820a..219d2c2d 100644 --- a/packages/ludic.render3d/gltf.ludic +++ b/packages/ludic.render3d/gltf.ludic @@ -123,20 +123,15 @@ function gltf_accessor(idx: int) -> pointer { function gltf_attrib(m: Mesh, attrs: Val, name: pointer, loc: int) -> bool { if value_has(attrs, name) == 0 { return false } let data = gltf_accessor(value_as_int(value_get(attrs, name))) - let b = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, b) - gl_buffer_data(GL_ARRAY_BUFFER, gltf_count * gltf_comps * 4, data, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(loc) - gl_vertex_attrib_pointer(loc, gltf_comps, GL_FLOAT, 0, 0, null) + gpu_mesh_vertices(m, data, gltf_count * gltf_comps * 4, GPU_STATIC) + gpu_mesh_attr(m, loc, gltf_comps, GPU_F32, 0, 0, false) free(data) - if loc == 0 { m.vbo = b } return true } function gltf_prim(p: Val) -> Prim { let pr = new Prim - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let attrs = value_get(p, "attributes") gltf_attrib(m, attrs, "POSITION", 0) gltf_attrib(m, attrs, "NORMAL", 1) @@ -144,14 +139,11 @@ function gltf_prim(p: Val) -> Prim { skin_attribs(m, attrs) # JOINTS_0 / WEIGHTS_0 onto 5 / 6, when the mesh has them let idx = gltf_accessor(value_as_int(value_get(p, "indices"))) var isz = 4 - m.itype = GL_UNSIGNED_INT - if gltf_ctype == 5123 { isz = 2; m.itype = GL_UNSIGNED_SHORT } - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, gltf_count * isz, idx, GL_STATIC_DRAW) + if gltf_ctype == 5123 { isz = 2 } + gpu_mesh_indices(m, idx, gltf_count * isz, isz) free(idx) m.count = gltf_count - gl_bind_vertex_array(0) + gpu_mesh_done(m) pr.mesh = m # material textures if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) } diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 9b06ca63..d7954618 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -11,6 +11,7 @@ # - the fixed-function render state: depth test/func/write, blending, face # culling, colour writes, alpha-to-coverage, depth bias, scissor # - uniforms: looked up by program and name (gpu_uniform), set by the u_* setters +# - vertex data: meshes, their attribute layouts, instance and stream buffers, draws # # Render state is cached. A pipeline API bakes this state into an object picked by # key; OpenGL gets the same effect by only telling the driver what changed. The @@ -318,3 +319,138 @@ function gpu_caps_probe() -> void { # Whether the renderer actually draws a feature yet. The Vulkan renderer is being built; # until a feature lands, choosing it is saved and shown, and says it takes effect later. function gpu_feature_implemented(f: int) -> bool { return false } + +# ---- vertex data -------------------------------------------------------------------- +# A Mesh is built through these and records what it is made of - which buffer feeds which +# attribute, at what stride and offset, per vertex or per instance - so a backend that bakes +# vertex input into a pipeline (Vulkan) can read the layout back. On OpenGL each call is the +# GL it replaces, in the same order: a vertex array object per mesh, bound while it is built. +const GPU_F32: int = 1 +const GPU_U8: int = 2 +const GPU_U16: int = 3 +const GPU_STATIC: int = 0 +const GPU_DYNAMIC: int = 1 +const GPU_STREAM: int = 2 +const GPU_MAX_ATTRS: int = 8 +const GPU_ATTR_W: int = 7 # per attribute index: buffer, comps, type, stride, offset, normalized, per instance +const GPU_MAX_VBUFS: int = 8 + +function gpu_gl_type(t: int) -> int { + if t == GPU_U8 { return GL_UNSIGNED_BYTE } + if t == GPU_U16 { return GL_UNSIGNED_SHORT } + return GL_FLOAT +} +function gpu_type_bytes(t: int) -> int { + if t == GPU_U8 { return 1 } + if t == GPU_U16 { return 2 } + return 4 +} +function gpu_gl_usage(u: int) -> int { + if u == GPU_DYNAMIC { return GL_DYNAMIC_DRAW } + if u == GPU_STREAM { return GL_STREAM_DRAW } + return GL_STATIC_DRAW +} + +# a new mesh, its vertex array bound: the vertex, attribute and index calls below describe it +function gpu_mesh_new() -> Mesh { + let m = new Mesh + m.attrs = words(GPU_MAX_ATTRS * GPU_ATTR_W) + for i in 0 .. GPU_MAX_ATTRS * GPU_ATTR_W { m.attrs[i] = 0 } + m.vbufs = words(GPU_MAX_VBUFS) + m.vao = gl_vao() + return m +} +# a vertex buffer for the mesh being built (data may be null: storage only); returns it +function gpu_mesh_vertices(m: Mesh, data: pointer, nbytes: int, usage: int) -> int { + let b = gl_buffer() + gl_bind_buffer(GL_ARRAY_BUFFER, b) + gl_buffer_data(GL_ARRAY_BUFFER, nbytes, data, gpu_gl_usage(usage)) + if m.vbo == 0 { m.vbo = b } + if m.n_vbufs < GPU_MAX_VBUFS { m.vbufs[m.n_vbufs] = b; m.n_vbufs += 1 } + m.cur_buf = b + return b +} +function gpu_mesh_record(m: Mesh, index: int, comps: int, type: int, stride: int, offset: int, normalized: bool, inst: bool) -> void { + if index < 0 or index >= GPU_MAX_ATTRS { return } + let o = index * GPU_ATTR_W + var st = stride + if st == 0 { st = comps * gpu_type_bytes(type) } + m.attrs[o] = m.cur_buf; m.attrs[o + 1] = comps; m.attrs[o + 2] = type; m.attrs[o + 3] = st + m.attrs[o + 4] = offset; m.attrs[o + 5] = gpu_b(normalized); m.attrs[o + 6] = gpu_b(inst) + if index + 1 > m.n_attrs { m.n_attrs = index + 1 } +} +# attribute `index` read from the last vertex buffer (stride 0: tightly packed) +function gpu_mesh_attr(m: Mesh, index: int, comps: int, type: int, stride: int, offset: int, normalized: bool) -> void { + gl_enable_vertex_attrib_array(index) + gl_vertex_attrib_pointer(index, comps, gpu_gl_type(type), gpu_b(normalized), stride, gl_ptr(null, offset)) + gpu_mesh_record(m, index, comps, type, stride, offset, normalized, false) +} +# the index buffer: 4-byte or 2-byte indices +function gpu_mesh_indices(m: Mesh, data: pointer, nbytes: int, index_bytes: int) -> void { + m.itype = GL_UNSIGNED_INT + if index_bytes == 2 { m.itype = GL_UNSIGNED_SHORT } + m.ebo = gl_buffer() + gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) + gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, nbytes, data, GL_STATIC_DRAW) +} +# finished describing: nothing else is bound to it by accident +function gpu_mesh_done(m: Mesh) -> void { gl_bind_vertex_array(0) } + +# Per-instance data: `buf` feeds the attributes named next, one element per instance. A mesh +# drawn from different instance buffers (the scatter layers' LOD buckets) is re-pointed here +# before each draw; on Vulkan that is a vertex-buffer binding, not a change of layout. +function gpu_mesh_bind_instances(m: Mesh, buf: int) -> void { + gl_bind_vertex_array(m.vao) + gl_bind_buffer(GL_ARRAY_BUFFER, buf) + m.cur_buf = buf + m.ibuf = buf +} +function gpu_mesh_attr_inst(m: Mesh, index: int, comps: int, type: int, stride: int, offset: int) -> void { + gl_enable_vertex_attrib_array(index) + gl_vertex_attrib_pointer(index, comps, gpu_gl_type(type), 0, stride, gl_ptr(null, offset)) + gl_vertex_attrib_divisor(index, 1) + gpu_mesh_record(m, index, comps, type, stride, offset, false, true) +} + +# a buffer on its own (instances, a stream): made, filled whole, freed +function gpu_buffer_new() -> int { return gl_buffer() } +function gpu_buffer_upload(buf: int, nbytes: int, data: pointer, usage: int) -> void { + gl_bind_buffer(GL_ARRAY_BUFFER, buf) + gl_buffer_data(GL_ARRAY_BUFFER, nbytes, data, gpu_gl_usage(usage)) +} +function gpu_buffer_free(buf: int) -> void { + if buf == 0 { return } + let ids = gpu_tmp() + ids[0] = buf + gl_delete_buffers(1, ids) +} + +# drawing +function gpu_mesh_bind(m: Mesh) -> void { gl_bind_vertex_array(m.vao) } +function gpu_mesh_unbind() -> void { gl_bind_vertex_array(0) } +function gpu_draw_mesh(m: Mesh) -> void { + gl_bind_vertex_array(m.vao) + if m.ebo != 0 { gl_draw_elements(m.mode, m.count, m.itype, null) } + else { gl_draw_arrays(m.mode, 0, m.count) } +} +function gpu_draw_mesh_instanced(m: Mesh, n: int) -> void { + gl_bind_vertex_array(m.vao) + if m.ebo != 0 { gl_draw_elements_instanced(m.mode, m.count, m.itype, null, n) } + else { gl_draw_arrays_instanced(m.mode, 0, m.count, n) } +} +# the bound mesh's indices again (a patch mesh drawn once per terrain node) +function gpu_draw_bound_elements(m: Mesh) -> void { gl_draw_elements(m.mode, m.count, m.itype, null) } +# vertices [first, first + count) of the bound mesh, as triangles (the overlay's ranges) +function gpu_draw_range(m: Mesh, first: int, count: int) -> void { gl_draw_arrays(GL_TRIANGLES, first, count) } + +function gpu_mesh_free(m: Mesh) -> void { + if m == null { return } + let ids = gpu_tmp() + if m.vbufs != null { + for i in 0 .. m.n_vbufs { ids[0] = m.vbufs[i]; gl_delete_buffers(1, ids) } + m.n_vbufs = 0 + } else if m.vbo != 0 { ids[0] = m.vbo; gl_delete_buffers(1, ids) } + m.vbo = 0 + if m.ebo != 0 { ids[0] = m.ebo; gl_delete_buffers(1, ids); m.ebo = 0 } + if m.vao != 0 { ids[0] = m.vao; gl_delete_vertex_arrays(1, ids); m.vao = 0 } +} diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index 11abfcdf..ffe1c904 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -23,8 +23,7 @@ var grass_dbg: int = 0 # a blade: `rows` rows of 2 vertices (x across, y along, z bend), attribute 2 = uv function grass_blade_mesh(rows: int) -> Mesh { - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let v = gl_floats(rows * 2 * 5) var k = 0 for r in 0 .. rows { @@ -39,11 +38,9 @@ function grass_blade_mesh(rows: int) -> Mesh { k += 5 } } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(rows * 2 * 5), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 3, GL_FLOAT, 0, 20, null) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 2, GL_FLOAT, 0, 20, gl_ptr(null, 12)) + gpu_mesh_vertices(m, v, gl_bytes_of(rows * 2 * 5), GPU_STATIC) + gpu_mesh_attr(m, 0, 3, GPU_F32, 20, 0, false) + gpu_mesh_attr(m, 2, 2, GPU_F32, 20, 12, false) free(v) let nq = rows - 1 let idx = words(nq * 6) @@ -52,12 +49,10 @@ 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 } - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, nq * 6 * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, nq * 6 * 4, 4) free(idx) m.count = nq * 6 - gl_bind_vertex_array(0) + gpu_mesh_done(m) return m } @@ -157,7 +152,7 @@ function grass_draw() -> void { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.15)) gpu_cull(false) grass_draws = 0 - gl_bind_vertex_array(grass_mesh.vao) + gpu_mesh_bind(grass_mesh) grass_tiles(16, F_ZERO, fi(300)) grass_tiles(64, fi(300), fi(1200)) grass_tiles(256, fi(1200), grass_radius) diff --git a/packages/ludic.render3d/mesh.ludic b/packages/ludic.render3d/mesh.ludic index 1160c234..e387fcee 100644 --- a/packages/ludic.render3d/mesh.ludic +++ b/packages/ludic.render3d/mesh.ludic @@ -1,34 +1,31 @@ # ============================================================================ -# mesh.ludic — vertex data on the GPU: a Mesh record (VAO + buffers + a draw -# call), and the procedural meshes the renderer needs (a grid for the terrain, -# a full-screen triangle, a unit quad for instanced cards). +# mesh.ludic — vertex data on the GPU: a Mesh record (buffers + a draw call), and +# the procedural meshes the renderer needs (a grid for the terrain, a full-screen +# triangle, a unit quad for instanced cards). Built and drawn through gpu.ludic. # ============================================================================ property Mesh { - vao: int = 0, - vbo: int = 0, + vao: int = 0, # OpenGL's vertex array object (gpu.ludic's, on that backend) + vbo: int = 0, # the first vertex buffer ebo: int = 0, count: int = 0, # indices (ebo != 0) or vertices mode: int = 4, # GL_TRIANGLES - itype: int = 0x1405 # GL_UNSIGNED_INT + itype: int = 0x1405, # GL_UNSIGNED_INT + attrs: words, # the layout, GPU_ATTR_W words per attribute index (gpu.ludic) + n_attrs: int = 0, + vbufs: words, # every vertex buffer the mesh owns, freed with it + n_vbufs: int = 0, + cur_buf: int = 0, # the buffer the next attribute reads (while building) + ibuf: int = 0 # the instance buffer attached last } -function mesh_draw(m: Mesh) -> void { - gl_bind_vertex_array(m.vao) - if m.ebo != 0 { gl_draw_elements(m.mode, m.count, m.itype, null) } - else { gl_draw_arrays(m.mode, 0, m.count) } -} -function mesh_draw_instanced(m: Mesh, n: int) -> void { - gl_bind_vertex_array(m.vao) - if m.ebo != 0 { gl_draw_elements_instanced(m.mode, m.count, m.itype, null, n) } - else { gl_draw_arrays_instanced(m.mode, 0, m.count, n) } -} +function mesh_draw(m: Mesh) -> void { gpu_draw_mesh(m) } +function mesh_draw_instanced(m: Mesh, n: int) -> void { gpu_draw_mesh_instanced(m, n) } # A flat n x n vertex grid over [-half, half]^2 in x/z, y = 0. Attribute 0 = (x, z). # The terrain vertex shader lifts it with the height map. function mesh_grid(n: int, half: int) -> Mesh { - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let nv = n * n let v = gl_floats(nv * 2) var k = 0 @@ -40,11 +37,8 @@ function mesh_grid(n: int, half: int) -> Mesh { k += 2 } } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(nv * 2), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0) - gl_vertex_attrib_pointer(0, 2, GL_FLOAT, 0, 8, null) + gpu_mesh_vertices(m, v, gl_bytes_of(nv * 2), GPU_STATIC) + gpu_mesh_attr(m, 0, 2, GPU_F32, 8, 0, false) free(v) let ni = (n - 1) * (n - 1) * 6 let idx = words(ni) @@ -57,103 +51,41 @@ function mesh_grid(n: int, half: int) -> Mesh { k += 6 } } - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, ni * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, ni * 4, 4) free(idx) m.count = ni - gl_bind_vertex_array(0) - return m -} - -# The grid as patches (4 control points per cell) for tessellation shaders. -function mesh_grid_patches(n: int, half: int) -> Mesh { - let m = mesh_grid(n, half) - # rebuild the index buffer as quads - let nq = (n - 1) * (n - 1) * 4 - let idx = words(nq) - var k = 0 - for j in 0 .. n - 1 { - for i in 0 .. n - 1 { - let a = j * n + i - idx[k] = a; idx[k + 1] = a + 1; idx[k + 2] = a + n + 1; idx[k + 3] = a + n - k += 4 - } - } - gl_bind_vertex_array(m.vao) - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, nq * 4, idx, GL_STATIC_DRAW) - gl_bind_vertex_array(0) - free(idx) - m.count = nq - m.mode = GL_PATCHES + gpu_mesh_done(m) return m } # A full-screen triangle with no attributes (the vertex shader uses gl_VertexID). function mesh_fullscreen() -> Mesh { - let m = new Mesh - m.vao = gl_vao() - gl_bind_vertex_array(0) + let m = gpu_mesh_new() + gpu_mesh_done(m) m.count = 3 return m } # A unit quad in x/y ([-0.5, 0.5] x [0, 1]) with uv, attribute 0 = xy, 1 = uv. function mesh_card() -> Mesh { - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let v = gl_floats(16) gl_put(v, 0, -0.5); gl_put(v, 1, 0.0); gl_put(v, 2, 0.0); gl_put(v, 3, 0.0) gl_put(v, 4, 0.5); gl_put(v, 5, 0.0); gl_put(v, 6, 1.0); gl_put(v, 7, 0.0) gl_put(v, 8, 0.5); gl_put(v, 9, 1.0); gl_put(v, 10, 1.0); gl_put(v, 11, 1.0) gl_put(v, 12, -0.5); gl_put(v, 13, 1.0); gl_put(v, 14, 0.0); gl_put(v, 15, 1.0) - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, 64, v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0) - gl_vertex_attrib_pointer(0, 2, GL_FLOAT, 0, 16, null) - gl_enable_vertex_attrib_array(1) - gl_vertex_attrib_pointer(1, 2, GL_FLOAT, 0, 16, gl_ptr(null, 8)) + gpu_mesh_vertices(m, v, 64, GPU_STATIC) + gpu_mesh_attr(m, 0, 2, GPU_F32, 16, 0, false) + gpu_mesh_attr(m, 1, 2, GPU_F32, 16, 8, false) free(v) let idx = words(6) idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3 - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, 24, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, 24, 4) free(idx) m.count = 6 - gl_bind_vertex_array(0) + gpu_mesh_done(m) return m } -# Attach a per-instance float buffer (n floats per instance, split into vec4 -# attributes from `first_attr`) to a mesh's VAO. Returns the buffer id. -function mesh_instance_buffer(m: Mesh, first_attr: int, floats_per: int, data: pointer, count: int) -> int { - gl_bind_vertex_array(m.vao) - let b = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, b) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(floats_per * count), data, GL_STATIC_DRAW) - var a = 0 - var off = 0 - while off < floats_per { - var sz = floats_per - off - if sz > 4 { sz = 4 } - gl_enable_vertex_attrib_array(first_attr + a) - gl_vertex_attrib_pointer(first_attr + a, sz, GL_FLOAT, 0, floats_per * 4, gl_ptr(null, off * 4)) - gl_vertex_attrib_divisor(first_attr + a, 1) - a += 1 - off += 4 - } - gl_bind_vertex_array(0) - return b -} - -# release a mesh's GL objects (a mesh this package built for something being thrown away) -function mesh_free(m: Mesh) -> void { - if m == null { return } - let ids = gl_scratch() - if m.vbo != 0 { ids[0] = m.vbo; gl_delete_buffers(1, ids); m.vbo = 0 } - if m.ebo != 0 { ids[0] = m.ebo; gl_delete_buffers(1, ids); m.ebo = 0 } - if m.vao != 0 { ids[0] = m.vao; gl_delete_vertex_arrays(1, ids); m.vao = 0 } -} +# release a mesh's GPU objects (a mesh this package built for something being thrown away) +function mesh_free(m: Mesh) -> void { gpu_mesh_free(m) } diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index a2898f28..156aafd0 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -12,7 +12,7 @@ const OV_MAX_QUADS: int = 6000 const OV_FLOATS: int = 8 # x, y, u, v, r, g, b, a var ov_prog: int = 0 -var ov_vao: int = 0 +var ov_mesh: Mesh = null var ov_vbo: int = 0 var ov_buf: pointer = null var ov_n: int = 0 @@ -55,15 +55,12 @@ function overlay_init(font_dir: string) -> bool { r3d_program_log("overlay.vert", "overlay.frag", "") ov_prog = gl_program("#version 410 core\n" + r3d_shader_file("overlay.vert"), "#version 410 core\n" + r3d_shader_file("overlay.frag")) if ov_prog == 0 { print("overlay: program failed"); return false } - ov_vao = gl_vao() - gl_bind_vertex_array(ov_vao) - ov_vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, ov_vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(OV_MAX_QUADS * 6 * OV_FLOATS), null, GL_DYNAMIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 2, GL_FLOAT, 0, OV_FLOATS * 4, null) - gl_enable_vertex_attrib_array(1); gl_vertex_attrib_pointer(1, 2, GL_FLOAT, 0, OV_FLOATS * 4, gl_ptr(null, 8)) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 4, GL_FLOAT, 0, OV_FLOATS * 4, gl_ptr(null, 16)) - gl_bind_vertex_array(0) + ov_mesh = gpu_mesh_new() + ov_vbo = gpu_mesh_vertices(ov_mesh, null, gl_bytes_of(OV_MAX_QUADS * 6 * OV_FLOATS), GPU_DYNAMIC) + gpu_mesh_attr(ov_mesh, 0, 2, GPU_F32, OV_FLOATS * 4, 0, false) + gpu_mesh_attr(ov_mesh, 1, 2, GPU_F32, OV_FLOATS * 4, 8, false) + gpu_mesh_attr(ov_mesh, 2, 4, GPU_F32, OV_FLOATS * 4, 16, false) + gpu_mesh_done(ov_mesh) ov_buf = gl_floats(OV_MAX_QUADS * 6 * OV_FLOATS) ov_ranges = words(OV_MAX_RANGES * OV_RANGE_W) ov_white = tex_solid(255, 255, 255, 255) @@ -193,9 +190,8 @@ function ov_flush() -> void { ov_close_range() if ov_n == 0 { ov_nr = 0; ov_range_start = 0; return } gl_use_program(ov_prog) - gl_bind_vertex_array(ov_vao) - gl_bind_buffer(GL_ARRAY_BUFFER, ov_vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(ov_n * 6 * OV_FLOATS), ov_buf, GL_STREAM_DRAW) + gpu_mesh_bind(ov_mesh) + gpu_buffer_upload(ov_vbo, gl_bytes_of(ov_n * 6 * OV_FLOATS), ov_buf, GPU_STREAM) var last = -1 var clipped = false for i in 0 .. ov_nr { @@ -212,10 +208,10 @@ function ov_flush() -> void { clipped = true gpu_scissor(ov_ranges[o + 3], ov_ranges[o + 4], ov_ranges[o + 5], ov_ranges[o + 6]) } else if clipped { gpu_scissor_off(); clipped = false } - gl_draw_arrays(GL_TRIANGLES, ov_ranges[o + 1] * 6, ov_ranges[o + 2] * 6) + gpu_draw_range(ov_mesh, ov_ranges[o + 1] * 6, ov_ranges[o + 2] * 6) } if clipped { gpu_scissor_off() } - gl_bind_vertex_array(0) + gpu_mesh_unbind() ov_n = 0; ov_nr = 0; ov_range_start = 0 } function ov_end() -> void { diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index be134e75..779a1b6a 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -68,14 +68,20 @@ property Layer { lvl: words # scratch: the level chosen per gathered instance } +# the procedural models' one layout: position, normal, uv, interleaved at 32 bytes +function sc_model_layout(m: Mesh) -> void { + gpu_mesh_attr(m, 0, 3, GPU_F32, 32, 0, false) + gpu_mesh_attr(m, 1, 3, GPU_F32, 32, 12, false) + gpu_mesh_attr(m, 2, 2, GPU_F32, 32, 24, false) +} + # Two crossed unit quads (x in [-0.5, 0.5], y in [0, 1]), attribute 0 = pos, # 1 = the quad's facing normal, 2 = uv. Scaled per layer to the atlas card size. function model_cross_card() -> Model { let model = new Model model.prims = new []Prim let pr = new Prim - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let v = gl_floats(8 * 8) var k = 0 for q in 0 .. 2 { @@ -89,22 +95,16 @@ function model_cross_card() -> Model { k += 8 } } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(64), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 3, GL_FLOAT, 0, 32, null) - gl_enable_vertex_attrib_array(1); gl_vertex_attrib_pointer(1, 3, GL_FLOAT, 0, 32, gl_ptr(null, 12)) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 2, GL_FLOAT, 0, 32, gl_ptr(null, 24)) + gpu_mesh_vertices(m, v, gl_bytes_of(64), GPU_STATIC) + sc_model_layout(m) free(v) 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 - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, 48, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, 48, 4) free(idx) m.count = 12 - gl_bind_vertex_array(0) + gpu_mesh_done(m) pr.mesh = m if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) } pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white @@ -127,8 +127,7 @@ function model_lupine() -> Model { let model = new Model model.prims = new []Prim let pr = new Prim - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() # quads: stem x2 + tiers 12 x 2 + 3 leaves = 29 quads let nq = 29 let v = gl_floats(nq * 4 * 8) @@ -172,19 +171,13 @@ function model_lupine() -> Model { idx[qi] = b; idx[qi + 1] = b + 1; idx[qi + 2] = b + 2; idx[qi + 3] = b; idx[qi + 4] = b + 2; idx[qi + 5] = b + 3 qi += 6 } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(nq * 4 * 8), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 3, GL_FLOAT, 0, 32, null) - gl_enable_vertex_attrib_array(1); gl_vertex_attrib_pointer(1, 3, GL_FLOAT, 0, 32, gl_ptr(null, 12)) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 2, GL_FLOAT, 0, 32, gl_ptr(null, 24)) + gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC) + sc_model_layout(m) free(v) - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, nq * 6 * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, nq * 6 * 4, 4) free(idx) m.count = nq * 6 - gl_bind_vertex_array(0) + gpu_mesh_done(m) pr.mesh = m if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) } pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white @@ -199,8 +192,7 @@ function model_lupine_dense() -> Model { let model = new Model model.prims = new []Prim let pr = new Prim - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let nfl = 220 let nq = 2 + nfl + 5 let v = gl_floats(nq * 4 * 8) @@ -252,19 +244,13 @@ function model_lupine_dense() -> Model { idx[qi] = b; idx[qi + 1] = b + 1; idx[qi + 2] = b + 2; idx[qi + 3] = b; idx[qi + 4] = b + 2; idx[qi + 5] = b + 3 qi += 6 } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(nq * 4 * 8), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 3, GL_FLOAT, 0, 32, null) - gl_enable_vertex_attrib_array(1); gl_vertex_attrib_pointer(1, 3, GL_FLOAT, 0, 32, gl_ptr(null, 12)) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 2, GL_FLOAT, 0, 32, gl_ptr(null, 24)) + gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC) + sc_model_layout(m) free(v) - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, nq * 6 * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, nq * 6 * 4, 4) free(idx) m.count = nq * 6 - gl_bind_vertex_array(0) + gpu_mesh_done(m) pr.mesh = m if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) } pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white @@ -278,8 +264,7 @@ function model_blade() -> Model { let model = new Model model.prims = new []Prim let pr = new Prim - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let rows = 5 let v = gl_floats(rows * 2 * 8) var k = 0 @@ -298,12 +283,8 @@ function model_blade() -> Model { k += 8 } } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(rows * 2 * 8), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 3, GL_FLOAT, 0, 32, null) - gl_enable_vertex_attrib_array(1); gl_vertex_attrib_pointer(1, 3, GL_FLOAT, 0, 32, gl_ptr(null, 12)) - gl_enable_vertex_attrib_array(2); gl_vertex_attrib_pointer(2, 2, GL_FLOAT, 0, 32, gl_ptr(null, 24)) + gpu_mesh_vertices(m, v, gl_bytes_of(rows * 2 * 8), GPU_STATIC) + sc_model_layout(m) free(v) let ni = (rows - 1) * 6 let idx = words(ni) @@ -314,12 +295,10 @@ function model_blade() -> Model { idx[k + 3] = a + 1; idx[k + 4] = a + 3; idx[k + 5] = a + 2 k += 6 } - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, ni * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, ni * 4, 4) free(idx) m.count = ni - gl_bind_vertex_array(0) + gpu_mesh_done(m) pr.mesh = m if gltf_white == 0 { gltf_white = tex_solid(200, 200, 200, 255); gltf_flat = tex_solid(128, 128, 255, 255) } pr.diff = gltf_white; pr.nrm = gltf_flat; pr.arm = gltf_white @@ -389,24 +368,18 @@ function scatter_init() -> void { let one = gl_floats(INST_FLOATS) for i in 0 .. INST_FLOATS { gl_put_bits(one, i, F_ZERO) } gl_put_bits(one, 3, F_ONE); gl_put_bits(one, 5, F_ONE) - sc_ident_buf = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, sc_ident_buf) - gl_buffer_data(GL_ARRAY_BUFFER, INST_FLOATS * 4, one, GL_STATIC_DRAW) + sc_ident_buf = gpu_buffer_new() + gpu_buffer_upload(sc_ident_buf, INST_FLOATS * 4, one, GPU_STATIC) free(one) sc_layers = new []Layer } -# attach a GL instance buffer to a VAO at attributes 3, 4 -function scatter_attach(vao: int, buf: int) -> void { - gl_bind_vertex_array(vao) - gl_bind_buffer(GL_ARRAY_BUFFER, buf) - gl_enable_vertex_attrib_array(3) - gl_vertex_attrib_pointer(3, 4, GL_FLOAT, 0, INST_FLOATS * 4, null) - gl_vertex_attrib_divisor(3, 1) - gl_enable_vertex_attrib_array(4) - gl_vertex_attrib_pointer(4, 4, GL_FLOAT, 0, INST_FLOATS * 4, gl_ptr(null, 16)) - gl_vertex_attrib_divisor(4, 1) - gl_bind_vertex_array(0) +# feed a mesh its instances from `buf`: attribute 3 = position + scale, 4 = sin, cos, seed, wind +function scatter_attach(m: Mesh, buf: int) -> void { + gpu_mesh_bind_instances(m, buf) + gpu_mesh_attr_inst(m, 3, 4, GPU_F32, INST_FLOATS * 4, 0) + gpu_mesh_attr_inst(m, 4, 4, GPU_F32, INST_FLOATS * 4, 16) + gpu_mesh_done(m) } function layer_new(model: Model, cap: int, foliage: bool, wind: int, near: int, cull: int) -> Layer { @@ -421,11 +394,11 @@ function layer_new(model: Model, cap: int, foliage: bool, wind: int, near: int, l.inst = words(cap * INST_FLOATS) l.scratch = words(cap * INST_FLOATS) l.last_cam = v3_new(fi(100000), F_ZERO, F_ZERO) - l.buf = gl_buffer() - l.imp_buf = gl_buffer() - l.sh_buf = gl_buffer() + l.buf = gpu_buffer_new() + l.imp_buf = gpu_buffer_new() + l.sh_buf = gpu_buffer_new() l.rough = F_ONE - for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh.vao, l.buf) } + for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh, l.buf) } push(sc_layers, l) return l } @@ -469,7 +442,7 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor { gpu_cull(false) gpu_blend(false) # the model's prims temporarily take the identity instance - for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh.vao, sc_ident_buf) } + for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh, sc_ident_buf) } let view = m4_new(); let proj = m4_new() let eye = words(3); let at = words(3); let up = v3_new(F_ZERO, F_ONE, F_ZERO) let cy = f_add(model.ymin, f_mul(model.height, F_HALF)) @@ -521,9 +494,9 @@ function layer_set_lods(l: Layer, models: []Model, dists: words) -> void { l.lod_dist = words(l.n_lods); l.lod_card = words(l.n_lods); l.lod_buf = words(l.n_lods); l.n_lod = words(l.n_lods) for k in 0 .. l.n_lods { l.lod_dist[k] = dists[k]; l.lod_card[k] = 0; l.n_lod[k] = 0 - l.lod_buf[k] = gl_buffer() + l.lod_buf[k] = gpu_buffer_new() let m = models[k] - for i in 0 .. len(m.prims) { scatter_attach(m.prims[i].mesh.vao, l.lod_buf[k]) } + for i in 0 .. len(m.prims) { scatter_attach(m.prims[i].mesh, l.lod_buf[k]) } } l.model = models[0] l.near = dists[l.n_lods - 1] @@ -534,7 +507,7 @@ function layer_lod_card(l: Layer, k: int) -> void { l.lod_card[k] = 1 } function layer_set_impostor(l: Layer, im: Impostor) -> void { l.imp = im - scatter_attach(sc_card.vao, l.imp_buf) + scatter_attach(sc_card, l.imp_buf) } # ---- per frame ----------------------------------------------------------------------- @@ -596,8 +569,7 @@ function layer_grid_build(l: Layer, cell: int) -> void { free(cellof); free(fill) if l.vis == null { l.vis = words(l.cap * INST_FLOATS) } l.n_sh = l.count - gl_bind_buffer(GL_ARRAY_BUFFER, l.sh_buf) - gl_buffer_data(GL_ARRAY_BUFFER, l.count * INST_FLOATS * 4, l.inst, GL_STATIC_DRAW) + gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC) } # gather the instances of the cells the camera can see (and that are within cull) @@ -671,21 +643,19 @@ function layer_partition_lods(l: Layer, src: words, total: int) -> void { for k in 0 .. n { l.n_lod[k] = counts[k] if counts[k] > 0 { - gl_bind_buffer(GL_ARRAY_BUFFER, l.lod_buf[k]) - gl_buffer_data(GL_ARRAY_BUFFER, counts[k] * INST_FLOATS * 4, mem_off(tmp, start[k] * INST_FLOATS * 4), GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.lod_buf[k], counts[k] * INST_FLOATS * 4, mem_off(tmp, start[k] * INST_FLOATS * 4), GPU_DYNAMIC) } } l.n_near = counts[0] l.n_far = counts[n] if sc_dbg_lod and total > 1000 { print(`lod partition: total {total} dropped {counts[n + 1]} far {counts[n]} l0 {counts[0]} l1 {counts[1]} l2 {counts[2]} l3 {counts[3]} dist0 {f_fx(l.lod_dist[0])} dist3 {f_fx(l.lod_dist[n - 1])} cull {f_fx(l.cull)} cam {f_fx(cam_pos[0])} {f_fx(cam_pos[2])} first {f_fx(src[0])} {f_fx(src[2])}`) } if l.n_far > 0 { - gl_bind_buffer(GL_ARRAY_BUFFER, l.imp_buf) - gl_buffer_data(GL_ARRAY_BUFFER, l.n_far * INST_FLOATS * 4, mem_off(tmp, start[n] * INST_FLOATS * 4), GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.imp_buf, l.n_far * INST_FLOATS * 4, mem_off(tmp, start[n] * INST_FLOATS * 4), GPU_DYNAMIC) } # casters: the whole (gathered) set from the shadow buffer, unless the impostor casts if l.gcell == 0 { l.n_sh = total - if total > 0 { gl_bind_buffer(GL_ARRAY_BUFFER, l.sh_buf); gl_buffer_data(GL_ARRAY_BUFFER, total * INST_FLOATS * 4, src, GL_DYNAMIC_DRAW) } + if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, src, GPU_DYNAMIC) } } free(counts); free(start); free(fill) } @@ -702,8 +672,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 and l.n_lods <= 1 { l.n_near = l.count; l.n_far = 0; l.n_sh = l.count - gl_bind_buffer(GL_ARRAY_BUFFER, l.buf) - gl_buffer_data(GL_ARRAY_BUFFER, l.count * INST_FLOATS * 4, l.inst, GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC) prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4) return } @@ -753,15 +722,12 @@ function layer_update(l: Layer) -> void { if l.gcell == 0 { l.n_sh = l.count if l.count > 0 { - gl_bind_buffer(GL_ARRAY_BUFFER, l.sh_buf) - gl_buffer_data(GL_ARRAY_BUFFER, l.count * INST_FLOATS * 4, l.inst, GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC) } } - gl_bind_buffer(GL_ARRAY_BUFFER, l.buf) - gl_buffer_data(GL_ARRAY_BUFFER, nn * INST_FLOATS * 4, tmp, GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, tmp, GPU_DYNAMIC) if nf > 0 { - gl_bind_buffer(GL_ARRAY_BUFFER, l.imp_buf) - gl_buffer_data(GL_ARRAY_BUFFER, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GL_DYNAMIC_DRAW) + gpu_buffer_upload(l.imp_buf, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GPU_DYNAMIC) } prof_layer_add(gl_now_us() - t_lu, (nn + nf + l.n_sh) * INST_FLOATS * 4) } @@ -857,7 +823,7 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool, gpu_cull(false) for i in 0 .. len(model.prims) { let pr = model.prims[i] - scatter_attach(pr.mesh.vao, vb) + scatter_attach(pr.mesh, vb) if not card { r3d_bind_2d(p, "u_diff", 0, pr.diff) if not shadow { r3d_bind_2d(p, "u_nrm", 1, pr.nrm); r3d_bind_2d(p, "u_arm", 2, pr.arm) } @@ -896,7 +862,7 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void { } gpu_cull(false) if not shadow and sc_a2c { gpu_alpha_to_coverage(true) } - scatter_attach(sc_card.vao, l.imp_buf) + scatter_attach(sc_card, l.imp_buf) mesh_draw_instanced(sc_card, l.n_far) gpu_alpha_to_coverage(false) } @@ -918,7 +884,7 @@ function layer_draw_shadow(l: Layer, light_vp: words) -> void { u_v3(gpu_uniform(p, "u_face_dir"), sun_dir) u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos) gpu_cull(false) - scatter_attach(sc_card.vao, l.sh_buf) + scatter_attach(sc_card, l.sh_buf) mesh_draw_instanced(sc_card, l.n_sh) } @@ -946,7 +912,7 @@ function layer_draw_depth(l: Layer, model: Model, vb: int, cnt: int) -> void { gpu_cull(false) for i in 0 .. len(model.prims) { let pr = model.prims[i] - scatter_attach(pr.mesh.vao, vb) + scatter_attach(pr.mesh, vb) r3d_bind_2d(p, "u_diff", 0, pr.diff) mesh_draw_instanced(pr.mesh, cnt) } @@ -1065,9 +1031,8 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int { } } } - let buf = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, buf) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(n9 * INST_FLOATS), inst, GL_STATIC_DRAW) + let buf = gpu_buffer_new() + gpu_buffer_upload(buf, gl_bytes_of(n9 * INST_FLOATS), inst, GPU_STATIC) free(inst) # straight down: the window is exactly one tile let view = m4_new(); let proj = m4_new() @@ -1088,7 +1053,7 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int { r3d_bind_2d(bake, "u_arm", 2, l.atlas.normal) for i in 0 .. len(l.model.prims) { let pr = l.model.prims[i] - scatter_attach(pr.mesh.vao, buf) + scatter_attach(pr.mesh, buf) mesh_draw_instanced(pr.mesh, n9) } } @@ -1098,8 +1063,7 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int { gl_delete_framebuffers(1, ids) ids[0] = rb gl_delete_renderbuffers(1, ids) - ids[0] = buf - gl_delete_buffers(1, ids) + gpu_buffer_free(buf) gl_bind_texture(GL_TEXTURE_2D, tex) gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) @@ -1121,10 +1085,10 @@ function scatter_clear_all() -> void { let ids = gl_scratch() for i in 0 .. len(sc_layers) { let l = sc_layers[i] - if l.buf != 0 { ids[0] = l.buf; gl_delete_buffers(1, ids) } - if l.imp_buf != 0 { ids[0] = l.imp_buf; gl_delete_buffers(1, ids) } - if l.sh_buf != 0 { ids[0] = l.sh_buf; gl_delete_buffers(1, ids) } - if l.lod_buf != null { for k in 0 .. l.n_lods { ids[0] = l.lod_buf[k]; gl_delete_buffers(1, ids) } } + if l.buf != 0 { gpu_buffer_free(l.buf) } + if l.imp_buf != 0 { gpu_buffer_free(l.imp_buf) } + if l.sh_buf != 0 { gpu_buffer_free(l.sh_buf) } + if l.lod_buf != null { for k in 0 .. l.n_lods { gpu_buffer_free(l.lod_buf[k]) } } if l.inst != null { free(l.inst) } if l.scratch != null { free(l.scratch) } if l.tint != null { free(l.tint) } diff --git a/packages/ludic.render3d/skin.ludic b/packages/ludic.render3d/skin.ludic index c04003fc..5bd1d16d 100644 --- a/packages/ludic.render3d/skin.ludic +++ b/packages/ludic.render3d/skin.ludic @@ -51,25 +51,19 @@ function skin_attribs(m: Mesh, attrs: Val) -> bool { if value_has(attrs, "JOINTS_0") == 0 or value_has(attrs, "WEIGHTS_0") == 0 { return false } let jd = gltf_accessor(value_as_int(value_get(attrs, "JOINTS_0"))) var jsz = 1 - var jtype = GL_UNSIGNED_BYTE - if gltf_ctype == 5123 { jsz = 2; jtype = GL_UNSIGNED_SHORT } - let jb = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, jb) - gl_buffer_data(GL_ARRAY_BUFFER, gltf_count * gltf_comps * jsz, jd, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(5) - gl_vertex_attrib_pointer(5, gltf_comps, jtype, 0, 0, null) # integers, read as floats + var jtype = GPU_U8 + if gltf_ctype == 5123 { jsz = 2; jtype = GPU_U16 } + gpu_mesh_vertices(m, jd, gltf_count * gltf_comps * jsz, GPU_STATIC) + gpu_mesh_attr(m, 5, gltf_comps, jtype, 0, 0, false) # integers, read as floats free(jd) let wd = gltf_accessor(value_as_int(value_get(attrs, "WEIGHTS_0"))) var wsz = 4 - var wtype = GL_FLOAT - var norm = 0 - if gltf_ctype == 5123 { wsz = 2; wtype = GL_UNSIGNED_SHORT; norm = 1 } - if gltf_ctype == 5121 { wsz = 1; wtype = GL_UNSIGNED_BYTE; norm = 1 } - let wb = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, wb) - gl_buffer_data(GL_ARRAY_BUFFER, gltf_count * gltf_comps * wsz, wd, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(6) - gl_vertex_attrib_pointer(6, gltf_comps, wtype, norm, 0, null) + var wtype = GPU_F32 + var norm = false + if gltf_ctype == 5123 { wsz = 2; wtype = GPU_U16; norm = true } + if gltf_ctype == 5121 { wsz = 1; wtype = GPU_U8; norm = true } + gpu_mesh_vertices(m, wd, gltf_count * gltf_comps * wsz, GPU_STATIC) + gpu_mesh_attr(m, 6, gltf_comps, wtype, 0, 0, norm) free(wd) return true } diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 2d2ed12e..3508d7f9 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -544,7 +544,7 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target { shadow_bind(p) sky_bind_lighting(p) ter_sun_pass = true - gl_bind_vertex_array(cd_mesh.vao) + gpu_mesh_bind(cd_mesh) cdlod_select(CD_LEVELS - 1, 0, 0) ter_sun_pass = false if Os.has_env("R3D_DUMP_SUN") and not ter_sun_dumped and not ter_reflect { ter_sun_dumped = true; tex_dump(t.color, w, h, "build/dbg_sun.ppm") } @@ -587,7 +587,7 @@ function terrain_draw() -> void { cd_draws = 0 cd_far_draws = 0 cd_near_draws = 0 - gl_bind_vertex_array(cd_mesh.vao) + gpu_mesh_bind(cd_mesh) if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_LINE) } cdlod_select(CD_LEVELS - 1, 0, 0) if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_FILL) } @@ -602,16 +602,13 @@ var ter_printed: bool = false # shader places, scales and morphs it. Levels are drawn out to cd_range[k] = 48 * 2^k m, # so cells are 1 m within 48 m, 2 m to 96 m, 4 m to 192 m ... 256 m at the root. function cdlod_init() -> void { - let m = new Mesh - m.vao = gl_vao() + let m = gpu_mesh_new() let n = CD_G + 1 let v = gl_floats(n * n * 2) var k = 0 for j in 0 .. n { for i in 0 .. n { gl_put_bits(v, k, fr(i, CD_G)); gl_put_bits(v, k + 1, fr(j, CD_G)); k += 2 } } - m.vbo = gl_buffer() - gl_bind_buffer(GL_ARRAY_BUFFER, m.vbo) - gl_buffer_data(GL_ARRAY_BUFFER, gl_bytes_of(n * n * 2), v, GL_STATIC_DRAW) - gl_enable_vertex_attrib_array(0); gl_vertex_attrib_pointer(0, 2, GL_FLOAT, 0, 8, null) + gpu_mesh_vertices(m, v, gl_bytes_of(n * n * 2), GPU_STATIC) + gpu_mesh_attr(m, 0, 2, GPU_F32, 8, 0, false) free(v) let ni = CD_G * CD_G * 6 let idx = words(ni) @@ -624,12 +621,10 @@ function cdlod_init() -> void { k += 6 } } - m.ebo = gl_buffer() - gl_bind_buffer(GL_ELEMENT_ARRAY_BUFFER, m.ebo) - gl_buffer_data(GL_ELEMENT_ARRAY_BUFFER, ni * 4, idx, GL_STATIC_DRAW) + gpu_mesh_indices(m, idx, ni * 4, 4) free(idx) m.count = ni - gl_bind_vertex_array(0) + gpu_mesh_done(m) cd_mesh = m cd_range = words(CD_LEVELS) var r = fi(48) @@ -713,7 +708,7 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void { var st0 = F_ZERO if level > 0 { st0 = cd_range[level - 1] } u_f2(gpu_uniform(ter_sun_prog, "u_morph"), f_lerp(st0, cd_range[level], fl(0.7)), cd_range[level]) - gl_draw_elements(GL_TRIANGLES, cd_mesh.count, GL_UNSIGNED_INT, null) + gpu_draw_bound_elements(cd_mesh) return } let n = CD_LEAVES >> level @@ -735,7 +730,7 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void { if level > 0 { start = cd_range[level - 1] } start = f_lerp(start, cd_range[level], fl(0.7)) u_f2(gpu_uniform(p, "u_morph"), start, cd_range[level]) - gl_draw_elements(GL_TRIANGLES, cd_mesh.count, GL_UNSIGNED_INT, null) + gpu_draw_bound_elements(cd_mesh) cd_draws += 1 }