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 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 10:29:28 +03:00
parent 43c379aa0f
commit 4d3479d431
10 changed files with 201 additions and 113 deletions

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)