refactor(render3d): render targets and passes behind gpu.ludic

Framebuffers and their attachments, renderbuffers (multisampled too), draw and read buffers,
completeness checks, blits, viewports, clears, the screen framebuffer, the multisample enable,
the wireframe switch and GL error checks now go through gpu_fb_* / gpu_rb_* / gpu_viewport /
gpu_clear / gpu_blit / gpu_check, and no other file names them. Each call is the one GL call it
replaces, in the same order: the fixed viewpoints render bit-identically and the game's
self-tests report exactly what they did.

What is attached to each framebuffer - colour slots, a depth texture or one layer of an array,
renderbuffers and their samples - is recorded as it is attached, for a backend that builds
render passes and image views. gpu_read_screen is the frame read-back a photograph takes.

R3D_GLCHECK=1 checks, around every draw, clear and blit, that the bound framebuffer is complete
and that no error is left behind, naming the framebuffer. It has already narrowed the old
"gl error 1286 at terrain shadow bake": the error is pending before a draw after the map self-
test, so it comes from a call that is not a draw, clear or blit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 10:41:43 +03:00
parent 4d3479d431
commit 78c1f4f9ca
10 changed files with 294 additions and 150 deletions

View file

@ -13,6 +13,7 @@
# - 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 targets and passes: framebuffers, attachments, blits, viewports, clears, the screen
#
# 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
@ -430,17 +431,25 @@ function gpu_buffer_free(buf: int) -> void {
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 {
gpu_glcheck_before("a draw")
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) }
gpu_glcheck_after("a draw")
}
function gpu_draw_mesh_instanced(m: Mesh, n: int) -> void {
gpu_glcheck_before("an instanced draw")
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) }
gpu_glcheck_after("an instanced draw")
}
# 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) }
function gpu_draw_bound_elements(m: Mesh) -> void {
gpu_glcheck_before("a terrain patch")
gl_draw_elements(m.mode, m.count, m.itype, null)
gpu_glcheck_after("a terrain patch")
}
# 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) }
@ -547,3 +556,166 @@ function gpu_bind_sampler(prog: int, name: string, unit: int, kind: int, tex: in
gpu_tex_bind(kind, tex)
u_i(gpu_uniform(prog, name), unit)
}
# ---- render targets and passes -------------------------------------------------------------
# A framebuffer is a handle (OpenGL's name). What is attached to it - colour textures in slots,
# a depth texture or one layer of an array, renderbuffers and their sample count - is recorded as
# it is attached, which is what a backend with render passes and image views builds from. Each
# call is the GL call it replaces, in the renderer's order.
#
# gpu_check(tag) reports a pending error under a name, as gl_check did. R3D_GLCHECK=1 adds a
# completeness check of the bound framebuffer whenever a viewport is set, naming the handle, so
# a pass drawing into an incomplete target says which one; unset, it calls nothing.
const GPU_FB_W: int = 8 # per handle: colour 0, colour 1, depth texture, depth layer + 1, colour rb, depth rb, samples, colour layer + 1
var gpu_fb: words = null
var gpu_fb_cap: int = 0
var gpu_fb_cur: int = 0
var gpu_glcheck: int = -1
var gpu_rb_samples: int = 0
var gpu_drawbufs: words = null
function gpu_fb_at(fb: int) -> int {
if fb <= 0 { return -1 }
if fb >= gpu_fb_cap {
var cap = gpu_fb_cap * 2
if cap < 64 { cap = 64 }
while cap <= fb { cap = cap * 2 }
let t = words(cap * GPU_FB_W)
for i in 0 .. cap * GPU_FB_W { t[i] = 0 }
if gpu_fb != null { mem_copy(t, gpu_fb, gpu_fb_cap * GPU_FB_W * 4); free(gpu_fb) }
gpu_fb = t
gpu_fb_cap = cap
}
return fb * GPU_FB_W
}
function gpu_glcheck_on() -> bool {
if gpu_glcheck < 0 { gpu_glcheck = 0; if Os.has_env("R3D_GLCHECK") { gpu_glcheck = 1 } }
return gpu_glcheck == 1
}
function gpu_check(tag: string) -> int { return gl_check(tag) }
# a named checkpoint that costs nothing unless R3D_GLCHECK is set
function gpu_debug_check(tag: string) -> void { if gpu_glcheck_on() { gl_check(tag) } }
function gpu_fb_new() -> int { return gl_framebuffer() }
function gpu_fb_bind(fb: int) -> void {
gl_bind_framebuffer(GL_FRAMEBUFFER, fb)
gpu_fb_cur = fb
}
function gpu_fb_bind_read(fb: int) -> void { gl_bind_framebuffer(GL_READ_FRAMEBUFFER, fb) }
function gpu_fb_bind_draw(fb: int) -> void { gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, fb) }
function gpu_fb_color(slot: int, tex: int) -> void {
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_TEXTURE_2D, tex, 0)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 and slot < 2 { gpu_fb[o + slot] = tex; if slot == 0 { gpu_fb[o + 7] = 0 } }
}
function gpu_fb_color_layer(slot: int, tex: int, layer: int) -> void {
gl_framebuffer_texture_layer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, tex, 0, layer)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 and slot < 2 { gpu_fb[o + slot] = tex; if slot == 0 { gpu_fb[o + 7] = layer + 1 } }
}
function gpu_fb_depth(tex: int) -> void {
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, tex, 0)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 { gpu_fb[o + 2] = tex; gpu_fb[o + 3] = 0 }
}
function gpu_fb_depth_layer(tex: int, layer: int) -> void {
gl_framebuffer_texture_layer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, tex, 0, layer)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 { gpu_fb[o + 2] = tex; gpu_fb[o + 3] = layer + 1 }
}
function gpu_rb_new() -> int {
let ids = gpu_tmp()
gl_gen_renderbuffers(1, ids)
return ids[0]
}
# storage for a renderbuffer: samples > 0 makes it multisampled
function gpu_rb_storage(rb: int, ifmt: int, w: int, h: int, samples: int) -> void {
gl_bind_renderbuffer(GL_RENDERBUFFER, rb)
if samples > 0 { gl_renderbuffer_storage_multisample(GL_RENDERBUFFER, samples, ifmt, w, h) }
else { gl_renderbuffer_storage(GL_RENDERBUFFER, ifmt, w, h) }
gpu_rb_samples = samples
}
function gpu_fb_color_rb(slot: int, rb: int) -> void {
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_RENDERBUFFER, rb)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 { gpu_fb[o + 4] = rb; gpu_fb[o + 6] = gpu_rb_samples }
}
function gpu_fb_depth_rb(rb: int) -> void {
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb)
let o = gpu_fb_at(gpu_fb_cur)
if o >= 0 { gpu_fb[o + 5] = rb; gpu_fb[o + 6] = gpu_rb_samples }
}
# colour slots 0 .. n-1 are drawn into (several: an MRT bake)
function gpu_fb_draw_buffers(n: int) -> void {
if gpu_drawbufs == null { gpu_drawbufs = words(8) }
for i in 0 .. n { gpu_drawbufs[i] = GL_COLOR_ATTACHMENT0 + i }
gl_draw_buffers(n, gpu_drawbufs)
}
# a depth-only target: no colour is drawn or read
function gpu_fb_no_color() -> void {
gl_draw_buffer(GL_NONE)
gl_read_buffer(GL_NONE)
}
function gpu_fb_status() -> int { return gl_check_framebuffer_status(GL_FRAMEBUFFER) }
function gpu_fb_free(fb: int) -> void {
if fb == 0 { return }
let ids = gpu_tmp()
ids[0] = fb
gl_delete_framebuffers(1, ids)
let o = gpu_fb_at(fb)
if o >= 0 { for i in 0 .. GPU_FB_W { gpu_fb[o + i] = 0 } }
}
function gpu_rb_free(rb: int) -> void {
if rb == 0 { return }
let ids = gpu_tmp()
ids[0] = rb
gl_delete_renderbuffers(1, ids)
}
function gpu_viewport(x: int, y: int, w: int, h: int) -> void { gl_viewport(x, y, w, h) }
# R3D_GLCHECK: before a draw or a clear, the bound framebuffer must be complete; after it, no
# error may be pending. Each report names the framebuffer and what was being done, once per
# distinct message, so the first bad pass is found without a flood. (Checking when a viewport
# is set reported the shadow pass, which sets its viewport before attaching a cascade.)
var gpu_glcheck_seen: []string = null
function gpu_glcheck_say(msg: string) -> void {
if gpu_glcheck_seen == null { gpu_glcheck_seen = new []string }
for i in 0 .. len(gpu_glcheck_seen) { if gpu_glcheck_seen[i] == msg { return } }
push(gpu_glcheck_seen, msg)
print(msg)
}
function gpu_glcheck_before(what: string) -> void {
if not gpu_glcheck_on() { return }
let pending = gl_get_error()
if pending != 0 { gpu_glcheck_say(`gpu: error {pending} pending before {what} into framebuffer {gpu_fb_cur}`) }
let st = gl_check_framebuffer_status(GL_FRAMEBUFFER)
if st != GL_FRAMEBUFFER_COMPLETE { gpu_glcheck_say(`gpu: framebuffer {gpu_fb_cur} incomplete ({st}) at {what}`) }
}
function gpu_glcheck_after(what: string) -> void {
if not gpu_glcheck_on() { return }
let e = gl_get_error()
if e != 0 { gpu_glcheck_say(`gpu: error {e} from {what} into framebuffer {gpu_fb_cur}`) }
}
function gpu_clear_color(r: fixed, g: fixed, b: fixed, a: fixed) -> void { gl_clear_color(r, g, b, a) }
function gpu_clear(mask: int) -> void {
gpu_glcheck_before("a clear")
gl_clear(mask)
gpu_glcheck_after("a clear")
}
# the bound read framebuffer's [0, w) x [0, h) into the bound draw framebuffer's, unscaled
function gpu_blit(w: int, h: int, mask: int) -> void {
gl_blit_framebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST)
gpu_glcheck_after("a blit")
}
# the framebuffer the finished frame is presented from (an offscreen one, headless)
function gpu_screen_fb() -> int { return gl_screen }
function gpu_multisample(on: bool) -> void { gpu_gl_cap(GL_MULTISAMPLE, gpu_b(on)) }
function gpu_wireframe(on: bool) -> void {
if on { gl_polygon_mode(GL_FRONT_AND_BACK, GL_LINE) } else { gl_polygon_mode(GL_FRONT_AND_BACK, GL_FILL) }
}
# the presented frame as RGB8, bottom row first (a photograph)
function gpu_read_screen(w: int, h: int, out: pointer) -> void {
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, gl_screen)
gl_pixel_storei(GL_PACK_ALIGNMENT, 1)
gl_read_pixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, out)
}

View file

@ -155,8 +155,8 @@ function ov_glyph(cp: int) -> int {
# start drawing onto the screen: blending on, depth off
function ov_begin() -> void {
if not ov_ready { return }
gl_bind_framebuffer(GL_FRAMEBUFFER, gl_screen_fbo())
gl_viewport(0, 0, gl_w, gl_h)
gpu_fb_bind(gpu_screen_fb())
gpu_viewport(0, 0, gl_w, gl_h)
gpu_depth_test(false)
gpu_cull(false)
gpu_blend(true)

View file

@ -59,20 +59,17 @@ function post_init(w: int, h: int) -> void {
post_w = w; post_h = h
post_hdr = target_new(w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
if post_ms_samples > 1 {
post_ms_fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, post_ms_fbo)
let ids = gl_scratch()
gl_gen_renderbuffers(1, ids)
gl_bind_renderbuffer(GL_RENDERBUFFER, ids[0])
gl_renderbuffer_storage_multisample(GL_RENDERBUFFER, post_ms_samples, GL_RGBA16F, w, h)
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ids[0])
gl_gen_renderbuffers(1, ids)
gl_bind_renderbuffer(GL_RENDERBUFFER, ids[0])
gl_renderbuffer_storage_multisample(GL_RENDERBUFFER, post_ms_samples, GL_DEPTH_COMPONENT32F, w, h)
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, ids[0])
let st = gl_check_framebuffer_status(GL_FRAMEBUFFER)
post_ms_fbo = gpu_fb_new()
gpu_fb_bind(post_ms_fbo)
let rbc = gpu_rb_new()
gpu_rb_storage(rbc, GL_RGBA16F, w, h, post_ms_samples)
gpu_fb_color_rb(0, rbc)
let rbd = gpu_rb_new()
gpu_rb_storage(rbd, GL_DEPTH_COMPONENT32F, w, h, post_ms_samples)
gpu_fb_depth_rb(rbd)
let st = gpu_fb_status()
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: msaa framebuffer incomplete {st}`); post_ms_fbo = 0 }
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind(0)
}
post_bloom = new []Target
var bw = w / 2; var bh = h / 2
@ -163,28 +160,28 @@ function post_measure() -> void {
function post_begin_scene() -> void {
target_bind(post_hdr)
if post_ms_fbo != 0 { gl_bind_framebuffer(GL_FRAMEBUFFER, post_ms_fbo); gl_enable(GL_MULTISAMPLE) }
if post_ms_fbo != 0 { gpu_fb_bind(post_ms_fbo); gpu_multisample(true) }
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_depth_write(true)
gpu_cull(true)
gpu_cull_face(GL_BACK)
gl_clear_color(0.0, 0.0, 0.0, 1.0)
gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
gpu_clear_color(0.0, 0.0, 0.0, 1.0)
gpu_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
}
# resolve the multisampled scene into the plain HDR target (colour + depth)
function post_resolve() -> void {
if post_ms_fbo != 0 {
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, post_ms_fbo)
gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, post_hdr.fbo)
gl_blit_framebuffer(0, 0, post_w, post_h, 0, 0, post_w, post_h, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST)
gpu_fb_bind_read(post_ms_fbo)
gpu_fb_bind_draw(post_hdr.fbo)
gpu_blit(post_w, post_h, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
}
# the depth copy every pass after this may read while the frame is still being drawn into
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, post_hdr.fbo)
gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, post_depth_copy.fbo)
gl_blit_framebuffer(0, 0, post_w, post_h, 0, 0, post_w, post_h, GL_DEPTH_BUFFER_BIT, GL_NEAREST)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind_read(post_hdr.fbo)
gpu_fb_bind_draw(post_depth_copy.fbo)
gpu_blit(post_w, post_h, GL_DEPTH_BUFFER_BIT)
gpu_fb_bind(0)
}
# There is no temporal anti-aliasing. It was reprojecting every pixel through the
@ -200,19 +197,19 @@ function post_resolve() -> void {
# sheet laid over the ground: the bottom is seen THROUGH the water, tinted and dimmed by
# how far the light travelled, instead of being the dry terrain showing through an alpha.
function post_capture_scene() -> void {
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, post_hdr.fbo)
gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, post_scene.fbo)
gl_blit_framebuffer(0, 0, post_w, post_h, 0, 0, post_w, post_h, GL_COLOR_BUFFER_BIT, GL_NEAREST)
gl_bind_framebuffer(GL_FRAMEBUFFER, post_hdr.fbo)
gl_viewport(0, 0, post_w, post_h)
gpu_fb_bind_read(post_hdr.fbo)
gpu_fb_bind_draw(post_scene.fbo)
gpu_blit(post_w, post_h, GL_COLOR_BUFFER_BIT)
gpu_fb_bind(post_hdr.fbo)
gpu_viewport(0, 0, post_w, post_h)
}
# Keep a copy of the finished scene colour: the SSGI bounce reads last frame's colour.
function post_capture_prev() -> void {
gl_bind_framebuffer(GL_READ_FRAMEBUFFER, post_hdr.fbo)
gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, post_prev.fbo)
gl_blit_framebuffer(0, 0, post_w, post_h, 0, 0, post_w, post_h, GL_COLOR_BUFFER_BIT, GL_NEAREST)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind_read(post_hdr.fbo)
gpu_fb_bind_draw(post_prev.fbo)
gpu_blit(post_w, post_h, GL_COLOR_BUFFER_BIT)
gpu_fb_bind(0)
post_frame += 1
}
@ -295,8 +292,8 @@ function post_tonemap(color_tex: int) -> void {
u_f3(gpu_uniform(post_p_tone, "u_gain"), fl(0.99), fl(0.995), fl(1.0))
mesh_draw(post_fs)
# sharpen + grain onto the screen
gl_bind_framebuffer(GL_FRAMEBUFFER, gl_screen)
gl_viewport(0, 0, gl_w, gl_h)
gpu_fb_bind(gpu_screen_fb())
gpu_viewport(0, 0, gl_w, gl_h)
gl_use_program(post_p_sharp)
r3d_bind_2d(post_p_sharp, "u_src", 0, post_ldr.color)
u_f2(gpu_uniform(post_p_sharp, "u_texel"), fr(1, post_w), fr(1, post_h))

View file

@ -101,27 +101,26 @@ property Target {
function target_new(w: int, h: int, ifmt: int, fmt: int, ty: int, with_depth: bool, filter: int) -> Target {
let t = new Target
t.w = w; t.h = h
t.fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, t.fbo)
t.fbo = gpu_fb_new()
gpu_fb_bind(t.fbo)
t.color = tex_target(w, h, ifmt, fmt, ty, filter)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, t.color, 0)
gpu_fb_color(0, t.color)
if with_depth {
t.depth = tex_target(w, h, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, GL_NEAREST)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, t.depth, 0)
gpu_fb_depth(t.depth)
}
let st = gl_check_framebuffer_status(GL_FRAMEBUFFER)
let st = gpu_fb_status()
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: framebuffer incomplete {st} ({w}x{h})`) }
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind(0)
return t
}
function target_free(t: Target) -> void {
if t == null { return }
let ids = gl_scratch()
ids[0] = t.fbo; gl_delete_framebuffers(1, ids)
gpu_fb_free(t.fbo)
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)
gl_viewport(0, 0, t.w, t.h)
gpu_fb_bind(t.fbo)
gpu_viewport(0, 0, t.w, t.h)
}

