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

@ -66,14 +66,14 @@ function ac_prog_new(vs: string, fs: string, defs: string) -> AcProg {
let a = new AcProg
a.prog = r3d_program(vs, fs, defs)
let p = a.prog
a.l_model = gl_uniform(p, "u_model"); a.l_skin = gl_uniform(p, "u_skinned"); a.l_lvp = gl_uniform(p, "u_light_vp")
a.l_bones = gl_uniform(p, "u_bones[0]"); if a.l_bones < 0 { a.l_bones = gl_uniform(p, "u_bones") }
a.l_tint = gl_uniform(p, "u_tint"); a.l_rough = gl_uniform(p, "u_rough_scale"); a.l_emis = gl_uniform(p, "u_emissive")
a.l_view = gl_uniform(p, "u_view"); a.l_proj = gl_uniform(p, "u_proj"); a.l_mh = gl_uniform(p, "u_model_h")
a.l_out = gl_uniform(p, "u_outline"); a.l_ocol = gl_uniform(p, "u_outline_col")
a.l_model = gpu_uniform(p, "u_model"); a.l_skin = gpu_uniform(p, "u_skinned"); a.l_lvp = gpu_uniform(p, "u_light_vp")
a.l_bones = gpu_uniform(p, "u_bones[0]"); if a.l_bones < 0 { a.l_bones = gpu_uniform(p, "u_bones") }
a.l_tint = gpu_uniform(p, "u_tint"); a.l_rough = gpu_uniform(p, "u_rough_scale"); a.l_emis = gpu_uniform(p, "u_emissive")
a.l_view = gpu_uniform(p, "u_view"); a.l_proj = gpu_uniform(p, "u_proj"); a.l_mh = gpu_uniform(p, "u_model_h")
a.l_out = gpu_uniform(p, "u_outline"); a.l_ocol = gpu_uniform(p, "u_outline_col")
# the samplers never move: units 0, 1, 2
gl_use_program(p)
gl_uniform1i(gl_uniform(p, "u_diff"), 0); gl_uniform1i(gl_uniform(p, "u_nrm"), 1); gl_uniform1i(gl_uniform(p, "u_arm"), 2)
u_i(gpu_uniform(p, "u_diff"), 0); u_i(gpu_uniform(p, "u_nrm"), 1); u_i(gpu_uniform(p, "u_arm"), 2)
return a
}
function actor_init() -> void {
@ -161,14 +161,14 @@ function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void {
gl_use_program(p)
u_mat4(ap.l_model, a.mat)
var skinned = F_ZERO
if a.skin != null { skinned = F_ONE; gl_uniform_matrix4fv(ap.l_bones, a.skin.n_joints, 0, a.skin.bones) }
else if a.model.skin != null { skinned = F_ONE; gl_uniform_matrix4fv(ap.l_bones, a.model.skin.n_joints, 0, a.model.skin.bones) }
if a.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
else if a.model.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
u_f(ap.l_skin, skinned)
if not shadow {
u_f(ap.l_emis, a.emissive)
u_f(ap.l_rough, a.rough)
}
gl_disable(GL_CULL_FACE)
gpu_cull(false)
let model = a.model
for i in 0 .. len(model.prims) {
let pr = model.prims[i]
@ -248,7 +248,7 @@ function actor_draw() -> void {
actor_draw_one(a, ap, false)
}
actor_draw_outlines()
gl_enable(GL_CULL_FACE)
gpu_cull(true)
}
# The rim around a highlighted actor: the model again, its vertices pushed out along their
# normals (skin.vert, OUTLINE) and its front faces culled, so only the far side of the
@ -258,8 +258,8 @@ function actor_draw_outlines() -> void {
var any = ac_oq_n > 0
for i in 0 .. len(ac_actors) { if ac_actors[i].outline != 0 and ac_visible(ac_actors[i], false) { any = true; break } }
if not any { return }
gl_enable(GL_CULL_FACE)
gl_cull_face(GL_FRONT)
gpu_cull(true)
gpu_cull_face(GL_FRONT)
for i in 0 .. len(ac_actors) {
let a = ac_actors[i]
if a.outline == 0 or not ac_visible(a, false) { continue }
@ -284,13 +284,13 @@ function actor_draw_outlines() -> void {
for k in 0 .. len(q.model.prims) { mesh_draw(q.model.prims[k].mesh) }
}
ac_oq_closed = true
gl_cull_face(GL_BACK)
gpu_cull_face(GL_BACK)
}
function actor_draw_outline_one(a: Actor, ap: AcProg) -> void {
u_mat4(ap.l_model, a.mat)
var skinned = F_ZERO
if a.skin != null { skinned = F_ONE; gl_uniform_matrix4fv(ap.l_bones, a.skin.n_joints, 0, a.skin.bones) }
else if a.model.skin != null { skinned = F_ONE; gl_uniform_matrix4fv(ap.l_bones, a.model.skin.n_joints, 0, a.model.skin.bones) }
if a.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
else if a.model.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.model.skin.n_joints, a.model.skin.bones) }
u_f(ap.l_skin, skinned)
let model = a.model
for i in 0 .. len(model.prims) {

View file

@ -190,16 +190,16 @@ function daylight_hand(x: int, y: int, z: int, dx: int, dy: int, dz: int, cone:
v3_set(hand_color, r, g, b)
}
function daylight_bind(prog: int) -> void {
u_v3(gl_uniform(prog, "u_hand_pos"), hand_pos)
u_v3(gl_uniform(prog, "u_hand_color"), hand_color)
u_v3(gl_uniform(prog, "u_hand_dir"), hand_dir)
u_f(gl_uniform(prog, "u_hand_cone"), hand_cone)
u_v3(gl_uniform(prog, "u_ibl_scale"), day_ibl)
u_v3(gl_uniform(prog, "u_moon_dir"), day_moon_dir)
u_f(gl_uniform(prog, "u_moon_phase"), day_moon_phase)
u_f(gl_uniform(prog, "u_moon_illum"), day_moon_illum)
u_f(gl_uniform(prog, "u_moon_haze"), day_overcast)
u_f(gl_uniform(prog, "u_daylight"), day_light)
u_v3(gl_uniform(prog, "u_fire_pos"), fire_pos)
u_v3(gl_uniform(prog, "u_fire_color"), fire_color)
u_v3(gpu_uniform(prog, "u_hand_pos"), hand_pos)
u_v3(gpu_uniform(prog, "u_hand_color"), hand_color)
u_v3(gpu_uniform(prog, "u_hand_dir"), hand_dir)
u_f(gpu_uniform(prog, "u_hand_cone"), hand_cone)
u_v3(gpu_uniform(prog, "u_ibl_scale"), day_ibl)
u_v3(gpu_uniform(prog, "u_moon_dir"), day_moon_dir)
u_f(gpu_uniform(prog, "u_moon_phase"), day_moon_phase)
u_f(gpu_uniform(prog, "u_moon_illum"), day_moon_illum)
u_f(gpu_uniform(prog, "u_moon_haze"), day_overcast)
u_f(gpu_uniform(prog, "u_daylight"), day_light)
u_v3(gpu_uniform(prog, "u_fire_pos"), fire_pos)
u_v3(gpu_uniform(prog, "u_fire_color"), fire_color)
}

View file

@ -196,11 +196,4 @@ function m4_xform_point(o: words, m: words, x: int, y: int, z: int) -> int {
return f_add(f_add(f_mul(m[3], x), f_mul(m[7], y)), f_add(f_mul(m[11], z), m[15]))
}
# ---- uniforms ------------------------------------------------------------------
function u_mat4(loc: int, m: words) -> void { gl_uniform_matrix4fv(loc, 1, 0, m) }
function u_f(loc: int, v: int) -> void { let t = gl_scratch(); t[0] = v; gl_uniform1fv(loc, 1, t) }
function u_f2(loc: int, x: int, y: int) -> void { let t = gl_scratch(); t[0] = x; t[1] = y; gl_uniform2fv(loc, 1, t) }
function u_f3(loc: int, x: int, y: int, z: int) -> void { let t = gl_scratch(); t[0] = x; t[1] = y; t[2] = z; gl_uniform3fv(loc, 1, t) }
function u_f4(loc: int, x: int, y: int, z: int, w: int) -> void { let t = gl_scratch(); t[0] = x; t[1] = y; t[2] = z; t[3] = w; gl_uniform4fv(loc, 1, t) }
function u_v3(loc: int, v: words) -> void { gl_uniform3fv(loc, 1, v) }
function u_i(loc: int, v: int) -> void { gl_uniform1i(loc, v) }
# ---- uniforms: see gpu.ludic (gpu_uniform and the u_* setters) ----

View file

@ -0,0 +1,320 @@
# ============================================================================
# gpu.ludic — the seam between the renderer and a graphics API.
#
# render3d was written straight against OpenGL, so there was nowhere to put a second
# API. This file is where that stops: the renderer asks for what it wants here, and
# the backend decides how to say it. OpenGL is the default and the fallback; Vulkan
# is the second backend (docs: maroon-lake docs/plan/37-vulkan.md).
#
# The port is incremental. What has moved behind the seam so far:
# - the choice of backend (R3D_GFX=gl|vk, or gpu_request before r3d_init)
# - the fixed-function render state: depth test/func/write, blending, face
# culling, colour writes, alpha-to-coverage, depth bias, scissor
# - uniforms: looked up by program and name (gpu_uniform), set by the u_* setters
#
# 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
# cache is only right if NOTHING else in the renderer touches that state, so no
# gl_enable / gl_disable / gl_depth_func / gl_depth_mask / gl_blend_func /
# gl_cull_face / gl_color_mask / gl_scissor / gl_polygon_offset may appear outside
# this file, and neither may any gl_uniform* call.
# ============================================================================
const GPU_GL: int = 1
const GPU_VK: int = 2
var gpu_kind: int = 0 # the backend running; 0 until gpu_select
var gpu_wanted: int = 0 # what was asked for (setting or R3D_GFX)
var gpu_fallback_reason: string = null # why the wanted backend is not the one running
# Ask for a backend before r3d_init ("gl", "opengl", "vk", "vulkan"). The environment
# (R3D_GFX) wins over it, so a test or a take can force one whatever the setting says.
function gpu_request(name: string) -> void { gpu_wanted = gpu_kind_of(name) }
function gpu_kind_of(name: string) -> int {
if name == "vk" or name == "vulkan" { return GPU_VK }
if name == "gl" or name == "opengl" { return GPU_GL }
return GPU_GL
}
function gpu_name(kind: int) -> string {
if kind == GPU_VK { return "vulkan" }
return "opengl"
}
# Settle the backend. Anything that cannot run lands on OpenGL with a reason a
# caller can show the player (gpu_fallback_reason).
function gpu_select() -> int {
if gpu_kind != 0 { return gpu_kind }
if Os.has_env("R3D_GFX") { gpu_wanted = gpu_kind_of(Os.env("R3D_GFX")) }
if gpu_wanted == 0 { gpu_wanted = GPU_GL }
gpu_kind = GPU_GL
if gpu_wanted == GPU_VK {
gpu_fallback_reason = "the Vulkan renderer is not built yet"
print(`r3d: vulkan requested: {gpu_fallback_reason}; using opengl`)
}
return gpu_kind
}
function gpu_backend() -> string { return gpu_name(gpu_kind) }
function gpu_is_gl() -> bool { return gpu_kind != GPU_VK }
# ---- render state ----------------------------------------------------------------
# -1 = not known yet: the first set always reaches the driver, so the cache never
# assumes a default the context might not have.
var gpu_s_depth_test: int = -1
var gpu_s_depth_func: int = -1
var gpu_s_depth_write: int = -1
var gpu_s_blend: int = -1
var gpu_s_blend_src: int = -1
var gpu_s_blend_dst: int = -1
var gpu_s_cull: int = -1
var gpu_s_cull_face: int = -1
var gpu_s_color_write: int = -1
var gpu_s_a2c: int = -1
function gpu_b(on: bool) -> int { if on { return 1 }; return 0 }
# Forget the cache: after anything outside the renderer may have changed GL state
# (a context rebuilt, a foreign library drawing into the frame).
function gpu_state_forget() -> void {
gpu_s_depth_test = -1; gpu_s_depth_func = -1; gpu_s_depth_write = -1
gpu_s_blend = -1; gpu_s_blend_src = -1; gpu_s_blend_dst = -1
gpu_s_cull = -1; gpu_s_cull_face = -1; gpu_s_color_write = -1; gpu_s_a2c = -1
}
function gpu_gl_cap(cap: int, on: int) -> void { if on == 1 { gl_enable(cap) } else { gl_disable(cap) } }
function gpu_depth_test(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_depth_test { return }
gpu_s_depth_test = v
gpu_gl_cap(GL_DEPTH_TEST, v)
}
# GL_LESS, GL_LEQUAL, GL_EQUAL, GL_ALWAYS, ... (the comparison names are the same in every API)
function gpu_depth_func(f: int) -> void {
if f == gpu_s_depth_func { return }
gpu_s_depth_func = f
gl_depth_func(f)
}
function gpu_depth_write(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_depth_write { return }
gpu_s_depth_write = v
gl_depth_mask(v)
}
function gpu_blend(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_blend { return }
gpu_s_blend = v
gpu_gl_cap(GL_BLEND, v)
}
function gpu_blend_func(src: int, dst: int) -> void {
if src == gpu_s_blend_src and dst == gpu_s_blend_dst { return }
gpu_s_blend_src = src; gpu_s_blend_dst = dst
gl_blend_func(src, dst)
}
function gpu_cull(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_cull { return }
gpu_s_cull = v
gpu_gl_cap(GL_CULL_FACE, v)
}
# GL_BACK or GL_FRONT
function gpu_cull_face(face: int) -> void {
if face == gpu_s_cull_face { return }
gpu_s_cull_face = face
gl_cull_face(face)
}
# all four channels together: nothing in the renderer writes a partial mask
function gpu_color_write(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_color_write { return }
gpu_s_color_write = v
gl_color_mask(v, v, v, v)
}
function gpu_alpha_to_coverage(on: bool) -> void {
let v = gpu_b(on)
if v == gpu_s_a2c { return }
gpu_s_a2c = v
gpu_gl_cap(GL_SAMPLE_ALPHA_TO_COVERAGE, v)
}
# Depth bias for the shadow casters; (0, 0) turns it off. factor/units are fixed, as
# gl_polygon_offset takes them. A pipeline API bakes the bias into the pipeline.
var gpu_s_bias: int = -1
function gpu_depth_bias(factor: fixed, units: fixed) -> void {
var v = 1
if factor == 0.0 and units == 0.0 { v = 0 }
if v != gpu_s_bias { gpu_s_bias = v; gpu_gl_cap(GL_POLYGON_OFFSET_FILL, v) }
if v == 1 { gl_polygon_offset(factor, units) }
}
# A scissor rectangle in top-down pixels (x, y from the top-left of the drawable), or
# off. Every API but OpenGL counts rows from the top; the GL backend flips it.
var gpu_s_scissor: int = -1
function gpu_scissor(x: int, y_top: int, w: int, h: int) -> void {
if gpu_s_scissor != 1 { gpu_s_scissor = 1; gl_enable(GL_SCISSOR_TEST) }
gl_scissor(x, gl_h - y_top - h, w, h)
}
function gpu_scissor_off() -> void {
if gpu_s_scissor == 0 { return }
gpu_s_scissor = 0
gl_disable(GL_SCISSOR_TEST)
}
# ---- uniforms --------------------------------------------------------------------
# A uniform is found by its program and its name, and set through a handle. On OpenGL
# the handle is the location. On a pipeline API it will name a slot in the program's
# uniform block, so the u_* setters below are the only code that knows which. Arrays
# are looked up by their first element ("u_bones[0]"), as every GL driver accepts.
function gpu_uniform(prog: int, name: string) -> int { return gl_get_uniform_location(prog, name) }
var gpu_u_tmp: words = null
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
# float bits (IEEE singles in an int), like every other number in the renderer
function u_f(loc: int, v: int) -> void { let t = gpu_tmp(); t[0] = v; gl_uniform1fv(loc, 1, t) }
function u_f2(loc: int, x: int, y: int) -> void { let t = gpu_tmp(); t[0] = x; t[1] = y; gl_uniform2fv(loc, 1, t) }
function u_f3(loc: int, x: int, y: int, z: int) -> void { let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; gl_uniform3fv(loc, 1, t) }
function u_f4(loc: int, x: int, y: int, z: int, w: int) -> void { let t = gpu_tmp(); t[0] = x; t[1] = y; t[2] = z; t[3] = w; gl_uniform4fv(loc, 1, t) }
function u_v3(loc: int, v: words) -> void { gl_uniform3fv(loc, 1, v) }
function u_fv(loc: int, n: int, v: words) -> void { gl_uniform1fv(loc, n, v) }
function u_mat4(loc: int, m: words) -> void { gl_uniform_matrix4fv(loc, 1, 0, m) }
function u_mat4n(loc: int, n: int, m: words) -> void { gl_uniform_matrix4fv(loc, n, 0, m) }
function u_i(loc: int, v: int) -> void { gl_uniform1i(loc, v) }
# ---- what this machine can do ----------------------------------------------------
# The advanced graphics features are Windows features: the Vulkan renderer, ray tracing,
# DLSS, Reflex, HDR output, mesh-shader ground cover. gpu_caps_probe() asks Vulkan what
# the GPU offers - on Windows only - and a game's settings screen greys out whatever this
# machine cannot use, with the most specific reason. Detected every start, never saved:
# a settings file carried to another machine must not carry a stale "supported".
const GF_VULKAN: int = 0
const GF_RT_SHADOWS: int = 1
const GF_RT_REFLECTIONS: int = 2
const GF_RT_AO: int = 3
const GF_DLSS: int = 4
const GF_DLSS_RR: int = 5
const GF_DLSS_FG: int = 6
const GF_DLSS5: int = 7
const GF_REFLEX: int = 8
const GF_HDR: int = 9
const GF_MESH_GRASS: int = 10
const GF_COUNT: int = 11
var gpu_cap_probed: bool = false
var gpu_cap_windows: bool = false # the advanced features exist on this platform at all
var gpu_cap_vulkan: bool = false # a Vulkan loader, an instance and a device
var gpu_cap_floor: bool = false # Vulkan 1.3 with everything the renderer's floor needs
var gpu_cap_rt: bool = false # ray query + acceleration structures
var gpu_cap_mesh: bool = false # VK_EXT_mesh_shader
var gpu_cap_nvidia: bool = false
var gpu_cap_rtx: int = 0 # the RTX generation (20, 30, 40, 50); 0 = not RTX
var gpu_cap_reflex: bool = false # VK_NV_low_latency2
var gpu_cap_hdr: bool = false # the instance offers HDR colour spaces
var gpu_cap_device: string = ""
# 20 for "NVIDIA GeForce RTX 2080", 50 for "RTX 5090"; 30 for a workstation "RTX A4000"
function gpu_rtx_generation(name: string) -> int {
let at = Text.index_of(name, "RTX ")
if at < 0 { return 0 }
let p: pointer = name
let c = p[at + 4]
if c >= '0' and c <= '9' { return (c - '0') * 10 }
return 30
}
function gpu_ext_in(props: bytes, n: int, want: string) -> bool {
for i in 0 .. n {
if string(Vk.at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
}
return false
}
# R3D_CAPS=rtx50|rtx40|rtx30|amd|intel|none pretends to be a Windows machine with that GPU,
# so the settings screen can be shot and tested anywhere
function gpu_caps_fake(kind: string) -> void {
gpu_cap_windows = true
if kind == "none" { return }
gpu_cap_vulkan = true; gpu_cap_floor = true; gpu_cap_hdr = true
if kind == "amd" or kind == "intel" { gpu_cap_rt = true; gpu_cap_mesh = true; gpu_cap_device = `test {kind} GPU`; return }
gpu_cap_nvidia = true; gpu_cap_rt = true; gpu_cap_mesh = true; gpu_cap_reflex = true
gpu_cap_rtx = gpu_rtx_generation(`RTX {kind[3 .. 5]}`)
gpu_cap_device = `test NVIDIA GeForce RTX {kind[3 .. 5]}`
}
function gpu_caps_probe() -> void {
if gpu_cap_probed { return }
gpu_cap_probed = true
if Os.has_env("R3D_CAPS") { gpu_caps_fake(Os.env("R3D_CAPS")); return }
gpu_cap_windows = Os.platform() == "windows"
if not gpu_cap_windows { return }
if Vk.open() == 0 { return }
let cnt = bytes(4)
Vk.put_i32(cnt, 0, 0)
Vk.enumerate_instance_extension_properties(null, cnt, null)
let nie = Vk.get_i32(cnt, 0)
let iexts = bytes(nie * VkExtensionProperties_sizeof + 8)
Vk.enumerate_instance_extension_properties(null, cnt, iexts)
gpu_cap_hdr = gpu_ext_in(iexts, nie, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)
let app = bytes(VkApplicationInfo_sizeof)
Vk.zero(app, VkApplicationInfo_sizeof)
Vk.put_i32(app, VkApplicationInfo_sType, VK_STRUCTURE_TYPE_APPLICATION_INFO)
Vk.put_i32(app, VkApplicationInfo_apiVersion, (1 << 22) | (3 << 12))
let ici = bytes(VkInstanceCreateInfo_sizeof)
Vk.zero(ici, VkInstanceCreateInfo_sizeof)
Vk.put_i32(ici, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
Vk.put_ptr(ici, VkInstanceCreateInfo_pApplicationInfo, app)
let out = bytes(8)
if Vk.create_instance(ici, null, out) != VK_SUCCESS { return }
let inst = Vk.get_ptr(out, 0)
Vk.put_i32(cnt, 0, 0)
Vk.enumerate_physical_devices(inst, cnt, null)
let nd = Vk.get_i32(cnt, 0)
let devs = bytes(nd * 8 + 8)
Vk.enumerate_physical_devices(inst, cnt, devs)
let props = bytes(VkPhysicalDeviceProperties_sizeof)
# the renderer runs on the first discrete GPU, else the first one listed
var pick = -1
for d in 0 .. nd {
Vk.get_physical_device_properties(Vk.get_ptr(devs, d * 8), props)
if pick < 0 and Vk.get_i32(props, VkPhysicalDeviceProperties_deviceType) == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU { pick = d }
}
if pick < 0 and nd > 0 { pick = 0 }
if pick >= 0 {
let pd = Vk.get_ptr(devs, pick * 8)
gpu_cap_vulkan = true
Vk.get_physical_device_properties(pd, props)
gpu_cap_device = string(Vk.at(props, VkPhysicalDeviceProperties_deviceName))
gpu_cap_nvidia = Vk.get_i32(props, VkPhysicalDeviceProperties_vendorID) == 4318
if gpu_cap_nvidia { gpu_cap_rtx = gpu_rtx_generation(gpu_cap_device) }
let api = Vk.get_i32(props, VkPhysicalDeviceProperties_apiVersion)
let f13 = bytes(VkPhysicalDeviceVulkan13Features_sizeof)
Vk.zero(f13, VkPhysicalDeviceVulkan13Features_sizeof)
Vk.put_i32(f13, VkPhysicalDeviceVulkan13Features_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES)
let f12 = bytes(VkPhysicalDeviceVulkan12Features_sizeof)
Vk.zero(f12, VkPhysicalDeviceVulkan12Features_sizeof)
Vk.put_i32(f12, VkPhysicalDeviceVulkan12Features_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES)
Vk.put_ptr(f12, VkPhysicalDeviceVulkan12Features_pNext, f13)
let f2 = bytes(VkPhysicalDeviceFeatures2_sizeof)
Vk.zero(f2, VkPhysicalDeviceFeatures2_sizeof)
Vk.put_i32(f2, VkPhysicalDeviceFeatures2_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
Vk.put_ptr(f2, VkPhysicalDeviceFeatures2_pNext, f12)
Vk.get_physical_device_features2(pd, f2)
let major = (api >> 22) & 127
let minor = (api >> 12) & 1023
gpu_cap_floor = (major > 1 or (major == 1 and minor >= 3)) and Vk.get_i32(f13, VkPhysicalDeviceVulkan13Features_dynamicRendering) == 1 and Vk.get_i32(f13, VkPhysicalDeviceVulkan13Features_synchronization2) == 1 and Vk.get_i32(f12, VkPhysicalDeviceVulkan12Features_descriptorIndexing) == 1 and Vk.get_i32(f12, VkPhysicalDeviceVulkan12Features_bufferDeviceAddress) == 1 and Vk.get_i32(f12, VkPhysicalDeviceVulkan12Features_timelineSemaphore) == 1
Vk.put_i32(cnt, 0, 0)
Vk.enumerate_device_extension_properties(pd, null, cnt, null)
let ne = Vk.get_i32(cnt, 0)
let dexts = bytes(ne * VkExtensionProperties_sizeof + 8)
Vk.enumerate_device_extension_properties(pd, null, cnt, dexts)
gpu_cap_rt = gpu_ext_in(dexts, ne, VK_KHR_RAY_QUERY_EXTENSION_NAME) and gpu_ext_in(dexts, ne, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)
gpu_cap_mesh = gpu_ext_in(dexts, ne, VK_EXT_MESH_SHADER_EXTENSION_NAME)
gpu_cap_reflex = gpu_ext_in(dexts, ne, VK_NV_LOW_LATENCY_2_EXTENSION_NAME)
}
Vk.destroy_instance(inst, null)
print(`r3d: gpu caps: {gpu_cap_device} vulkan {gpu_cap_vulkan} floor {gpu_cap_floor} rt {gpu_cap_rt} mesh {gpu_cap_mesh} rtx {gpu_cap_rtx} reflex {gpu_cap_reflex} hdr {gpu_cap_hdr}`)
}
# Whether the renderer actually draws a feature yet. The Vulkan renderer is being built;
# until a feature lands, choosing it is saved and shown, and says it takes effect later.
function gpu_feature_implemented(f: int) -> bool { return false }

View file

@ -84,7 +84,7 @@ function grass_count_at(d: int) -> int {
function grass_tiles(size: int, d_min: int, d_max: int) -> void {
let p = grass_prog
let cells = size / GRASS_CELL
gl_uniform1i(gl_uniform(p, "u_tile_cells"), cells)
u_i(gpu_uniform(p, "u_tile_cells"), cells)
let sz = fi(size)
let half = f_mul(sz, F_HALF)
let reach = f_add(d_max, f_mul(half, fl(1.5)))
@ -108,8 +108,8 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
if cam_sphere_visible(cx, cy, cz, f_add(corner_r, fi(6))) {
let per = grass_count_at(dnear)
if per > 0 {
u_f2(gl_uniform(p, "u_tile"), ox, oz)
gl_uniform1i(gl_uniform(p, "u_per_cell"), per)
u_f2(gpu_uniform(p, "u_tile"), ox, oz)
u_i(gpu_uniform(p, "u_per_cell"), per)
mesh_draw_instanced(grass_mesh, per * cells * cells)
grass_draws += 1
}
@ -124,42 +124,42 @@ function grass_draw() -> void {
if not grass_on or ter_reflect or grass_prog == 0 { return }
let p = grass_prog
gl_use_program(p)
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_mat4(gl_uniform(p, "u_vp"), cam_vp_clean)
u_f(gl_uniform(p, "u_wind"), grass_wind)
u_f(gl_uniform(p, "u_rough_scale"), F_ONE)
u_v3(gl_uniform(p, "u_tint"), sc_blade_tint)
u_v3(gl_uniform(p, "u_blade_base"), sc_blade_base)
u_v3(gl_uniform(p, "u_blade_tip"), sc_blade_tip)
u_f(gl_uniform(p, "u_cull"), grass_radius)
u_f(gl_uniform(p, "u_model_h"), F_ZERO)
u_f(gl_uniform(p, "u_s0"), grass_s0)
u_f(gl_uniform(p, "u_d0"), grass_d0)
u_f(gl_uniform(p, "u_radius"), grass_radius)
gl_uniform1i(gl_uniform(p, "u_dbg"), grass_dbg)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_mat4(gpu_uniform(p, "u_vp"), cam_vp_clean)
u_f(gpu_uniform(p, "u_wind"), grass_wind)
u_f(gpu_uniform(p, "u_rough_scale"), F_ONE)
u_v3(gpu_uniform(p, "u_tint"), sc_blade_tint)
u_v3(gpu_uniform(p, "u_blade_base"), sc_blade_base)
u_v3(gpu_uniform(p, "u_blade_tip"), sc_blade_tip)
u_f(gpu_uniform(p, "u_cull"), grass_radius)
u_f(gpu_uniform(p, "u_model_h"), F_ZERO)
u_f(gpu_uniform(p, "u_s0"), grass_s0)
u_f(gpu_uniform(p, "u_d0"), grass_d0)
u_f(gpu_uniform(p, "u_radius"), grass_radius)
u_i(gpu_uniform(p, "u_dbg"), grass_dbg)
var orthotex = ter_ortho_tex
var oon = F_ONE
if orthotex == 0 { orthotex = ter_height_tex; oon = F_ZERO }
r3d_bind_2d(p, "u_ortho", 4, orthotex)
u_f(gl_uniform(p, "u_ortho_on"), oon)
u_f(gpu_uniform(p, "u_ortho_on"), oon)
var lake = fl(-100000.0)
if ter_lake_ex != 0 { lake = ter_lake_level }
u_f(gl_uniform(p, "u_lake_level"), lake)
u_f(gpu_uniform(p, "u_lake_level"), lake)
var sea = lake
if ter_sea_set { sea = ter_sea_level }
u_f(gl_uniform(p, "u_sea_level"), sea)
u_f4(gl_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez)
u_f(gl_uniform(p, "u_snow_line"), ter_snow_line)
u_f(gpu_uniform(p, "u_sea_level"), sea)
u_f4(gpu_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez)
u_f(gpu_uniform(p, "u_snow_line"), ter_snow_line)
sky_bind_lighting(p)
shadow_bind(p)
fog_bind(p)
u_f(gl_uniform(p, "u_spec_scale"), fl(0.15))
gl_disable(GL_CULL_FACE)
u_f(gpu_uniform(p, "u_spec_scale"), fl(0.15))
gpu_cull(false)
grass_draws = 0
gl_bind_vertex_array(grass_mesh.vao)
grass_tiles(16, F_ZERO, fi(300))
grass_tiles(64, fi(300), fi(1200))
grass_tiles(256, fi(1200), grass_radius)
gl_enable(GL_CULL_FACE)
gpu_cull(true)
}

View file

@ -159,12 +159,12 @@ 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)
gl_disable(GL_DEPTH_TEST)
gl_disable(GL_CULL_FACE)
gl_enable(GL_BLEND)
gl_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gpu_depth_test(false)
gpu_cull(false)
gpu_blend(true)
gpu_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gl_use_program(ov_prog)
u_f2(gl_uniform(ov_prog, "u_screen"), fi(gl_w), fi(gl_h))
u_f2(gpu_uniform(ov_prog, "u_screen"), fi(gl_w), fi(gl_h))
ov_n = 0; ov_nr = 0; ov_range_start = 0
ov_tex = ov_white
ov_clip_w = 0
@ -204,24 +204,24 @@ function ov_flush() -> void {
r3d_bind_2d(ov_prog, "u_tex", 0, t)
var is_font = F_ZERO
if t == ov_font { is_font = F_ONE }
u_f(gl_uniform(ov_prog, "u_is_font"), is_font)
u_f(gpu_uniform(ov_prog, "u_is_font"), is_font)
last = t
}
if ov_ranges[o + 5] > 0 {
if not clipped { gl_enable(GL_SCISSOR_TEST); clipped = true }
gl_scissor(ov_ranges[o + 3], gl_h - ov_ranges[o + 4] - ov_ranges[o + 6], ov_ranges[o + 5], ov_ranges[o + 6])
} else if clipped { gl_disable(GL_SCISSOR_TEST); clipped = false }
clipped = true
gpu_scissor(ov_ranges[o + 3], ov_ranges[o + 4], ov_ranges[o + 5], ov_ranges[o + 6])
} else if clipped { gpu_scissor_off(); clipped = false }
gl_draw_arrays(GL_TRIANGLES, ov_ranges[o + 1] * 6, ov_ranges[o + 2] * 6)
}
if clipped { gl_disable(GL_SCISSOR_TEST) }
if clipped { gpu_scissor_off() }
gl_bind_vertex_array(0)
ov_n = 0; ov_nr = 0; ov_range_start = 0
}
function ov_end() -> void {
if not ov_open { return }
ov_flush()
gl_disable(GL_BLEND)
gl_enable(GL_DEPTH_TEST)
gpu_blend(false)
gpu_depth_test(true)
ov_open = false
}
function ov_use_tex(t: int) -> void {

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

View file

@ -75,7 +75,7 @@ function r3d_program(vs: string, fs: string, defines: string) -> int {
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)
gl_uniform1i(gl_get_uniform_location(prog, name), unit)
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) }

View file

@ -3,6 +3,7 @@
# Import this one file; the game supplies scene_draw() / scene_draw_casters().
# ============================================================================
import "fmath.ludic"
import "gpu.ludic"
import "prof.ludic"
import "programs.ludic"
import "texture.ludic"

View file

@ -33,15 +33,15 @@ function r3d_prepass_off() -> bool {
return r3d_prepass_env == 1
}
function fog_bind(prog: int) -> void {
u_f(gl_uniform(prog, "u_clip_y"), r3d_clip_y)
u_f(gl_uniform(prog, "u_spec_scale"), F_ONE)
u_f(gl_uniform(prog, "u_fog_density"), r3d_fog_density)
u_f(gl_uniform(prog, "u_fog_height_falloff"), r3d_fog_falloff)
u_f(gl_uniform(prog, "u_fog_base"), r3d_fog_base)
u_f(gpu_uniform(prog, "u_clip_y"), r3d_clip_y)
u_f(gpu_uniform(prog, "u_spec_scale"), F_ONE)
u_f(gpu_uniform(prog, "u_fog_density"), r3d_fog_density)
u_f(gpu_uniform(prog, "u_fog_height_falloff"), r3d_fog_falloff)
u_f(gpu_uniform(prog, "u_fog_base"), r3d_fog_base)
var cs = r3d_cloud_shadow
if Os.has_env("R3D_NOCLOUD") { cs = F_ZERO }
u_f(gl_uniform(prog, "u_cloud_shadow"), cs)
u_f(gl_uniform(prog, "u_time"), r3d_time)
u_f(gpu_uniform(prog, "u_cloud_shadow"), cs)
u_f(gpu_uniform(prog, "u_time"), r3d_time)
}
# profiling switches (environment): R3D_NOSHADOW R3D_NOGI R3D_MSAA=n R3D_NOBLADES R3D_NOCARDS R3D_NOTREES R3D_NEAR=m
@ -72,6 +72,7 @@ function r3d_env_flags() -> void {
}
function r3d_init(w: int, h: int, title: string) -> bool {
r3d_env_flags()
gpu_select()
if not gl_open(w, h, title) { print("r3d: no OpenGL context"); return false }
if Os.has_env("R3D_NOVSYNC") { gl_vsync(0) }
var renderer: string = gl_get_string(GL_RENDERER)
@ -97,21 +98,21 @@ function r3d_init(w: int, h: int, title: string) -> bool {
}
function r3d_draw_sky() -> void {
gl_depth_func(GL_LEQUAL)
gl_depth_mask(0)
gl_disable(GL_CULL_FACE)
gpu_depth_func(GL_LEQUAL)
gpu_depth_write(false)
gpu_cull(false)
let p = r3d_sky_prog
gl_use_program(p)
r3d_bind_2d(p, "u_sky", 0, sky_tex)
sky_bind_rot(p)
sky_bind_lighting(p)
u_mat4(gl_uniform(p, "u_inv_vp"), cam_inv_vp)
u_f(gl_uniform(p, "u_sky_gain"), fl(0.95))
u_f(gl_uniform(p, "u_sky_sat"), fl(1.35))
u_f(gl_uniform(p, "u_time"), r3d_time)
u_mat4(gpu_uniform(p, "u_inv_vp"), cam_inv_vp)
u_f(gpu_uniform(p, "u_sky_gain"), fl(0.95))
u_f(gpu_uniform(p, "u_sky_sat"), fl(1.35))
u_f(gpu_uniform(p, "u_time"), r3d_time)
mesh_draw(sky_fullscreen)
gl_depth_mask(1)
gl_depth_func(GL_LESS)
gpu_depth_write(true)
gpu_depth_func(GL_LESS)
}
# the drawable changed size: the camera's aspect and every screen-sized target follow
@ -171,9 +172,9 @@ function r3d_frame(time: int) -> void {
# target, and depth drawn after it lands in the sun buffer, not the scene's.
if not r3d_prepass_off() {
prof_begin("foliage prepass")
gl_color_mask(0, 0, 0, 0)
gpu_color_write(false)
scatter_draw_depth()
gl_color_mask(1, 1, 1, 1)
gpu_color_write(true)
prof_end()
sc_prepass = true
}
@ -206,8 +207,8 @@ function r3d_frame(time: int) -> void {
prof_begin("water surface")
post_capture_scene()
target_bind(post_hdr)
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
water_draw(post_depth_copy.depth)
prof_end()
}

View file

@ -464,10 +464,10 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
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)
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_disable(GL_CULL_FACE)
gl_disable(GL_BLEND)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_cull(false)
gpu_blend(false)
# the model's prims temporarily take the identity instance
for i in 0 .. len(model.prims) { scatter_attach(model.prims[i].mesh.vao, sc_ident_buf) }
let view = m4_new(); let proj = m4_new()
@ -485,9 +485,9 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
v3_set(eye, f_mul(f_sin(a), f_mul(r, fi(4))), f_add(cy, f_mul(r, fl(0.5))), f_neg(f_mul(f_cos(a), f_mul(r, fi(4)))))
m4_look_at(view, eye, at, up)
m4_ortho(proj, f_neg(r), r, f_neg(hh), hh, fl(0.1), f_mul(r, fi(9)))
u_mat4(gl_uniform(bake, "u_view"), view)
u_mat4(gl_uniform(bake, "u_proj"), proj)
u_f(gl_uniform(bake, "u_wind"), F_ZERO)
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)
for i in 0 .. len(model.prims) {
let pr = model.prims[i]
@ -816,45 +816,45 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool,
gl_use_program(p)
# over the prepass: only the fragment the prepass kept, at exactly its depth (a texel
# it cut would otherwise pass LEQUAL over the terrain behind and draw the quad solid)
if p == sc_prog_fol_eq { gl_depth_func(GL_EQUAL) }
if p == sc_prog_fol_eq { gpu_depth_func(GL_EQUAL) }
var ground = F_ZERO
if l.grounded { ground = F_ONE }
u_f(gl_uniform(p, "u_ground"), ground)
u_f(gpu_uniform(p, "u_ground"), ground)
if l.grounded { terrain_bind_height(p) }
u_f(gl_uniform(p, "u_time"), r3d_time)
u_f(gl_uniform(p, "u_wind"), l.wind)
u_f(gpu_uniform(p, "u_time"), r3d_time)
u_f(gpu_uniform(p, "u_wind"), l.wind)
var mh = F_ZERO
if not card and l.foliage { mh = model.height }
u_f(gl_uniform(p, "u_model_h"), mh)
if not card and l.foliage and not shadow and sc_a2c { gl_enable(GL_SAMPLE_ALPHA_TO_COVERAGE) }
u_f(gpu_uniform(p, "u_model_h"), mh)
if not card and l.foliage and not shadow and sc_a2c { gpu_alpha_to_coverage(true) }
if card {
u_f(gl_uniform(p, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gl_uniform(p, "u_card_h"), l.atlas.height)
u_f(gpu_uniform(p, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gpu_uniform(p, "u_card_h"), l.atlas.height)
r3d_bind_2d(p, "u_diff", 0, l.atlas.albedo)
r3d_bind_2d(p, "u_nrm", 1, l.atlas.normal)
gl_enable(GL_SAMPLE_ALPHA_TO_COVERAGE)
gpu_alpha_to_coverage(true)
}
if shadow { u_mat4(gl_uniform(p, "u_light_vp"), light_vp) }
if shadow { u_mat4(gpu_uniform(p, "u_light_vp"), light_vp) }
else {
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_v3(gl_uniform(p, "u_tint"), l.tint)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_v3(gpu_uniform(p, "u_tint"), l.tint)
if sc_dbg_lod and sc_dbg_level >= 0 {
if sc_dbg_tint == null { sc_dbg_tint = v3_new(F_ONE, F_ONE, F_ONE) }
let k = sc_dbg_level
var r = F_ZERO; var g = F_ZERO; var b = F_ZERO
if k == 0 { r = fi(3) } else if k == 1 { g = fi(3) } else if k == 2 { b = fi(3) } else { r = fi(3); g = fi(3) }
v3_set(sc_dbg_tint, r, g, b)
u_v3(gl_uniform(p, "u_tint"), sc_dbg_tint)
u_v3(gpu_uniform(p, "u_tint"), sc_dbg_tint)
}
u_f(gl_uniform(p, "u_rough_scale"), l.rough)
if l.blade { u_v3(gl_uniform(p, "u_blade_base"), sc_blade_base); u_v3(gl_uniform(p, "u_blade_tip"), sc_blade_tip) }
u_f(gl_uniform(p, "u_cull"), l.cull)
u_f(gpu_uniform(p, "u_rough_scale"), l.rough)
if l.blade { u_v3(gpu_uniform(p, "u_blade_base"), sc_blade_base); u_v3(gpu_uniform(p, "u_blade_tip"), sc_blade_tip) }
u_f(gpu_uniform(p, "u_cull"), l.cull)
sky_bind_lighting(p)
shadow_bind(p)
fog_bind(p)
if l.foliage { u_f(gl_uniform(p, "u_spec_scale"), fl(0.05)) }
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.05)) }
}
gl_disable(GL_CULL_FACE)
gpu_cull(false)
for i in 0 .. len(model.prims) {
let pr = model.prims[i]
scatter_attach(pr.mesh.vao, vb)
@ -864,8 +864,8 @@ function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool,
}
mesh_draw_instanced(pr.mesh, cnt)
}
gl_disable(GL_SAMPLE_ALPHA_TO_COVERAGE)
if p == sc_prog_fol_eq { gl_depth_func(GL_LESS) }
gpu_alpha_to_coverage(false)
if p == sc_prog_fol_eq { gpu_depth_func(GL_LESS) }
}
function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
@ -874,31 +874,31 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
if shadow { p = sc_imp_prog_shadow }
gl_use_program(p)
let im = l.imp
u_f(gl_uniform(p, "u_radius"), im.radius)
u_f(gl_uniform(p, "u_height"), im.height)
u_f(gl_uniform(p, "u_tiles"), fi(im.tiles))
u_f(gpu_uniform(p, "u_radius"), im.radius)
u_f(gpu_uniform(p, "u_height"), im.height)
u_f(gpu_uniform(p, "u_tiles"), fi(im.tiles))
r3d_bind_2d(p, "u_atlas_albedo", 0, im.albedo)
if shadow {
u_mat4(gl_uniform(p, "u_light_vp"), light_vp)
u_v3(gl_uniform(p, "u_face_dir"), sun_dir)
if r3d_debug_shadow and not sc_printed { sc_printed = true; print(`imp shadow prog {p} face_dir loc {gl_uniform(p, "u_face_dir")} sun {f_fx(sun_dir[0])} {f_fx(sun_dir[1])} {f_fx(sun_dir[2])} cam {f_fx(cam_pos[0])} {f_fx(cam_pos[1])} {f_fx(cam_pos[2])} n_far {l.n_far}`) }
u_v3(gl_uniform(p, "u_cam_pos"), cam_pos)
u_mat4(gpu_uniform(p, "u_light_vp"), light_vp)
u_v3(gpu_uniform(p, "u_face_dir"), sun_dir)
if r3d_debug_shadow and not sc_printed { sc_printed = true; print(`imp shadow prog {p} face_dir loc {gpu_uniform(p, "u_face_dir")} sun {f_fx(sun_dir[0])} {f_fx(sun_dir[1])} {f_fx(sun_dir[2])} cam {f_fx(cam_pos[0])} {f_fx(cam_pos[1])} {f_fx(cam_pos[2])} n_far {l.n_far}`) }
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
} else {
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_v3(gl_uniform(p, "u_tint"), l.tint)
if sc_dbg_lod { if sc_dbg_tint == null { sc_dbg_tint = v3_new(F_ONE, F_ONE, F_ONE) }; v3_set(sc_dbg_tint, fi(3), F_ZERO, fi(3)); u_v3(gl_uniform(p, "u_tint"), sc_dbg_tint) }
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_v3(gpu_uniform(p, "u_tint"), l.tint)
if sc_dbg_lod { if sc_dbg_tint == null { sc_dbg_tint = v3_new(F_ONE, F_ONE, F_ONE) }; v3_set(sc_dbg_tint, fi(3), F_ZERO, fi(3)); u_v3(gpu_uniform(p, "u_tint"), sc_dbg_tint) }
r3d_bind_2d(p, "u_atlas_normal", 1, im.normal)
sky_bind_lighting(p)
shadow_bind(p)
fog_bind(p)
if l.foliage { u_f(gl_uniform(p, "u_spec_scale"), fl(0.05)) }
if l.foliage { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.05)) }
}
gl_disable(GL_CULL_FACE)
if not shadow and sc_a2c { gl_enable(GL_SAMPLE_ALPHA_TO_COVERAGE) }
gpu_cull(false)
if not shadow and sc_a2c { gpu_alpha_to_coverage(true) }
scatter_attach(sc_card.vao, l.imp_buf)
mesh_draw_instanced(sc_card, l.n_far)
gl_disable(GL_SAMPLE_ALPHA_TO_COVERAGE)
gpu_alpha_to_coverage(false)
}
# Cast from the impostor card, always, for every instance in the layer. The lit pass may
@ -910,14 +910,14 @@ function layer_draw_shadow(l: Layer, light_vp: words) -> void {
let p = sc_imp_prog_shadow
gl_use_program(p)
let im = l.imp
u_f(gl_uniform(p, "u_radius"), im.radius)
u_f(gl_uniform(p, "u_height"), im.height)
u_f(gl_uniform(p, "u_tiles"), fi(im.tiles))
u_f(gpu_uniform(p, "u_radius"), im.radius)
u_f(gpu_uniform(p, "u_height"), im.height)
u_f(gpu_uniform(p, "u_tiles"), fi(im.tiles))
r3d_bind_2d(p, "u_atlas_albedo", 0, im.albedo)
u_mat4(gl_uniform(p, "u_light_vp"), light_vp)
u_v3(gl_uniform(p, "u_face_dir"), sun_dir)
u_v3(gl_uniform(p, "u_cam_pos"), cam_pos)
gl_disable(GL_CULL_FACE)
u_mat4(gpu_uniform(p, "u_light_vp"), light_vp)
u_v3(gpu_uniform(p, "u_face_dir"), sun_dir)
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
gpu_cull(false)
scatter_attach(sc_card.vao, l.sh_buf)
mesh_draw_instanced(sc_card, l.n_sh)
}
@ -935,15 +935,15 @@ function layer_draw_depth(l: Layer, model: Model, vb: int, cnt: int) -> void {
gl_use_program(p)
var ground = F_ZERO
if l.grounded { ground = F_ONE }
u_f(gl_uniform(p, "u_ground"), ground)
u_f(gpu_uniform(p, "u_ground"), ground)
if l.grounded { terrain_bind_height(p) }
u_f(gl_uniform(p, "u_time"), r3d_time)
u_f(gl_uniform(p, "u_wind"), l.wind)
u_f(gl_uniform(p, "u_model_h"), model.height)
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_f(gl_uniform(p, "u_clip_y"), r3d_clip_y)
gl_disable(GL_CULL_FACE)
u_f(gpu_uniform(p, "u_time"), r3d_time)
u_f(gpu_uniform(p, "u_wind"), l.wind)
u_f(gpu_uniform(p, "u_model_h"), model.height)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_f(gpu_uniform(p, "u_clip_y"), r3d_clip_y)
gpu_cull(false)
for i in 0 .. len(model.prims) {
let pr = model.prims[i]
scatter_attach(pr.mesh.vao, vb)
@ -968,7 +968,7 @@ function scatter_draw_depth() -> void {
layer_draw_depth(l, l.model, l.buf, l.n_near)
}
}
gl_enable(GL_CULL_FACE)
gpu_cull(true)
}
function scatter_draw() -> void {
for i in 0 .. len(sc_layers) {
@ -981,7 +981,7 @@ function scatter_draw() -> void {
layer_draw_near(l, false, null, false)
layer_draw_far(l, false, null)
}
gl_enable(GL_CULL_FACE)
gpu_cull(true)
}
# Nothing here is keyed off the cascade. Every skip that used to be — ground cover past
# the 250 m cascade, blades past the nearest, the scanned mesh past the second — made a
@ -1039,10 +1039,10 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
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)
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_disable(GL_CULL_FACE)
gl_disable(GL_BLEND)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_cull(false)
gpu_blend(false)
# the clumps, and eight wrapped copies so the tile's edges continue
let half = f_mul(tile, F_HALF)
let n9 = count * 9
@ -1076,14 +1076,14 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
m4_ortho(proj, f_neg(half), half, f_neg(half), half, fl(0.1), fi(12))
let bake = sc_bake_card_prog
gl_use_program(bake)
u_mat4(gl_uniform(bake, "u_view"), view)
u_mat4(gl_uniform(bake, "u_proj"), proj)
u_f(gl_uniform(bake, "u_wind"), F_ZERO)
u_f(gl_uniform(bake, "u_time"), F_ZERO)
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)
u_f(gpu_uniform(bake, "u_time"), F_ZERO)
for li in 0 .. len(layers) {
let l = layers[li]
if l.atlas == null { continue }
u_f(gl_uniform(bake, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gl_uniform(bake, "u_card_h"), l.atlas.height)
u_f(gpu_uniform(bake, "u_card_w"), f_mul(l.atlas.radius, F_TWO)); u_f(gpu_uniform(bake, "u_card_h"), l.atlas.height)
r3d_bind_2d(bake, "u_diff", 0, l.atlas.albedo)
r3d_bind_2d(bake, "u_arm", 2, l.atlas.normal)
for i in 0 .. len(l.model.prims) {

View file

@ -116,11 +116,10 @@ function shadow_pass() -> void {
var near = cam_near
gl_bind_framebuffer(GL_FRAMEBUFFER, sh_fbo)
gl_viewport(0, 0, SHADOW_RES, SHADOW_RES)
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_enable(GL_POLYGON_OFFSET_FILL)
gl_polygon_offset(2.0, 4.0)
gl_disable(GL_CULL_FACE)
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
gpu_depth_bias(2.0, 4.0)
gpu_cull(false)
for c in 0 .. SHADOW_CASCADES {
sh_cascade = c
shadow_fit(c, near, sh_split[c])
@ -135,7 +134,7 @@ function shadow_pass() -> void {
scene_draw_casters(vp)
}
}
gl_disable(GL_POLYGON_OFFSET_FILL)
gpu_depth_bias(0.0, 0.0)
gl_bind_framebuffer(GL_FRAMEBUFFER, 0)
if r3d_debug_shadow { shadow_dump() }
if r3d_debug_shadow and not sh_printed2 {
@ -222,8 +221,8 @@ function shadow_dump() -> void {
var sh_printed: bool = false
# a uniform array's location: some drivers only answer to the "[0]" spelling
function sh_loc(prog: int, name: string) -> int {
var loc = gl_uniform(prog, name + "[0]")
if loc < 0 { loc = gl_uniform(prog, name) }
var loc = gpu_uniform(prog, name + "[0]")
if loc < 0 { loc = gpu_uniform(prog, name) }
return loc
}
function shadow_bind(prog: int) -> void {
@ -234,17 +233,17 @@ function shadow_bind(prog: int) -> void {
if ts == 0 { ts = ter_height_tex; ts_on = F_ZERO }
r3d_bind_2d(prog, "u_tershadow", 6, ts)
terrain_bind_height(prog)
u_f(gl_uniform(prog, "u_ts_on"), ts_on)
u_f(gl_uniform(prog, "u_ts_half"), fi(TERRAIN_HALF))
u_f2(gl_uniform(prog, "u_ts_origin"), ter_ox, ter_oz)
var loc = gl_uniform(prog, "u_cascade_vp[0]")
if loc < 0 { loc = gl_uniform(prog, "u_cascade_vp") }
if r3d_debug_shadow and not sh_printed { sh_printed = true; print(`cascade vp loc {loc} / {gl_uniform(prog, "u_cascade_vp")} split loc {gl_uniform(prog, "u_cascade_split")} shadow loc {gl_uniform(prog, "u_shadow")}`) }
gl_uniform_matrix4fv(loc, SHADOW_CASCADES, 0, sh_vp)
gl_uniform1fv(sh_loc(prog, "u_cascade_split"), SHADOW_CASCADES, sh_split)
if r3d_debug_shadow and not sh_printed3 { sh_printed3 = true; print(`range {f_fx(sh_range[0])} {f_fx(sh_range[1])} {f_fx(sh_range[2])} {f_fx(sh_range[3])} texel*1000 {f_fx(f_mul(sh_texel[0], fi(1000)))} {f_fx(f_mul(sh_texel[1], fi(1000)))} {f_fx(f_mul(sh_texel[2], fi(1000)))} {f_fx(f_mul(sh_texel[3], fi(1000)))} locs {gl_uniform(prog, "u_cascade_range")} {gl_uniform(prog, "u_cascade_texel")}`) }
gl_uniform1fv(sh_loc(prog, "u_cascade_range"), SHADOW_CASCADES, sh_range)
u_f(gpu_uniform(prog, "u_ts_on"), ts_on)
u_f(gpu_uniform(prog, "u_ts_half"), fi(TERRAIN_HALF))
u_f2(gpu_uniform(prog, "u_ts_origin"), ter_ox, ter_oz)
var loc = gpu_uniform(prog, "u_cascade_vp[0]")
if loc < 0 { loc = gpu_uniform(prog, "u_cascade_vp") }
if r3d_debug_shadow and not sh_printed { sh_printed = true; print(`cascade vp loc {loc} / {gpu_uniform(prog, "u_cascade_vp")} split loc {gpu_uniform(prog, "u_cascade_split")} shadow loc {gpu_uniform(prog, "u_shadow")}`) }
u_mat4n(loc, SHADOW_CASCADES, sh_vp)
u_fv(sh_loc(prog, "u_cascade_split"), SHADOW_CASCADES, sh_split)
if r3d_debug_shadow and not sh_printed3 { sh_printed3 = true; print(`range {f_fx(sh_range[0])} {f_fx(sh_range[1])} {f_fx(sh_range[2])} {f_fx(sh_range[3])} texel*1000 {f_fx(f_mul(sh_texel[0], fi(1000)))} {f_fx(f_mul(sh_texel[1], fi(1000)))} {f_fx(f_mul(sh_texel[2], fi(1000)))} {f_fx(f_mul(sh_texel[3], fi(1000)))} locs {gpu_uniform(prog, "u_cascade_range")} {gpu_uniform(prog, "u_cascade_texel")}`) }
u_fv(sh_loc(prog, "u_cascade_range"), SHADOW_CASCADES, sh_range)
if Os.has_env("R3D_FORCE") { sh_force = Text.to_int(Os.env("R3D_FORCE")) }
gl_uniform1i(gl_uniform(prog, "u_force_cascade"), sh_force)
gl_uniform1fv(sh_loc(prog, "u_cascade_texel"), SHADOW_CASCADES, sh_texel)
u_i(gpu_uniform(prog, "u_force_cascade"), sh_force)
u_fv(sh_loc(prog, "u_cascade_texel"), SHADOW_CASCADES, sh_texel)
}

View file

@ -204,9 +204,9 @@ function skin_pose(sk: Skin) -> void {
# the joint matrices onto a program's u_bones[]
function skin_bind(sk: Skin, prog: int) -> void {
var loc = gl_uniform(prog, "u_bones[0]")
if loc < 0 { loc = gl_uniform(prog, "u_bones") }
gl_uniform_matrix4fv(loc, sk.n_joints, 0, sk.bones)
var loc = gpu_uniform(prog, "u_bones[0]")
if loc < 0 { loc = gpu_uniform(prog, "u_bones") }
u_mat4n(loc, sk.n_joints, sk.bones)
}
# the same skeleton posed on its own: shares the rest data, owns the pose and the matrices

View file

@ -39,7 +39,7 @@ function sky_set_rot(yaw: int) -> void {
sky_yaw = yaw
sky_rot_s = f_sin(yaw); sky_rot_c = f_cos(yaw)
}
function sky_bind_rot(prog: int) -> void { u_f2(gl_uniform(prog, "u_sky_rot"), sky_rot_s, sky_rot_c) }
function sky_bind_rot(prog: int) -> void { u_f2(gpu_uniform(prog, "u_sky_rot"), sky_rot_s, sky_rot_c) }
# direction for an equirect uv (matches equirectUV in lighting.glsl)
function sky_dir_from_uv(o: words, u: int, v: int) -> void {
@ -77,9 +77,9 @@ function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, ro
gl_use_program(prog)
r3d_bind_2d(prog, "u_sky", 0, sky_tex)
sky_bind_rot(prog)
u_f(gl_uniform(prog, "u_sun_clip"), hdr_clip)
u_f(gl_uniform(prog, "u_rough"), rough)
u_f(gl_uniform(prog, "u_sky_w"), fi(sky_w))
u_f(gpu_uniform(prog, "u_sun_clip"), hdr_clip)
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()
@ -88,7 +88,7 @@ function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, ro
}
function sky_precompute() -> void {
gl_disable(GL_DEPTH_TEST)
gpu_depth_test(false)
if sky_irradiance != 0 {
let ids = gl_scratch()
ids[0] = sky_irradiance; gl_delete_textures(1, ids)
@ -126,9 +126,9 @@ 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_2d(prog, "u_brdf", 14, sky_brdf)
u_v3(gl_uniform(prog, "u_sun_dir"), sun_dir)
u_v3(gl_uniform(prog, "u_sun_color"), sun_color)
u_v3(gl_uniform(prog, "u_cam_pos"), cam_pos)
u_f(gl_uniform(prog, "u_prefilter_levels"), fi(SKY_PREFILTER_LEVELS))
u_v3(gpu_uniform(prog, "u_sun_dir"), sun_dir)
u_v3(gpu_uniform(prog, "u_sun_color"), sun_color)
u_v3(gpu_uniform(prog, "u_cam_pos"), cam_pos)
u_f(gpu_uniform(prog, "u_prefilter_levels"), fi(SKY_PREFILTER_LEVELS))
daylight_bind(prog)
}

View file

@ -227,21 +227,21 @@ function terrain_generate() -> void {
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)
gl_disable(GL_DEPTH_TEST)
gpu_depth_test(false)
gl_use_program(p)
u_f(gl_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
if ter_dem_tex != 0 {
r3d_bind_2d(p, "u_dem", 0, ter_dem_tex)
u_f(gl_uniform(p, "u_dem_min"), ter_dem_min)
u_f(gl_uniform(p, "u_dem_max"), ter_dem_max)
u_f(gl_uniform(p, "u_dem_base"), ter_dem_base)
u_f2(gl_uniform(p, "u_origin"), ter_ox, ter_oz)
u_f4(gl_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez)
u_f(gl_uniform(p, "u_lake_level"), ter_lake_level)
u_f(gl_uniform(p, "u_sea_level"), ter_sea_gen())
u_f4(gl_uniform(p, "u_isle"), ter_isle_cx, ter_isle_cz, ter_isle_r, ter_isle_fall)
u_f(gl_uniform(p, "u_isle_mode"), fi(ter_isle_mode))
u_f(gl_uniform(p, "u_dem_blur"), ter_dem_blur)
u_f(gpu_uniform(p, "u_dem_min"), ter_dem_min)
u_f(gpu_uniform(p, "u_dem_max"), ter_dem_max)
u_f(gpu_uniform(p, "u_dem_base"), ter_dem_base)
u_f2(gpu_uniform(p, "u_origin"), ter_ox, ter_oz)
u_f4(gpu_uniform(p, "u_lake"), ter_lake_cx, ter_lake_cz, ter_lake_ex, ter_lake_ez)
u_f(gpu_uniform(p, "u_lake_level"), ter_lake_level)
u_f(gpu_uniform(p, "u_sea_level"), ter_sea_gen())
u_f4(gpu_uniform(p, "u_isle"), ter_isle_cx, ter_isle_cz, ter_isle_r, ter_isle_fall)
u_f(gpu_uniform(p, "u_isle_mode"), fi(ter_isle_mode))
u_f(gpu_uniform(p, "u_dem_blur"), ter_dem_blur)
}
mesh_draw(sky_fullscreen)
# second pass: R = height, GBA = the smooth surface normal, baked once (ternormal.frag)
@ -251,7 +251,7 @@ function terrain_generate() -> void {
let pn = r3d_program("fullscreen.vert", "ternormal.frag", "")
gl_use_program(pn)
r3d_bind_2d(pn, "u_src", 0, raw)
u_f(gl_uniform(pn, "u_half"), fi(TERRAIN_HALF))
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)
@ -271,8 +271,8 @@ function terrain_generate() -> void {
# The height field for shaders that place things on the ground (model.vert's u_ground)
function terrain_bind_height(p: int) -> void {
r3d_bind_2d(p, "u_ts_height", 5, ter_height_tex)
u_f(gl_uniform(p, "u_ts_half"), fi(TERRAIN_HALF))
u_f2(gl_uniform(p, "u_ts_origin"), ter_ox, ter_oz)
u_f(gpu_uniform(p, "u_ts_half"), fi(TERRAIN_HALF))
u_f2(gpu_uniform(p, "u_ts_origin"), ter_ox, ter_oz)
}
# Bake the height-field sun shadow (see tershadow.frag). Cheap enough to redo whenever
@ -286,12 +286,12 @@ function terrain_bake_shadow() -> void {
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)
gl_disable(GL_DEPTH_TEST)
gl_disable(GL_BLEND)
gpu_depth_test(false)
gpu_blend(false)
gl_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
u_f(gl_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_v3(gl_uniform(p, "u_sun"), sun_dir)
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()
@ -452,31 +452,31 @@ function terrain_bind_prog(p: int) -> void {
r3d_bind_2d(p, "u_ortho", 4, orthotex)
var oon = F_ZERO
if ter_ortho_tex != 0 { oon = F_ONE }
u_f(gl_uniform(p, "u_ortho_on"), oon)
u_f(gpu_uniform(p, "u_ortho_on"), oon)
r3d_bind_2d(p, "u_rock_d", 7, ter_tex[6]); r3d_bind_2d(p, "u_rock_n", 8, ter_tex[7]); r3d_bind_2d(p, "u_rock_a", 9, ter_tex[8])
r3d_bind_2d(p, "u_snow_d", 10, ter_tex[9])
if ter_carpet != 0 { r3d_bind_2d(p, "u_carpet", 11, ter_carpet); u_f(gl_uniform(p, "u_carpet_on"), F_ONE) }
else { r3d_bind_2d(p, "u_carpet", 11, ter_tex[0]); u_f(gl_uniform(p, "u_carpet_on"), F_ZERO) }
u_f(gl_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_f(gl_uniform(p, "u_texel"), fr(1, TERRAIN_RES))
u_f(gl_uniform(p, "u_snow_line"), ter_snow_line)
if ter_carpet != 0 { r3d_bind_2d(p, "u_carpet", 11, ter_carpet); u_f(gpu_uniform(p, "u_carpet_on"), F_ONE) }
else { r3d_bind_2d(p, "u_carpet", 11, ter_tex[0]); u_f(gpu_uniform(p, "u_carpet_on"), F_ZERO) }
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_f(gpu_uniform(p, "u_texel"), fr(1, TERRAIN_RES))
u_f(gpu_uniform(p, "u_snow_line"), ter_snow_line)
var lake = fl(-100000.0)
if ter_lake_ex != 0 { lake = ter_lake_level }
u_f(gl_uniform(p, "u_lake_level"), lake)
u_f(gpu_uniform(p, "u_lake_level"), lake)
# the shoreline, forest and scree gates read the sea; unset it is what they always read
var sea = lake
if ter_sea_set { sea = ter_sea_level }
u_f(gl_uniform(p, "u_sea_level"), sea)
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_f2(gl_uniform(p, "u_origin"), ter_ox, ter_oz)
u_f(gl_uniform(p, "u_far_split"), ter_far_split)
u_f(gl_uniform(p, "u_far_band"), ter_far_band)
u_f(gpu_uniform(p, "u_sea_level"), sea)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_f2(gpu_uniform(p, "u_origin"), ter_ox, ter_oz)
u_f(gpu_uniform(p, "u_far_split"), ter_far_split)
u_f(gpu_uniform(p, "u_far_band"), ter_far_band)
sky_bind_lighting(p)
shadow_bind(p)
fog_bind(p)
u_v3(gl_uniform(p, "u_cam_pos"), cam_pos)
u_f(gl_uniform(p, "u_grid"), fi(CD_G))
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
u_f(gpu_uniform(p, "u_grid"), fi(CD_G))
# 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.
@ -523,24 +523,24 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target {
let st = gl_check_framebuffer_status(GL_FRAMEBUFFER)
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: sun-visibility framebuffer incomplete {st}`) }
}
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_depth_mask(1)
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)
let p = ter_sun_prog
gl_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
u_f(gl_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_f2(gl_uniform(p, "u_origin"), ter_ox, ter_oz)
u_v3(gl_uniform(p, "u_cam_pos"), cam_pos)
u_f(gl_uniform(p, "u_grid"), fi(CD_G))
u_f(gl_uniform(p, "u_far_split"), ter_far_split)
u_f(gl_uniform(p, "u_far_band"), ter_far_band)
u_f(gl_uniform(p, "u_clip_y"), r3d_clip_y)
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_f2(gpu_uniform(p, "u_origin"), ter_ox, ter_oz)
u_v3(gpu_uniform(p, "u_cam_pos"), cam_pos)
u_f(gpu_uniform(p, "u_grid"), fi(CD_G))
u_f(gpu_uniform(p, "u_far_split"), ter_far_split)
u_f(gpu_uniform(p, "u_far_band"), ter_far_band)
u_f(gpu_uniform(p, "u_clip_y"), r3d_clip_y)
shadow_bind(p)
sky_bind_lighting(p)
ter_sun_pass = true
@ -575,11 +575,11 @@ function terrain_draw() -> void {
target_bind(post_hdr)
if post_ms_fbo != 0 { gl_bind_framebuffer(GL_FRAMEBUFFER, post_ms_fbo) }
}
gl_enable(GL_DEPTH_TEST)
gpu_depth_test(true)
# the prepass already laid this geometry's depth down: only the frontmost fragment of
# each pixel has anything to shade, and it meets that depth exactly
gl_depth_func(GL_LEQUAL)
gl_depth_mask(1)
gpu_depth_func(GL_LEQUAL)
gpu_depth_write(true)
terrain_bind_prog(ter_prog)
terrain_bind_prog(ter_prog_far)
terrain_bind_prog(ter_prog_near)
@ -591,7 +591,7 @@ function terrain_draw() -> void {
if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_LINE) }
cdlod_select(CD_LEVELS - 1, 0, 0)
if ter_wire { gl_polygon_mode(GL_FRONT_AND_BACK, GL_FILL) }
gl_depth_func(GL_LESS)
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})`) }
}
var ter_wire: bool = false
@ -709,10 +709,10 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void {
if ter_sun_pass {
let t = gl_scratch()
t[0] = x0; t[1] = z0; t[2] = size
gl_uniform3fv(gl_uniform(ter_sun_prog, "u_node"), 1, t)
u_v3(gpu_uniform(ter_sun_prog, "u_node"), t)
var st0 = F_ZERO
if level > 0 { st0 = cd_range[level - 1] }
u_f2(gl_uniform(ter_sun_prog, "u_morph"), f_lerp(st0, cd_range[level], fl(0.7)), cd_range[level])
u_f2(gpu_uniform(ter_sun_prog, "u_morph"), f_lerp(st0, cd_range[level], fl(0.7)), cd_range[level])
gl_draw_elements(GL_TRIANGLES, cd_mesh.count, GL_UNSIGNED_INT, null)
return
}
@ -730,11 +730,11 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void {
if p != ter_prog_cur { gl_use_program(p); ter_prog_cur = p }
let t = gl_scratch()
t[0] = x0; t[1] = z0; t[2] = size
gl_uniform3fv(gl_uniform(p, "u_node"), 1, t)
u_v3(gpu_uniform(p, "u_node"), t)
var start = F_ZERO
if level > 0 { start = cd_range[level - 1] }
start = f_lerp(start, cd_range[level], fl(0.7))
u_f2(gl_uniform(p, "u_morph"), start, cd_range[level])
u_f2(gpu_uniform(p, "u_morph"), start, cd_range[level])
gl_draw_elements(GL_TRIANGLES, cd_mesh.count, GL_UNSIGNED_INT, null)
cd_draws += 1
}

View file

@ -67,11 +67,11 @@ function water_reflection_pass() -> void {
v3_copy(cam_pos, eye)
r3d_clip_y = f_sub(water_level, fl(0.05))
target_bind(water_refl)
gl_enable(GL_DEPTH_TEST)
gl_depth_func(GL_LESS)
gl_depth_mask(1)
gl_enable(GL_CULL_FACE)
gl_cull_face(GL_FRONT) # the mirror flips the winding
gpu_depth_test(true)
gpu_depth_func(GL_LESS)
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)
sc_freeze = true
@ -84,7 +84,7 @@ function water_reflection_pass() -> void {
ter_reflect = false
sc_skip_blade = sb
sc_freeze = false
gl_cull_face(GL_BACK)
gpu_cull_face(GL_BACK)
if Os.has_env("R3D_DUMP_REFL") and not water_dumped { water_dumped = true; tex_dump(water_refl.color, water_refl.w, water_refl.h, "build/dbg_refl.ppm") }
# restore
r3d_clip_y = 0xCF000000
@ -138,14 +138,14 @@ function water_draw(depth_tex: int) -> void {
if not water_on or wb_n == 0 { return }
let p = water_prog
gl_use_program(p)
u_mat4(gl_uniform(p, "u_view"), cam_view)
u_mat4(gl_uniform(p, "u_proj"), cam_proj)
u_mat4(gl_uniform(p, "u_inv_vp"), cam_inv_vp)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_mat4(gpu_uniform(p, "u_inv_vp"), cam_inv_vp)
# gl_FragCoord here runs over the scene target, which is post_w x post_h — not the
# window. They are the same size only at a render scale of 1; at anything less, taking
# the window's size sent the refraction and depth reads into the wrong corner of the
# frame, and the lake showed a squashed copy of it instead of its own bed.
u_f2(gl_uniform(p, "u_screen"), fi(post_w), fi(post_h))
u_f2(gpu_uniform(p, "u_screen"), fi(post_w), fi(post_h))
var ron = F_ZERO
if water_refl != null and wb_primary >= 0 {
# bind on its own unit first: generating the mip chain re-binds the texture on the active unit,
@ -162,26 +162,26 @@ function water_draw(depth_tex: int) -> void {
# Opaque. The surface composites the refracted bed itself, so there is nothing for
# hardware blending to do — and an alpha was what left see-through gaps in the foam
# and a clear band at the shore wide enough to give the plane away.
gl_disable(GL_BLEND)
gl_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gpu_blend(false)
gpu_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
# the surface writes depth: the ambient-occlusion and temporal passes read the frame's depth,
# and the bed 9 m below the shore would otherwise darken a band along the water line
gl_depth_mask(1)
gl_disable(GL_CULL_FACE)
gpu_depth_write(true)
gpu_cull(false)
# every body is the same plane at its own level and bounds; only the mirrored one samples
# the reflection - another body reading it would show the wrong world upside down
for i in 0 .. wb_n {
u_f(gl_uniform(p, "u_level"), wb_level[i])
u_f2(gl_uniform(p, "u_center"), wb_cx[i], wb_cz[i])
u_f2(gl_uniform(p, "u_extent"), wb_ex[i], wb_ez[i])
u_f(gpu_uniform(p, "u_level"), wb_level[i])
u_f2(gpu_uniform(p, "u_center"), wb_cx[i], wb_cz[i])
u_f2(gpu_uniform(p, "u_extent"), wb_ex[i], wb_ez[i])
var r = F_ZERO
if i == wb_primary { r = ron }
u_f(gl_uniform(p, "u_refl_on"), r)
u_f(gpu_uniform(p, "u_refl_on"), r)
# every body but the mirrored sea is clipped to the ellipse inside its bounds
var clip = F_ONE
if i == wb_primary { clip = F_ZERO }
u_f(gl_uniform(p, "u_clip_ellipse"), clip)
u_f(gpu_uniform(p, "u_clip_ellipse"), clip)
mesh_draw(water_mesh)
}
gl_disable(GL_BLEND)
gpu_blend(false)
}