diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 4d69bc86..7e521d89 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -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) +} diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index 156aafd0..3ed53533 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -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) diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index b0b11ab3..fb24edb1 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -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)) diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 00d3801c..069f0d6d 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -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) } diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index 29f10fa0..4326c0ec 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.ludic @@ -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") } diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index c22327db..25270e91 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -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) } diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index 497a0597..1339874e 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -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 diff --git a/packages/ludic.render3d/sky.ludic b/packages/ludic.render3d/sky.ludic index 3dc274fb..21de6f13 100644 --- a/packages/ludic.render3d/sky.ludic +++ b/packages/ludic.render3d/sky.ludic @@ -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) diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 57b162b9..853dec8c 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -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") } diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index d7da9d33..832fd8ab 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -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