View file

@ -93,7 +93,7 @@ function r3d_init(w: int, h: int, title: string) -> bool {
r3d_fog_density = fl(0.00014)
r3d_fog_falloff = fl(0.002)
r3d_ready = true
gl_check("r3d init")
gpu_check("r3d init")
return true
}
@ -226,5 +226,5 @@ function r3d_frame(time: int) -> void {
post_capture_prev()
prof_end()
prof_collect()
gl_check("frame")
gpu_check("frame")
}

View file

@ -421,22 +421,17 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
let aw = tiles * tw
im.albedo = tex_target(aw, th, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
im.normal = tex_target(aw, th, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
let fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, im.albedo, 0)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, im.normal, 0)
let ids = gl_scratch()
gl_gen_renderbuffers(1, ids)
let rb = ids[0]
gl_bind_renderbuffer(GL_RENDERBUFFER, rb)
gl_renderbuffer_storage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, aw, th)
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb)
let bufs = words(2)
bufs[0] = GL_COLOR_ATTACHMENT0; bufs[1] = GL_COLOR_ATTACHMENT1
gl_draw_buffers(2, bufs)
gl_viewport(0, 0, aw, th)
gl_clear_color(0.0, 0.0, 0.0, 0.0)
gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
let fbo = gpu_fb_new()
gpu_fb_bind(fbo)
gpu_fb_color(0, im.albedo)
gpu_fb_color(1, im.normal)
let rb = gpu_rb_new()
gpu_rb_storage(rb, GL_DEPTH_COMPONENT24, aw, th, 0)
gpu_fb_depth_rb(rb)
gpu_fb_draw_buffers(2)
gpu_viewport(0, 0, aw, th)
gpu_clear_color(0.0, 0.0, 0.0, 0.0)
gpu_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_cull(false)
@ -461,7 +456,7 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
u_mat4(gpu_uniform(bake, "u_view"), view)
u_mat4(gpu_uniform(bake, "u_proj"), proj)
u_f(gpu_uniform(bake, "u_wind"), F_ZERO)
gl_viewport(t * tw, 0, tw, th)
gpu_viewport(t * tw, 0, tw, th)
for i in 0 .. len(model.prims) {
let pr = model.prims[i]
r3d_bind_2d(bake, "u_diff", 0, pr.diff)
@ -469,19 +464,17 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
mesh_draw_instanced(pr.mesh, 1)
}
}
free(view); free(proj); free(eye); free(at); free(up); free(bufs)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
ids[0] = fbo
gl_delete_framebuffers(1, ids)
ids[0] = rb
gl_delete_renderbuffers(1, ids)
free(view); free(proj); free(eye); free(at); free(up)
gpu_fb_bind(0)
gpu_fb_free(fbo)
gpu_rb_free(rb)
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")
gpu_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
}
@ -990,21 +983,16 @@ function cb_rnd() -> int {
}
function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
let tex = tex_target(res, res, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
let fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)
let ids = gl_scratch()
gl_gen_renderbuffers(1, ids)
let rb = ids[0]
gl_bind_renderbuffer(GL_RENDERBUFFER, rb)
gl_renderbuffer_storage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, res, res)
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb)
let bufs = words(1)
bufs[0] = GL_COLOR_ATTACHMENT0
gl_draw_buffers(1, bufs)
gl_viewport(0, 0, res, res)
gl_clear_color(0.0, 0.0, 0.0, 0.0)
gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
let fbo = gpu_fb_new()
gpu_fb_bind(fbo)
gpu_fb_color(0, tex)
let rb = gpu_rb_new()
gpu_rb_storage(rb, GL_DEPTH_COMPONENT24, res, res, 0)
gpu_fb_depth_rb(rb)
gpu_fb_draw_buffers(1)
gpu_viewport(0, 0, res, res)
gpu_clear_color(0.0, 0.0, 0.0, 0.0)
gpu_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_cull(false)
@ -1057,12 +1045,10 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
mesh_draw_instanced(pr.mesh, n9)
}
}
free(view); free(proj); free(eye); free(at); free(up); free(bufs)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
ids[0] = fbo
gl_delete_framebuffers(1, ids)
ids[0] = rb
gl_delete_renderbuffers(1, ids)
free(view); free(proj); free(eye); free(at); free(up)
gpu_fb_bind(0)
gpu_fb_free(fbo)
gpu_rb_free(rb)
gpu_buffer_free(buf)
gpu_tex_bind(GPU_TEX2D, tex)
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
@ -1070,7 +1056,7 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
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")
gpu_check("carpet bake")
return tex
}
@ -1082,7 +1068,6 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
function scatter_clear_all() -> void {
stream_clear_all()
if sc_layers == null { return }
let ids = gl_scratch()
for i in 0 .. len(sc_layers) {
let l = sc_layers[i]
if l.buf != 0 { gpu_buffer_free(l.buf) }

View file

@ -34,11 +34,10 @@ function shadow_init() -> void {
gl_put(border, 0, 1.0); gl_put(border, 1, 1.0); gl_put(border, 2, 1.0); gl_put(border, 3, 1.0)
gpu_tex_border(GPU_TEX2D_ARRAY, border)
free(border)
sh_fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, sh_fbo)
gl_draw_buffer(GL_NONE)
gl_read_buffer(GL_NONE)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
sh_fbo = gpu_fb_new()
gpu_fb_bind(sh_fbo)
gpu_fb_no_color()
gpu_fb_bind(0)
sh_vp = words(16 * SHADOW_CASCADES)
sh_split = words(SHADOW_CASCADES)
sh_range = words(SHADOW_CASCADES)
@ -114,8 +113,8 @@ function shadow_cascade_vp(c: int) -> words { return mem_off(sh_vp, c * 64) }
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
function shadow_pass() -> void {
var near = cam_near
gl_bind_framebuffer(GL_FRAMEBUFFER, sh_fbo)
gl_viewport(0, 0, SHADOW_RES, SHADOW_RES)
gpu_fb_bind(sh_fbo)
gpu_viewport(0, 0, SHADOW_RES, SHADOW_RES)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_depth_bias(2.0, 4.0)
@ -124,9 +123,9 @@ function shadow_pass() -> void {
sh_cascade = c
shadow_fit(c, near, sh_split[c])
near = sh_split[c]
gl_framebuffer_texture_layer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, sh_tex, 0, c)
if r3d_debug and c == 0 { let st = gl_check_framebuffer_status(GL_FRAMEBUFFER); print(`shadow fbo status {st}`) }
gl_clear(GL_DEPTH_BUFFER_BIT)
gpu_fb_depth_layer(sh_tex, c)
if r3d_debug and c == 0 { let st = gpu_fb_status(); print(`shadow fbo status {st}`) }
gpu_clear(GL_DEPTH_BUFFER_BIT)
let vp = shadow_cascade_vp(c)
# shadows off (a video setting): the cascades stay cleared, so everything reads lit
if sh_enabled {
@ -135,7 +134,7 @@ function shadow_pass() -> void {
}
}
gpu_depth_bias(0.0, 0.0)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind(0)
if r3d_debug_shadow { shadow_dump() }
if r3d_debug_shadow and not sh_printed2 {
sh_printed2 = true

View file

@ -69,11 +69,11 @@ function sky_load(path: string) -> bool {
}
function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, rough: int) -> void {
let fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
if layer < 0 { gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_tex, 0) }
else { gl_framebuffer_texture_layer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target_tex, 0, layer) }
gl_viewport(0, 0, w, h)
let fbo = gpu_fb_new()
gpu_fb_bind(fbo)
if layer < 0 { gpu_fb_color(0, target_tex) }
else { gpu_fb_color_layer(0, target_tex, layer) }
gpu_viewport(0, 0, w, h)
gl_use_program(prog)
r3d_bind_2d(prog, "u_sky", 0, sky_tex)
sky_bind_rot(prog)
@ -81,16 +81,13 @@ function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, ro
u_f(gpu_uniform(prog, "u_rough"), rough)
u_f(gpu_uniform(prog, "u_sky_w"), fi(sky_w))
mesh_draw(sky_fullscreen)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
let ids = gl_scratch()
ids[0] = fbo
gl_delete_framebuffers(1, ids)
gpu_fb_bind(0)
gpu_fb_free(fbo)
}
function sky_precompute() -> void {
gpu_depth_test(false)
if sky_irradiance != 0 {
let ids = gl_scratch()
gpu_tex_free(sky_irradiance)
gpu_tex_free(sky_prefilter)
gpu_tex_free(sky_brdf)
@ -118,7 +115,7 @@ function sky_precompute() -> void {
let p_brdf = sky_p_brdf
sky_brdf = tex_target(256, 256, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR)
sky_convolve(p_brdf, sky_brdf, -1, 256, 256, F_ZERO)
gl_check("sky precompute")
gpu_check("sky precompute")
}
# bind the IBL set + sun for a lit program (units 12..14)

View file

@ -222,10 +222,10 @@ function terrain_generate() -> void {
if ter_smooth { defs = "#define SMOOTH\n" }
let p = r3d_program("fullscreen.vert", "heightgen.frag", defs)
ter_height_tex = tex_target(TERRAIN_RES, TERRAIN_RES, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
let fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ter_height_tex, 0)
gl_viewport(0, 0, TERRAIN_RES, TERRAIN_RES)
let fbo = gpu_fb_new()
gpu_fb_bind(fbo)
gpu_fb_color(0, ter_height_tex)
gpu_viewport(0, 0, TERRAIN_RES, TERRAIN_RES)
gpu_depth_test(false)
gl_use_program(p)
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
@ -246,7 +246,7 @@ function terrain_generate() -> void {
# second pass: R = height, GBA = the smooth surface normal, baked once (ternormal.frag)
let raw = ter_height_tex
ter_height_tex = tex_target(TERRAIN_RES, TERRAIN_RES, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_LINEAR)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ter_height_tex, 0)
gpu_fb_color(0, ter_height_tex)
let pn = r3d_program("fullscreen.vert", "ternormal.frag", "")
gl_use_program(pn)
r3d_bind_2d(pn, "u_src", 0, raw)
@ -259,12 +259,10 @@ function terrain_generate() -> void {
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
gl_delete_framebuffers(1, ids)
gpu_fb_bind(0)
gpu_fb_free(fbo)
gl_delete_program(p)
gl_check("terrain generate")
gpu_check("terrain generate")
}
# The height field for shaders that place things on the ground (model.vert's u_ground)
@ -281,10 +279,10 @@ function terrain_bake_shadow() -> void {
if ter_shadow_tex == 0 { ter_shadow_tex = tex_target(TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_LINEAR) }
if ter_shadow_prog == 0 { ter_shadow_prog = r3d_program("fullscreen.vert", "tershadow.frag", "#define NOISE_ONLY\n") }
let p = ter_shadow_prog
let fbo = gl_framebuffer()
gl_bind_framebuffer(GL_FRAMEBUFFER, fbo)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ter_shadow_tex, 0)
gl_viewport(0, 0, TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES)
let fbo = gpu_fb_new()
gpu_fb_bind(fbo)
gpu_fb_color(0, ter_shadow_tex)
gpu_viewport(0, 0, TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES)
gpu_depth_test(false)
gpu_blend(false)
gl_use_program(p)
@ -292,12 +290,10 @@ function terrain_bake_shadow() -> void {
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_v3(gpu_uniform(p, "u_sun"), sun_dir)
mesh_draw(sky_fullscreen)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
let ids = gl_scratch()
ids[0] = fbo
gl_delete_framebuffers(1, ids)
gpu_fb_bind(0)
gpu_fb_free(fbo)
ter_shadow_yaw = sky_yaw
gl_check("terrain shadow bake")
gpu_check("terrain shadow bake")
}
# height at world (x, z) — float bits, bilinear over the CPU copy
@ -424,7 +420,7 @@ function terrain_init() -> void {
ter_far_band = fi(60)
if Os.has_env("R3D_TBAND") { ter_far_band = fi(Text.to_int(Os.env("R3D_TBAND"))) }
ter_snow_line = fi(880)
gl_check("terrain init")
gpu_check("terrain init")
}
# draw into the current cascade with the given light view-projection
@ -516,18 +512,18 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target {
# an error per draw and a pass that silently does nothing. One call a pass is cheaper
# than any scheme for noticing.
target_bind(t)
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0)
gpu_fb_depth(depth)
if not ter_sun_checked {
ter_sun_checked = true
let st = gl_check_framebuffer_status(GL_FRAMEBUFFER)
let st = gpu_fb_status()
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: sun-visibility framebuffer incomplete {st}`) }
}
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_depth_write(true)
# the depth is the frame's own and was cleared with it; only the visibility is cleared
gl_clear_color(1.0, 1.0, 1.0, 1.0) # unshadowed where nothing is drawn
gl_clear(GL_COLOR_BUFFER_BIT)
gpu_clear_color(1.0, 1.0, 1.0, 1.0) # unshadowed where nothing is drawn
gpu_clear(GL_COLOR_BUFFER_BIT)
let p = ter_sun_prog
gl_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
@ -572,7 +568,7 @@ function terrain_draw() -> void {
if ter_reflect { target_bind(water_refl) }
else {
target_bind(post_hdr)
if post_ms_fbo != 0 { gl_bind_framebuffer(GL_FRAMEBUFFER, post_ms_fbo) }
if post_ms_fbo != 0 { gpu_fb_bind(post_ms_fbo) }
}
gpu_depth_test(true)
# the prepass already laid this geometry's depth down: only the frontmost fragment of
@ -587,9 +583,9 @@ function terrain_draw() -> void {
cd_far_draws = 0
cd_near_draws = 0
gpu_mesh_bind(cd_mesh)
if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_LINE) }
if ter_wire { gpu_wireframe(true) }
cdlod_select(CD_LEVELS - 1, 0, 0)
if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_FILL) }
if ter_wire { gpu_wireframe(false) }
gpu_depth_func(GL_LESS)
if r3d_debug and not ter_printed { ter_printed = true; print(`cdlod patches drawn: {cd_draws} (far {cd_far_draws}, near {cd_near_draws}, band {cd_draws - cd_far_draws - cd_near_draws})`) }
}
@ -763,7 +759,6 @@ function cdlod_select(level: int, ix: int, iz: int) -> bool {
# belongs to the process stays: the patch mesh, the material textures, the programs, the
# 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 { 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 }
@ -792,5 +787,5 @@ function terrain_reload(dem: string, emin: int, emax: int, base: int, ox: int, o
terrain_generate()
terrain_bake_shadow()
cdlod_bounds()
gl_check("terrain reload")
gpu_check("terrain reload")
}

View file

@ -72,8 +72,8 @@ function water_reflection_pass() -> void {
gpu_depth_write(true)
gpu_cull(true)
gpu_cull_face(GL_FRONT) # the mirror flips the winding
gl_clear_color(0.0, 0.0, 0.0, 1.0)
gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
gpu_clear_color(0.0, 0.0, 0.0, 1.0)
gpu_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
sc_freeze = true
let sb = sc_skip_blade
sc_skip_blade = true # blades are invisible at this scale in a reflection
@ -93,7 +93,7 @@ function water_reflection_pass() -> void {
m4_copy(cam_inv_vp, mem_off(water_saved, 128))
v3_set(cam_pos, sx, sy, sz)
free(eye); free(fwd); free(up); free(at)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
gpu_fb_bind(0)
}
# the program and the plane are the process's, built once; the bodies are the map's