diff --git a/changes/render3d-gpu-layer.md b/changes/render3d-gpu-layer.md new file mode 100644 index 00000000..b88cc183 --- /dev/null +++ b/changes/render3d-gpu-layer.md @@ -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 + (`_sizeof`) and field offsets (`_`). 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). diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index bf414238..346cf200 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -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) { diff --git a/packages/ludic.render3d/daylight.ludic b/packages/ludic.render3d/daylight.ludic index 716debc3..9e4cfbf9 100644 --- a/packages/ludic.render3d/daylight.ludic +++ b/packages/ludic.render3d/daylight.ludic @@ -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) } diff --git a/packages/ludic.render3d/fmath.ludic b/packages/ludic.render3d/fmath.ludic index e5fd6af3..40f99d2c 100644 --- a/packages/ludic.render3d/fmath.ludic +++ b/packages/ludic.render3d/fmath.ludic @@ -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) ---- diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic new file mode 100644 index 00000000..9b06ca63 --- /dev/null +++ b/packages/ludic.render3d/gpu.ludic @@ -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 } diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index 81a8b6e1..11abfcdf 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -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) } diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index d15fc5d4..55623ebc 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -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 { diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index f888c3e2..f948f5dc 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -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) } diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 0dcef821..e285a457 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -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) } diff --git a/packages/ludic.render3d/r3d.ludic b/packages/ludic.render3d/r3d.ludic index 8dd59e71..dbece3f6 100644 --- a/packages/ludic.render3d/r3d.ludic +++ b/packages/ludic.render3d/r3d.ludic @@ -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" diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index c472f376..29f10fa0 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.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() } diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 5d89877b..be134e75 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -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) { diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index 2bf44cef..cd3852c5 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -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) } diff --git a/packages/ludic.render3d/skin.ludic b/packages/ludic.render3d/skin.ludic index a814d27f..c04003fc 100644 --- a/packages/ludic.render3d/skin.ludic +++ b/packages/ludic.render3d/skin.ludic @@ -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 diff --git a/packages/ludic.render3d/sky.ludic b/packages/ludic.render3d/sky.ludic index 3fc71805..8e52eeec 100644 --- a/packages/ludic.render3d/sky.ludic +++ b/packages/ludic.render3d/sky.ludic @@ -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) } diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index be1f5270..2d2ed12e 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -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 } diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index 05e8ec78..5fbee8be 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -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) }