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:
parent
208cad7ca1
commit
54eeef5f8d
17 changed files with 652 additions and 312 deletions
26
changes/render3d-gpu-layer.md
Normal file
26
changes/render3d-gpu-layer.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
Vulkan for Ludic, and the start of a second renderer beside OpenGL.
|
||||
|
||||
- **`Vk.*`**: every command of Vulkan 1.0-1.4 and of the extensions a modern renderer is
|
||||
built around (swapchain, HDR colour spaces, ray query and acceleration structures, opacity
|
||||
micromaps, mesh shaders, variable rate shading, memory budget, pipeline libraries, NVIDIA
|
||||
low latency, portability for MoltenVK), with every constant and every struct's size
|
||||
(`<Struct>_sizeof`) and field offsets (`<Struct>_<field>`). Generated from the Vulkan
|
||||
registry by **`ludic-dev vkgen`** into `runtime/native/vk_api.ludic` and `vk_thunks.ll`;
|
||||
structs are plain memory filled by name with `Vk.put_i32` / `put_i64` / `put_ptr`. Every
|
||||
size and offset was checked against the SDK's C headers (3128 facts). A C float is float
|
||||
bits in an int; 64-bit values and non-dispatchable handles are `long`.
|
||||
- **The loader is opened at run time**, never linked: `vk_win.ll` (`vulkan-1.dll`) and
|
||||
`vk_mac.ll` (`libvulkan.1.dylib`, MoltenVK). `Vk.open()` returning 0 means no Vulkan, and
|
||||
the program carries on. `ludicc` and `ludic build` link both files for any program that
|
||||
uses `Vk.*`.
|
||||
- `examples/rendering/vk_probe.ludic` reports what a machine's Vulkan can do;
|
||||
`vk_compute.ludic` runs a Slang compute shader (`vk_compute.slang`) and reads the picture
|
||||
back - on an RTX 3070 Ti and on an M4 Pro through MoltenVK, clean under the validation layer.
|
||||
- **render3d `gpu.ludic`**: the seam between the renderer and a graphics API. Render state
|
||||
and uniforms go through `gpu_*` / `u_*` and nowhere else (OpenGL frames unchanged);
|
||||
`R3D_GFX=gl|vk` or `gpu_request` chooses a backend, falling back to OpenGL with a reason;
|
||||
`gpu_caps_probe()` detects, on Windows, the Vulkan 1.3 floor, ray tracing, mesh shaders,
|
||||
the NVIDIA RTX generation, Reflex and HDR, for a settings screen to grey out what a machine
|
||||
cannot use (`R3D_CAPS=rtx50|rtx40|rtx30|amd|intel|none` pretends, for tests).
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) ----
|
||||
|
|
|
|||
320
packages/ludic.render3d/gpu.ludic
Normal file
320
packages/ludic.render3d/gpu.ludic
Normal 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 }
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue