feat(render3d): gpu.ludic - the seam for a second graphics API, and what the GPU can do

Render state (depth, blending, culling, colour writes, alpha-to-coverage, depth bias,
scissor) and uniforms go through gpu_* / u_* and nowhere else; OpenGL state is cached and
only changes reach the driver. Frames are bit-identical to before at the fixed viewpoints.
R3D_GFX=gl|vk or gpu_request chooses a backend, falling back to OpenGL with a reason.

gpu_caps_probe() asks Vulkan, on Windows, for the 1.3 floor, ray tracing, mesh shaders, the
NVIDIA RTX generation, Reflex and HDR colour spaces, for a game's settings to grey out what
a machine cannot use; R3D_CAPS pretends to be a given card for tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 09:52:12 +03:00
parent 208cad7ca1
commit 54eeef5f8d
17 changed files with 652 additions and 312 deletions

View file

@ -144,17 +144,17 @@ function post_measure() -> void {
if post_p_adapt == 0 { post_p_adapt = r3d_program("fullscreen.vert", "adapt.frag", "") }
let next = 1 - post_adapt_i
target_bind(post_adapt_t[next])
gl_disable(GL_DEPTH_TEST)
gpu_depth_test(false)
gl_use_program(post_p_adapt)
r3d_bind_2d(post_p_adapt, "u_scene", 0, post_hdr.color)
r3d_bind_2d(post_p_adapt, "u_prev", 1, post_adapt_t[post_adapt_i].color)
u_f(gl_uniform(post_p_adapt, "u_lod"), fi(post_mips - 1))
u_f(gl_uniform(post_p_adapt, "u_key"), post_key)
u_f(gl_uniform(post_p_adapt, "u_max"), post_exposure_max)
u_f(gl_uniform(post_p_adapt, "u_rate"), fl(0.08))
u_f(gpu_uniform(post_p_adapt, "u_lod"), fi(post_mips - 1))
u_f(gpu_uniform(post_p_adapt, "u_key"), post_key)
u_f(gpu_uniform(post_p_adapt, "u_max"), post_exposure_max)
u_f(gpu_uniform(post_p_adapt, "u_rate"), fl(0.08))
var reset = F_ZERO
if post_adapt_reset { reset = F_ONE; post_adapt_reset = false }
u_f(gl_uniform(post_p_adapt, "u_reset"), reset)
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)
@ -164,11 +164,11 @@ 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) }
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_depth_mask(1)
gl_enable(GL_CULL_FACE)
gl_cull_face(GL_BACK)
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)
}
@ -217,30 +217,30 @@ function post_capture_prev() -> void {
}
function post_ssao_pass() -> void {
gl_disable(GL_DEPTH_TEST)
gl_disable(GL_BLEND)
gpu_depth_test(false)
gpu_blend(false)
target_bind(post_ao)
gl_use_program(post_p_ao)
r3d_bind_2d(post_p_ao, "u_depth", 0, post_hdr.depth)
r3d_bind_2d(post_p_ao, "u_prev_color", 1, post_prev.color)
u_f(gl_uniform(post_p_ao, "u_frame"), fi(post_frame % 64))
u_mat4(gl_uniform(post_p_ao, "u_inv_proj"), cam_inv_proj)
u_mat4(gl_uniform(post_p_ao, "u_proj"), cam_proj)
u_f2(gl_uniform(post_p_ao, "u_texel"), fr(1, post_w), fr(1, post_h))
u_f(gl_uniform(post_p_ao, "u_radius"), post_ao_radius)
u_f(gl_uniform(post_p_ao, "u_intensity"), post_ao_intensity)
u_f(gpu_uniform(post_p_ao, "u_frame"), fi(post_frame % 64))
u_mat4(gpu_uniform(post_p_ao, "u_inv_proj"), cam_inv_proj)
u_mat4(gpu_uniform(post_p_ao, "u_proj"), cam_proj)
u_f2(gpu_uniform(post_p_ao, "u_texel"), fr(1, post_w), fr(1, post_h))
u_f(gpu_uniform(post_p_ao, "u_radius"), post_ao_radius)
u_f(gpu_uniform(post_p_ao, "u_intensity"), post_ao_intensity)
mesh_draw(post_fs)
target_bind(post_ao_blur)
gl_use_program(post_p_ao_blur)
r3d_bind_2d(post_p_ao_blur, "u_ao", 0, post_ao.color)
r3d_bind_2d(post_p_ao_blur, "u_depth", 1, post_hdr.depth)
u_f2(gl_uniform(post_p_ao_blur, "u_texel"), fr(1, post_ao.w), fr(1, post_ao.h))
u_f2(gpu_uniform(post_p_ao_blur, "u_texel"), fr(1, post_ao.w), fr(1, post_ao.h))
mesh_draw(post_fs)
}
function post_bloom_pass() -> void {
gl_disable(GL_DEPTH_TEST)
gl_disable(GL_BLEND)
gpu_depth_test(false)
gpu_blend(false)
var src = post_color
var sw = post_w; var sh = post_h
gl_use_program(post_p_down)
@ -248,60 +248,60 @@ function post_bloom_pass() -> void {
let t = post_bloom[i]
target_bind(t)
r3d_bind_2d(post_p_down, "u_src", 0, src)
u_f2(gl_uniform(post_p_down, "u_texel"), fr(1, sw), fr(1, sh))
u_f2(gpu_uniform(post_p_down, "u_texel"), fr(1, sw), fr(1, sh))
var th = f_neg1()
if i == 0 { th = fl(1.2) }
u_f(gl_uniform(post_p_down, "u_threshold"), th)
u_f(gpu_uniform(post_p_down, "u_threshold"), th)
mesh_draw(post_fs)
src = t.color; sw = t.w; sh = t.h
}
gl_use_program(post_p_up)
gl_enable(GL_BLEND)
gl_blend_func(GL_ONE, GL_ONE)
gpu_blend(true)
gpu_blend_func(GL_ONE, GL_ONE)
var i = BLOOM_LEVELS - 1
while i > 0 {
let from = post_bloom[i]
let to = post_bloom[i - 1]
target_bind(to)
r3d_bind_2d(post_p_up, "u_src", 0, from.color)
u_f2(gl_uniform(post_p_up, "u_texel"), fr(1, from.w), fr(1, from.h))
u_f(gl_uniform(post_p_up, "u_radius"), F_ONE)
u_f2(gpu_uniform(post_p_up, "u_texel"), fr(1, from.w), fr(1, from.h))
u_f(gpu_uniform(post_p_up, "u_radius"), F_ONE)
mesh_draw(post_fs)
i -= 1
}
gl_disable(GL_BLEND)
gpu_blend(false)
}
function post_tonemap(color_tex: int) -> void {
if post_auto { post_measure() }
target_bind(post_ldr)
gl_disable(GL_DEPTH_TEST)
gpu_depth_test(false)
gl_use_program(post_p_tone)
r3d_bind_2d(post_p_tone, "u_hdr", 0, color_tex)
r3d_bind_2d(post_p_tone, "u_bloom", 1, post_bloom[0].color)
r3d_bind_2d(post_p_tone, "u_ao", 2, post_ao_blur.color)
u_f(gl_uniform(post_p_tone, "u_ao_strength"), post_ao_strength)
u_f(gl_uniform(post_p_tone, "u_gi_strength"), post_gi_strength)
u_f(gl_uniform(post_p_tone, "u_exposure"), post_exposure)
u_f(gpu_uniform(post_p_tone, "u_ao_strength"), post_ao_strength)
u_f(gpu_uniform(post_p_tone, "u_gi_strength"), post_gi_strength)
u_f(gpu_uniform(post_p_tone, "u_exposure"), post_exposure)
var auto = F_ZERO
if post_auto and post_adapt_t != null { auto = F_ONE; r3d_bind_2d(post_p_tone, "u_adapt", 3, post_adapt_t[post_adapt_i].color) }
u_f(gl_uniform(post_p_tone, "u_auto"), auto)
u_f(gl_uniform(post_p_tone, "u_bloom_strength"), post_bloom_strength)
u_f(gl_uniform(post_p_tone, "u_vignette"), post_vignette)
u_f(gl_uniform(post_p_tone, "u_saturation"), post_saturation)
u_f(gl_uniform(post_p_tone, "u_contrast"), post_contrast)
u_f3(gl_uniform(post_p_tone, "u_wb"), fl(1.02), F_ONE, fl(0.97))
u_f3(gl_uniform(post_p_tone, "u_lift"), fl(0.004), fl(0.004), fl(0.012))
u_f3(gl_uniform(post_p_tone, "u_gain"), fl(0.99), fl(0.995), fl(1.0))
u_f(gpu_uniform(post_p_tone, "u_auto"), auto)
u_f(gpu_uniform(post_p_tone, "u_bloom_strength"), post_bloom_strength)
u_f(gpu_uniform(post_p_tone, "u_vignette"), post_vignette)
u_f(gpu_uniform(post_p_tone, "u_saturation"), post_saturation)
u_f(gpu_uniform(post_p_tone, "u_contrast"), post_contrast)
u_f3(gpu_uniform(post_p_tone, "u_wb"), fl(1.02), F_ONE, fl(0.97))
u_f3(gpu_uniform(post_p_tone, "u_lift"), fl(0.004), fl(0.004), fl(0.012))
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)
gl_use_program(post_p_sharp)
r3d_bind_2d(post_p_sharp, "u_src", 0, post_ldr.color)
u_f2(gl_uniform(post_p_sharp, "u_texel"), fr(1, post_w), fr(1, post_h))
u_f(gl_uniform(post_p_sharp, "u_amount"), post_sharpen)
u_f(gl_uniform(post_p_sharp, "u_grain"), post_grain)
u_f(gl_uniform(post_p_sharp, "u_time"), r3d_time)
u_f2(gpu_uniform(post_p_sharp, "u_texel"), fr(1, post_w), fr(1, post_h))
u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen)
u_f(gpu_uniform(post_p_sharp, "u_grain"), post_grain)
u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time)
mesh_draw(post_fs)
}