From c4226b36fe8340e89ff9191e73a3c77b878cea81 Mon Sep 17 00:00:00 2001 From: asrael Date: Wed, 4 Feb 2026 07:37:20 -0600 Subject: [PATCH] use tiled atlas texture sampling, increase shader speed using inv sqrt --- demo/mod/first_person3d.fnl | 6 +- demo/mod/menu.fnl | 16 +- pxl8.sh | 96 +++++- pxl8d/build.rs | 8 +- pxl8d/src/bsp.rs | 32 ++ pxl8d/src/math.rs | 39 ++- pxl8d/src/procgen.rs | 189 ++++++++++-- pxl8d/src/sim.rs | 14 + src/bsp/pxl8_bsp_render.c | 21 +- src/bsp/pxl8_bsp_render.h | 1 - src/core/pxl8_macros.h | 8 + src/gfx/pxl8_atlas.h | 4 +- src/gfx/pxl8_gfx.c | 121 ++++---- src/gfx/pxl8_gfx.h | 8 + src/gfx/pxl8_mesh.h | 1 - src/gfx/pxl8_render.c | 546 +++++++++++++++++++++++---------- src/gfx/pxl8_render.h | 30 +- src/gfx/pxl8_render_types.h | 32 +- src/gfx/pxl8_shader.h | 28 +- src/gfx/pxl8_shader_builtins.h | 64 ++-- src/gfx/pxl8_shader_registry.c | 4 +- src/gfx/shaders/cpu/lit.c | 62 ++-- src/gfx/shaders/cpu/unlit.c | 18 +- src/lua/pxl8.lua | 2 + src/lua/pxl8/gfx.lua | 29 +- src/lua/pxl8/world.lua | 4 - src/math/pxl8_math.c | 61 +++- src/math/pxl8_math.h | 74 ++--- src/math/pxl8_noise.c | 20 +- src/script/pxl8_script_ffi.h | 4 +- src/vxl/pxl8_vxl_render.c | 5 - src/vxl/pxl8_vxl_render.h | 3 +- src/world/pxl8_world.c | 14 - src/world/pxl8_world.h | 1 - 34 files changed, 1045 insertions(+), 520 deletions(-) diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index 85faee3..fefa7ec 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -353,10 +353,10 @@ (pxl8.push_target) (pxl8.begin_frame_3d camera lights { - :ambient 30 + :ambient 25 :fog_density 0.0 :celestial_dir [0.5 -0.8 0.3] - :celestial_intensity 0.5}) + :celestial_intensity 0.3}) (pxl8.clear_depth) (sky.update-gradient 1 2 6 6 10 18) @@ -364,7 +364,7 @@ (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt) (pxl8.clear_depth) - (world:set_wireframe (menu.is-wireframe)) + (pxl8.set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) (when chunk diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 214baf8..4219e13 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -136,18 +136,18 @@ (set selected-item nil))) (fn draw-gfx-panel [] - (pxl8.gui_window 200 100 240 280 "GFX") + (pxl8.gui_window 200 60 240 220 "GFX") (let [baked-label (if baked-lighting "Baked Lighting: On" "Baked Lighting: Off")] - (when (menu-button 40 215 147 210 30 baked-label) + (when (menu-button 40 215 107 210 24 baked-label) (set baked-lighting (not baked-lighting)))) (let [dynamic-label (if dynamic-lighting "Dynamic Lighting: On" "Dynamic Lighting: Off")] - (when (menu-button 41 215 182 210 30 dynamic-label) + (when (menu-button 41 215 134 210 24 dynamic-label) (set dynamic-lighting (not dynamic-lighting)))) - (pxl8.gui_label 215 220 (.. "Render: " render-distance) 15) - (let [(changed new-val) (gui:slider_int 30 215 235 210 16 render-distance 1 8)] + (pxl8.gui_label 215 162 (.. "Render: " render-distance) 15) + (let [(changed new-val) (gui:slider_int 30 215 175 210 14 render-distance 1 8)] (when changed (set render-distance new-val) (let [w (world-mod.World.get) @@ -156,14 +156,14 @@ (when n (n:set_chunk_settings new-val sim-distance))))) (let [tex-label (if textures "Textures: On" "Textures: Off")] - (when (menu-button 42 215 260 210 30 tex-label) + (when (menu-button 42 215 194 210 24 tex-label) (set textures (not textures)))) (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] - (when (menu-button 43 215 295 210 30 wire-label) + (when (menu-button 43 215 221 210 24 wire-label) (set wireframe (not wireframe)))) - (when (menu-button 32 215 340 210 30 "Back") + (when (menu-button 32 215 248 210 24 "Back") (set current-panel :main) (set selected-item nil))) diff --git a/pxl8.sh b/pxl8.sh index 28b7ea5..605e1eb 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -8,7 +8,7 @@ if command -v ccache >/dev/null 2>&1; then CC="ccache $CC" fi -CFLAGS="-std=c23 -Wall -Wextra" +CFLAGS="-std=c23 -Wall -Wextra -Wno-missing-braces" LIBS="-lm" MODE="debug" BUILDDIR=".build" @@ -170,8 +170,8 @@ compile_source_file() { compile_shaders() { local build_mode="$1" local shader_dir="src/gfx/shaders/cpu" - local so_dir=".build/shaders/cpu" - local obj_dir=".build/shaders/cpu/obj" + local so_dir=".build/$build_mode/shaders/cpu" + local obj_dir=".build/$build_mode/shaders/cpu/obj" if [[ ! -d "$shader_dir" ]]; then return 0 @@ -240,15 +240,17 @@ print_usage() { echo " clean Remove build artifacts" echo " help Show this help message" echo " install Install pxl8 to ~/.local/bin" + echo " profile Profile with perf and generate flamegraph (Linux)" echo " run Build and run pxl8 (optional: cart.pxc or folder)" echo " update Download/update all dependencies" echo " vendor Fetch source for dependencies (ex. SDL3)" echo echo -e "${BOLD}OPTIONS:${NC}" - echo " --all Clean both build artifacts and dependencies" - echo " --cache Clear ccache (use with clean)" - echo " --deps Clean only dependencies" - echo " --release Build/run/clean in release mode (default: debug)" + echo " --all Clean both build artifacts and dependencies" + echo " --cache Clear ccache (use with clean)" + echo " --deps Clean only dependencies" + echo " --duration=N Profile duration in seconds (default: 30)" + echo " --release Build/run/clean in release mode (default: debug)" } setup_sdl3() { @@ -324,6 +326,20 @@ update_fennel() { fi } +update_flamegraph() { + print_info "Fetching FlameGraph" + + if [[ -d "lib/FlameGraph/.git" ]]; then + cd lib/FlameGraph && git pull --quiet origin master + cd - > /dev/null + else + rm -rf lib/FlameGraph + git clone --quiet https://github.com/brendangregg/FlameGraph.git lib/FlameGraph + fi + + print_info "Updated FlameGraph" +} + update_linenoise() { print_info "Fetching linenoise" @@ -690,6 +706,72 @@ case "$COMMAND" in bash tools/aseprite/pxl8-ase.sh "$@" ;; + profile) + if [[ "$(uname)" != "Linux" ]]; then + print_error "Profiling with perf is only supported on Linux" + exit 1 + fi + + if ! command -v perf >/dev/null 2>&1; then + print_error "perf not found. Install linux-tools or perf package." + exit 1 + fi + + if [[ ! -d "lib/FlameGraph" ]]; then + mkdir -p lib + update_flamegraph + fi + + "$0" build || exit 1 + + PROFILE_DIR=".build/debug/profile" + mkdir -p "$PROFILE_DIR" + + CART="" + PERF_DURATION=30 + for arg in "$@"; do + if [[ "$arg" =~ ^--duration=([0-9]+)$ ]]; then + PERF_DURATION="${BASH_REMATCH[1]}" + elif [[ "$arg" != "--release" ]] && [[ -z "$CART" ]]; then + CART="$arg" + fi + done + + [[ -z "$CART" ]] && CART="demo" + + TIMESTAMP=$(date +"%Y%m%d_%H%M%S") + PERF_DATA="$PROFILE_DIR/perf_${TIMESTAMP}.data" + PERF_SCRIPT="$PROFILE_DIR/perf_${TIMESTAMP}.perf" + FOLDED="$PROFILE_DIR/perf_${TIMESTAMP}.folded" + SVG="$PROFILE_DIR/flamegraph_${TIMESTAMP}.svg" + + print_info "Starting server..." + ./bin/debug/pxl8d & + SERVER_PID=$! + sleep 0.5 + + trap "kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null" EXIT + + print_info "Profiling pxl8 for ${PERF_DURATION}s (Ctrl+C to stop early)..." + perf record -F 99 -g --call-graph dwarf -o "$PERF_DATA" -- \ + timeout "${PERF_DURATION}s" ./bin/debug/pxl8 "$CART" 2>/dev/null || true + + print_info "Processing profile data..." + perf script -i "$PERF_DATA" > "$PERF_SCRIPT" + + print_info "Generating flamegraph..." + lib/FlameGraph/stackcollapse-perf.pl "$PERF_SCRIPT" > "$FOLDED" + lib/FlameGraph/flamegraph.pl --cp --colors orange --title "pxl8 profile" "$FOLDED" > "$SVG" + + rm -f "$PERF_DATA" "$PERF_SCRIPT" "$FOLDED" + + print_info "Flamegraph: $SVG" + + if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$SVG" 2>/dev/null & + fi + ;; + help|--help|-h|"") print_usage ;; diff --git a/pxl8d/build.rs b/pxl8d/build.rs index 6b45bbf..2ff503d 100644 --- a/pxl8d/build.rs +++ b/pxl8d/build.rs @@ -36,9 +36,8 @@ fn main() { let bindings = bindgen::Builder::default() .header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap()) - .header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap()) - .header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap()) .header(pxl8_src.join("math/pxl8_noise.h").to_str().unwrap()) + .header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap()) .clang_arg(format!("-I{}", pxl8_src.join("bsp").display())) .clang_arg(format!("-I{}", pxl8_src.join("core").display())) .clang_arg(format!("-I{}", pxl8_src.join("math").display())) @@ -50,6 +49,11 @@ fn main() { .blocklist_item("FP_ZERO") .blocklist_item("FP_SUBNORMAL") .blocklist_item("FP_NORMAL") + .blocklist_type("pxl8_vec2") + .blocklist_type("pxl8_vec3") + .blocklist_type("pxl8_vec4") + .blocklist_type("pxl8_mat4") + .raw_line("pub use crate::math::{pxl8_vec2, pxl8_vec3, pxl8_vec4, pxl8_mat4};") .clang_arg("-DPXL8_NO_SIMD") .use_core() .rustified_enum(".*") diff --git a/pxl8d/src/bsp.rs b/pxl8d/src/bsp.rs index 0ef5fa0..8d36245 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -101,6 +101,38 @@ impl Default for CellPortals { } } +impl Clone for Face { + fn clone(&self) -> Self { + Self { + first_edge: self.first_edge, + lightmap_offset: self.lightmap_offset, + num_edges: self.num_edges, + plane_id: self.plane_id, + side: self.side, + styles: self.styles, + material_id: self.material_id, + aabb_min: self.aabb_min, + aabb_max: self.aabb_max, + } + } +} + +impl Clone for Plane { + fn clone(&self) -> Self { + Self { + dist: self.dist, + normal: self.normal, + type_: self.type_, + } + } +} + +impl Clone for Vertex { + fn clone(&self) -> Self { + Self { position: self.position } + } +} + pub struct Bsp { inner: pxl8_bsp, pub cell_portals: Box<[CellPortals]>, diff --git a/pxl8d/src/math.rs b/pxl8d/src/math.rs index ea2b272..250ec0f 100644 --- a/pxl8d/src/math.rs +++ b/pxl8d/src/math.rs @@ -1,8 +1,43 @@ use core::ops::{Add, Mul, Sub}; -use crate::pxl8::pxl8_vec3; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} -pub type Vec3 = pxl8_vec3; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec4 { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Mat4 { + pub m: [f32; 16], +} + +#[allow(non_camel_case_types)] +pub type pxl8_vec2 = Vec2; +#[allow(non_camel_case_types)] +pub type pxl8_vec3 = Vec3; +#[allow(non_camel_case_types)] +pub type pxl8_vec4 = Vec4; +#[allow(non_camel_case_types)] +pub type pxl8_mat4 = Mat4; pub const VEC3_ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; pub const VEC3_Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; diff --git a/pxl8d/src/procgen.rs b/pxl8d/src/procgen.rs index 0da79f0..1df3441 100644 --- a/pxl8d/src/procgen.rs +++ b/pxl8d/src/procgen.rs @@ -6,6 +6,7 @@ use libm::sqrtf; use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex}; use crate::math::{Vec3, Vec3Ext}; +use crate::pxl8::{pxl8_vec3_cross, pxl8_vec3_dot, pxl8_vec3_normalize, pxl8_vec3_scale, pxl8_vec3_add, pxl8_vec3_sub}; pub const CELL_SIZE: f32 = 64.0; pub const WALL_HEIGHT: f32 = 128.0; @@ -402,36 +403,139 @@ fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) { bsp.visdata = visdata; } +const AO_NUM_SAMPLES: usize = 16; +const AO_RAY_LENGTH: f32 = 48.0; + +fn generate_hemisphere_samples(normal: Vec3) -> [Vec3; AO_NUM_SAMPLES] { + let tangent = if normal.y.abs() < 0.9 { + unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(0.0, 1.0, 0.0))) } + } else { + unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(1.0, 0.0, 0.0))) } + }; + let bitangent = unsafe { pxl8_vec3_cross(normal, tangent) }; + + let mut samples = [Vec3::new(0.0, 0.0, 0.0); AO_NUM_SAMPLES]; + for i in 0..AO_NUM_SAMPLES { + let phi = (i as f32 / AO_NUM_SAMPLES as f32) * core::f32::consts::TAU; + let theta = ((i as f32 + 0.5) / AO_NUM_SAMPLES as f32) * (core::f32::consts::PI * 0.45); + let (sin_phi, cos_phi) = (libm::sinf(phi), libm::cosf(phi)); + let (sin_theta, cos_theta) = (libm::sinf(theta), libm::cosf(theta)); + + let local_x = sin_theta * cos_phi; + let local_y = cos_theta; + let local_z = sin_theta * sin_phi; + + unsafe { + let t_contrib = pxl8_vec3_scale(tangent, local_x); + let n_contrib = pxl8_vec3_scale(normal, local_y); + let b_contrib = pxl8_vec3_scale(bitangent, local_z); + samples[i] = pxl8_vec3_normalize(pxl8_vec3_add(pxl8_vec3_add(t_contrib, n_contrib), b_contrib)); + } + } + samples +} + +fn ray_triangle_intersect(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3, max_dist: f32) -> bool { + let edge1 = unsafe { pxl8_vec3_sub(v1, v0) }; + let edge2 = unsafe { pxl8_vec3_sub(v2, v0) }; + let h = unsafe { pxl8_vec3_cross(dir, edge2) }; + let a = unsafe { pxl8_vec3_dot(edge1, h) }; + + if a > -0.0001 && a < 0.0001 { + return false; + } + + let f = 1.0 / a; + let s = unsafe { pxl8_vec3_sub(origin, v0) }; + let u = f * unsafe { pxl8_vec3_dot(s, h) }; + if u < 0.0 || u > 1.0 { + return false; + } + + let q = unsafe { pxl8_vec3_cross(s, edge1) }; + let v = f * unsafe { pxl8_vec3_dot(dir, q) }; + if v < 0.0 || u + v > 1.0 { + return false; + } + + let t = f * unsafe { pxl8_vec3_dot(edge2, q) }; + t > 0.001 && t < max_dist +} + +fn compute_vertex_ao(bsp: &BspBuilder, pos: Vec3, normal: Vec3) -> f32 { + let samples = generate_hemisphere_samples(normal); + let offset_pos = unsafe { pxl8_vec3_add(pos, pxl8_vec3_scale(normal, 0.5)) }; + + let mut occluded = 0; + + for sample_dir in &samples { + 'face_loop: for face in &bsp.faces { + if face.num_edges < 3 { + continue; + } + + let mut verts = [Vec3::new(0.0, 0.0, 0.0); 4]; + let mut num_verts = 0usize; + + for e in 0..face.num_edges.min(4) { + let surfedge_idx = (face.first_edge + e as u32) as usize; + if surfedge_idx >= bsp.surfedges.len() { + continue; + } + + let edge_idx = bsp.surfedges[surfedge_idx]; + let vert_idx = if edge_idx >= 0 { + let ei = edge_idx as usize; + if ei >= bsp.edges.len() { continue; } + bsp.edges[ei].vertex[0] as usize + } else { + let ei = (-edge_idx) as usize; + if ei >= bsp.edges.len() { continue; } + bsp.edges[ei].vertex[1] as usize + }; + + if vert_idx < bsp.vertices.len() { + verts[num_verts] = bsp.vertices[vert_idx].position; + num_verts += 1; + } + } + + if num_verts >= 3 { + if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[1], verts[2], AO_RAY_LENGTH) { + occluded += 1; + break 'face_loop; + } + if num_verts == 4 { + if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[2], verts[3], AO_RAY_LENGTH) { + occluded += 1; + break 'face_loop; + } + } + } + } + } + + 1.0 - (occluded as f32 / AO_NUM_SAMPLES as f32) +} + fn compute_vertex_light( pos: Vec3, normal: Vec3, lights: &[LightSource], - ambient: f32, ) -> f32 { - let mut total = ambient; + let mut total = 0.0; for light in lights { - let to_light = Vec3::new( - light.position.x - pos.x, - light.position.y - pos.y, - light.position.z - pos.z, - ); - - let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z; - let dist = sqrtf(dist_sq).max(1.0); + let to_light = unsafe { pxl8_vec3_sub(light.position, pos) }; + let dist = unsafe { pxl8_vec3_dot(to_light, to_light) }; + let dist = sqrtf(dist).max(1.0); if dist > light.radius { continue; } - let inv_dist = 1.0 / dist; - let light_dir = Vec3::new( - to_light.x * inv_dist, - to_light.y * inv_dist, - to_light.z * inv_dist, - ); - - let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0); + let light_dir = unsafe { pxl8_vec3_normalize(to_light) }; + let ndotl = unsafe { pxl8_vec3_dot(normal, light_dir) }.max(0.0); let attenuation = (1.0 - dist / light.radius).max(0.0); let attenuation = attenuation * attenuation; @@ -442,12 +546,13 @@ fn compute_vertex_light( total.min(1.0) } -fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) { +fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) { if bsp.vertices.is_empty() { return; } bsp.vertex_lights = vec![0u32; bsp.vertices.len()]; + let mut vertex_normals: Vec> = vec![None; bsp.vertices.len()]; for f in 0..bsp.faces.len() { let face = &bsp.faces[f]; @@ -478,13 +583,27 @@ fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], amb continue; } - let pos = bsp.vertices[vert_idx].position; - let light = compute_vertex_light(pos, normal, lights, ambient); + vertex_normals[vert_idx] = Some(normal); - let light_byte = (light * 255.0) as u8; - bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF; + let pos = bsp.vertices[vert_idx].position; + let direct = compute_vertex_light(pos, normal, lights); + let direct_byte = ((direct * 255.0).min(255.0)) as u8; + + bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0x00FFFFFF) | ((direct_byte as u32) << 24); } } + + for vert_idx in 0..bsp.vertices.len() { + let normal = match vertex_normals[vert_idx] { + Some(n) => n, + None => continue, + }; + let pos = bsp.vertices[vert_idx].position; + let ao = compute_vertex_ao(bsp, pos, normal); + let ao_byte = ((ao * 255.0).min(255.0)) as u8; + + bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0xFF00FFFF) | ((ao_byte as u32) << 16); + } } fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { @@ -926,17 +1045,27 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; - let lights: Vec = rooms.iter().map(|room| { + let fireball_pos = Vec3::new(384.0, light_height, 324.0); + let fireball_exclusion_radius = 150.0; + + let lights: Vec = rooms.iter().filter_map(|room| { let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; - LightSource { - position: Vec3::new(cx, light_height, cy), - intensity: 0.8, - radius: 300.0, + let cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; + + let dx = cx - fireball_pos.x; + let dz = cz - fireball_pos.z; + if dx * dx + dz * dz < fireball_exclusion_radius * fireball_exclusion_radius { + return None; } + + Some(LightSource { + position: Vec3::new(cx, light_height, cz), + intensity: 1.8, + radius: 160.0, + }) }).collect(); - compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1); + compute_bsp_vertex_lighting(&mut bsp, &lights); bsp.into() } diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index e3dc5c1..e916861 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -28,6 +28,20 @@ impl Default for Entity { } } +impl Clone for Entity { + fn clone(&self) -> Self { + Self { + pos: self.pos, + vel: self.vel, + yaw: self.yaw, + pitch: self.pitch, + flags: self.flags, + kind: self.kind, + _pad: self._pad, + } + } +} + pub struct Simulation { pub entities: Vec, pub free_list: Vec, diff --git a/src/bsp/pxl8_bsp_render.c b/src/bsp/pxl8_bsp_render.c index 317e3fc..8afe551 100644 --- a/src/bsp/pxl8_bsp_render.c +++ b/src/bsp/pxl8_bsp_render.c @@ -118,7 +118,7 @@ static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const } static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state, - u32 face_id, pxl8_mesh* mesh) { + u32 face_id, pxl8_mesh* mesh, u8 ambient) { const pxl8_bsp_face* face = &bsp->faces[face_id]; if (face->num_edges < 3) return; @@ -179,7 +179,11 @@ static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_stat u8 light = 255; if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) { - light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; + u32 packed = bsp->vertex_lights[vert_idx]; + u8 direct = (packed >> 24) & 0xFF; + u8 ao = (packed >> 16) & 0xFF; + f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao; + light = (u8)(combined > 255.0f ? 255.0f : combined); } pxl8_vertex vtx = { @@ -230,7 +234,7 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_mesh* mesh = pxl8_mesh_create(64, 192); if (!mesh) return; - collect_face_to_mesh(bsp, NULL, face_id, mesh); + collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx)); if (mesh->index_count > 0) { pxl8_mat4 identity = pxl8_mat4_identity(); @@ -272,7 +276,7 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, } current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh); + collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); } if (mesh->index_count > 0 && current_material < state->num_materials) { @@ -395,7 +399,7 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, } current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh); + collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); } } @@ -445,10 +449,3 @@ void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const state->materials[material_id].u_offset = u_offset; state->materials[material_id].v_offset = v_offset; } - -void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe) { - if (!state || !state->materials) return; - for (u32 i = 0; i < state->num_materials; i++) { - state->materials[i].wireframe = wireframe; - } -} diff --git a/src/bsp/pxl8_bsp_render.h b/src/bsp/pxl8_bsp_render.h index 04b1cb3..3c4cb4e 100644 --- a/src/bsp/pxl8_bsp_render.h +++ b/src/bsp/pxl8_bsp_render.h @@ -23,7 +23,6 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material); void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material); -void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe); #ifdef __cplusplus } diff --git a/src/core/pxl8_macros.h b/src/core/pxl8_macros.h index ed3509d..f289461 100644 --- a/src/core/pxl8_macros.h +++ b/src/core/pxl8_macros.h @@ -2,6 +2,14 @@ #include +#if defined(__GNUC__) || defined(__clang__) +#define pxl8_likely(x) __builtin_expect(!!(x), 1) +#define pxl8_unlikely(x) __builtin_expect(!!(x), 0) +#else +#define pxl8_likely(x) (x) +#define pxl8_unlikely(x) (x) +#endif + #ifndef pxl8_min #define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) #endif diff --git a/src/gfx/pxl8_atlas.h b/src/gfx/pxl8_atlas.h index 2a429f9..94e0a95 100644 --- a/src/gfx/pxl8_atlas.h +++ b/src/gfx/pxl8_atlas.h @@ -6,10 +6,10 @@ typedef struct pxl8_atlas pxl8_atlas; typedef struct pxl8_atlas_entry { bool active; + i32 h, w, x, y; + u8 log2_h, log2_w; u32 texture_id; - i32 x, y, w, h; u32 tiled_base; - u8 log2_w; } pxl8_atlas_entry; static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) { diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index 3b9cd1b..1dadd12 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -34,16 +34,9 @@ typedef struct pxl8_target_entry { } pxl8_target_entry; #define PXL8_MAX_FRAME_RESOURCES 512 -#define PXL8_TEXTURE_CACHE_SIZE 256 #define PXL8_STREAM_VB_SIZE (256 * 1024) #define PXL8_STREAM_IB_SIZE (512 * 1024) -typedef struct pxl8_texture_cache_entry { - u32 texture_id; - pxl8_gfx_texture handle; - bool valid; -} pxl8_texture_cache_entry; - typedef struct pxl8_frame_resources { pxl8_gfx_texture textures[PXL8_MAX_FRAME_RESOURCES]; pxl8_gfx_buffer buffers[PXL8_MAX_FRAME_RESOURCES]; @@ -85,13 +78,13 @@ struct pxl8_gfx { pxl8_mat4 view_proj; pxl8_3d_frame frame; - pxl8_texture_cache_entry texture_cache[PXL8_TEXTURE_CACHE_SIZE]; pxl8_gfx_buffer stream_vb; pxl8_gfx_buffer stream_ib; u32 stream_vb_capacity; u32 stream_ib_capacity; u32 stream_vb_offset; u32 stream_ib_offset; + bool wireframe; }; pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { @@ -198,26 +191,6 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { return 0; } -static pxl8_gfx_texture texture_cache_lookup(pxl8_gfx* gfx, u32 texture_id) { - u32 slot = texture_id % PXL8_TEXTURE_CACHE_SIZE; - pxl8_texture_cache_entry* entry = &gfx->texture_cache[slot]; - if (entry->valid && entry->texture_id == texture_id) { - return entry->handle; - } - return (pxl8_gfx_texture){PXL8_GFX_INVALID_ID}; -} - -static void texture_cache_insert(pxl8_gfx* gfx, u32 texture_id, pxl8_gfx_texture handle) { - u32 slot = texture_id % PXL8_TEXTURE_CACHE_SIZE; - pxl8_texture_cache_entry* entry = &gfx->texture_cache[slot]; - if (entry->valid && entry->texture_id != texture_id) { - pxl8_destroy_texture(gfx->renderer, entry->handle); - } - entry->texture_id = texture_id; - entry->handle = handle; - entry->valid = true; -} - pxl8_gfx* pxl8_gfx_create( const pxl8_hal* hal, void* platform_data, @@ -496,6 +469,16 @@ void pxl8_gfx_present(pxl8_gfx* gfx) { gfx->hal->present(gfx->platform_data); } +void pxl8_gfx_reset_stats(pxl8_gfx* gfx) { + if (!gfx || !gfx->renderer) return; + pxl8_renderer_reset_stats(gfx->renderer); +} + +const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx) { + if (!gfx || !gfx->renderer) return NULL; + return pxl8_renderer_get_stats(gfx->renderer); +} + pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { pxl8_viewport vp = {0}; vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height); @@ -840,44 +823,7 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return; pxl8_frame_resources* res = &gfx->frame_res; - bool is_wireframe = material->wireframe; - - pxl8_gfx_texture tex_handle = (pxl8_gfx_texture){PXL8_GFX_INVALID_ID}; - if (!is_wireframe && material->texture_id != UINT32_MAX) { - tex_handle = texture_cache_lookup(gfx, material->texture_id); - - if (!pxl8_gfx_handle_valid(tex_handle)) { - const pxl8_atlas_entry* tex_entry = NULL; - if (gfx->atlas) { - tex_entry = pxl8_atlas_get_entry(gfx->atlas, material->texture_id); - } - - if (tex_entry && tex_entry->active) { - const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); - u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); - u8* tex_data = pxl8_calloc(tex_entry->w * tex_entry->h, 1); - if (tex_data) { - for (i32 row = 0; row < tex_entry->h; row++) { - memcpy(tex_data + row * tex_entry->w, - atlas_pixels + (tex_entry->y + row) * atlas_width + tex_entry->x, - tex_entry->w); - } - u32 tex_size = (u32)(tex_entry->w * tex_entry->h); - pxl8_gfx_texture_desc tex_desc = { - .width = (u32)tex_entry->w, - .height = (u32)tex_entry->h, - .format = PXL8_GFX_FORMAT_INDEXED8, - .data = { .ptr = tex_data, .size = tex_size }, - }; - tex_handle = pxl8_create_texture(gfx->renderer, &tex_desc); - pxl8_free(tex_data); - if (pxl8_gfx_handle_valid(tex_handle)) { - texture_cache_insert(gfx, material->texture_id, tex_handle); - } - } - } - } - } + bool is_wireframe = gfx->wireframe; const char* shader_name = "lit"; if (material->emissive || (!material->dynamic_lighting && !material->per_pixel)) { @@ -885,7 +831,13 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo } pxl8_gfx_pipeline_desc pipe_desc = { - .blend = { .enabled = false }, + .blend = { + .enabled = false, + .src = PXL8_GFX_BLEND_ONE, + .dst = PXL8_GFX_BLEND_ZERO, + .alpha_test = false, + .alpha_ref = 0, + }, .depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS }, .dither = material->dither, .double_sided = material->double_sided, @@ -893,15 +845,36 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo .rasterizer = { .cull = PXL8_GFX_CULL_BACK, .fill = is_wireframe ? PXL8_GFX_FILL_WIREFRAME : PXL8_GFX_FILL_SOLID }, .shader = pxl8_shader_registry_get(shader_name), }; + + switch (material->blend_mode) { + case PXL8_BLEND_ALPHA_TEST: + pipe_desc.blend.alpha_test = true; + pipe_desc.blend.alpha_ref = material->alpha; + break; + case PXL8_BLEND_ALPHA: + pipe_desc.blend.enabled = true; + pipe_desc.blend.src = PXL8_GFX_BLEND_SRC_ALPHA; + pipe_desc.blend.dst = PXL8_GFX_BLEND_INV_SRC_ALPHA; + break; + case PXL8_BLEND_ADDITIVE: + pipe_desc.blend.enabled = true; + pipe_desc.blend.src = PXL8_GFX_BLEND_ONE; + pipe_desc.blend.dst = PXL8_GFX_BLEND_ONE; + break; + case PXL8_BLEND_OPAQUE: + default: + break; + } pxl8_gfx_pipeline pipeline = pxl8_create_pipeline(gfx->renderer, &pipe_desc); if (res->pipeline_count < PXL8_MAX_FRAME_RESOURCES) { res->pipelines[res->pipeline_count++] = pipeline; } pxl8_gfx_bindings_desc bind_desc = { - .textures = { tex_handle }, + .atlas = gfx->atlas, .colormap = gfx->colormap, .palette = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL, + .texture_id = material->texture_id, }; pxl8_gfx_bindings bindings = pxl8_create_bindings(gfx->renderer, &bind_desc); if (res->bindings_count < PXL8_MAX_FRAME_RESOURCES) { @@ -1101,3 +1074,15 @@ u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) { u8 b = (abgr >> 16) & 0xFF; return pxl8_palette_find_closest(gfx->palette, r, g, b); } + +void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled) { + if (gfx) gfx->wireframe = enabled; +} + +bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx) { + return gfx ? gfx->wireframe : false; +} + +u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx) { + return gfx ? gfx->frame.uniforms.ambient : 0; +} diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index cd53b5c..8a09a33 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -10,6 +10,7 @@ #include "pxl8_gui_palette.h" typedef struct pxl8_gfx pxl8_gfx; +typedef struct pxl8_gfx_stats pxl8_gfx_stats; typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0, @@ -25,6 +26,8 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx); void pxl8_gfx_present(pxl8_gfx* gfx); void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt); void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); +void pxl8_gfx_reset_stats(pxl8_gfx* gfx); +const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx); u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color); @@ -62,6 +65,11 @@ void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx); u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b); u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index); +void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled); +bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx); + +u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx); + #ifdef __cplusplus } #endif diff --git a/src/gfx/pxl8_mesh.h b/src/gfx/pxl8_mesh.h index 57c5d15..ddc8b4f 100644 --- a/src/gfx/pxl8_mesh.h +++ b/src/gfx/pxl8_mesh.h @@ -33,7 +33,6 @@ typedef struct pxl8_gfx_material { bool dynamic_lighting; bool emissive; bool per_pixel; - bool wireframe; } pxl8_gfx_material; typedef struct pxl8_vertex { diff --git a/src/gfx/pxl8_render.c b/src/gfx/pxl8_render.c index 8be2f2d..3a5e355 100644 --- a/src/gfx/pxl8_render.c +++ b/src/gfx/pxl8_render.c @@ -2,6 +2,7 @@ #include "pxl8_atlas.h" #include "pxl8_colormap.h" #include "pxl8_dither.h" +#include "pxl8_hal.h" #include "pxl8_log.h" #include "pxl8_mem.h" #include "pxl8_mesh.h" @@ -11,6 +12,16 @@ #include #include +#if PXL8_GFX_ENABLE_STATS +#define STATS_INC(stats, field, val) do { (stats)->field += (val); } while (0) +#define STATS_START() pxl8_get_ticks_ns() +#define STATS_ADD(stats, field, start) do { (stats)->field += pxl8_get_ticks_ns() - (start); } while (0) +#else +#define STATS_INC(stats, field, val) do { (void)(stats); } while (0) +#define STATS_START() 0 +#define STATS_ADD(stats, field, start) do { (void)(stats); (void)(start); } while (0) +#endif + typedef struct { pxl8_vec4 clip_pos; pxl8_vec3 world_pos; @@ -31,25 +42,118 @@ typedef struct { i32 y_start, y_end; f32 inv_total; u32 target_width, target_height; + i32 clip_min_x, clip_min_y; + i32 clip_max_x, clip_max_y; } tri_setup; +static inline bool depth_test_pass(pxl8_gfx_compare_func func, u16 src, u16 dst) { + switch (func) { + case PXL8_GFX_COMPARE_NEVER: return false; + case PXL8_GFX_COMPARE_LESS: return src < dst; + case PXL8_GFX_COMPARE_EQUAL: return src == dst; + case PXL8_GFX_COMPARE_LEQUAL: return src <= dst; + case PXL8_GFX_COMPARE_GREATER: return src > dst; + case PXL8_GFX_COMPARE_NOTEQUAL: return src != dst; + case PXL8_GFX_COMPARE_GEQUAL: return src >= dst; + case PXL8_GFX_COMPARE_ALWAYS: return true; + } + return true; +} + +static inline f32 blend_factor_value(pxl8_gfx_blend_factor factor, f32 src_a, f32 dst_a) { + switch (factor) { + case PXL8_GFX_BLEND_ZERO: return 0.0f; + case PXL8_GFX_BLEND_ONE: return 1.0f; + case PXL8_GFX_BLEND_SRC_ALPHA: return src_a; + case PXL8_GFX_BLEND_INV_SRC_ALPHA: return 1.0f - src_a; + case PXL8_GFX_BLEND_DST_ALPHA: return dst_a; + case PXL8_GFX_BLEND_INV_DST_ALPHA: return 1.0f - dst_a; + } + return 1.0f; +} + +static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) { + if (!palette) return 0; + u8 best_idx = 1; + u32 best_dist = 0xFFFFFFFF; + for (u32 i = 1; i < 256; i++) { + u8 pr = palette[i] & 0xFF; + u8 pg = (palette[i] >> 8) & 0xFF; + u8 pb = (palette[i] >> 16) & 0xFF; + i32 dr = (i32)r - (i32)pr; + i32 dg = (i32)g - (i32)pg; + i32 db = (i32)b - (i32)pb; + u32 dist = (u32)(dr * dr + dg * dg + db * db); + if (dist < best_dist) { + best_dist = dist; + best_idx = (u8)i; + if (dist == 0) break; + } + } + return best_idx; +} + +static u8 blend_indexed( + const pxl8_gfx_pipeline_desc* pipeline, + u8 src, + u8 dst, + const u32* palette, + const u8* colormap +) { + (void)colormap; + if (!pipeline || !pipeline->blend.enabled) return src; + if (src == 0) return dst; + if (!palette) return src; + + f32 src_a = src == 0 ? 0.0f : 1.0f; + f32 dst_a = dst == 0 ? 0.0f : 1.0f; + f32 sf = blend_factor_value(pipeline->blend.src, src_a, dst_a); + f32 df = blend_factor_value(pipeline->blend.dst, src_a, dst_a); + + if (sf == 1.0f && df == 0.0f) return src; + if (sf == 0.0f && df == 1.0f) return dst; + + u8 sr = palette[src] & 0xFF; + u8 sg = (palette[src] >> 8) & 0xFF; + u8 sb = (palette[src] >> 16) & 0xFF; + + u8 dr = palette[dst] & 0xFF; + u8 dg = (palette[dst] >> 8) & 0xFF; + u8 db = (palette[dst] >> 16) & 0xFF; + + i32 out_r = (i32)(sr * sf + dr * df); + i32 out_g = (i32)(sg * sf + dg * df); + i32 out_b = (i32)(sb * sf + db * df); + + if (out_r < 0) out_r = 0; + if (out_g < 0) out_g = 0; + if (out_b < 0) out_b = 0; + if (out_r > 255) out_r = 255; + if (out_g > 255) out_g = 255; + if (out_b > 255) out_b = 255; + + return palette_find_closest(palette, (u8)out_r, (u8)out_g, (u8)out_b); +} + +static inline pxl8_vec4 vec4_lerp(pxl8_vec4 a, pxl8_vec4 b, f32 t) { + return (pxl8_vec4){ + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t, + a.w + (b.w - a.w) * t + }; +} + static raster_vertex lerp_raster_vertex(const raster_vertex* a, const raster_vertex* b, f32 t) { - raster_vertex out; - out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t; - out.clip_pos.y = a->clip_pos.y + (b->clip_pos.y - a->clip_pos.y) * t; - out.clip_pos.z = a->clip_pos.z + (b->clip_pos.z - a->clip_pos.z) * t; - out.clip_pos.w = a->clip_pos.w + (b->clip_pos.w - a->clip_pos.w) * t; - out.world_pos.x = a->world_pos.x + (b->world_pos.x - a->world_pos.x) * t; - out.world_pos.y = a->world_pos.y + (b->world_pos.y - a->world_pos.y) * t; - out.world_pos.z = a->world_pos.z + (b->world_pos.z - a->world_pos.z) * t; - out.normal.x = a->normal.x + (b->normal.x - a->normal.x) * t; - out.normal.y = a->normal.y + (b->normal.y - a->normal.y) * t; - out.normal.z = a->normal.z + (b->normal.z - a->normal.z) * t; - out.u = a->u + (b->u - a->u) * t; - out.v = a->v + (b->v - a->v) * t; - out.color = (u8)(a->color + (b->color - a->color) * t); - out.light = (u8)(a->light + (b->light - a->light) * t); - return out; + return (raster_vertex){ + .clip_pos = vec4_lerp(a->clip_pos, b->clip_pos, t), + .world_pos = pxl8_vec3_lerp(a->world_pos, b->world_pos, t), + .normal = pxl8_vec3_lerp(a->normal, b->normal, t), + .u = pxl8_lerp(a->u, b->u, t), + .v = pxl8_lerp(a->v, b->v, t), + .color = (u8)(a->color + (b->color - a->color) * t), + .light = (u8)(a->light + (b->light - a->light) * t), + }; } static i32 clip_triangle_near( @@ -101,26 +205,33 @@ static i32 clip_triangle_near( static bool setup_tri( tri_setup* setup, const raster_vertex* vo0, const raster_vertex* vo1, const raster_vertex* vo2, - u32 width, u32 height, bool double_sided + i32 viewport_x, i32 viewport_y, u32 viewport_w, u32 viewport_h, + i32 clip_min_x, i32 clip_min_y, i32 clip_max_x, i32 clip_max_y, + pxl8_gfx_cull_mode cull, bool double_sided ) { - f32 hw = (f32)width * 0.5f; - f32 hh = (f32)height * 0.5f; + if (viewport_w == 0 || viewport_h == 0) return false; - setup->p0.x = hw + vo0->clip_pos.x / vo0->clip_pos.w * hw; - setup->p0.y = hh - vo0->clip_pos.y / vo0->clip_pos.w * hh; + f32 hw = (f32)viewport_w * 0.5f; + f32 hh = (f32)viewport_h * 0.5f; + + setup->p0.x = (f32)viewport_x + hw + vo0->clip_pos.x / vo0->clip_pos.w * hw; + setup->p0.y = (f32)viewport_y + hh - vo0->clip_pos.y / vo0->clip_pos.w * hh; setup->p0.z = vo0->clip_pos.z / vo0->clip_pos.w; - setup->p1.x = hw + vo1->clip_pos.x / vo1->clip_pos.w * hw; - setup->p1.y = hh - vo1->clip_pos.y / vo1->clip_pos.w * hh; + setup->p1.x = (f32)viewport_x + hw + vo1->clip_pos.x / vo1->clip_pos.w * hw; + setup->p1.y = (f32)viewport_y + hh - vo1->clip_pos.y / vo1->clip_pos.w * hh; setup->p1.z = vo1->clip_pos.z / vo1->clip_pos.w; - setup->p2.x = hw + vo2->clip_pos.x / vo2->clip_pos.w * hw; - setup->p2.y = hh - vo2->clip_pos.y / vo2->clip_pos.w * hh; + setup->p2.x = (f32)viewport_x + hw + vo2->clip_pos.x / vo2->clip_pos.w * hw; + setup->p2.y = (f32)viewport_y + hh - vo2->clip_pos.y / vo2->clip_pos.w * hh; setup->p2.z = vo2->clip_pos.z / vo2->clip_pos.w; f32 cross = (setup->p1.x - setup->p0.x) * (setup->p2.y - setup->p0.y) - (setup->p1.y - setup->p0.y) * (setup->p2.x - setup->p0.x); - if (!double_sided && cross >= 0.0f) return false; + if (!double_sided) { + if (cull == PXL8_GFX_CULL_BACK && cross >= 0.0f) return false; + if (cull == PXL8_GFX_CULL_FRONT && cross <= 0.0f) return false; + } const raster_vertex* sorted[3] = {vo0, vo1, vo2}; @@ -142,8 +253,8 @@ static bool setup_tri( i32 y0_int = (i32)floorf(setup->p0.y); i32 y2_int = (i32)ceilf(setup->p2.y) - 1; - setup->y_start = y0_int < 0 ? 0 : y0_int; - setup->y_end = y2_int >= (i32)height ? (i32)height - 1 : y2_int; + setup->y_start = y0_int < clip_min_y ? clip_min_y : y0_int; + setup->y_end = y2_int > clip_max_y ? clip_max_y : y2_int; setup->w_recip.x = 1.0f / sorted[0]->clip_pos.w; setup->w_recip.y = 1.0f / sorted[1]->clip_pos.w; @@ -171,8 +282,12 @@ static bool setup_tri( setup->normal = sorted[0]->normal; setup->inv_total = 1.0f / total_height; - setup->target_width = width; - setup->target_height = height; + setup->target_width = viewport_w; + setup->target_height = viewport_h; + setup->clip_min_x = clip_min_x; + setup->clip_min_y = clip_min_y; + setup->clip_max_x = clip_max_x; + setup->clip_max_y = clip_max_y; return true; } @@ -184,11 +299,24 @@ static void rasterize_triangle( u32* light_accum, u32 fb_width, pxl8_shader_fn shader, + const pxl8_gfx_pipeline_desc* pipeline, const pxl8_shader_bindings* bindings, - const pxl8_shader_uniforms* uniforms + const pxl8_shader_uniforms* uniforms, + pxl8_gfx_stats* stats ) { const i32 SUBDIV = 16; + if (setup->y_start > setup->y_end) return; + + bool depth_test = pipeline && pipeline->depth.test; + bool depth_write = pipeline && pipeline->depth.write; + pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS; + bool alpha_test = pipeline && pipeline->blend.alpha_test; + u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0; + bool blend_enabled = pipeline && pipeline->blend.enabled; + const u32* palette = bindings ? bindings->palette : NULL; + const u8* colormap = bindings ? bindings->colormap : NULL; + for (i32 y = setup->y_start; y <= setup->y_end; y++) { f32 yf = (f32)y + 0.5f; f32 alpha = (yf - setup->p0.y) * setup->inv_total; @@ -265,8 +393,8 @@ static void rasterize_triangle( i32 x_start = (i32)floorf(x_start_fp); i32 x_end = (i32)ceilf(x_end_fp) - 1; - if (x_start < 0) x_start = 0; - if (x_end >= (i32)setup->target_width) x_end = (i32)setup->target_width - 1; + if (x_start < setup->clip_min_x) x_start = setup->clip_min_x; + if (x_end > setup->clip_max_x) x_end = setup->clip_max_x; if (x_start > x_end) continue; f32 span_width = x_end_fp - x_start_fp; @@ -313,18 +441,10 @@ static void rasterize_triangle( f32 u_end = (uw + duw * (f32)span_len) * pw_end; f32 v_end = (vw + dvw * (f32)span_len) * pw_end; - f32 l_start_fp = lw * pw_start; - f32 l_end_fp = (lw + dlw * (f32)span_len) * pw_end; - f32 c_start_fp = cw * pw_start; - f32 c_end_fp = (cw + dcw * (f32)span_len) * pw_end; - if (l_start_fp > 255.0f) l_start_fp = 255.0f; - if (l_start_fp < 0.0f) l_start_fp = 0.0f; - if (l_end_fp > 255.0f) l_end_fp = 255.0f; - if (l_end_fp < 0.0f) l_end_fp = 0.0f; - if (c_start_fp > 255.0f) c_start_fp = 255.0f; - if (c_start_fp < 0.0f) c_start_fp = 0.0f; - if (c_end_fp > 255.0f) c_end_fp = 255.0f; - if (c_end_fp < 0.0f) c_end_fp = 0.0f; + f32 l_start_fp = pxl8_clamp(lw * pw_start, 0.0f, 255.0f); + f32 l_end_fp = pxl8_clamp((lw + dlw * (f32)span_len) * pw_end, 0.0f, 255.0f); + f32 c_start_fp = pxl8_clamp(cw * pw_start, 0.0f, 255.0f); + f32 c_end_fp = pxl8_clamp((cw + dcw * (f32)span_len) * pw_end, 0.0f, 255.0f); f32 wx_start = wxw * pw_start; f32 wy_start = wyw * pw_start; @@ -352,32 +472,44 @@ static void rasterize_triangle( f32 wz_a = wz_start; for (i32 px = x; px <= span_end; px++) { - f32 depth_norm = (z_a + 1.0f) * 0.5f; - if (depth_norm < 0.0f) depth_norm = 0.0f; - if (depth_norm > 1.0f) depth_norm = 1.0f; + f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f); u16 z16 = (u16)(depth_norm * 65535.0f); - if (z16 < zrow[px]) { + STATS_INC(stats, depth_tests, 1); + bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]); + if (depth_pass) { + STATS_INC(stats, depth_passes, 1); pxl8_shader_ctx frag_ctx = { .x = px, .y = y, - .v_uv = { u_a, v_a }, + .v_uv = { { u_a, v_a } }, .v_world = { wx_a, wy_a, wz_a }, .v_normal = setup->normal, .v_light = l_a / 255.0f, .v_color = c_a, .v_depth = z_a, - .bindings = bindings, - .uniforms = uniforms, .out_light_color = 0, }; - u8 color = shader(&frag_ctx); - if (color != 0) { - prow[px] = color; - zrow[px] = z16; - if (lrow && frag_ctx.out_light_color != 0) { - lrow[px] = frag_ctx.out_light_color; + u8 color = shader(&frag_ctx, bindings, uniforms); + STATS_INC(stats, shader_calls, 1); + + if (!(alpha_test && color <= alpha_ref)) { + if (color != 0) { + u8 out_color = color; + if (blend_enabled) { + out_color = blend_indexed(pipeline, color, prow[px], palette, colormap); + } + + prow[px] = out_color; + if (depth_write) { + zrow[px] = z16; + } + STATS_INC(stats, pixels_written, 1); + if (lrow && frag_ctx.out_light_color != 0) { + lrow[px] = frag_ctx.out_light_color; + STATS_INC(stats, light_writes, 1); + } } } } @@ -406,9 +538,45 @@ static void rasterize_triangle( } } +static void draw_line_clipped( + u8* fb, + u32 fb_w, + u32 fb_h, + i32 x0, + i32 y0, + i32 x1, + i32 y1, + u8 color, + i32 clip_min_x, + i32 clip_min_y, + i32 clip_max_x, + i32 clip_max_y, + pxl8_gfx_stats* stats +) { + i32 dx = abs(x1 - x0); + i32 dy = -abs(y1 - y0); + i32 sx = x0 < x1 ? 1 : -1; + i32 sy = y0 < y1 ? 1 : -1; + i32 err = dx + dy; + + while (true) { + if (x0 >= clip_min_x && x0 <= clip_max_x && y0 >= clip_min_y && y0 <= clip_max_y) { + if (x0 >= 0 && y0 >= 0 && x0 < (i32)fb_w && y0 < (i32)fb_h) { + fb[y0 * (i32)fb_w + x0] = color; + STATS_INC(stats, pixels_written, 1); + } + } + if (x0 == x1 && y0 == y1) break; + i32 e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } +} + #define SLOT_INDEX(id) ((id) & 0xFFFF) #define SLOT_GEN(id) ((id) >> 16) #define MAKE_ID(index, gen) (((u32)(gen) << 16) | (u32)(index)) +#define NEXT_GEN(gen) ((u16)((gen) == 0xFFFF ? 1 : (gen) + 1)) typedef struct { pxl8_gfx_bindings_desc desc; @@ -432,18 +600,22 @@ typedef struct { bool active; } pass_slot; +#define PXL8_PIPELINE_CACHE_SIZE 64 + +typedef struct { + u32 hash; + u32 slot_idx; + u32 last_used_frame; + bool valid; +} pipeline_cache_entry; + typedef struct { pxl8_gfx_pipeline_desc desc; u16 generation; bool active; + bool cached; } pipeline_slot; -typedef struct { - pxl8_gfx_sampler_desc desc; - u16 generation; - bool active; -} sampler_slot; - typedef struct { void* data; u32 width; @@ -460,11 +632,13 @@ struct pxl8_renderer { texture_slot textures[PXL8_GFX_MAX_TEXTURES]; buffer_slot buffers[PXL8_GFX_MAX_BUFFERS]; - sampler_slot samplers[PXL8_GFX_MAX_SAMPLERS]; pipeline_slot pipelines[PXL8_GFX_MAX_PIPELINES]; bindings_slot bindings[PXL8_GFX_MAX_BINDINGS]; pass_slot passes[PXL8_GFX_MAX_PASSES]; + pipeline_cache_entry pipeline_cache[PXL8_PIPELINE_CACHE_SIZE]; + u32 frame_counter; + pxl8_gfx_pass current_pass; pxl8_gfx_pipeline current_pipeline; pxl8_gfx_bindings current_bindings; @@ -476,6 +650,7 @@ struct pxl8_renderer { u32 scissor_w, scissor_h; pxl8_shader_fn shader; + pxl8_gfx_stats stats; }; struct pxl8_gfx_cmdbuf { @@ -492,6 +667,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) { r->viewport_h = height; r->scissor_w = width; r->scissor_h = height; + pxl8_renderer_reset_stats(r); return r; } @@ -518,6 +694,15 @@ void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn) { if (r) r->shader = fn; } +void pxl8_renderer_reset_stats(pxl8_renderer* r) { + if (!r) return; + memset(&r->stats, 0, sizeof(r->stats)); +} + +const pxl8_gfx_stats* pxl8_renderer_get_stats(const pxl8_renderer* r) { + return r ? &r->stats : NULL; +} + static u32 texture_byte_size(pxl8_gfx_texture_format fmt, u32 w, u32 h) { switch (fmt) { case PXL8_GFX_FORMAT_INDEXED8: return w * h; @@ -542,7 +727,7 @@ pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_de s->height = desc->height; s->format = desc->format; s->usage = desc->usage; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; + s->generation = NEXT_GEN(s->generation); s->active = true; return (pxl8_gfx_texture){ MAKE_ID(i, s->generation) }; } @@ -569,7 +754,7 @@ pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* s->append_pos = 0; s->type = desc->type; s->usage = desc->usage; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; + s->generation = NEXT_GEN(s->generation); s->active = true; return (pxl8_gfx_buffer){ MAKE_ID(i, s->generation) }; } @@ -578,26 +763,12 @@ pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* return (pxl8_gfx_buffer){ PXL8_GFX_INVALID_ID }; } -pxl8_gfx_sampler pxl8_create_sampler(pxl8_renderer* r, const pxl8_gfx_sampler_desc* desc) { - for (u32 i = 0; i < PXL8_GFX_MAX_SAMPLERS; i++) { - if (!r->samplers[i].active) { - sampler_slot* s = &r->samplers[i]; - s->desc = *desc; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; - s->active = true; - return (pxl8_gfx_sampler){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of sampler slots"); - return (pxl8_gfx_sampler){ PXL8_GFX_INVALID_ID }; -} - pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc) { for (u32 i = 0; i < PXL8_GFX_MAX_PIPELINES; i++) { if (!r->pipelines[i].active) { pipeline_slot* s = &r->pipelines[i]; s->desc = *desc; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; + s->generation = NEXT_GEN(s->generation); s->active = true; return (pxl8_gfx_pipeline){ MAKE_ID(i, s->generation) }; } @@ -611,7 +782,7 @@ pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings if (!r->bindings[i].active) { bindings_slot* s = &r->bindings[i]; s->desc = *desc; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; + s->generation = NEXT_GEN(s->generation); s->active = true; return (pxl8_gfx_bindings){ MAKE_ID(i, s->generation) }; } @@ -625,7 +796,7 @@ pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc) if (!r->passes[i].active) { pass_slot* s = &r->passes[i]; s->desc = *desc; - s->generation = (s->generation + 1) ? s->generation + 1 : 1; + s->generation = NEXT_GEN(s->generation); s->active = true; return (pxl8_gfx_pass){ MAKE_ID(i, s->generation) }; } @@ -670,13 +841,6 @@ void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf) { s->active = false; } -void pxl8_destroy_sampler(pxl8_renderer* r, pxl8_gfx_sampler smp) { - u32 idx = SLOT_INDEX(smp.id); - if (idx < PXL8_GFX_MAX_SAMPLERS && r->samplers[idx].generation == SLOT_GEN(smp.id)) { - r->samplers[idx].active = false; - } -} - void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip) { u32 idx = SLOT_INDEX(pip.id); if (idx < PXL8_GFX_MAX_PIPELINES && r->pipelines[idx].generation == SLOT_GEN(pip.id)) { @@ -739,17 +903,17 @@ u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) { return r->buffers[SLOT_INDEX(buf.id)].size; } -void* pxl8_texture_ptr(pxl8_renderer* r, pxl8_gfx_texture tex) { +void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) { if (!VALID_TEX(r, tex)) return NULL; return r->textures[SLOT_INDEX(tex.id)].data; } -u32 pxl8_texture_width(pxl8_renderer* r, pxl8_gfx_texture tex) { +u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex) { if (!VALID_TEX(r, tex)) return 0; return r->textures[SLOT_INDEX(tex.id)].width; } -u32 pxl8_texture_height(pxl8_renderer* r, pxl8_gfx_texture tex) { +u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) { if (!VALID_TEX(r, tex)) return 0; return r->textures[SLOT_INDEX(tex.id)].height; } @@ -759,18 +923,6 @@ pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_textu return r->textures[SLOT_INDEX(tex.id)].format; } -void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) { - return pxl8_texture_ptr(r, tex); -} - -u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex) { - return pxl8_texture_width(r, tex); -} - -u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) { - return pxl8_texture_height(r, tex); -} - pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) { pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf)); cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd)); @@ -865,6 +1017,9 @@ static void execute_draw( if (!VALID_PASS(r, r->current_pass)) return; if (!VALID_PIPELINE(r, r->current_pipeline)) return; + u64 exec_start = STATS_START(); + STATS_INC(&r->stats, draw_calls, 1); + buffer_slot* vb = &r->buffers[SLOT_INDEX(cmd->vertex_buffer.id)]; buffer_slot* ib = use_indices ? &r->buffers[SLOT_INDEX(cmd->index_buffer.id)] : NULL; pass_slot* pass = &r->passes[SLOT_INDEX(r->current_pass.id)]; @@ -872,10 +1027,12 @@ static void execute_draw( if (!VALID_TEX(r, pass->desc.color.texture)) { pxl8_error("draw: invalid color texture"); + STATS_ADD(&r->stats, execute_draw_ns, exec_start); return; } if (!VALID_TEX(r, pass->desc.depth.texture)) { pxl8_error("draw: invalid depth texture"); + STATS_ADD(&r->stats, execute_draw_ns, exec_start); return; } @@ -887,6 +1044,42 @@ static void execute_draw( u32 fb_w = color_tex->width; u32 fb_h = color_tex->height; + if (r->viewport_w == 0 || r->viewport_h == 0) { + STATS_ADD(&r->stats, execute_draw_ns, exec_start); + return; + } + + i32 vp_x = r->viewport_x; + i32 vp_y = r->viewport_y; + u32 vp_w = r->viewport_w; + u32 vp_h = r->viewport_h; + + i32 clip_min_x = vp_x; + i32 clip_min_y = vp_y; + i32 clip_max_x = vp_x + (i32)vp_w - 1; + i32 clip_max_y = vp_y + (i32)vp_h - 1; + + if (r->scissor_w > 0 && r->scissor_h > 0) { + i32 sc_min_x = r->scissor_x; + i32 sc_min_y = r->scissor_y; + i32 sc_max_x = r->scissor_x + (i32)r->scissor_w - 1; + i32 sc_max_y = r->scissor_y + (i32)r->scissor_h - 1; + + if (sc_min_x > clip_min_x) clip_min_x = sc_min_x; + if (sc_min_y > clip_min_y) clip_min_y = sc_min_y; + if (sc_max_x < clip_max_x) clip_max_x = sc_max_x; + if (sc_max_y < clip_max_y) clip_max_y = sc_max_y; + } + + if (clip_min_x < 0) clip_min_x = 0; + if (clip_min_y < 0) clip_min_y = 0; + if (clip_max_x >= (i32)fb_w) clip_max_x = (i32)fb_w - 1; + if (clip_max_y >= (i32)fb_h) clip_max_y = (i32)fb_h - 1; + if (clip_min_x > clip_max_x || clip_min_y > clip_max_y) { + STATS_ADD(&r->stats, execute_draw_ns, exec_start); + return; + } + u32* light_accum = NULL; if (VALID_TEX(r, pass->desc.light_accum.texture)) { texture_slot* light_tex = &r->textures[SLOT_INDEX(pass->desc.light_accum.texture.id)]; @@ -901,7 +1094,10 @@ static void execute_draw( f32 near = 0.1f; pxl8_shader_fn shader = pip->desc.shader; - if (!shader) return; + if (!shader) { + STATS_ADD(&r->stats, execute_draw_ns, exec_start); + return; + } pxl8_shader_bindings shader_bindings = {0}; pxl8_shader_uniforms shader_uniforms = r->current_draw_params.shader; @@ -913,19 +1109,26 @@ static void execute_draw( shader_bindings.colormap = (const u8*)bnd->desc.colormap; shader_bindings.palette = bnd->desc.palette; - pxl8_gfx_texture tex0 = bnd->desc.textures[0]; - if (tex0.id != PXL8_GFX_INVALID_ID && VALID_TEX(r, tex0)) { - texture_slot* ts = &r->textures[SLOT_INDEX(tex0.id)]; - shader_bindings.texture = ts->data; - shader_bindings.tex_width = ts->width; - shader_bindings.tex_height = ts->height; + const pxl8_atlas* atlas = bnd->desc.atlas; + if (atlas && bnd->desc.texture_id != UINT32_MAX) { + const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(atlas, bnd->desc.texture_id); + if (entry && entry->active) { + shader_bindings.atlas = pxl8_atlas_get_pixels_tiled(atlas); + shader_bindings.width = (u32)entry->w; + shader_bindings.height = (u32)entry->h; + shader_bindings.use_tiled = true; + shader_bindings.tiled.base = entry->tiled_base; + shader_bindings.tiled.log2_w = entry->log2_w; + } } } bool double_sided = pip->desc.double_sided; + pxl8_gfx_cull_mode cull_mode = pip->desc.rasterizer.cull; bool is_wireframe = pip->desc.rasterizer.fill == PXL8_GFX_FILL_WIREFRAME; for (u32 i = cmd->first_index; i < cmd->first_index + cmd->index_count; i += 3) { + STATS_INC(&r->stats, triangles, 1); u16 i0, i1, i2; if (use_indices) { if (i + 2 >= ib->size / sizeof(u16)) break; @@ -956,65 +1159,98 @@ static void execute_draw( rv1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1); rv2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2); - pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p0); - pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p1); - pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p2); - rv0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; - rv1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; - rv2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; + if (!is_wireframe) { + pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p0); + pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p1); + pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(r->current_draw_params.model, p2); + rv0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; + rv1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; + rv2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; - rv0.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v0->normal)); - rv1.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v1->normal)); - rv2.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v2->normal)); + rv0.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v0->normal)); + rv1.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v1->normal)); + rv2.normal = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(r->current_draw_params.model, v2->normal)); - rv0.u = v0->u; rv0.v = v0->v; - rv1.u = v1->u; rv1.v = v1->v; - rv2.u = v2->u; rv2.v = v2->v; + rv0.u = v0->u; rv0.v = v0->v; + rv1.u = v1->u; rv1.v = v1->v; + rv2.u = v2->u; rv2.v = v2->v; - rv0.color = v0->color; - rv1.color = v1->color; - rv2.color = v2->color; + rv0.color = v0->color; + rv1.color = v1->color; + rv2.color = v2->color; - rv0.light = v0->light; - rv1.light = v1->light; - rv2.light = v2->light; + rv0.light = v0->light; + rv1.light = v1->light; + rv2.light = v2->light; + } else { + rv0.world_pos = (pxl8_vec3){0, 0, 0}; + rv1.world_pos = (pxl8_vec3){0, 0, 0}; + rv2.world_pos = (pxl8_vec3){0, 0, 0}; + rv0.normal = (pxl8_vec3){0, 0, 0}; + rv1.normal = (pxl8_vec3){0, 0, 0}; + rv2.normal = (pxl8_vec3){0, 0, 0}; + rv0.u = 0.0f; rv0.v = 0.0f; + rv1.u = 0.0f; rv1.v = 0.0f; + rv2.u = 0.0f; rv2.v = 0.0f; + rv0.color = 0; rv1.color = 0; rv2.color = 0; + rv0.light = 0; rv1.light = 0; rv2.light = 0; + } raster_vertex clipped[6]; i32 clipped_count = clip_triangle_near(&rv0, &rv1, &rv2, near, clipped); for (i32 t = 0; t < clipped_count; t += 3) { + STATS_INC(&r->stats, clipped_triangles, 1); if (is_wireframe) { - f32 hw = (f32)fb_w * 0.5f; - f32 hh = (f32)fb_h * 0.5f; + f32 hw = (f32)vp_w * 0.5f; + f32 hh = (f32)vp_h * 0.5f; raster_vertex* wv0 = &clipped[t]; raster_vertex* wv1 = &clipped[t+1]; raster_vertex* wv2 = &clipped[t+2]; - i32 sx0 = (i32)(hw + wv0->clip_pos.x / wv0->clip_pos.w * hw); - i32 sy0 = (i32)(hh - wv0->clip_pos.y / wv0->clip_pos.w * hh); - i32 sx1 = (i32)(hw + wv1->clip_pos.x / wv1->clip_pos.w * hw); - i32 sy1 = (i32)(hh - wv1->clip_pos.y / wv1->clip_pos.w * hh); - i32 sx2 = (i32)(hw + wv2->clip_pos.x / wv2->clip_pos.w * hw); - i32 sy2 = (i32)(hh - wv2->clip_pos.y / wv2->clip_pos.w * hh); + i32 sx0 = (i32)((f32)vp_x + hw + wv0->clip_pos.x / wv0->clip_pos.w * hw); + i32 sy0 = (i32)((f32)vp_y + hh - wv0->clip_pos.y / wv0->clip_pos.w * hh); + i32 sx1 = (i32)((f32)vp_x + hw + wv1->clip_pos.x / wv1->clip_pos.w * hw); + i32 sy1 = (i32)((f32)vp_y + hh - wv1->clip_pos.y / wv1->clip_pos.w * hh); + i32 sx2 = (i32)((f32)vp_x + hw + wv2->clip_pos.x / wv2->clip_pos.w * hw); + i32 sy2 = (i32)((f32)vp_y + hh - wv2->clip_pos.y / wv2->clip_pos.w * hh); + + f32 cross = (f32)(sx1 - sx0) * (f32)(sy2 - sy0) - (f32)(sy1 - sy0) * (f32)(sx2 - sx0); + if (!double_sided) { + if (cull_mode == PXL8_GFX_CULL_BACK && cross >= 0.0f) continue; + if (cull_mode == PXL8_GFX_CULL_FRONT && cross <= 0.0f) continue; + } u8 wire_color = v0->color ? v0->color : 15; - pxl8_draw_line(r, pass->desc.color.texture, sx0, sy0, sx1, sy1, wire_color); - pxl8_draw_line(r, pass->desc.color.texture, sx1, sy1, sx2, sy2, wire_color); - pxl8_draw_line(r, pass->desc.color.texture, sx2, sy2, sx0, sy0, wire_color); + draw_line_clipped(fb, fb_w, fb_h, sx0, sy0, sx1, sy1, wire_color, + clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats); + draw_line_clipped(fb, fb_w, fb_h, sx1, sy1, sx2, sy2, wire_color, + clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats); + draw_line_clipped(fb, fb_w, fb_h, sx2, sy2, sx0, sy0, wire_color, + clip_min_x, clip_min_y, clip_max_x, clip_max_y, &r->stats); } else { tri_setup setup; - if (!setup_tri(&setup, &clipped[t], &clipped[t+1], &clipped[t+2], fb_w, fb_h, double_sided)) { + if (!setup_tri(&setup, &clipped[t], &clipped[t+1], &clipped[t+2], + vp_x, vp_y, vp_w, vp_h, + clip_min_x, clip_min_y, clip_max_x, clip_max_y, + cull_mode, double_sided)) { continue; } - rasterize_triangle(&setup, fb, zb, light_accum, fb_w, shader, &shader_bindings, &shader_uniforms); + u64 raster_start = STATS_START(); + rasterize_triangle(&setup, fb, zb, light_accum, fb_w, shader, &pip->desc, + &shader_bindings, &shader_uniforms, &r->stats); + STATS_ADD(&r->stats, raster_ns, raster_start); } } } + + STATS_ADD(&r->stats, execute_draw_ns, exec_start); } -void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) { +void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) { + u64 submit_start = STATS_START(); for (u32 i = 0; i < cb->count; i++) { pxl8_gfx_cmd* cmd = &cb->commands[i]; switch (cmd->type) { @@ -1070,10 +1306,7 @@ void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) { r->buffers[i].append_pos = 0; } } -} - -void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) { - pxl8_submit(r, cb); + STATS_ADD(&r->stats, submit_ns, submit_start); } void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color) { @@ -1221,8 +1454,8 @@ void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i3 } } -void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, - const u32* palette, u32* output) { +void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, + const u32* palette, u32* output) { if (!VALID_TEX(r, color)) return; texture_slot* cs = &r->textures[SLOT_INDEX(color.id)]; @@ -1256,9 +1489,9 @@ void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture lig bg += (i32)((f32)(lg - 128) * t * 2.0f); bb += (i32)((f32)(lb - 128) * t * 2.0f); - if (br < 0) br = 0; if (br > 255) br = 255; - if (bg < 0) bg = 0; if (bg > 255) bg = 255; - if (bb < 0) bb = 0; if (bb > 255) bb = 255; + br = pxl8_clamp_byte(br); + bg = pxl8_clamp_byte(bg); + bb = pxl8_clamp_byte(bb); base = (u32)br | ((u32)bg << 8) | ((u32)bb << 16) | 0xFF000000; } @@ -1267,8 +1500,3 @@ void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture lig output[i] = base | 0xFF000000; } } - -void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, - const u32* palette, u32* output) { - pxl8_resolve(r, color, light_accum, palette, output); -} diff --git a/src/gfx/pxl8_render.h b/src/gfx/pxl8_render.h index 5786885..906def2 100644 --- a/src/gfx/pxl8_render.h +++ b/src/gfx/pxl8_render.h @@ -4,6 +4,10 @@ #include "pxl8_render_types.h" #include "pxl8_shader.h" +#ifndef PXL8_GFX_ENABLE_STATS +#define PXL8_GFX_ENABLE_STATS 1 +#endif + #ifdef __cplusplus extern "C" { #endif @@ -11,9 +15,25 @@ extern "C" { typedef struct pxl8_renderer pxl8_renderer; typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf; +typedef struct pxl8_gfx_stats { + u64 draw_calls; + u64 triangles; + u64 clipped_triangles; + u64 depth_tests; + u64 depth_passes; + u64 shader_calls; + u64 pixels_written; + u64 light_writes; + u64 submit_ns; + u64 execute_draw_ns; + u64 raster_ns; +} pxl8_gfx_stats; + pxl8_renderer* pxl8_renderer_create(u32 width, u32 height); void pxl8_renderer_destroy(pxl8_renderer* r); void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn); +void pxl8_renderer_reset_stats(pxl8_renderer* r); +const pxl8_gfx_stats* pxl8_renderer_get_stats(const pxl8_renderer* r); u32 pxl8_renderer_get_width(const pxl8_renderer* r); u32 pxl8_renderer_get_height(const pxl8_renderer* r); @@ -22,14 +42,12 @@ pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc); pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc); pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc); -pxl8_gfx_sampler pxl8_create_sampler(pxl8_renderer* r, const pxl8_gfx_sampler_desc* desc); pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_desc* desc); void pxl8_destroy_bindings(pxl8_renderer* r, pxl8_gfx_bindings bnd); void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf); void pxl8_destroy_pass(pxl8_renderer* r, pxl8_gfx_pass pass); void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip); -void pxl8_destroy_sampler(pxl8_renderer* r, pxl8_gfx_sampler smp); void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex); void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data); @@ -39,11 +57,8 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_ void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf); u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf); -void* pxl8_texture_ptr(pxl8_renderer* r, pxl8_gfx_texture tex); void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex); -u32 pxl8_texture_width(pxl8_renderer* r, pxl8_gfx_texture tex); u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex); -u32 pxl8_texture_height(pxl8_renderer* r, pxl8_gfx_texture tex); u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex); pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex); @@ -61,7 +76,6 @@ void pxl8_set_viewport(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h); void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32 first, u32 count, u32 base_vertex); -void pxl8_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb); void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb); void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color); @@ -76,10 +90,8 @@ void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color); void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color); -void pxl8_resolve(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, - const u32* palette, u32* output); void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, - const u32* palette, u32* output); + const u32* palette, u32* output); #ifdef __cplusplus } diff --git a/src/gfx/pxl8_render_types.h b/src/gfx/pxl8_render_types.h index 60f2ea9..d100c26 100644 --- a/src/gfx/pxl8_render_types.h +++ b/src/gfx/pxl8_render_types.h @@ -1,5 +1,6 @@ #pragma once +#include "pxl8_atlas.h" #include "pxl8_colormap.h" #include "pxl8_lights.h" #include "pxl8_math.h" @@ -10,17 +11,14 @@ extern "C" { #endif -#define PXL8_GFX_MAX_TEXTURES 256 -#define PXL8_GFX_MAX_BUFFERS 512 -#define PXL8_GFX_MAX_SAMPLERS 32 -#define PXL8_GFX_MAX_PIPELINES 128 #define PXL8_GFX_MAX_BINDINGS 256 +#define PXL8_GFX_MAX_BUFFERS 512 #define PXL8_GFX_MAX_PASSES 32 -#define PXL8_GFX_MAX_BOUND_TEXTURES 4 +#define PXL8_GFX_MAX_PIPELINES 128 +#define PXL8_GFX_MAX_TEXTURES 256 typedef struct { u32 id; } pxl8_gfx_buffer; typedef struct { u32 id; } pxl8_gfx_texture; -typedef struct { u32 id; } pxl8_gfx_sampler; typedef struct { u32 id; } pxl8_gfx_pipeline; typedef struct { u32 id; } pxl8_gfx_bindings; typedef struct { u32 id; } pxl8_gfx_pass; @@ -95,17 +93,6 @@ typedef enum pxl8_gfx_store_op { PXL8_GFX_STORE_DONT_CARE, } pxl8_gfx_store_op; -typedef enum pxl8_gfx_filter { - PXL8_GFX_FILTER_NEAREST, - PXL8_GFX_FILTER_LINEAR, -} pxl8_gfx_filter; - -typedef enum pxl8_gfx_wrap { - PXL8_GFX_WRAP_REPEAT, - PXL8_GFX_WRAP_CLAMP, - PXL8_GFX_WRAP_MIRROR, -} pxl8_gfx_wrap; - typedef struct pxl8_gfx_buffer_desc { pxl8_gfx_range data; u32 capacity; @@ -122,13 +109,6 @@ typedef struct pxl8_gfx_texture_desc { bool render_target; } pxl8_gfx_texture_desc; -typedef struct pxl8_gfx_sampler_desc { - pxl8_gfx_filter min_filter; - pxl8_gfx_filter mag_filter; - pxl8_gfx_wrap wrap_u; - pxl8_gfx_wrap wrap_v; -} pxl8_gfx_sampler_desc; - typedef pxl8_shader_fn pxl8_gfx_shader; typedef struct pxl8_gfx_pipeline_desc { @@ -159,10 +139,10 @@ typedef struct pxl8_gfx_pipeline_desc { } pxl8_gfx_pipeline_desc; typedef struct pxl8_gfx_bindings_desc { - pxl8_gfx_texture textures[PXL8_GFX_MAX_BOUND_TEXTURES]; - pxl8_gfx_sampler samplers[PXL8_GFX_MAX_BOUND_TEXTURES]; + const pxl8_atlas* atlas; const pxl8_colormap* colormap; const u32* palette; + u32 texture_id; } pxl8_gfx_bindings_desc; typedef struct pxl8_gfx_pass_color_attachment { diff --git a/src/gfx/pxl8_shader.h b/src/gfx/pxl8_shader.h index 8348df4..3241a21 100644 --- a/src/gfx/pxl8_shader.h +++ b/src/gfx/pxl8_shader.h @@ -39,34 +39,42 @@ typedef struct pxl8_shader_uniforms { u32 lights_count; bool textures; f32 time; - bool wireframe; } pxl8_shader_uniforms; typedef struct pxl8_shader_bindings { const u8* colormap; const u32* palette; - const u8* texture; - u32 tex_width; - u32 tex_height; + const u8* atlas; + u32 width, height; + bool use_tiled; + union { + struct { + u32 stride; + u32 x, y; + } linear; + struct { + u32 base; + u8 log2_w; + } tiled; + }; } pxl8_shader_bindings; typedef struct pxl8_shader_ctx { i32 x, y; - pxl8_vec2 v_uv; pxl8_vec3 v_world; pxl8_vec3 v_normal; f32 v_color; f32 v_light; f32 v_depth; - - const pxl8_shader_bindings* bindings; - const pxl8_shader_uniforms* uniforms; - u32 out_light_color; } pxl8_shader_ctx; -typedef u8 (*pxl8_shader_fn)(pxl8_shader_ctx* ctx); +typedef u8 (*pxl8_shader_fn)( + pxl8_shader_ctx* ctx, + const pxl8_shader_bindings* bindings, + const pxl8_shader_uniforms* uniforms +); #ifdef __cplusplus } diff --git a/src/gfx/pxl8_shader_builtins.h b/src/gfx/pxl8_shader_builtins.h index baaf693..abde7a5 100644 --- a/src/gfx/pxl8_shader_builtins.h +++ b/src/gfx/pxl8_shader_builtins.h @@ -5,28 +5,10 @@ #include "pxl8_shader.h" #include "pxl8_types.h" -#include - #ifdef __cplusplus extern "C" { #endif -static inline f32 pxl8_abs(f32 x) { return fabsf(x); } -static inline f32 pxl8_ceil(f32 x) { return ceilf(x); } -static inline f32 pxl8_cos(f32 x) { return cosf(x); } -static inline f32 pxl8_exp(f32 x) { return expf(x); } -static inline f32 pxl8_floor(f32 x) { return floorf(x); } -static inline f32 pxl8_log(f32 x) { return logf(x); } -static inline f32 pxl8_pow(f32 x, f32 y) { return powf(x, y); } -static inline f32 pxl8_sin(f32 x) { return sinf(x); } -static inline f32 pxl8_sqrt(f32 x) { return sqrtf(x); } -static inline f32 pxl8_tan(f32 x) { return tanf(x); } - -static inline f32 pxl8_min(f32 a, f32 b) { return a < b ? a : b; } -static inline f32 pxl8_max(f32 a, f32 b) { return a > b ? a : b; } -static inline f32 pxl8_clamp(f32 x, f32 lo, f32 hi) { return pxl8_min(pxl8_max(x, lo), hi); } -static inline f32 pxl8_lerp(f32 a, f32 b, f32 t) { return a + (b - a) * t; } - static inline pxl8_vec2 pxl8_swizzle_xy(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.y}; } static inline pxl8_vec2 pxl8_swizzle_xz(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.z}; } static inline pxl8_vec2 pxl8_swizzle_xw(pxl8_vec4 v) { return (pxl8_vec2){v.x, v.w}; } @@ -39,27 +21,39 @@ static inline pxl8_vec3 pxl8_swizzle_xyw(pxl8_vec4 v) { return (pxl8_vec3){v.x, static inline pxl8_vec3 pxl8_swizzle_xzw(pxl8_vec4 v) { return (pxl8_vec3){v.x, v.z, v.w}; } static inline pxl8_vec3 pxl8_swizzle_yzw(pxl8_vec4 v) { return (pxl8_vec3){v.y, v.z, v.w}; } -static inline u8 pxl8_sample_indexed(pxl8_shader_ctx* ctx, pxl8_vec2 uv) { - const u8* tex = ctx->bindings->texture; - if (!tex) return 0; - u32 w = ctx->bindings->tex_width; - u32 h = ctx->bindings->tex_height; - if (w == 0 || h == 0) return 0; - i32 tx = (i32)(uv.x * (f32)w) & (i32)(w - 1); - i32 ty = (i32)(uv.y * (f32)h) & (i32)(h - 1); - return tex[ty * w + tx]; +static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) { + u32 tile_y = v >> 3; + u32 tile_x = u >> 3; + u32 local_y = v & 7; + u32 local_x = u & 7; + return (tile_y << (log2_w + 3)) | (tile_x << 6) | (local_y << 3) | local_x; } -static inline u8 pxl8_colormap_lookup(pxl8_shader_ctx* ctx, u8 color, u8 light) { - const u8* cm = ctx->bindings->colormap; +static inline u8 pxl8_sample_indexed(const pxl8_shader_bindings* b, pxl8_vec2 uv) { + if (!b || !b->atlas) return 0; + u32 w = b->width; + u32 h = b->height; + if (w == 0 || h == 0) return 0; + u32 u = (u32)(uv.x * (f32)w) & (w - 1); + u32 v = (u32)(uv.y * (f32)h) & (h - 1); + if (b->use_tiled) { + return b->atlas[b->tiled.base + pxl8_tile_addr(u, v, b->tiled.log2_w)]; + } else { + return b->atlas[(b->linear.y + v) * b->linear.stride + b->linear.x + u]; + } +} + +static inline u8 pxl8_colormap_lookup(const pxl8_shader_bindings* b, u8 color, u8 light) { + if (!b) return color; + const u8* cm = b->colormap; if (!cm) return color; u32 row = light >> 5; return cm[(row << 8) | (u32)color]; } -static inline f32 pxl8_light_falloff(pxl8_shader_ctx* ctx, u32 light_idx) { - if (light_idx >= ctx->uniforms->lights_count) return 0.0f; - const pxl8_light* light = &ctx->uniforms->lights[light_idx]; +static inline f32 pxl8_light_falloff(const pxl8_shader_ctx* ctx, const pxl8_shader_uniforms* u, u32 light_idx) { + if (!u || light_idx >= u->lights_count) return 0.0f; + const pxl8_light* light = &u->lights[light_idx]; f32 dx = light->position.x - ctx->v_world.x; f32 dy = light->position.y - ctx->v_world.y; f32 dz = light->position.z - ctx->v_world.z; @@ -68,9 +62,9 @@ static inline f32 pxl8_light_falloff(pxl8_shader_ctx* ctx, u32 light_idx) { return 1.0f - dist_sq * light->inv_radius_sq; } -static inline u32 pxl8_light_color(pxl8_shader_ctx* ctx, u32 light_idx) { - if (light_idx >= ctx->uniforms->lights_count) return 0; - const pxl8_light* light = &ctx->uniforms->lights[light_idx]; +static inline u32 pxl8_light_color(const pxl8_shader_uniforms* u, u32 light_idx) { + if (!u || light_idx >= u->lights_count) return 0; + const pxl8_light* light = &u->lights[light_idx]; return (u32)light->r | ((u32)light->g << 8) | ((u32)light->b << 16); } diff --git a/src/gfx/pxl8_shader_registry.c b/src/gfx/pxl8_shader_registry.c index 02e09ef..370f183 100644 --- a/src/gfx/pxl8_shader_registry.c +++ b/src/gfx/pxl8_shader_registry.c @@ -2,8 +2,8 @@ #include -extern u8 pxl8_shader_lit(pxl8_shader_ctx* ctx); -extern u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx); +extern u8 pxl8_shader_lit(pxl8_shader_ctx* ctx, const pxl8_shader_bindings* bindings, const pxl8_shader_uniforms* uniforms); +extern u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx, const pxl8_shader_bindings* bindings, const pxl8_shader_uniforms* uniforms); void pxl8_shader_registry_init(void) {} void pxl8_shader_registry_reload(void) {} diff --git a/src/gfx/shaders/cpu/lit.c b/src/gfx/shaders/cpu/lit.c index 16480ff..b9d55c9 100644 --- a/src/gfx/shaders/cpu/lit.c +++ b/src/gfx/shaders/cpu/lit.c @@ -1,13 +1,18 @@ +#include "pxl8_macros.h" #include "pxl8_shader.h" #include "pxl8_shader_builtins.h" -u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { +u8 pxl8_shader_lit( + pxl8_shader_ctx* ctx, + const pxl8_shader_bindings* bindings, + const pxl8_shader_uniforms* uniforms +) { u8 tex_idx = 0; - if (ctx->bindings && ctx->bindings->texture) { - tex_idx = pxl8_sample_indexed(ctx, ctx->v_uv); - if (tex_idx == 0) return 0; + if (bindings && bindings->atlas) { + tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv); + if (pxl8_unlikely(tex_idx == 0)) return 0; } else { - if (ctx->uniforms && ctx->uniforms->dither) { + if (uniforms && uniforms->dither) { tex_idx = pxl8_gfx_dither(ctx->v_color, (u32)ctx->x, (u32)ctx->y); } else { f32 clamped = pxl8_clamp(ctx->v_color, 0.0f, 255.0f); @@ -17,24 +22,16 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { f32 light = ctx->v_light; - const pxl8_shader_uniforms* u = ctx->uniforms; - if (u) { - f32 ambient = (f32)u->ambient / 255.0f; + if (uniforms) { + f32 ambient = (f32)uniforms->ambient / 255.0f; if (ambient > light) light = ambient; - if (u->celestial_intensity > 0.0f) { - f32 dx = u->celestial_dir.x; - f32 dy = u->celestial_dir.y; - f32 dz = u->celestial_dir.z; - f32 len = pxl8_sqrt(dx * dx + dy * dy + dz * dz); - if (len > 0.0001f) { - dx /= len; - dy /= len; - dz /= len; - f32 ndotl = -(ctx->v_normal.x * dx + ctx->v_normal.y * dy + ctx->v_normal.z * dz); - if (ndotl > 0.0f) { - light += ndotl * u->celestial_intensity; - } + if (uniforms->celestial_intensity > 0.0f) { + f32 ndotl = -(ctx->v_normal.x * uniforms->celestial_dir.x + + ctx->v_normal.y * uniforms->celestial_dir.y + + ctx->v_normal.z * uniforms->celestial_dir.z); + if (ndotl > 0.0f) { + light += ndotl * uniforms->celestial_intensity; } } @@ -43,16 +40,15 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { f32 dyn_g = 0.0f; f32 dyn_b = 0.0f; - for (u32 i = 0; i < u->lights_count; i++) { - const pxl8_light* l = &u->lights[i]; + for (u32 i = 0; i < uniforms->lights_count; i++) { + const pxl8_light* l = &uniforms->lights[i]; f32 lx = l->position.x - ctx->v_world.x; f32 ly = l->position.y - ctx->v_world.y; f32 lz = l->position.z - ctx->v_world.z; f32 dist_sq = lx * lx + ly * ly + lz * lz; if (dist_sq >= l->radius_sq) continue; - f32 dist = pxl8_sqrt(dist_sq); - f32 inv_dist = dist > 0.0001f ? (1.0f / dist) : 0.0f; + f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq); f32 nx = lx * inv_dist; f32 ny = ly * inv_dist; f32 nz = lz * inv_dist; @@ -62,6 +58,10 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { f32 falloff = 1.0f - dist_sq * l->inv_radius_sq; if (falloff <= 0.0f) continue; + if (uniforms->dither && falloff < 0.33f) { + f32 threshold = (PXL8_BAYER_4X4[((u32)ctx->y & 3) * 4 + ((u32)ctx->x & 3)] + 0.5f) * (1.0f / 16.0f); + if (falloff < threshold * 0.33f) continue; + } f32 strength = ((f32)l->intensity / 255.0f) * falloff * ndotl; if (strength <= 0.0f) continue; @@ -73,7 +73,7 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { } if (dyn_strength > 0.0f) { - f32 inv = 1.0f / dyn_strength; + f32 inv = pxl8_fast_rcp(dyn_strength); u8 r = (u8)pxl8_clamp(dyn_r * inv, 0.0f, 255.0f); u8 g = (u8)pxl8_clamp(dyn_g * inv, 0.0f, 255.0f); u8 b = (u8)pxl8_clamp(dyn_b * inv, 0.0f, 255.0f); @@ -88,16 +88,16 @@ u8 pxl8_shader_lit(pxl8_shader_ctx* ctx) { f32 light_f = light * 255.0f; u8 light_u8 = (u8)light_f; - if (ctx->uniforms && ctx->uniforms->dither) { + if (uniforms && uniforms->dither) { light_u8 = pxl8_gfx_dither(light_f, (u32)ctx->x, (u32)ctx->y); } - u8 shaded = pxl8_colormap_lookup(ctx, tex_idx, light_u8); + u8 shaded = pxl8_colormap_lookup(bindings, tex_idx, light_u8); - if (ctx->uniforms && ctx->uniforms->emissive) { + if (uniforms && uniforms->emissive) { u32 rgb = 0x00FFFFFF; - if (ctx->bindings && ctx->bindings->palette) { - rgb = ctx->bindings->palette[tex_idx] & 0x00FFFFFF; + if (bindings && bindings->palette) { + rgb = bindings->palette[tex_idx] & 0x00FFFFFF; } pxl8_set_light_tint(ctx, rgb, 1.0f); } diff --git a/src/gfx/shaders/cpu/unlit.c b/src/gfx/shaders/cpu/unlit.c index a637b03..b3ff4c6 100644 --- a/src/gfx/shaders/cpu/unlit.c +++ b/src/gfx/shaders/cpu/unlit.c @@ -1,13 +1,17 @@ #include "pxl8_shader.h" #include "pxl8_shader_builtins.h" -u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx) { +u8 pxl8_shader_unlit( + pxl8_shader_ctx* ctx, + const pxl8_shader_bindings* bindings, + const pxl8_shader_uniforms* uniforms +) { u8 tex_idx = 0; - if (ctx->bindings && ctx->bindings->texture) { - tex_idx = pxl8_sample_indexed(ctx, ctx->v_uv); + if (bindings && bindings->atlas) { + tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv); if (tex_idx == 0) return 0; } else { - if (ctx->uniforms && ctx->uniforms->dither) { + if (uniforms && uniforms->dither) { tex_idx = pxl8_gfx_dither(ctx->v_color, (u32)ctx->x, (u32)ctx->y); } else { f32 clamped = pxl8_clamp(ctx->v_color, 0.0f, 255.0f); @@ -15,10 +19,10 @@ u8 pxl8_shader_unlit(pxl8_shader_ctx* ctx) { } } - if (ctx->uniforms && ctx->uniforms->emissive) { + if (uniforms && uniforms->emissive) { u32 rgb = 0x00FFFFFF; - if (ctx->bindings && ctx->bindings->palette) { - rgb = ctx->bindings->palette[tex_idx] & 0x00FFFFFF; + if (bindings && bindings->palette) { + rgb = bindings->palette[tex_idx] & 0x00FFFFFF; } pxl8_set_light_tint(ctx, rgb, 1.0f); } diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index a0a16d1..0f5c486 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -101,6 +101,8 @@ pxl8.draw_line_3d = gfx.draw_line_3d pxl8.draw_mesh = gfx.draw_mesh pxl8.end_frame_3d = gfx.end_frame_3d pxl8.project_points = gfx.project_points +pxl8.set_wireframe = gfx.set_wireframe +pxl8.get_wireframe = gfx.get_wireframe pxl8.Lights = effects.Lights pxl8.create_lights = effects.Lights.new diff --git a/src/lua/pxl8/gfx.lua b/src/lua/pxl8/gfx.lua index d1dfe98..64cbf87 100644 --- a/src/lua/pxl8/gfx.lua +++ b/src/lua/pxl8/gfx.lua @@ -258,7 +258,6 @@ function gfx.draw_mesh(mesh, opts) emissive = opts.emissive or false, per_pixel = opts.per_pixel or false, texture_id = opts.texture or 0xFFFFFFFF, - wireframe = opts.wireframe or false, }) C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) end @@ -272,15 +271,22 @@ function gfx.begin_frame_3d(camera, lights, uniforms) u.fog_density = uniforms.fog_density or 0.0 u.time = uniforms.time or 0.0 + local cx, cy, cz if uniforms.celestial_dir then - u.celestial_dir.x = uniforms.celestial_dir[1] or 0 - u.celestial_dir.y = uniforms.celestial_dir[2] or -1 - u.celestial_dir.z = uniforms.celestial_dir[3] or 0 + cx = uniforms.celestial_dir[1] or 0 + cy = uniforms.celestial_dir[2] or -1 + cz = uniforms.celestial_dir[3] or 0 else - u.celestial_dir.x = 0 - u.celestial_dir.y = -1 - u.celestial_dir.z = 0 + cx, cy, cz = 0, -1, 0 end + local len = math.sqrt(cx * cx + cy * cy + cz * cz) + if len > 0.0001 then + local inv = 1.0 / len + cx, cy, cz = cx * inv, cy * inv, cz * inv + end + u.celestial_dir.x = cx + u.celestial_dir.y = cy + u.celestial_dir.z = cz u.celestial_intensity = uniforms.celestial_intensity or 0.0 local lights_ptr = lights and lights._ptr or nil @@ -313,6 +319,14 @@ function gfx.create_vec3_array(count) return ffi.new("pxl8_vec3[?]", count) end +function gfx.set_wireframe(enabled) + C.pxl8_gfx_set_wireframe(core.gfx, enabled) +end + +function gfx.get_wireframe() + return C.pxl8_gfx_get_wireframe(core.gfx) +end + local Material = {} Material.__index = Material @@ -327,7 +341,6 @@ function Material.new(opts) emissive = opts.emissive or false, per_pixel = opts.per_pixel or false, texture_id = opts.texture or 0xFFFFFFFF, - wireframe = opts.wireframe or false, }) return setmetatable({ _ptr = mat }, Material) end diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index bb93c62..990e53b 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -117,10 +117,6 @@ function World:set_sim_distance(distance) C.pxl8_world_set_sim_distance(self._ptr, distance) end -function World:set_wireframe(enabled) - C.pxl8_world_set_wireframe(self._ptr, enabled) -end - function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius) local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) diff --git a/src/math/pxl8_math.c b/src/math/pxl8_math.c index 2efb8f5..fb605a4 100644 --- a/src/math/pxl8_math.c +++ b/src/math/pxl8_math.c @@ -1,5 +1,50 @@ #include "pxl8_math.h" +f32 pxl8_fast_inv_sqrt(f32 x) { +#if defined(PXL8_NO_SIMD) + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(f32*)&i; + x = x * (1.5f - half * x * x); + return x; +#elif defined(__x86_64__) || defined(_M_X64) + __m128 v = _mm_set_ss(x); + v = _mm_rsqrt_ss(v); + return _mm_cvtss_f32(v); +#elif defined(__aarch64__) || defined(_M_ARM64) + float32x2_t v = vdup_n_f32(x); + float32x2_t est = vrsqrte_f32(v); + est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est)); + return vget_lane_f32(est, 0); +#else + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(f32*)&i; + x = x * (1.5f - half * x * x); + return x; +#endif +} + +f32 pxl8_fast_rcp(f32 x) { +#if defined(PXL8_NO_SIMD) + return 1.0f / x; +#elif defined(__x86_64__) || defined(_M_X64) + __m128 v = _mm_set_ss(x); + __m128 rcp = _mm_rcp_ss(v); + rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp)))); + return _mm_cvtss_f32(rcp); +#elif defined(__aarch64__) || defined(_M_ARM64) + float32x2_t v = vdup_n_f32(x); + float32x2_t est = vrecpe_f32(v); + est = vmul_f32(est, vrecps_f32(v, est)); + return vget_lane_f32(est, 0); +#else + return 1.0f / x; +#endif +} + u32 pxl8_hash32(u32 x) { x ^= x >> 16; x *= 0x85EBCA6Bu; @@ -18,10 +63,6 @@ u64 pxl8_hash64(u64 x) { return x; } -f32 pxl8_lerp_f32(f32 a, f32 b, f32 t) { - return a + (b - a) * t; -} - f32 pxl8_smoothstep(f32 t) { return t * t * (3.0f - 2.0f * t); } @@ -56,11 +97,11 @@ f32 pxl8_vec2_length(pxl8_vec2 v) { } pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) { - f32 len = pxl8_vec2_length(v); + f32 len_sq = pxl8_vec2_dot(v, v); - if (len < 1e-6f) return (pxl8_vec2){0}; + if (len_sq < 1e-12f) return (pxl8_vec2){0}; - return pxl8_vec2_scale(v, 1.0f / len); + return pxl8_vec2_scale(v, pxl8_fast_inv_sqrt(len_sq)); } pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) { @@ -112,11 +153,11 @@ pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) { } pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) { - f32 len = pxl8_vec3_length(v); + f32 len_sq = pxl8_vec3_dot(v, v); - if (len < 1e-6f) return (pxl8_vec3){0}; + if (len_sq < 1e-12f) return (pxl8_vec3){0}; - return pxl8_vec3_scale(v, 1.0f / len); + return pxl8_vec3_scale(v, pxl8_fast_inv_sqrt(len_sq)); } pxl8_mat4 pxl8_mat4_identity(void) { diff --git a/src/math/pxl8_math.h b/src/math/pxl8_math.h index e5821a0..6cf314b 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -15,50 +15,25 @@ #define PXL8_PI 3.14159265358979323846f #define PXL8_TAU (PXL8_PI * 2.0f) -static inline f32 pxl8_fast_inv_sqrt(f32 x) { -#if defined(PXL8_NO_SIMD) - f32 half = 0.5f * x; - i32 i = *(i32*)&x; - i = 0x5f3759df - (i >> 1); - x = *(f32*)&i; - x = x * (1.5f - half * x * x); - return x; -#elif defined(__x86_64__) || defined(_M_X64) - __m128 v = _mm_set_ss(x); - v = _mm_rsqrt_ss(v); - return _mm_cvtss_f32(v); -#elif defined(__aarch64__) || defined(_M_ARM64) - float32x2_t v = vdup_n_f32(x); - float32x2_t est = vrsqrte_f32(v); - est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est)); - return vget_lane_f32(est, 0); -#else - f32 half = 0.5f * x; - i32 i = *(i32*)&x; - i = 0x5f3759df - (i >> 1); - x = *(f32*)&i; - x = x * (1.5f - half * x * x); - return x; -#endif -} +#define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) +#define pxl8_max(a, b) ((a) > (b) ? (a) : (b)) +#define pxl8_clamp(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x))) +#define pxl8_clamp_byte(x) pxl8_clamp(x, 0, 255) -static inline f32 pxl8_fast_rcp(f32 x) { -#if defined(PXL8_NO_SIMD) - return 1.0f / x; -#elif defined(__x86_64__) || defined(_M_X64) - __m128 v = _mm_set_ss(x); - __m128 rcp = _mm_rcp_ss(v); - rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp)))); - return _mm_cvtss_f32(rcp); -#elif defined(__aarch64__) || defined(_M_ARM64) - float32x2_t v = vdup_n_f32(x); - float32x2_t est = vrecpe_f32(v); - est = vmul_f32(est, vrecps_f32(v, est)); - return vget_lane_f32(est, 0); -#else - return 1.0f / x; -#endif -} +#define pxl8_abs(x) fabsf(x) +#define pxl8_ceil(x) ceilf(x) +#define pxl8_floor(x) floorf(x) +#define pxl8_sqrt(x) sqrtf(x) +#define pxl8_sin(x) sinf(x) +#define pxl8_cos(x) cosf(x) +#define pxl8_tan(x) tanf(x) +#define pxl8_exp(x) expf(x) +#define pxl8_log(x) logf(x) +#define pxl8_pow(x, y) powf(x, y) +#define pxl8_lerp(a, b, t) ((a) + ((b) - (a)) * (t)) + +f32 pxl8_fast_inv_sqrt(f32 x); +f32 pxl8_fast_rcp(f32 x); typedef union pxl8_vec2 { struct { f32 x, y; }; @@ -66,15 +41,17 @@ typedef union pxl8_vec2 { f32 v[2]; } pxl8_vec2; -typedef struct pxl8_vec3 { - f32 x, y, z; +typedef union pxl8_vec3 { + struct { f32 x, y, z; }; + f32 v[3]; } pxl8_vec3; -typedef struct pxl8_vec4 { - f32 x, y, z, w; +typedef union pxl8_vec4 { + struct { f32 x, y, z, w; }; + f32 v[4]; } pxl8_vec4; -typedef struct pxl8_mat4 { +typedef union pxl8_mat4 { f32 m[16]; } pxl8_mat4; @@ -108,7 +85,6 @@ extern "C" { u32 pxl8_hash32(u32 x); u64 pxl8_hash64(u64 x); -f32 pxl8_lerp_f32(f32 a, f32 b, f32 t); f32 pxl8_smoothstep(f32 t); pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b); diff --git a/src/math/pxl8_noise.c b/src/math/pxl8_noise.c index df3fa1f..b6e9ed9 100644 --- a/src/math/pxl8_noise.c +++ b/src/math/pxl8_noise.c @@ -24,9 +24,9 @@ f32 pxl8_value_noise(f32 x, f32 z, u64 seed) { f32 c01 = pxl8_noise2d(x0, z1, seed); f32 c11 = pxl8_noise2d(x1, z1, seed); - f32 a = pxl8_lerp_f32(c00, c10, tx); - f32 b = pxl8_lerp_f32(c01, c11, tx); - return pxl8_lerp_f32(a, b, tz); + f32 a = pxl8_lerp(c00, c10, tx); + f32 b = pxl8_lerp(c01, c11, tx); + return pxl8_lerp(a, b, tz); } f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) { @@ -50,15 +50,15 @@ f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) { f32 c011 = pxl8_noise3d(x0, y1, z1, seed); f32 c111 = pxl8_noise3d(x1, y1, z1, seed); - f32 a00 = pxl8_lerp_f32(c000, c100, tx); - f32 a10 = pxl8_lerp_f32(c010, c110, tx); - f32 a01 = pxl8_lerp_f32(c001, c101, tx); - f32 a11 = pxl8_lerp_f32(c011, c111, tx); + f32 a00 = pxl8_lerp(c000, c100, tx); + f32 a10 = pxl8_lerp(c010, c110, tx); + f32 a01 = pxl8_lerp(c001, c101, tx); + f32 a11 = pxl8_lerp(c011, c111, tx); - f32 b0 = pxl8_lerp_f32(a00, a10, ty); - f32 b1 = pxl8_lerp_f32(a01, a11, ty); + f32 b0 = pxl8_lerp(a00, a10, ty); + f32 b1 = pxl8_lerp(a01, a11, ty); - return pxl8_lerp_f32(b0, b1, tz); + return pxl8_lerp(b0, b1, tz); } f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves) { diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index 57f37a0..036da56 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -44,6 +44,8 @@ static const char* pxl8_ffi_cdefs = "void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);\n" "void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);\n" "i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n" +"void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled);\n" +"bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx);\n" "void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_clear(pxl8_gfx* ctx, u32 color);\n" @@ -272,7 +274,6 @@ static const char* pxl8_ffi_cdefs = " bool dynamic_lighting;\n" " bool emissive;\n" " bool per_pixel;\n" -" bool wireframe;\n" "} pxl8_gfx_material;\n" "\n" "typedef struct pxl8_vertex {\n" @@ -422,7 +423,6 @@ static const char* pxl8_ffi_cdefs = "pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" "void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n" -"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n" "i32 pxl8_world_get_render_distance(const pxl8_world* world);\n" "void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);\n" "i32 pxl8_world_get_sim_distance(const pxl8_world* world);\n" diff --git a/src/vxl/pxl8_vxl_render.c b/src/vxl/pxl8_vxl_render.c index a4ebbdc..06e5264 100644 --- a/src/vxl/pxl8_vxl_render.c +++ b/src/vxl/pxl8_vxl_render.c @@ -569,8 +569,3 @@ pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) { void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) { pxl8_free(state); } - -void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe) { - if (!state) return; - state->wireframe = wireframe; -} diff --git a/src/vxl/pxl8_vxl_render.h b/src/vxl/pxl8_vxl_render.h index 2762c46..6e5b6cf 100644 --- a/src/vxl/pxl8_vxl_render.h +++ b/src/vxl/pxl8_vxl_render.h @@ -9,12 +9,11 @@ extern "C" { #endif typedef struct pxl8_vxl_render_state { - bool wireframe; + u8 _unused; } pxl8_vxl_render_state; pxl8_vxl_render_state* pxl8_vxl_render_state_create(void); void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state); -void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe); typedef struct pxl8_vxl_mesh_config { bool ambient_occlusion; diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index 4b7cbcc..48913c0 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -332,12 +332,10 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); - bool wireframe = world->vxl_render_state && world->vxl_render_state->wireframe; pxl8_gfx_material mat = { .texture_id = 0, .dynamic_lighting = true, .alpha = 255, - .wireframe = wireframe, }; i32 r = world->render_distance; @@ -426,18 +424,6 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_ pxl8_bsp_set_material(world->bsp_render_state, material_id, material); } -void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) { - if (!world) return; - - ensure_bsp_render_state(world); - if (world->bsp_render_state) { - pxl8_bsp_set_wireframe(world->bsp_render_state, enabled); - } - if (world->vxl_render_state) { - pxl8_vxl_set_wireframe(world->vxl_render_state, enabled); - } -} - i32 pxl8_world_get_render_distance(const pxl8_world* world) { if (!world) return 3; return world->render_distance; diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index a9024d9..11f31fd 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -35,7 +35,6 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); void pxl8_world_sync(pxl8_world* world, pxl8_net* net); void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material); -void pxl8_world_set_wireframe(pxl8_world* world, bool enabled); i32 pxl8_world_get_render_distance(const pxl8_world* world); void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);