From 4d3479d431ee17ebc8d3109a99d05d2690fbc026 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 10:29:28 +0300 Subject: [PATCH] refactor(render3d): textures behind gpu.ludic, their sampler state recorded Texture creation, 2D and array uploads, pixel packing, filters, wraps, comparison, border, anisotropy, mipmaps, texture units, read-backs and freeing now go through gpu_tex_* and gpu_bind_sampler, and no other file names them. Each call is the one GL call it replaces, in the same order, so OpenGL renders bit-identically at the fixed viewpoints and the game's self-tests report what they did before. What those calls say about a texture - size, format, layers, min and mag filter, wraps, comparison, mipmaps, anisotropy - is recorded per handle as it is set: a backend with immutable images and separate sampler objects creates both from exactly that. r3d_bind_tex takes a texture kind (GPU_TEX2D / GPU_TEX2D_ARRAY) instead of a GL target. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/actor.ludic | 8 +-- packages/ludic.render3d/gpu.ludic | 93 ++++++++++++++++++++++++++ packages/ludic.render3d/post.ludic | 10 +-- packages/ludic.render3d/programs.ludic | 12 ++-- packages/ludic.render3d/scatter.ludic | 28 ++++---- packages/ludic.render3d/shadow.ludic | 30 ++++----- packages/ludic.render3d/sky.ludic | 26 +++---- packages/ludic.render3d/terrain.ludic | 31 +++++---- packages/ludic.render3d/texture.ludic | 72 ++++++++++---------- packages/ludic.render3d/water.ludic | 4 +- 10 files changed, 201 insertions(+), 113 deletions(-) diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 346cf200..86ff6f30 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -177,10 +177,10 @@ function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void { if a.ptint != null and a.ptint[i * 4] != 0 { u_f3(ap.l_tint, a.ptint[i * 4 + 1], a.ptint[i * 4 + 2], a.ptint[i * 4 + 3]) } else { u_v3(ap.l_tint, a.tint) } } - gl_active_texture(GL_TEXTURE0); gl_bind_texture(GL_TEXTURE_2D, pr.diff) + gpu_tex_unit(0); gpu_tex_bind(GPU_TEX2D, pr.diff) if not shadow { - gl_active_texture(GL_TEXTURE0 + 1); gl_bind_texture(GL_TEXTURE_2D, pr.nrm) - gl_active_texture(GL_TEXTURE0 + 2); gl_bind_texture(GL_TEXTURE_2D, pr.arm) + gpu_tex_unit(1); gpu_tex_bind(GPU_TEX2D, pr.nrm) + gpu_tex_unit(2); gpu_tex_bind(GPU_TEX2D, pr.arm) } mesh_draw(pr.mesh) } @@ -296,7 +296,7 @@ function actor_draw_outline_one(a: Actor, ap: AcProg) -> void { for i in 0 .. len(model.prims) { let pr = model.prims[i] if a.hide != null and a.hide[i] != 0 { continue } - if a.cutout { gl_active_texture(GL_TEXTURE0); gl_bind_texture(GL_TEXTURE_2D, pr.diff) } + if a.cutout { gpu_tex_unit(0); gpu_tex_bind(GPU_TEX2D, pr.diff) } mesh_draw(pr.mesh) } } diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index d7954618..4d69bc86 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -12,6 +12,7 @@ # 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 +# - textures: creation, upload, sampler state, mipmaps, units, read-back, freeing # # 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 @@ -454,3 +455,95 @@ function gpu_mesh_free(m: Mesh) -> void { 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 } } + +# ---- textures ----------------------------------------------------------------------------- +# A texture is a handle (on OpenGL, the texture name). Each call below is the one GL call it +# replaces, in the renderer's own order, so OpenGL draws exactly what it drew. What those calls +# say about a texture - its size, format, filters, wraps, comparison, mipmaps, anisotropy - is +# recorded per handle as it is set, because a backend with immutable images and separate +# sampler objects (Vulkan) creates both from exactly that. Parameters apply to the texture last +# bound for that kind, which is how every call site here already works. +const GPU_TEX2D: int = 1 +const GPU_TEX2D_ARRAY: int = 2 +const GPU_TX_W: int = 12 # per handle: kind, w, h, layers, ifmt, min, mag, wrap s, wrap t, compare, mips, aniso +var gpu_tx: words = null +var gpu_tx_cap: int = 0 +var gpu_bound_2d: int = 0 +var gpu_bound_array: int = 0 + +function gpu_gl_target(kind: int) -> int { if kind == GPU_TEX2D_ARRAY { return GL_TEXTURE_2D_ARRAY }; return GL_TEXTURE_2D } +# the record for a handle, growing the table when a new name is larger than it +function gpu_tx_at(tex: int) -> int { + if tex <= 0 { return -1 } + if tex >= gpu_tx_cap { + var cap = gpu_tx_cap * 2 + if cap < 256 { cap = 256 } + while cap <= tex { cap = cap * 2 } + let t = words(cap * GPU_TX_W) + for i in 0 .. cap * GPU_TX_W { t[i] = 0 } + if gpu_tx != null { mem_copy(t, gpu_tx, gpu_tx_cap * GPU_TX_W * 4); free(gpu_tx) } + gpu_tx = t + gpu_tx_cap = cap + } + return tex * GPU_TX_W +} +function gpu_bound(kind: int) -> int { if kind == GPU_TEX2D_ARRAY { return gpu_bound_array }; return gpu_bound_2d } + +function gpu_tex_new() -> int { return gl_texture() } +function gpu_tex_unit(unit: int) -> void { gl_active_texture(GL_TEXTURE0 + unit) } +function gpu_tex_bind(kind: int, tex: int) -> void { + gl_bind_texture(gpu_gl_target(kind), tex) + if kind == GPU_TEX2D_ARRAY { gpu_bound_array = tex } else { gpu_bound_2d = tex } +} +# pixel transfer packing (alignment, byte swap) for the uploads and read-backs that follow +function gpu_pixel_store(pname: int, value: int) -> void { gl_pixel_storei(pname, value) } +function gpu_tex_image2d(ifmt: int, w: int, h: int, fmt: int, ty: int, data: pointer) -> void { + gl_tex_image2d(GL_TEXTURE_2D, 0, ifmt, w, h, 0, fmt, ty, data) + let o = gpu_tx_at(gpu_bound_2d) + if o >= 0 { gpu_tx[o] = GPU_TEX2D; gpu_tx[o + 1] = w; gpu_tx[o + 2] = h; gpu_tx[o + 3] = 1; gpu_tx[o + 4] = ifmt } +} +function gpu_tex_image3d(ifmt: int, w: int, h: int, layers: int, fmt: int, ty: int, data: pointer) -> void { + gl_tex_image3d(GL_TEXTURE_2D_ARRAY, 0, ifmt, w, h, layers, 0, fmt, ty, data) + let o = gpu_tx_at(gpu_bound_array) + if o >= 0 { gpu_tx[o] = GPU_TEX2D_ARRAY; gpu_tx[o + 1] = w; gpu_tx[o + 2] = h; gpu_tx[o + 3] = layers; gpu_tx[o + 4] = ifmt } +} +function gpu_tex_param(kind: int, pname: int, value: int) -> void { + gl_tex_parameteri(gpu_gl_target(kind), pname, value) + let o = gpu_tx_at(gpu_bound(kind)) + if o < 0 { return } + if pname == GL_TEXTURE_MIN_FILTER { gpu_tx[o + 5] = value } + if pname == GL_TEXTURE_MAG_FILTER { gpu_tx[o + 6] = value } + if pname == GL_TEXTURE_WRAP_S { gpu_tx[o + 7] = value } + if pname == GL_TEXTURE_WRAP_T { gpu_tx[o + 8] = value } + if pname == GL_TEXTURE_COMPARE_MODE { if value == GL_NONE { gpu_tx[o + 9] = 0 } } + if pname == GL_TEXTURE_COMPARE_FUNC { gpu_tx[o + 9] = value } +} +# a float parameter (fixed, as Gl.* takes it): anisotropy is the one the renderer sets +function gpu_tex_paramf(kind: int, pname: int, value: fixed) -> void { + gl_tex_parameterf(gpu_gl_target(kind), pname, value) + let o = gpu_tx_at(gpu_bound(kind)) + if o >= 0 and pname == 0x84FE { gpu_tx[o + 11] = fx_to_f32(value) } +} +# the border colour clamp-to-border reads (four fixed values in `rgba`) +function gpu_tex_border(kind: int, rgba: pointer) -> void { gl_tex_parameterfv(gpu_gl_target(kind), GL_TEXTURE_BORDER_COLOR, rgba) } +function gpu_tex_mips(kind: int) -> void { + gl_generate_mipmap(gpu_gl_target(kind)) + let o = gpu_tx_at(gpu_bound(kind)) + if o >= 0 { gpu_tx[o + 10] = 1 } +} +# level 0 of the bound texture into `out` +function gpu_tex_read(kind: int, fmt: int, ty: int, out: pointer) -> void { gl_get_tex_image(gpu_gl_target(kind), 0, fmt, ty, out) } +function gpu_tex_free(tex: int) -> void { + if tex == 0 { return } + let ids = gpu_tmp() + ids[0] = tex + gl_delete_textures(1, ids) + let o = gpu_tx_at(tex) + if o >= 0 { for i in 0 .. GPU_TX_W { gpu_tx[o + i] = 0 } } +} +# a texture on a unit for a program's sampler, by the sampler's name +function gpu_bind_sampler(prog: int, name: string, unit: int, kind: int, tex: int) -> void { + gpu_tex_unit(unit) + gpu_tex_bind(kind, tex) + u_i(gpu_uniform(prog, name), unit) +} diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index f948f5dc..b0b11ab3 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -133,9 +133,9 @@ var post_adapt_i: int = 0 var post_p_adapt: int = 0 var post_adapt_reset: bool = true function post_measure() -> void { - gl_bind_texture(GL_TEXTURE_2D, post_hdr.color) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) + gpu_tex_bind(GPU_TEX2D, post_hdr.color) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) if post_adapt_t == null { post_adapt_t = new []Target for k in 0 .. 2 { push(post_adapt_t, target_new(1, 1, GL_R32F, GL_RED, GL_FLOAT, false, GL_NEAREST)) } @@ -157,8 +157,8 @@ function post_measure() -> void { u_f(gpu_uniform(post_p_adapt, "u_reset"), reset) mesh_draw(post_fs) post_adapt_i = next - gl_bind_texture(GL_TEXTURE_2D, post_hdr.color) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + gpu_tex_bind(GPU_TEX2D, post_hdr.color) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) } function post_begin_scene() -> void { diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 4c6bf584..00d3801c 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -87,12 +87,8 @@ function r3d_program(vs: string, fs: string, defines: string) -> int { } # Bind a texture to a unit and point a sampler uniform at it. -function r3d_bind_tex(prog: int, name: string, unit: int, target: int, tex: int) -> void { - gl_active_texture(GL_TEXTURE0 + unit) - gl_bind_texture(target, tex) - u_i(gpu_uniform(prog, name), unit) -} -function r3d_bind_2d(prog: int, name: string, unit: int, tex: int) -> void { r3d_bind_tex(prog, name, unit, GL_TEXTURE_2D, tex) } +function r3d_bind_tex(prog: int, name: string, unit: int, kind: int, tex: int) -> void { gpu_bind_sampler(prog, name, unit, kind, tex) } +function r3d_bind_2d(prog: int, name: string, unit: int, tex: int) -> void { gpu_bind_sampler(prog, name, unit, GPU_TEX2D, tex) } # A framebuffer with one colour texture (and optionally a depth texture). property Target { @@ -122,8 +118,8 @@ function target_free(t: Target) -> void { if t == null { return } let ids = gl_scratch() ids[0] = t.fbo; gl_delete_framebuffers(1, ids) - ids[0] = t.color; gl_delete_textures(1, ids) - if t.depth != 0 { ids[0] = t.depth; gl_delete_textures(1, ids) } + gpu_tex_free(t.color) + if t.depth != 0 { gpu_tex_free(t.depth) } } function target_bind(t: Target) -> void { gl_bind_framebuffer(GL_FRAMEBUFFER, t.fbo) diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 779a1b6a..c22327db 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -475,12 +475,12 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor { gl_delete_framebuffers(1, ids) ids[0] = rb gl_delete_renderbuffers(1, ids) - gl_bind_texture(GL_TEXTURE_2D, im.albedo) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) - gl_bind_texture(GL_TEXTURE_2D, im.normal) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) + gpu_tex_bind(GPU_TEX2D, im.albedo) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) + gpu_tex_bind(GPU_TEX2D, im.normal) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) gl_check("impostor bake") if sc_debug_dump { tex_dump_alpha = true; tex_dump(im.albedo, aw, th, "build/atlas_alpha.ppm"); tex_dump_alpha = false } return im @@ -1064,12 +1064,12 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int { ids[0] = rb gl_delete_renderbuffers(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) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_tex_parameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, tex_anisotropy) - gl_generate_mipmap(GL_TEXTURE_2D) + gpu_tex_bind(GPU_TEX2D, tex) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_REPEAT) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_paramf(GPU_TEX2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, tex_anisotropy) + gpu_tex_mips(GPU_TEX2D) gl_check("carpet bake") return tex } @@ -1103,8 +1103,8 @@ function scatter_clear_all() -> void { # layer_cards built this layer's crossed card and its atlas itself if l.card and l.n_lods == 0 and l.model != null { for k in 0 .. len(l.model.prims) { mesh_free(l.model.prims[k].mesh) } } if l.card and l.atlas != null { - ids[0] = l.atlas.albedo; gl_delete_textures(1, ids) - ids[0] = l.atlas.normal; gl_delete_textures(1, ids) + gpu_tex_free(l.atlas.albedo) + gpu_tex_free(l.atlas.normal) } } sc_layers = new []Layer diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index cd3852c5..497a0597 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -21,18 +21,18 @@ var sh_corner: words = null var sh_cascade: int = 0 # the cascade being rendered (for casters that skip far ones) function shadow_init() -> void { - sh_tex = gl_texture() - gl_bind_texture(GL_TEXTURE_2D_ARRAY, sh_tex) - gl_tex_image3d(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, SHADOW_RES, SHADOW_RES, SHADOW_CASCADES, 0, GL_DEPTH_COMPONENT, GL_FLOAT, null) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL) + sh_tex = gpu_tex_new() + gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex) + gpu_tex_image3d(GL_DEPTH_COMPONENT32F, SHADOW_RES, SHADOW_RES, SHADOW_CASCADES, GL_DEPTH_COMPONENT, GL_FLOAT, null) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL) let border = gl_floats(4) gl_put(border, 0, 1.0); gl_put(border, 1, 1.0); gl_put(border, 2, 1.0); gl_put(border, 3, 1.0) - gl_tex_parameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, border) + gpu_tex_border(GPU_TEX2D_ARRAY, border) free(border) sh_fbo = gl_framebuffer() gl_bind_framebuffer(GL_FRAMEBUFFER, sh_fbo) @@ -175,10 +175,10 @@ var sh_probe_z: int = 0 function shadow_dump() -> void { let n = SHADOW_RES * SHADOW_RES let buf = words(n * SHADOW_CASCADES) - gl_bind_texture(GL_TEXTURE_2D_ARRAY, sh_tex) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE) - gl_get_tex_image(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT, GL_FLOAT, buf) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE) + 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_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE) if sh_probe_x != 0 { let vp = shadow_cascade_vp(2) let q = words(3) @@ -226,7 +226,7 @@ function sh_loc(prog: int, name: string) -> int { return loc } function shadow_bind(prog: int) -> void { - r3d_bind_tex(prog, "u_shadow", 15, GL_TEXTURE_2D_ARRAY, sh_tex) + r3d_bind_tex(prog, "u_shadow", 15, GPU_TEX2D_ARRAY, sh_tex) # the height-field shadow (terrain.ludic); a stand-in texture keeps the unit valid before the bake var ts = ter_shadow_tex var ts_on = F_ONE diff --git a/packages/ludic.render3d/sky.ludic b/packages/ludic.render3d/sky.ludic index 8e52eeec..3dc274fb 100644 --- a/packages/ludic.render3d/sky.ludic +++ b/packages/ludic.render3d/sky.ludic @@ -91,26 +91,26 @@ function sky_precompute() -> void { gpu_depth_test(false) if sky_irradiance != 0 { let ids = gl_scratch() - ids[0] = sky_irradiance; gl_delete_textures(1, ids) - ids[0] = sky_prefilter; gl_delete_textures(1, ids) - ids[0] = sky_brdf; gl_delete_textures(1, ids) + gpu_tex_free(sky_irradiance) + gpu_tex_free(sky_prefilter) + gpu_tex_free(sky_brdf) } # irradiance: 128 x 64 equirect if sky_p_irr == 0 { sky_p_irr = r3d_program("fullscreen.vert", "ibl_irradiance.frag", ""); sky_p_pre = r3d_program("fullscreen.vert", "ibl_prefilter.frag", ""); sky_p_brdf = r3d_program("fullscreen.vert", "ibl_brdf.frag", "") } let p_irr = sky_p_irr sky_irradiance = tex_target(128, 64, GL_RGB16F, GL_RGB, GL_FLOAT, GL_LINEAR) - gl_bind_texture(GL_TEXTURE_2D, sky_irradiance) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) + gpu_tex_bind(GPU_TEX2D, sky_irradiance) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT) sky_convolve(p_irr, sky_irradiance, -1, 128, 64, F_ZERO) # prefiltered specular: 6 roughness levels, 512 x 256 each, as a 2D array let p_pre = sky_p_pre - sky_prefilter = gl_texture() - gl_bind_texture(GL_TEXTURE_2D_ARRAY, sky_prefilter) - gl_tex_image3d(GL_TEXTURE_2D_ARRAY, 0, GL_RGB16F, 512, 256, SKY_PREFILTER_LEVELS, 0, GL_RGB, GL_FLOAT, null) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - gl_tex_parameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + sky_prefilter = gpu_tex_new() + gpu_tex_bind(GPU_TEX2D_ARRAY, sky_prefilter) + gpu_tex_image3d(GL_RGB16F, 512, 256, SKY_PREFILTER_LEVELS, GL_RGB, GL_FLOAT, null) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR) for l in 0 .. SKY_PREFILTER_LEVELS { sky_convolve(p_pre, sky_prefilter, l, 512, 256, fr(l, SKY_PREFILTER_LEVELS - 1)) } @@ -124,7 +124,7 @@ function sky_precompute() -> void { # bind the IBL set + sun for a lit program (units 12..14) function sky_bind_lighting(prog: int) -> void { r3d_bind_2d(prog, "u_irradiance", 12, sky_irradiance) - r3d_bind_tex(prog, "u_prefilter", 13, GL_TEXTURE_2D_ARRAY, sky_prefilter) + r3d_bind_tex(prog, "u_prefilter", 13, GPU_TEX2D_ARRAY, sky_prefilter) r3d_bind_2d(prog, "u_brdf", 14, sky_brdf) u_v3(gpu_uniform(prog, "u_sun_dir"), sun_dir) u_v3(gpu_uniform(prog, "u_sun_color"), sun_color) diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 3508d7f9..57b162b9 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -203,21 +203,20 @@ function terrain_use_ortho(path: string) -> void { ter_ortho_w = tex_w ter_ortho_c = tex_channels ter_ortho_tex = tex_upload(px, true, true) - gl_bind_texture(GL_TEXTURE_2D, ter_ortho_tex) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + gpu_tex_bind(GPU_TEX2D, ter_ortho_tex) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) } function terrain_use_dem(path: string, emin: int, emax: int, base: int, ox: int, oz: int) -> void { ter_dem_tex = tex_load(path, false) ter_dem_min = emin; ter_dem_max = emax; ter_dem_base = base ter_ox = ox; ter_oz = oz - gl_bind_texture(GL_TEXTURE_2D, ter_dem_tex) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + gpu_tex_bind(GPU_TEX2D, ter_dem_tex) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) } function terrain_generate() -> void { - var ids0: words = null var defs = "" if ter_dem_tex != 0 { defs = "#define DEM\n" } if ter_smooth { defs = "#define SMOOTH\n" } @@ -254,12 +253,12 @@ function terrain_generate() -> void { u_f(gpu_uniform(pn, "u_half"), fi(TERRAIN_HALF)) mesh_draw(sky_fullscreen) gl_delete_program(pn) - ids0 = gl_scratch(); ids0[0] = raw; gl_delete_textures(1, ids0) + gpu_tex_free(raw) # read the heights back for placement ter_heights = words(TERRAIN_RES * TERRAIN_RES) - gl_bind_texture(GL_TEXTURE_2D, ter_height_tex) - gl_pixel_storei(GL_PACK_ALIGNMENT, 4) - gl_get_tex_image(GL_TEXTURE_2D, 0, GL_RED, GL_FLOAT, ter_heights) + 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) gl_bind_framebuffer(GL_FRAMEBUFFER, 0) let ids = gl_scratch() ids[0] = fbo @@ -480,8 +479,8 @@ function terrain_bind_prog(p: int) -> void { # The ground reads its sun visibility out of the buffer tersun.frag filled, and has no # use for the cascade array shadow_bind just put on this unit; leaving both bound under # one unit is undefined ground, so the array comes off first. - gl_active_texture(GL_TEXTURE0 + 15) - gl_bind_texture(GL_TEXTURE_2D_ARRAY, 0) + gpu_tex_unit(15) + gpu_tex_bind(GPU_TEX2D_ARRAY, 0) r3d_bind_2d(p, "u_sunshadow", 15, ter_sun_tex) } @@ -765,9 +764,9 @@ function cdlod_select(level: int, ix: int, iz: int) -> bool { # frame-sized targets and the shadow texture, whose size does not depend on the map. function terrain_unload() -> void { let ids = gl_scratch() - if ter_height_tex != 0 { ids[0] = ter_height_tex; gl_delete_textures(1, ids); ter_height_tex = 0 } - if ter_dem_tex != 0 { ids[0] = ter_dem_tex; gl_delete_textures(1, ids); ter_dem_tex = 0 } - if ter_ortho_tex != 0 { ids[0] = ter_ortho_tex; gl_delete_textures(1, ids); ter_ortho_tex = 0 } + if ter_height_tex != 0 { gpu_tex_free(ter_height_tex); ter_height_tex = 0 } + if ter_dem_tex != 0 { gpu_tex_free(ter_dem_tex); ter_dem_tex = 0 } + if ter_ortho_tex != 0 { gpu_tex_free(ter_ortho_tex); ter_ortho_tex = 0 } if ter_heights != null { free(ter_heights); ter_heights = null } if ter_ortho_px != null { free(ter_ortho_px); ter_ortho_px = null } ter_ortho_w = 0 diff --git a/packages/ludic.render3d/texture.ludic b/packages/ludic.render3d/texture.ludic index 7ba0d5ae..272b4bcd 100644 --- a/packages/ludic.render3d/texture.ludic +++ b/packages/ludic.render3d/texture.ludic @@ -204,8 +204,8 @@ function tex_dilate(px: pointer, thresh: int, passes: int) -> void { # Upload the last-decoded samples as a 2D texture. srgb: colour data (8-bit only). function tex_upload(px: pointer, srgb: bool, mips: bool) -> int { - let id = gl_texture() - gl_bind_texture(GL_TEXTURE_2D, id) + let id = gpu_tex_new() + gpu_tex_bind(GPU_TEX2D, id) var fmt = GL_RED if tex_channels == 2 { fmt = GL_RG } if tex_channels == 3 { fmt = GL_RGB } @@ -218,25 +218,25 @@ function tex_upload(px: pointer, srgb: bool, mips: bool) -> int { if tex_channels == 2 { ifmt = GL_RG16 } if tex_channels == 3 { ifmt = GL_RGB16 } if tex_channels == 4 { ifmt = GL_RGBA16 } - gl_pixel_storei(GL_UNPACK_SWAP_BYTES, 1) + gpu_pixel_store(GL_UNPACK_SWAP_BYTES, 1) } else { if tex_channels == 2 { ifmt = GL_RG8 } if tex_channels == 3 { ifmt = GL_RGB8; if srgb { ifmt = GL_SRGB8 } } if tex_channels == 4 { ifmt = GL_RGBA8; if srgb { ifmt = GL_SRGB8_ALPHA8 } } - gl_pixel_storei(GL_UNPACK_SWAP_BYTES, 0) + gpu_pixel_store(GL_UNPACK_SWAP_BYTES, 0) } - gl_pixel_storei(GL_UNPACK_ALIGNMENT, 1) - gl_tex_image2d(GL_TEXTURE_2D, 0, ifmt, tex_w, tex_h, 0, fmt, ty, px) - gl_pixel_storei(GL_UNPACK_SWAP_BYTES, 0) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + gpu_pixel_store(GL_UNPACK_ALIGNMENT, 1) + gpu_tex_image2d(ifmt, tex_w, tex_h, fmt, ty, px) + gpu_pixel_store(GL_UNPACK_SWAP_BYTES, 0) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_REPEAT) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) if mips { - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) - gl_tex_parameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, tex_anisotropy) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) + gpu_tex_paramf(GPU_TEX2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, tex_anisotropy) } else { - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) } return id } @@ -362,28 +362,28 @@ function hdr_decode(path: pointer) -> words { function tex_load_hdr(path: pointer) -> int { let px = hdr_decode(path) if px == null { return 0 } - let id = gl_texture() - gl_bind_texture(GL_TEXTURE_2D, id) - gl_pixel_storei(GL_UNPACK_ALIGNMENT, 4) - gl_tex_image2d(GL_TEXTURE_2D, 0, GL_RGB16F, tex_w, tex_h, 0, GL_RGB, GL_FLOAT, px) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) + 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_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) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) free(px) return id } # An empty render-target texture of the given internal format (no mips, clamped). function tex_target(w: int, h: int, ifmt: int, fmt: int, ty: int, filter: int) -> int { - let id = gl_texture() - gl_bind_texture(GL_TEXTURE_2D, id) - gl_tex_image2d(GL_TEXTURE_2D, 0, ifmt, w, h, 0, fmt, ty, null) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter) - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter) + let id = gpu_tex_new() + gpu_tex_bind(GPU_TEX2D, id) + gpu_tex_image2d(ifmt, w, h, fmt, ty, null) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, filter) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, filter) return id } @@ -391,9 +391,9 @@ var tex_dump_alpha: bool = false # Debug: the brightest texel of an RGBA float texture and where it is. function tex_max(tex: int, w: int, h: int, tag: pointer) -> void { let buf = words(w * h * 4) - gl_bind_texture(GL_TEXTURE_2D, tex) - gl_pixel_storei(GL_PACK_ALIGNMENT, 4) - gl_get_tex_image(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, buf) + gpu_tex_bind(GPU_TEX2D, tex) + gpu_pixel_store(GL_PACK_ALIGNMENT, 4) + gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, buf) var best = F_ZERO; var bx = 0; var by = 0 var i = 0 while i < w * h { @@ -413,9 +413,9 @@ function tex_dump(tex: int, w: int, h: int, path: pointer) -> void { let f = file_open(path, "wb") if f == null { return } let buf = bytes(w * h * 4) - gl_bind_texture(GL_TEXTURE_2D, tex) - gl_pixel_storei(GL_PACK_ALIGNMENT, 1) - gl_get_tex_image(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf) + gpu_tex_bind(GPU_TEX2D, tex) + gpu_pixel_store(GL_PACK_ALIGNMENT, 1) + gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_UNSIGNED_BYTE, buf) let hdr = `P6\n{w} {h}\n255\n` file_write(f, hdr, len(hdr)) let row = bytes(w * 3) diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index 5fbee8be..d7da9d33 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -151,8 +151,8 @@ function water_draw(depth_tex: int) -> void { # bind on its own unit first: generating the mip chain re-binds the texture on the active unit, # and it must not displace the depth texture the shader reads for the shore r3d_bind_2d(p, "u_refl", 1, water_refl.color); ron = F_ONE - gl_tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - gl_generate_mipmap(GL_TEXTURE_2D) + gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) + gpu_tex_mips(GPU_TEX2D) } r3d_bind_2d(p, "u_depth", 0, depth_tex) r3d_bind_2d(p, "u_scene", 2, post_scene.color)