diff --git a/demo/main.fnl b/demo/main.fnl index 9fe84cf..e4cc30c 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -107,6 +107,4 @@ (transition:render)) (when (menu.is-paused) - (pxl8.push_target) - (menu.draw) - (pxl8.pop_target)))) + (menu.draw)))) diff --git a/demo/mod/entities.fnl b/demo/mod/entities.fnl index 1a17c01..829957b 100644 --- a/demo/mod/entities.fnl +++ b/demo/mod/entities.fnl @@ -168,8 +168,9 @@ (fn render-fireball [x y z wireframe] (when fireball-mesh (pxl8.draw_mesh fireball-mesh {:x x :y y :z z - :emissive true - :wireframe wireframe}))) + :passthrough true + :wireframe wireframe + :emissive 1.0}))) {:FIREBALL_COLOR FIREBALL_COLOR :get-door-position get-door-position diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index fefa7ec..c231e0b 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -1,6 +1,7 @@ (local pxl8 (require :pxl8)) (local effects (require :pxl8.effects)) (local net (require :pxl8.net)) +(local shader (require :pxl8.shader)) (local colormap (require :mod.colormap)) (local entities (require :mod.entities)) @@ -123,7 +124,9 @@ (set lights (pxl8.create_lights))) (entities.init textures) - (sky.generate-stars) + + (sky.generate-stars 12345) + (preload) (when (not ceiling-tex) @@ -133,17 +136,22 @@ (when (not trim-tex) (set trim-tex (textures.wood-trim 77777 WOOD_COLOR))) (when (not wall-tex) - (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)))) + (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR))) + + ) (fn setup-materials [] - (when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex) + (when (and world (not bsp-materials-setup) ceiling-tex floor-tex trim-tex wall-tex) (let [chunk (world:active_chunk)] (when (and chunk (chunk:ready)) - (let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true}) - trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true}) - wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})] + (let [ceiling-mat (pxl8.create_material {:texture ceiling-tex}) + floor-mat (pxl8.create_material {:texture floor-tex :lighting true}) + trim-mat (pxl8.create_material {:texture trim-tex :lighting true}) + wall-mat (pxl8.create_material {:texture wall-tex :lighting true})] + (world:set_bsp_material 0 floor-mat) (world:set_bsp_material 1 wall-mat) + (world:set_bsp_material 2 ceiling-mat) (world:set_bsp_material 3 trim-mat) (set bsp-materials-setup true)))))) @@ -191,7 +199,7 @@ (when (> portal-cooldown 0) (set portal-cooldown (- portal-cooldown dt))) - (when (and world network (<= portal-cooldown 0)) + (when (and network (<= portal-cooldown 0)) (let [chunk (world:active_chunk) in-bsp (not= chunk nil) (door-x door-z) (entities.get-door-position) @@ -232,7 +240,7 @@ expecting-bsp (> chunk-id 0) voxel-space (and (not chunk) (= chunk-id 0)) ready (or voxel-space (and chunk (chunk:ready)))] - (when world + (when ready (let [input (sample-input) grid-max (if voxel-space 100000 (* grid-size chunk-size)) movement-yaw cam-yaw] @@ -314,14 +322,10 @@ voxel-space (and world (not chunk) (not expecting-bsp)) ready (or voxel-space (and chunk (chunk:ready)))] - (when (not camera) - (pxl8.text "camera nil" 5 30 12)) - (when (not world) - (pxl8.text "world nil" 5 40 12)) (when (and world (not ready)) - (pxl8.text (.. "Waiting... chunk=" (tostring chunk) " voxel=" (tostring voxel-space)) 5 50 12)) + (pxl8.text "Waiting for world data..." 5 30 12)) - (when (and camera world) + (when (and camera world ready) (let [bob-offset (* (math.sin bob-time) bob-amount) eye-y (+ cam-y player-eye-height bob-offset land-squash) forward-x (- (math.sin cam-yaw)) @@ -349,22 +353,19 @@ r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase))) light-radius (* 150 (+ 0.95 r1 r2))] (lights:clear) - (lights:add light-x light-y light-z 0xFFB888 light-intensity light-radius) - - (pxl8.push_target) + (lights:add light-x light-y light-z 0xFF8C32 light-intensity light-radius) (pxl8.begin_frame_3d camera lights { - :ambient 25 + :ambient 30 :fog_density 0.0 :celestial_dir [0.5 -0.8 0.3] - :celestial_intensity 0.3}) + :celestial_intensity 0.5}) (pxl8.clear_depth) (sky.update-gradient 1 2 6 6 10 18) (sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe)) - (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt) (pxl8.clear_depth) - (pxl8.set_wireframe (menu.is-wireframe)) + (world:set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) (when chunk @@ -372,10 +373,16 @@ (entities.render-door (menu.is-wireframe) (if voxel-space 128 0)) - (pxl8.end_frame_3d) - (pxl8.pop_target)) + (pxl8.end_frame_3d)) + + ;; TODO: shader needs to run at present time, not mid-frame + ;; (shader.begin_frame) + ;; (shader.run shader.presets.light_with_fog + ;; {:fog_r 6 :fog_g 6 :fog_b 12 + ;; :fog_start 0.1 :fog_end 0.85}) + + (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt) - (pxl8.push_target) (let [cx (/ (pxl8.get_width) 2) cy (/ (pxl8.get_height) 2) crosshair-size 4 @@ -387,8 +394,7 @@ (pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 text-color) (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," (string.format "%.0f" cam-y) "," - (string.format "%.0f" cam-z)) 5 15 text-color)) - (pxl8.pop_target))))) + (string.format "%.0f" cam-z)) 5 15 text-color)))))) {:preload preload :is-connected is-connected diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 4219e13..5bee354 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -4,6 +4,7 @@ (local net-mod (require :pxl8.net)) (var paused false) +(var wireframe false) (var gui nil) (var render-distance 3) (var sim-distance 4) @@ -12,11 +13,6 @@ (var current-items []) (var pending-action nil) -(var baked-lighting true) -(var dynamic-lighting true) -(var textures true) -(var wireframe false) - (fn init [] (set gui (pxl8.create_gui)) (let [w (world-mod.World.get)] @@ -104,20 +100,24 @@ (or clicked (and is-selected (= pending-action label)))))) (fn draw-main-menu [] - (pxl8.gui_window 200 80 240 200 "pxl8 demo") + (pxl8.gui_window 200 80 240 235 "pxl8 demo") (when (menu-button 1 215 127 210 30 "Resume") (hide)) - (when (menu-button 5 215 162 210 30 "GFX") + (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] + (when (menu-button 4 215 162 210 30 wire-label) + (set wireframe (not wireframe)))) + + (when (menu-button 5 215 197 210 30 "GFX") (set current-panel :gfx) (set selected-item nil)) - (when (menu-button 6 215 197 210 30 "SFX") + (when (menu-button 6 215 232 210 30 "SFX") (set current-panel :sfx) (set selected-item nil)) - (when (menu-button 2 215 232 210 30 "Quit") + (when (menu-button 2 215 267 210 30 "Quit") (pxl8.quit))) (fn draw-sfx-panel [] @@ -136,18 +136,10 @@ (set selected-item nil))) (fn draw-gfx-panel [] - (pxl8.gui_window 200 60 240 220 "GFX") + (pxl8.gui_window 200 100 240 180 "GFX") - (let [baked-label (if baked-lighting "Baked Lighting: On" "Baked Lighting: Off")] - (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 134 210 24 dynamic-label) - (set dynamic-lighting (not dynamic-lighting)))) - - (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)] + (pxl8.gui_label 215 150 (.. "Render: " render-distance) 15) + (let [(changed new-val) (gui:slider_int 30 215 165 210 16 render-distance 1 8)] (when changed (set render-distance new-val) (let [w (world-mod.World.get) @@ -155,15 +147,16 @@ (when w (w:set_render_distance new-val)) (when n (n:set_chunk_settings new-val sim-distance))))) - (let [tex-label (if textures "Textures: On" "Textures: Off")] - (when (menu-button 42 215 194 210 24 tex-label) - (set textures (not textures)))) + (pxl8.gui_label 215 190 (.. "Sim: " sim-distance) 15) + (let [(changed new-val) (gui:slider_int 31 215 205 210 16 sim-distance 1 8)] + (when changed + (set sim-distance new-val) + (let [w (world-mod.World.get) + n (net-mod.get)] + (when w (w:set_sim_distance new-val)) + (when n (n:set_chunk_settings render-distance new-val))))) - (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] - (when (menu-button 43 215 221 210 24 wire-label) - (set wireframe (not wireframe)))) - - (when (menu-button 32 215 248 210 24 "Back") + (when (menu-button 32 215 240 210 30 "Back") (set current-panel :main) (set selected-item nil))) @@ -185,9 +178,6 @@ {:init init :is-paused (fn [] paused) - :is-baked-lighting (fn [] baked-lighting) - :is-dynamic-lighting (fn [] dynamic-lighting) - :is-textures (fn [] textures) :is-wireframe (fn [] wireframe) :toggle toggle :show show diff --git a/demo/mod/sky.fnl b/demo/mod/sky.fnl index eb6d2d0..f1727fe 100644 --- a/demo/mod/sky.fnl +++ b/demo/mod/sky.fnl @@ -176,8 +176,7 @@ (set star-count (+ (length tiny-stars) (length random-stars))) (set star-directions (pxl8.create_vec3_array star-count)) - (when pxl8.create_glows - (set star-glows (pxl8.create_glows 10000))) + (set star-glows (pxl8.create_glows 10000)) (set star-projected (pxl8.create_vec3_array star-count)) (var idx 0) @@ -248,7 +247,7 @@ (fn render [cam-x cam-y cam-z wireframe] (when (not sky-mesh) (create-sky-dome)) (when sky-mesh - (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :wireframe wireframe}))) + (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true :wireframe wireframe}))) {:render render :render-stars render-stars diff --git a/pxl8.sh b/pxl8.sh index 605e1eb..8bc63c0 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 -Wno-missing-braces" +CFLAGS="-std=c23 -Wall -Wextra" LIBS="-lm" MODE="debug" BUILDDIR=".build" @@ -23,7 +23,6 @@ fi case "$(uname)" in Linux) LINKER_FLAGS="$LINKER_FLAGS -rdynamic" - LIBS="$LIBS -ldl" ;; Darwin) export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -d '.' -f 1)" @@ -33,7 +32,6 @@ case "$(uname)" in ;; *) LINKER_FLAGS="$LINKER_FLAGS -rdynamic" - LIBS="$LIBS -ldl" ;; esac @@ -61,12 +59,6 @@ build_luajit() { build_server() { local mode="$1" - local server_bin - if [[ "$mode" == "release" ]]; then - server_bin="pxl8d/target/release/pxl8d" - else - server_bin="pxl8d/target/debug/pxl8d" - fi if [[ -d "pxl8d" ]]; then print_info "Building pxl8d ($mode mode)" cd pxl8d @@ -78,8 +70,6 @@ build_server() { local status=$? cd - > /dev/null if [[ $status -eq 0 ]]; then - mkdir -p "$BINDIR" - cp "$server_bin" "$BINDIR/pxl8d" print_info "Built pxl8d" else print_error "pxl8d build failed" @@ -88,7 +78,14 @@ build_server() { } start_server() { - local server_bin="$BINDIR/pxl8d" + local mode="$1" + local server_bin + if [[ "$mode" == "release" ]]; then + server_bin="pxl8d/target/release/pxl8d" + else + server_bin="pxl8d/target/debug/pxl8d" + fi + print_info "Server mode: $mode, binary: $server_bin" if [[ -f "$server_bin" ]]; then print_info "Starting server..." ./$server_bin & @@ -97,7 +94,7 @@ start_server() { sleep 0.5 else print_error "pxl8d binary not found: $server_bin" - print_error "Build first with: ./pxl8.sh build" + print_error "Build pxl8d first with: cd pxl8d && cargo build" fi } @@ -131,17 +128,8 @@ build_sdl() { } prefix_output() { - local in_warning=false while IFS= read -r line; do if [[ "$line" == *": warning:"* ]] || [[ "$line" == *": note:"* ]]; then - in_warning=true - echo -e "${YELLOW}${BOLD}[$(timestamp) WARN]${NC} $line" >&2 - elif [[ "$line" == *": error:"* ]] || [[ "$line" == *": fatal error:"* ]]; then - in_warning=false - echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $line" >&2 - elif [[ "$line" =~ ^[0-9]+\ (warning|error)s?\ generated ]] || [[ -z "$line" ]]; then - echo -e "${YELLOW}${BOLD}[$(timestamp) WARN]${NC} $line" >&2 - elif $in_warning; then echo -e "${YELLOW}${BOLD}[$(timestamp) WARN]${NC} $line" >&2 else echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $line" >&2 @@ -167,54 +155,6 @@ compile_source_file() { fi } -compile_shaders() { - local build_mode="$1" - local shader_dir="src/gfx/shaders/cpu" - local so_dir=".build/$build_mode/shaders/cpu" - local obj_dir=".build/$build_mode/shaders/cpu/obj" - - if [[ ! -d "$shader_dir" ]]; then - return 0 - fi - - [[ "$build_mode" == "debug" ]] && mkdir -p "$so_dir" - mkdir -p "$obj_dir" - - local SHADER_INCLUDES="-Isrc/core -Isrc/gfx -Isrc/math" - case "$(uname)" in - Darwin) SO_EXT="dylib" ;; - *) SO_EXT="so" ;; - esac - - # Compile C shaders directly - for shader in $(find "$shader_dir" -maxdepth 1 -name "*.c" 2>/dev/null); do - local shader_name=$(basename "${shader%.c}") - local so_file="$so_dir/${shader_name}.$SO_EXT" - local obj_file="$obj_dir/${shader_name}.o" - - # Debug: compile to .so for hot-reload - if [[ "$build_mode" == "debug" ]]; then - if [[ "$shader" -nt "$so_file" ]] || [[ ! -f "$so_file" ]]; then - $CC -shared -fPIC -O2 $SHADER_INCLUDES "$shader" -o "$so_file" 2>&1 | prefix_output || { - print_error "Shader build failed: $shader_name" - } - fi - fi - - # Compile to .o for release static linking - if [[ "$shader" -nt "$obj_file" ]] || [[ ! -f "$obj_file" ]]; then - $CC -c -O2 $SHADER_INCLUDES "$shader" -o "$obj_file" 2>&1 | prefix_output || { - print_error "Shader compile failed: $shader_name" - } - fi - done - - # Export shader object files for linking - SHADER_OBJECTS="" - for obj in $(find "$obj_dir" -name "*.o" 2>/dev/null); do - SHADER_OBJECTS="$SHADER_OBJECTS $obj" - done -} make_lib_dirs() { mkdir -p lib/linenoise lib/fennel lib/miniz @@ -240,17 +180,15 @@ 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 " --duration=N Profile duration in seconds (default: 30)" - 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 " --release Build/run/clean in release mode (default: debug)" } setup_sdl3() { @@ -326,20 +264,6 @@ 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" @@ -404,7 +328,6 @@ update_sdl() { print_info "Updated SDL3" } - COMMAND="$1" shift || true @@ -476,7 +399,7 @@ case "$COMMAND" in print_info "Compiler cache: ccache enabled" fi - INCLUDES="-Isrc/asset -Isrc/bsp -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/shader -Isrc/sfx -Isrc/sim -Isrc/vxl -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz -I.build/shaders/c" + INCLUDES="-Isrc/asset -Isrc/bsp -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/sim -Isrc/vxl -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" @@ -501,12 +424,11 @@ case "$COMMAND" in src/gfx/pxl8_atlas.c src/gfx/pxl8_blit.c src/gfx/pxl8_colormap.c + src/gfx/pxl8_cpu.c src/gfx/pxl8_dither.c - src/gfx/pxl8_render.c - src/gfx/pxl8_shader_registry.c - src/gfx/pxl8_shader_runtime.c src/gfx/pxl8_font.c src/gfx/pxl8_gfx.c + src/gfx/pxl8_glows.c src/gfx/pxl8_lightmap.c src/gfx/pxl8_lights.c src/gfx/pxl8_mesh.c @@ -539,11 +461,11 @@ case "$COMMAND" in LUAJIT_LIB="lib/luajit/src/libluajit.a" OBJECT_DIR="$BUILDDIR/obj" mkdir -p "$OBJECT_DIR" - + OBJECTS="" NEED_LINK=false SOURCES_COMPILED="" - + for src_file in $LIB_SOURCE_FILES; do obj_name=$(basename "$src_file" .c).o obj_file="$OBJECT_DIR/$obj_name" @@ -555,9 +477,7 @@ case "$COMMAND" in SOURCES_COMPILED="yes" fi done - - compile_shaders "$MODE" - + for src_file in $PXL8_SOURCE_FILES; do obj_name=$(basename "$src_file" .c).o obj_file="$OBJECT_DIR/$obj_name" @@ -589,7 +509,7 @@ case "$COMMAND" in if [[ "$LUAJIT_LIB" -nt "$EXECUTABLE" ]] || [[ "$NEED_LINK" == true ]]; then print_info "Linking executable" - if ! $CC $LINKER_FLAGS $OBJECTS $SHADER_OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then + if ! $CC $LINKER_FLAGS $OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then print_error "Linking failed" exit 1 fi @@ -658,7 +578,7 @@ case "$COMMAND" in if [[ "$CLEAN_ALL" == true ]]; then print_info "Removing build artifacts and dependencies" - rm -rf "$BUILD_PATH" "$BIN_PATH" .build/shaders lib + rm -rf "$BUILD_PATH" "$BIN_PATH" lib clean_server print_info "Cleaned all" elif [[ "$CLEAN_DEPS" == true ]]; then @@ -667,7 +587,7 @@ case "$COMMAND" in print_info "Cleaned dependencies" else print_info "Removing build artifacts" - rm -rf "$BUILD_PATH" "$BIN_PATH" .build/shaders + rm -rf "$BUILD_PATH" "$BIN_PATH" clean_server print_info "Cleaned" fi @@ -706,72 +626,6 @@ 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 2ff503d..6b45bbf 100644 --- a/pxl8d/build.rs +++ b/pxl8d/build.rs @@ -36,8 +36,9 @@ fn main() { let bindings = bindgen::Builder::default() .header(pxl8_src.join("core/pxl8_log.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()) + .header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap()) + .header(pxl8_src.join("math/pxl8_noise.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())) @@ -49,11 +50,6 @@ 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 8d36245..0ef5fa0 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -101,38 +101,6 @@ 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 250ec0f..ea2b272 100644 --- a/pxl8d/src/math.rs +++ b/pxl8d/src/math.rs @@ -1,43 +1,8 @@ use core::ops::{Add, Mul, Sub}; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct Vec2 { - pub x: f32, - pub y: f32, -} +use crate::pxl8::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 type Vec3 = pxl8_vec3; 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 1df3441..0da79f0 100644 --- a/pxl8d/src/procgen.rs +++ b/pxl8d/src/procgen.rs @@ -6,7 +6,6 @@ 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; @@ -403,139 +402,36 @@ 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 = 0.0; + let mut total = ambient; for light in lights { - 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); + 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); if dist > light.radius { continue; } - let light_dir = unsafe { pxl8_vec3_normalize(to_light) }; - let ndotl = unsafe { pxl8_vec3_dot(normal, light_dir) }.max(0.0); + 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 attenuation = (1.0 - dist / light.radius).max(0.0); let attenuation = attenuation * attenuation; @@ -546,13 +442,12 @@ fn compute_vertex_light( total.min(1.0) } -fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) { +fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) { 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]; @@ -583,27 +478,13 @@ fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) { continue; } - vertex_normals[vert_idx] = Some(normal); - 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; + let light = compute_vertex_light(pos, normal, lights, ambient); - bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0x00FFFFFF) | ((direct_byte as u32) << 24); + let light_byte = (light * 255.0) as u8; + bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF; } } - - 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) { @@ -1045,27 +926,17 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; - 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 lights: Vec = rooms.iter().map(|room| { let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - 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; + 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, } - - Some(LightSource { - position: Vec3::new(cx, light_height, cz), - intensity: 1.8, - radius: 160.0, - }) }).collect(); - compute_bsp_vertex_lighting(&mut bsp, &lights); + compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1); bsp.into() } diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index e916861..e3dc5c1 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -28,20 +28,6 @@ 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/asset/pxl8_embed.h b/src/asset/pxl8_embed.h index b33b40d..7139fa4 100644 --- a/src/asset/pxl8_embed.h +++ b/src/asset/pxl8_embed.h @@ -27,8 +27,12 @@ static const char embed_pxl8_effects[] = { #embed "src/lua/pxl8/effects.lua" , 0 }; -static const char embed_pxl8_gfx[] = { -#embed "src/lua/pxl8/gfx.lua" +static const char embed_pxl8_gfx2d[] = { +#embed "src/lua/pxl8/gfx2d.lua" +, 0 }; + +static const char embed_pxl8_gfx3d[] = { +#embed "src/lua/pxl8/gfx3d.lua" , 0 }; static const char embed_pxl8_gui[] = { @@ -82,7 +86,8 @@ static const pxl8_embed pxl8_embeds[] = { PXL8_EMBED_ENTRY(embed_pxl8_bytes, "pxl8.bytes"), PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"), PXL8_EMBED_ENTRY(embed_pxl8_effects, "pxl8.effects"), - PXL8_EMBED_ENTRY(embed_pxl8_gfx, "pxl8.gfx"), + PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"), + PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"), PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"), PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"), PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"), diff --git a/src/bsp/pxl8_bsp_render.c b/src/bsp/pxl8_bsp_render.c index 8afe551..5f85a65 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, u8 ambient) { + u32 face_id, pxl8_mesh* mesh) { const pxl8_bsp_face* face = &bsp->faces[face_id]; if (face->num_edges < 3) return; @@ -179,11 +179,7 @@ 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) { - 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); + light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; } pxl8_vertex vtx = { @@ -234,7 +230,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, pxl8_gfx_get_ambient(gfx)); + collect_face_to_mesh(bsp, NULL, face_id, mesh); if (mesh->index_count > 0) { pxl8_mat4 identity = pxl8_mat4_identity(); @@ -246,8 +242,15 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_bsp_render_state* state, pxl8_vec3 camera_pos) { - if (!gfx || !bsp || !state || bsp->num_faces == 0) return; - if (!state->materials || state->num_materials == 0) return; + if (!gfx || !bsp || !state || bsp->num_faces == 0) { + return; + } + if (!bsp->cell_portals || bsp->num_cell_portals == 0) { + return; + } + if (!state->materials || state->num_materials == 0) { + return; + } const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx); @@ -256,38 +259,6 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return; - if (!bsp->cell_portals || bsp->num_cell_portals == 0) { - pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); - if (!mesh) return; - - u32 current_material = 0xFFFFFFFF; - - for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) { - if (!face_in_frustum(bsp, face_id, frustum)) continue; - - const pxl8_bsp_face* face = &bsp->faces[face_id]; - u32 material_id = face->material_id; - if (material_id >= state->num_materials) continue; - - if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - pxl8_mesh_clear(mesh); - } - - current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); - } - - if (mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - } - - pxl8_mesh_destroy(mesh); - return; - } - if (!state->render_face_flags && state->num_faces > 0) { state->render_face_flags = pxl8_calloc(state->num_faces, 1); if (!state->render_face_flags) return; @@ -399,7 +370,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, pxl8_gfx_get_ambient(gfx)); + collect_face_to_mesh(bsp, state, face_id, mesh); } } @@ -449,3 +420,10 @@ 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 3c4cb4e..04b1cb3 100644 --- a/src/bsp/pxl8_bsp_render.h +++ b/src/bsp/pxl8_bsp_render.h @@ -23,6 +23,7 @@ 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.c b/src/core/pxl8.c index ac06e7d..642bc0e 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -393,16 +393,13 @@ pxl8_result pxl8_update(pxl8* sys) { } #ifdef PXL8_ASYNC_THREADS - if (game->world) { + if (game->world && (pxl8_world_local_player(game->world))) { pxl8_input_msg msg = {0}; msg.move_x = (pxl8_key_down(&game->input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "a") ? 1.0f : 0.0f); msg.move_y = (pxl8_key_down(&game->input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "s") ? 1.0f : 0.0f); - if (game->input.mouse_relative_mode) { - msg.look_dx = (f32)pxl8_mouse_dx(&game->input); - msg.look_dy = (f32)pxl8_mouse_dy(&game->input); - } + msg.look_dx = (f32)pxl8_mouse_dx(&game->input); + msg.look_dy = (f32)pxl8_mouse_dy(&game->input); msg.buttons = pxl8_key_down(&game->input, "space") ? 1 : 0; - pxl8_world_push_input(game->world, &msg); } pxl8_net_update(game->net, dt); @@ -412,6 +409,7 @@ pxl8_result pxl8_update(pxl8* sys) { pxl8_net_update(game->net, dt); pxl8_world_sync(game->world, game->net); } + pxl8_world_update(game->world, &game->input, dt); #endif @@ -565,6 +563,9 @@ void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) { sys->hal->set_relative_mouse_mode(sys->platform_data, enabled); if (sys->game) { sys->game->input.mouse_relative_mode = enabled; +#ifdef PXL8_ASYNC_THREADS + pxl8_world_pause_sim(sys->game->world, !enabled); +#endif } } diff --git a/src/core/pxl8_macros.h b/src/core/pxl8_macros.h index f289461..ed3509d 100644 --- a/src/core/pxl8_macros.h +++ b/src/core/pxl8_macros.h @@ -2,14 +2,6 @@ #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 94e0a95..2a429f9 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_backend.h b/src/gfx/pxl8_backend.h new file mode 100644 index 0000000..d7741b5 --- /dev/null +++ b/src/gfx/pxl8_backend.h @@ -0,0 +1,19 @@ +#ifndef PXL8_BACKEND_H +#define PXL8_BACKEND_H + +#include "pxl8_cpu.h" + +typedef enum { + PXL8_GFX_BACKEND_CPU, + PXL8_GFX_BACKEND_GPU, +} pxl8_gfx_backend_type; + +typedef struct { + pxl8_gfx_backend_type type; + union { + pxl8_cpu_backend* cpu; + void* gpu; + }; +} pxl8_gfx_backend; + +#endif diff --git a/src/gfx/pxl8_colormap.h b/src/gfx/pxl8_colormap.h index fb9f714..04f8fd0 100644 --- a/src/gfx/pxl8_colormap.h +++ b/src/gfx/pxl8_colormap.h @@ -58,7 +58,7 @@ static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, pxl8_ } static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity, u32 x, u32 y) { - u8 dithered = pxl8_gfx_dither((f32)intensity + 0.5f, x, y); + u8 dithered = pxl8_dither_light(intensity, x, y); u32 light_row = ((u32)light_color << 3) + (dithered >> 5); return cm->table[(light_row << 8) + pal_idx]; } diff --git a/src/gfx/pxl8_cpu.c b/src/gfx/pxl8_cpu.c new file mode 100644 index 0000000..2989d42 --- /dev/null +++ b/src/gfx/pxl8_cpu.c @@ -0,0 +1,1669 @@ +#include "pxl8_atlas.h" +#include "pxl8_bsp.h" +#include "pxl8_cpu.h" +#include "pxl8_log.h" +#include "pxl8_mem.h" + +#include +#include +#include + +struct pxl8_cpu_render_target { + u8* framebuffer; + u32 height; + u32 width; + u16* zbuffer; + u32* light_accum; +}; + +static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_pos, pxl8_vec3 normal) { + pxl8_vec3 to_light = pxl8_vec3_sub(light->position, world_pos); + f32 dist_sq = pxl8_vec3_dot(to_light, to_light); + + if (dist_sq >= light->radius_sq) { + return 0.0f; + } + + f32 intensity_norm = light->intensity * (1.0f / 255.0f); + f32 falloff = 1.0f - dist_sq * light->inv_radius_sq; + + if (dist_sq < 0.001f) { + return falloff * intensity_norm; + } + + f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq); + pxl8_vec3 light_dir = pxl8_vec3_scale(to_light, inv_dist); + f32 n_dot_l = pxl8_vec3_dot(normal, light_dir); + + if (n_dot_l <= 0.0f) { + return 0.0f; + } + + return n_dot_l * falloff * intensity_norm; +} + +typedef struct pxl8_light_result { + u8 light; + u32 light_color; +} pxl8_light_result; + +static pxl8_light_result calc_vertex_light( + pxl8_vec3 world_pos, + pxl8_vec3 normal, + const pxl8_3d_frame* frame +) { + f32 intensity = 0.25f + frame->uniforms.ambient * (1.0f / 255.0f); + + f32 accum_r = 0.0f; + f32 accum_g = 0.0f; + f32 accum_b = 0.0f; + f32 total_dynamic = 0.0f; + + f32 celestial_dot = -pxl8_vec3_dot(normal, frame->uniforms.celestial_dir); + if (celestial_dot > 0.0f) { + intensity += celestial_dot * frame->uniforms.celestial_intensity; + } + + f32 sky_factor = normal.y * 0.5f + 0.5f; + if (sky_factor < 0.0f) sky_factor = 0.0f; + intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f; + + for (u32 i = 0; i < frame->lights_count; i++) { + const pxl8_light* light = &frame->lights[i]; + f32 contrib = calc_light_intensity(light, world_pos, normal); + if (contrib > 0.0f) { + intensity += contrib; + total_dynamic += contrib; + + accum_r += light->r * contrib; + accum_g += light->g * contrib; + accum_b += light->b * contrib; + } + } + + u32 light_color = 0; + if (total_dynamic > 0.001f) { + f32 inv_total = 1.0f / total_dynamic; + f32 tint_strength = total_dynamic * 1.5f; + if (tint_strength > 1.0f) tint_strength = 1.0f; + + u32 r = (u32)(accum_r * inv_total); + u32 g = (u32)(accum_g * inv_total); + u32 b = (u32)(accum_b * inv_total); + u32 a = (u32)(tint_strength * 255.0f); + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + light_color = r | (g << 8) | (b << 16) | (a << 24); + } + + if (intensity < 0.0f) intensity = 0.0f; + if (intensity > 1.0f) intensity = 1.0f; + + return (pxl8_light_result){ + .light = (u8)(intensity * 255.0f), + .light_color = light_color, + }; +} + +#define PXL8_MAX_TARGET_STACK 8 + +struct pxl8_cpu_backend { + pxl8_cpu_render_target* current_target; + pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK]; + u32 target_stack_depth; + const pxl8_colormap* colormap; + const pxl8_palette_cube* palette_cube; + const u32* palette; + pxl8_3d_frame frame; + pxl8_mat4 mvp; + u32* output; + u32 output_size; + u32 light_tint; + f32 light_depth; + i32 light_leaf; +}; + +static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h, bool* visible) { + const u8 INSIDE = 0, LEFT = 1, RIGHT = 2, BOTTOM = 4, TOP = 8; + + u8 outcode0 = INSIDE, outcode1 = INSIDE; + if (*x0 < 0) outcode0 |= LEFT; + else if (*x0 >= w) outcode0 |= RIGHT; + if (*y0 < 0) outcode0 |= TOP; + else if (*y0 >= h) outcode0 |= BOTTOM; + + if (*x1 < 0) outcode1 |= LEFT; + else if (*x1 >= w) outcode1 |= RIGHT; + if (*y1 < 0) outcode1 |= TOP; + else if (*y1 >= h) outcode1 |= BOTTOM; + + *visible = true; + while (true) { + if ((outcode0 | outcode1) == 0) return; + if ((outcode0 & outcode1) != 0) { *visible = false; return; } + + u8 out = outcode0 ? outcode0 : outcode1; + i32 x, y; + i64 dx = *x1 - *x0, dy = *y1 - *y0; + + if (out & TOP) { x = *x0 + (i32)(dx * (0 - *y0) / dy); y = 0; } + else if (out & BOTTOM) { x = *x0 + (i32)(dx * (h - 1 - *y0) / dy); y = h - 1; } + else if (out & RIGHT) { y = *y0 + (i32)(dy * (w - 1 - *x0) / dx); x = w - 1; } + else { y = *y0 + (i32)(dy * (0 - *x0) / dx); x = 0; } + + if (out == outcode0) { + *x0 = x; *y0 = y; + outcode0 = INSIDE; + if (*x0 < 0) outcode0 |= LEFT; + else if (*x0 >= w) outcode0 |= RIGHT; + if (*y0 < 0) outcode0 |= TOP; + else if (*y0 >= h) outcode0 |= BOTTOM; + } else { + *x1 = x; *y1 = y; + outcode1 = INSIDE; + if (*x1 < 0) outcode1 |= LEFT; + else if (*x1 >= w) outcode1 |= RIGHT; + if (*y1 < 0) outcode1 |= TOP; + else if (*y1 >= h) outcode1 |= BOTTOM; + } + } +} + +pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) { + pxl8_cpu_backend* cpu = pxl8_calloc(1, sizeof(pxl8_cpu_backend)); + if (!cpu) return NULL; + + pxl8_cpu_render_target_desc desc = { + .width = width, + .height = height, + .with_depth = true, + .with_lighting = true, + }; + pxl8_cpu_render_target* base_target = pxl8_cpu_create_render_target(&desc); + if (!base_target) { + pxl8_free(cpu); + return NULL; + } + + cpu->target_stack[0] = base_target; + cpu->target_stack_depth = 1; + cpu->current_target = base_target; + + cpu->output_size = width * height; + cpu->output = pxl8_calloc(cpu->output_size, sizeof(u32)); + if (!cpu->output) { + pxl8_cpu_destroy_render_target(base_target); + pxl8_free(cpu); + return NULL; + } + + return cpu; +} + +void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) { + if (!cpu) return; + for (u32 i = 0; i < cpu->target_stack_depth; i++) { + pxl8_cpu_destroy_render_target(cpu->target_stack[i]); + } + pxl8_free(cpu->output); + pxl8_free(cpu); +} + +void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) { + if (!cpu || !frame) return; + cpu->frame = *frame; + cpu->mvp = pxl8_mat4_multiply(frame->projection, frame->view); + + cpu->light_tint = 0; + cpu->light_depth = 1.0f; + cpu->light_leaf = -1; + if (frame->lights_count > 0) { + const pxl8_light* light = &frame->lights[0]; + cpu->light_tint = light->r | (light->g << 8) | (light->b << 16); + + pxl8_vec4 light_clip = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){light->position.x, light->position.y, light->position.z, 1.0f}); + if (light_clip.w > 0.001f) { + cpu->light_depth = light_clip.z / light_clip.w; + } + + if (frame->bsp) { + cpu->light_leaf = pxl8_bsp_find_leaf(frame->bsp, light->position); + } + } +} + +void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) { + (void)cpu; +} + +void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + memset(render_target->framebuffer, color, render_target->width * render_target->height); +} + +void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) { + if (!cpu || !cpu->current_target) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + u32 count = render_target->width * render_target->height; + if (render_target->zbuffer) { + memset(render_target->zbuffer, 0xFF, count * sizeof(u16)); + } + if (render_target->light_accum) { + memset(render_target->light_accum, 0, count * sizeof(u32)); + } +} + +void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) { + if (cpu) cpu->colormap = cm; +} + +void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube) { + if (cpu) cpu->palette_cube = cube; +} + +void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) { + if (cpu) cpu->palette = palette; +} + +void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + if (x < 0 || x >= (i32)render_target->width || y < 0 || y >= (i32)render_target->height) return; + render_target->framebuffer[y * render_target->width + x] = color; +} + +u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return 0; + pxl8_cpu_render_target* render_target = cpu->current_target; + if (x < 0 || x >= (i32)render_target->width || y < 0 || y >= (i32)render_target->height) return 0; + return render_target->framebuffer[y * render_target->width + x]; +} + +void pxl8_cpu_draw_line_2d(pxl8_cpu_backend* cpu, i32 x0, i32 y0, i32 x1, i32 y1, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + bool visible; + clip_line_2d(&x0, &y0, &x1, &y1, (i32)render_target->width, (i32)render_target->height, &visible); + if (!visible) return; + + i32 dx = x1 - x0; + i32 dy = y1 - y0; + i32 sx = dx > 0 ? 1 : -1; + i32 sy = dy > 0 ? 1 : -1; + dx = dx > 0 ? dx : -dx; + dy = dy > 0 ? dy : -dy; + + i32 err = (dx > dy ? dx : -dy) / 2; + u8* fb = render_target->framebuffer; + u32 w = render_target->width; + + while (true) { + fb[(u32)y0 * w + (u32)x0] = color; + if (x0 == x1 && y0 == y1) break; + i32 e2 = err; + if (e2 > -dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } +} + +void pxl8_cpu_draw_rect(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color) { + if (!cpu) return; + pxl8_cpu_draw_line_2d(cpu, x, y, x + w - 1, y, color); + pxl8_cpu_draw_line_2d(cpu, x + w - 1, y, x + w - 1, y + h - 1, color); + pxl8_cpu_draw_line_2d(cpu, x + w - 1, y + h - 1, x, y + h - 1, color); + pxl8_cpu_draw_line_2d(cpu, x, y + h - 1, x, y, color); +} + +void pxl8_cpu_draw_rect_fill(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + i32 x0 = (x < 0) ? 0 : x; + i32 y0 = (y < 0) ? 0 : y; + i32 x1 = (x + w > (i32)render_target->width) ? (i32)render_target->width : x + w; + i32 y1 = (y + h > (i32)render_target->height) ? (i32)render_target->height : y + h; + + i32 rect_w = x1 - x0; + if (rect_w <= 0 || y1 <= y0) return; + + for (i32 py = y0; py < y1; py++) { + memset(render_target->framebuffer + py * render_target->width + x0, color, rect_w); + } +} + +void pxl8_cpu_draw_circle(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color) { + if (!cpu) return; + + i32 x = radius; + i32 y = 0; + i32 err = 0; + + while (x >= y) { + pxl8_cpu_draw_pixel(cpu, cx + x, cy + y, color); + pxl8_cpu_draw_pixel(cpu, cx + y, cy + x, color); + pxl8_cpu_draw_pixel(cpu, cx - y, cy + x, color); + pxl8_cpu_draw_pixel(cpu, cx - x, cy + y, color); + pxl8_cpu_draw_pixel(cpu, cx - x, cy - y, color); + pxl8_cpu_draw_pixel(cpu, cx - y, cy - x, color); + pxl8_cpu_draw_pixel(cpu, cx + y, cy - x, color); + pxl8_cpu_draw_pixel(cpu, cx + x, cy - y, color); + + if (err <= 0) { + y += 1; + err += 2 * y + 1; + } else { + x -= 1; + err -= 2 * x + 1; + } + } +} + +void pxl8_cpu_draw_circle_fill(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + i32 x0 = (cx - radius < 0) ? -cx : -radius; + i32 y0 = (cy - radius < 0) ? -cy : -radius; + i32 x1 = (cx + radius >= (i32)render_target->width) ? (i32)render_target->width - cx - 1 : radius; + i32 y1 = (cy + radius >= (i32)render_target->height) ? (i32)render_target->height - cy - 1 : radius; + + for (i32 dy = y0; dy <= y1; dy++) { + for (i32 dx = x0; dx <= x1; dx++) { + if (dx * dx + dy * dy <= radius * radius) { + render_target->framebuffer[(cy + dy) * render_target->width + (cx + dx)] = color; + } + } + } +} + +void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { + if (!cpu || !cpu->current_target) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f}); + pxl8_vec4 c1 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f}); + + if (c0.w <= 0.0f || c1.w <= 0.0f) return; + + f32 hw = (f32)render_target->width * 0.5f; + f32 hh = (f32)render_target->height * 0.5f; + + i32 x0 = (i32)(hw + c0.x / c0.w * hw); + i32 y0 = (i32)(hh - c0.y / c0.w * hh); + i32 x1 = (i32)(hw + c1.x / c1.w * hw); + i32 y1 = (i32)(hh - c1.y / c1.w * hh); + + pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color); +} + +typedef struct { + pxl8_vec4 clip_pos; + pxl8_vec3 world_pos; + pxl8_vec3 normal; + f32 u, v; + u8 color; + u8 light; +} vertex_output; + +static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, f32 t) { + vertex_output 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 = t < 0.5f ? a->color : b->color; + out.light = (u8)(a->light + (b->light - a->light) * t); + return out; +} + +static i32 clip_triangle_near( + const vertex_output* v0, const vertex_output* v1, const vertex_output* v2, + f32 near, vertex_output out[6] +) { + bool in0 = v0->clip_pos.w >= near; + bool in1 = v1->clip_pos.w >= near; + bool in2 = v2->clip_pos.w >= near; + i32 count = in0 + in1 + in2; + + if (count == 0) return 0; + if (count == 3) { + out[0] = *v0; out[1] = *v1; out[2] = *v2; + return 3; + } + + if (count == 1) { + const vertex_output *inside, *out_a, *out_b; + if (in0) { inside = v0; out_a = v1; out_b = v2; } + else if (in1) { inside = v1; out_a = v2; out_b = v0; } + else { inside = v2; out_a = v0; out_b = v1; } + + f32 t_a = (near - out_a->clip_pos.w) / (inside->clip_pos.w - out_a->clip_pos.w); + f32 t_b = (near - out_b->clip_pos.w) / (inside->clip_pos.w - out_b->clip_pos.w); + + out[0] = *inside; + out[1] = lerp_vertex(out_a, inside, t_a); + out[2] = lerp_vertex(out_b, inside, t_b); + return 3; + } + + const vertex_output *outside, *in_a, *in_b; + if (!in0) { outside = v0; in_a = v1; in_b = v2; } + else if (!in1) { outside = v1; in_a = v2; in_b = v0; } + else { outside = v2; in_a = v0; in_b = v1; } + + f32 t_a = (near - outside->clip_pos.w) / (in_a->clip_pos.w - outside->clip_pos.w); + f32 t_b = (near - outside->clip_pos.w) / (in_b->clip_pos.w - outside->clip_pos.w); + + vertex_output new_a = lerp_vertex(outside, in_a, t_a); + vertex_output new_b = lerp_vertex(outside, in_b, t_b); + + out[0] = *in_a; out[1] = *in_b; out[2] = new_b; + out[3] = *in_a; out[4] = new_b; out[5] = new_a; + return 6; +} + +typedef struct { + pxl8_vec3 p0, p1, p2; + pxl8_vec3 w_recip; + pxl8_vec3 u_w, v_w; + pxl8_vec3 l_w; + pxl8_vec3 c_w; + pxl8_vec3 world0_w, world1_w, world2_w; + pxl8_vec3 normal; + i32 y_start, y_end; + f32 inv_total; + u32 target_width, target_height; +} tri_setup; + +static bool setup_triangle( + tri_setup* setup, + const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, + u32 width, u32 height, bool double_sided +) { + f32 hw = (f32)width * 0.5f; + f32 hh = (f32)height * 0.5f; + + 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; + 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.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.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; + + const vertex_output* sorted[3] = {vo0, vo1, vo2}; + + if (setup->p0.y > setup->p1.y) { + pxl8_vec3 t = setup->p0; setup->p0 = setup->p1; setup->p1 = t; + const vertex_output* tv = sorted[0]; sorted[0] = sorted[1]; sorted[1] = tv; + } + if (setup->p0.y > setup->p2.y) { + pxl8_vec3 t = setup->p0; setup->p0 = setup->p2; setup->p2 = t; + const vertex_output* tv = sorted[0]; sorted[0] = sorted[2]; sorted[2] = tv; + } + if (setup->p1.y > setup->p2.y) { + pxl8_vec3 t = setup->p1; setup->p1 = setup->p2; setup->p2 = t; + const vertex_output* tv = sorted[1]; sorted[1] = sorted[2]; sorted[2] = tv; + } + + f32 total_height = setup->p2.y - setup->p0.y; + if (total_height < 1.0f) return false; + + 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->w_recip.x = 1.0f / sorted[0]->clip_pos.w; + setup->w_recip.y = 1.0f / sorted[1]->clip_pos.w; + setup->w_recip.z = 1.0f / sorted[2]->clip_pos.w; + + setup->u_w.x = sorted[0]->u * setup->w_recip.x; + setup->v_w.x = sorted[0]->v * setup->w_recip.x; + setup->u_w.y = sorted[1]->u * setup->w_recip.y; + setup->v_w.y = sorted[1]->v * setup->w_recip.y; + setup->u_w.z = sorted[2]->u * setup->w_recip.z; + setup->v_w.z = sorted[2]->v * setup->w_recip.z; + + setup->l_w.x = sorted[0]->light * setup->w_recip.x; + setup->l_w.y = sorted[1]->light * setup->w_recip.y; + setup->l_w.z = sorted[2]->light * setup->w_recip.z; + + setup->c_w.x = (f32)sorted[0]->color * setup->w_recip.x; + setup->c_w.y = (f32)sorted[1]->color * setup->w_recip.y; + setup->c_w.z = (f32)sorted[2]->color * setup->w_recip.z; + + setup->world0_w = pxl8_vec3_scale(sorted[0]->world_pos, setup->w_recip.x); + setup->world1_w = pxl8_vec3_scale(sorted[1]->world_pos, setup->w_recip.y); + setup->world2_w = pxl8_vec3_scale(sorted[2]->world_pos, setup->w_recip.z); + + setup->normal = sorted[0]->normal; + + setup->inv_total = 1.0f / total_height; + setup->target_width = width; + setup->target_height = height; + + return true; +} + +static void rasterize_triangle_opaque( + pxl8_cpu_backend* cpu, + const tri_setup* setup, + const pxl8_atlas* textures, u32 texture_id, bool dither +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + u32* light_accum = render_target->light_accum; + + bool facing_light = false; + f32 light_radius_sq = 0.0f; + f32 light_inv_radius_sq = 0.0f; + pxl8_vec3 light_pos = {0}; + if (cpu->frame.lights_count > 0) { + const pxl8_light* light = &cpu->frame.lights[0]; + light_pos = light->position; + light_radius_sq = light->radius_sq; + light_inv_radius_sq = light->inv_radius_sq; + + f32 avg_w = (setup->w_recip.x + setup->w_recip.y + setup->w_recip.z) / 3.0f; + f32 inv_w = (avg_w > 0.0001f) ? 1.0f / avg_w : 1.0f; + f32 cx = (setup->world0_w.x + setup->world1_w.x + setup->world2_w.x) / 3.0f * inv_w; + f32 cy = (setup->world0_w.y + setup->world1_w.y + setup->world2_w.y) / 3.0f * inv_w; + f32 cz = (setup->world0_w.z + setup->world1_w.z + setup->world2_w.z) / 3.0f * inv_w; + f32 to_light_x = light->position.x - cx; + f32 to_light_y = light->position.y - cy; + f32 to_light_z = light->position.z - cz; + f32 n_dot_l = setup->normal.x * to_light_x + setup->normal.y * to_light_y + setup->normal.z * to_light_z; + facing_light = n_dot_l > 0.0f; + + if (facing_light && setup->normal.y > 0.9f && cpu->frame.bsp && cpu->light_leaf >= 0) { + pxl8_vec3 centroid = {cx, cy, cz}; + i32 tri_leaf = pxl8_bsp_find_leaf(cpu->frame.bsp, centroid); + if (tri_leaf >= 0 && tri_leaf != cpu->light_leaf && + !pxl8_bsp_is_leaf_visible(cpu->frame.bsp, cpu->light_leaf, tri_leaf)) { + facing_light = false; + } + } + } + + f32 base_light = 0.25f + cpu->frame.uniforms.ambient * (1.0f / 255.0f); + base_light += cpu->frame.uniforms.celestial_intensity * 0.5f; + + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; + i32 tex_w = tex_entry ? (i32)tex_entry->w : 1; + i32 tex_h = tex_entry ? (i32)tex_entry->h : 1; + i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; + i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; + u32 tex_base = tex_entry ? tex_entry->tiled_base : 0; + u8 tex_log2_w = tex_entry ? tex_entry->log2_w : 0; + const u8* tex_pixels = textures ? pxl8_atlas_get_pixels_tiled(textures) : 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; + + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_uw = setup->u_w.x + (setup->u_w.z - setup->u_w.x) * alpha; + f32 a_vw = setup->v_w.x + (setup->v_w.z - setup->v_w.x) * alpha; + f32 a_wxw = setup->world0_w.x + (setup->world2_w.x - setup->world0_w.x) * alpha; + f32 a_wyw = setup->world0_w.y + (setup->world2_w.y - setup->world0_w.y) * alpha; + f32 a_wzw = setup->world0_w.z + (setup->world2_w.z - setup->world0_w.z) * alpha; + + f32 bx, bz, b_wr, b_uw, b_vw, b_wxw, b_wyw, b_wzw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; + + if (second_half) { + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_uw = setup->u_w.y + (setup->u_w.z - setup->u_w.y) * beta; + b_vw = setup->v_w.y + (setup->v_w.z - setup->v_w.y) * beta; + b_wxw = setup->world1_w.x + (setup->world2_w.x - setup->world1_w.x) * beta; + b_wyw = setup->world1_w.y + (setup->world2_w.y - setup->world1_w.y) * beta; + b_wzw = setup->world1_w.z + (setup->world2_w.z - setup->world1_w.z) * beta; + } else { + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_uw = setup->u_w.x + (setup->u_w.y - setup->u_w.x) * beta; + b_vw = setup->v_w.x + (setup->v_w.y - setup->v_w.x) * beta; + b_wxw = setup->world0_w.x + (setup->world1_w.x - setup->world0_w.x) * beta; + b_wyw = setup->world0_w.y + (setup->world1_w.y - setup->world0_w.y) * beta; + b_wzw = setup->world0_w.z + (setup->world1_w.z - setup->world0_w.z) * beta; + } + + f32 x_start_fp, x_end_fp, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end; + f32 wxw_start, wxw_end, wyw_start, wyw_end, wzw_start, wzw_end; + + if (ax <= bx) { + x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + uw_start = a_uw; uw_end = b_uw; + vw_start = a_vw; vw_end = b_vw; + wxw_start = a_wxw; wxw_end = b_wxw; + wyw_start = a_wyw; wyw_end = b_wyw; + wzw_start = a_wzw; wzw_end = b_wzw; + } else { + x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + uw_start = b_uw; uw_end = a_uw; + vw_start = b_vw; vw_end = a_vw; + wxw_start = b_wxw; wxw_end = a_wxw; + wyw_start = b_wyw; wyw_end = a_wyw; + wzw_start = b_wzw; wzw_end = a_wzw; + } + + 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 > x_end) continue; + + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; + + f32 dz = (z_end - z_start) * inv_width; + f32 dwr = (wr_end - wr_start) * inv_width; + f32 duw = (uw_end - uw_start) * inv_width; + f32 dvw = (vw_end - vw_start) * inv_width; + f32 dwxw = (wxw_end - wxw_start) * inv_width; + f32 dwyw = (wyw_end - wyw_start) * inv_width; + f32 dwzw = (wzw_end - wzw_start) * inv_width; + + f32 skip = (f32)x_start + 0.5f - x_start_fp; + f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 uw = uw_start + duw * skip; + f32 vw = vw_start + dvw * skip; + f32 wxw = wxw_start + dwxw * skip; + f32 wyw = wyw_start + dwyw * skip; + f32 wzw = wzw_start + dwzw * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; + + const i32 SUBDIV = 16; + i32 x = x_start; + + while (x <= x_end) { + i32 span_end = x + SUBDIV - 1; + if (span_end > x_end) span_end = x_end; + i32 span_len = span_end - x + 1; + + f32 pw_start = 1.0f / wr; + f32 pw_end = 1.0f / (wr + dwr * (f32)span_len); + + f32 u_start = uw * pw_start; + f32 v_start = vw * pw_start; + f32 u_end = (uw + duw * (f32)span_len) * pw_end; + f32 v_end = (vw + dvw * (f32)span_len) * pw_end; + + f32 wx_start = wxw * pw_start; + f32 wy_start = wyw * pw_start; + f32 wz_start = wzw * pw_start; + f32 wx_end = (wxw + dwxw * (f32)span_len) * pw_end; + f32 wy_end = (wyw + dwyw * (f32)span_len) * pw_end; + f32 wz_end = (wzw + dwzw * (f32)span_len) * pw_end; + + f32 l_start = base_light; + f32 l_end = base_light; + f32 tint_start = 0.0f; + f32 tint_end = 0.0f; + + if (facing_light && cpu->frame.lights_count > 0) { + f32 dx_s = light_pos.x - wx_start; + f32 dy_s = light_pos.y - wy_start; + f32 dz_s = light_pos.z - wz_start; + f32 dist_sq_s = dx_s * dx_s + dy_s * dy_s + dz_s * dz_s; + if (dist_sq_s < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_s * light_inv_radius_sq; + l_start += falloff; + tint_start = falloff; + } + + f32 dx_e = light_pos.x - wx_end; + f32 dy_e = light_pos.y - wy_end; + f32 dz_e = light_pos.z - wz_end; + f32 dist_sq_e = dx_e * dx_e + dy_e * dy_e + dz_e * dz_e; + if (dist_sq_e < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_e * light_inv_radius_sq; + l_end += falloff; + tint_end = falloff; + } + } + + if (l_start > 1.0f) l_start = 1.0f; + if (l_end > 1.0f) l_end = 1.0f; + l_start *= 255.0f; + l_end *= 255.0f; + + f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; + f32 du = (u_end - u_start) * inv_span; + f32 dv = (v_end - v_start) * inv_span; + f32 dl = (l_end - l_start) * inv_span; + f32 dt = (tint_end - tint_start) * inv_span; + + i32 u_fixed = (i32)(u_start * (f32)tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * (f32)tex_h * 65536.0f); + i32 du_fixed = (i32)(du * (f32)tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * (f32)tex_h * 65536.0f); + + f32 l_a = l_start; + f32 t_a = tint_start; + f32 z_a = z; + + 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; + u16 z16 = (u16)(depth_norm * 65535.0f); + if (z16 < zrow[px]) { + i32 tx = (u_fixed >> 16) & tex_mask_w; + i32 ty = (v_fixed >> 16) & tex_mask_h; + u8 tex_idx = tex_pixels ? tex_pixels[tex_base + pxl8_tile_addr((u32)tx, (u32)ty, tex_log2_w)] : 1; + + if (tex_idx != 0) { + u8 light = (u8)l_a; + if (dither) { + light = pxl8_dither_light(light, (u32)px, (u32)y); + } + u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx; + prow[px] = pal_idx; + zrow[px] = z16; + if (light_accum && t_a > 0.01f) { + u32 alpha = (u32)(t_a * 255.0f); + if (alpha > 255) alpha = 255; + u32 tint = (cpu->light_tint & 0x00FFFFFF) | (alpha << 24); + light_accum[row_start + (u32)px] = tint; + } + } + } + + u_fixed += du_fixed; + v_fixed += dv_fixed; + l_a += dl; + t_a += dt; + z_a += dz; + } + + wr += dwr * (f32)span_len; + uw += duw * (f32)span_len; + vw += dvw * (f32)span_len; + wxw += dwxw * (f32)span_len; + wyw += dwyw * (f32)span_len; + wzw += dwzw * (f32)span_len; + z += dz * (f32)span_len; + x = span_end + 1; + } + } +} + +static void rasterize_triangle_passthrough( + pxl8_cpu_backend* cpu, + const tri_setup* setup +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + + 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; + + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_cw = setup->c_w.x + (setup->c_w.z - setup->c_w.x) * alpha; + + f32 bx, bz, b_wr, b_cw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; + + if (second_half) { + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_cw = setup->c_w.y + (setup->c_w.z - setup->c_w.y) * beta; + } else { + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_cw = setup->c_w.x + (setup->c_w.y - setup->c_w.x) * beta; + } + + f32 x_start_fp, x_end_fp, z_start, z_end, wr_start, wr_end, cw_start, cw_end; + if (ax <= bx) { + x_start_fp = ax; x_end_fp = bx; + z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + cw_start = a_cw; cw_end = b_cw; + } else { + x_start_fp = bx; x_end_fp = ax; + z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + cw_start = b_cw; cw_end = a_cw; + } + + 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 > x_end) continue; + + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; + + f32 dz = (z_end - z_start) * inv_width; + f32 dwr = (wr_end - wr_start) * inv_width; + f32 dcw = (cw_end - cw_start) * inv_width; + + f32 skip = (f32)x_start + 0.5f - x_start_fp; + f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 cw = cw_start + dcw * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; + + for (i32 px = x_start; px <= x_end; px++) { + f32 depth_norm = (z + 1.0f) * 0.5f; + if (depth_norm < 0.0f) depth_norm = 0.0f; + if (depth_norm > 1.0f) depth_norm = 1.0f; + u16 z16 = (u16)(depth_norm * 65535.0f); + if (z16 < zrow[px]) { + f32 w = 1.0f / wr; + f32 color_fp = cw * w; + u8 color = pxl8_dither_float(color_fp, (u32)px, (u32)y); + prow[px] = color; + zrow[px] = z16; + } + z += dz; + wr += dwr; + cw += dcw; + } + } +} + +static void rasterize_triangle_alpha( + pxl8_cpu_backend* cpu, + const tri_setup* setup, + const pxl8_atlas* textures, u32 texture_id, u8 mat_alpha, bool dither +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + u32* light_accum = render_target->light_accum; + + bool facing_light = false; + f32 light_radius_sq = 0.0f; + f32 light_inv_radius_sq = 0.0f; + pxl8_vec3 light_pos = {0}; + if (cpu->frame.lights_count > 0) { + const pxl8_light* light = &cpu->frame.lights[0]; + light_pos = light->position; + light_radius_sq = light->radius_sq; + light_inv_radius_sq = light->inv_radius_sq; + + f32 avg_w = (setup->w_recip.x + setup->w_recip.y + setup->w_recip.z) / 3.0f; + f32 inv_w = (avg_w > 0.0001f) ? 1.0f / avg_w : 1.0f; + f32 cx = (setup->world0_w.x + setup->world1_w.x + setup->world2_w.x) / 3.0f * inv_w; + f32 cy = (setup->world0_w.y + setup->world1_w.y + setup->world2_w.y) / 3.0f * inv_w; + f32 cz = (setup->world0_w.z + setup->world1_w.z + setup->world2_w.z) / 3.0f * inv_w; + f32 to_light_x = light->position.x - cx; + f32 to_light_y = light->position.y - cy; + f32 to_light_z = light->position.z - cz; + f32 n_dot_l = setup->normal.x * to_light_x + setup->normal.y * to_light_y + setup->normal.z * to_light_z; + facing_light = n_dot_l > 0.0f; + + if (facing_light && setup->normal.y > 0.9f && cpu->frame.bsp && cpu->light_leaf >= 0) { + pxl8_vec3 centroid = {cx, cy, cz}; + i32 tri_leaf = pxl8_bsp_find_leaf(cpu->frame.bsp, centroid); + if (tri_leaf >= 0 && tri_leaf != cpu->light_leaf && + !pxl8_bsp_is_leaf_visible(cpu->frame.bsp, cpu->light_leaf, tri_leaf)) { + facing_light = false; + } + } + } + + f32 base_light = 0.25f + cpu->frame.uniforms.ambient * (1.0f / 255.0f); + base_light += cpu->frame.uniforms.celestial_intensity * 0.5f; + + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; + u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1; + i32 tex_w = tex_entry ? (i32)tex_entry->w : 1; + i32 tex_h = tex_entry ? (i32)tex_entry->h : 1; + i32 tex_stride = (i32)atlas_width; + i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; + i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; + u32 tex_base = tex_entry ? (u32)tex_entry->y * atlas_width + (u32)tex_entry->x : 0; + const u8* tex_pixels = textures ? pxl8_atlas_get_pixels(textures) : 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; + + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_uw = setup->u_w.x + (setup->u_w.z - setup->u_w.x) * alpha; + f32 a_vw = setup->v_w.x + (setup->v_w.z - setup->v_w.x) * alpha; + f32 a_wxw = setup->world0_w.x + (setup->world2_w.x - setup->world0_w.x) * alpha; + f32 a_wyw = setup->world0_w.y + (setup->world2_w.y - setup->world0_w.y) * alpha; + f32 a_wzw = setup->world0_w.z + (setup->world2_w.z - setup->world0_w.z) * alpha; + + f32 bx, bz, b_wr, b_uw, b_vw, b_wxw, b_wyw, b_wzw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; + + if (second_half) { + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_uw = setup->u_w.y + (setup->u_w.z - setup->u_w.y) * beta; + b_vw = setup->v_w.y + (setup->v_w.z - setup->v_w.y) * beta; + b_wxw = setup->world1_w.x + (setup->world2_w.x - setup->world1_w.x) * beta; + b_wyw = setup->world1_w.y + (setup->world2_w.y - setup->world1_w.y) * beta; + b_wzw = setup->world1_w.z + (setup->world2_w.z - setup->world1_w.z) * beta; + } else { + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_uw = setup->u_w.x + (setup->u_w.y - setup->u_w.x) * beta; + b_vw = setup->v_w.x + (setup->v_w.y - setup->v_w.x) * beta; + b_wxw = setup->world0_w.x + (setup->world1_w.x - setup->world0_w.x) * beta; + b_wyw = setup->world0_w.y + (setup->world1_w.y - setup->world0_w.y) * beta; + b_wzw = setup->world0_w.z + (setup->world1_w.z - setup->world0_w.z) * beta; + } + + f32 x_start_fp, x_end_fp, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end; + f32 wxw_start, wxw_end, wyw_start, wyw_end, wzw_start, wzw_end; + + if (ax <= bx) { + x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + uw_start = a_uw; uw_end = b_uw; + vw_start = a_vw; vw_end = b_vw; + wxw_start = a_wxw; wxw_end = b_wxw; + wyw_start = a_wyw; wyw_end = b_wyw; + wzw_start = a_wzw; wzw_end = b_wzw; + } else { + x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + uw_start = b_uw; uw_end = a_uw; + vw_start = b_vw; vw_end = a_vw; + wxw_start = b_wxw; wxw_end = a_wxw; + wyw_start = b_wyw; wyw_end = a_wyw; + wzw_start = b_wzw; wzw_end = a_wzw; + } + + 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 > x_end) continue; + + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; + + f32 dz = (z_end - z_start) * inv_width; + f32 dwr = (wr_end - wr_start) * inv_width; + f32 duw = (uw_end - uw_start) * inv_width; + f32 dvw = (vw_end - vw_start) * inv_width; + f32 dwxw = (wxw_end - wxw_start) * inv_width; + f32 dwyw = (wyw_end - wyw_start) * inv_width; + f32 dwzw = (wzw_end - wzw_start) * inv_width; + + f32 skip = (f32)x_start + 0.5f - x_start_fp; + f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 uw = uw_start + duw * skip; + f32 vw = vw_start + dvw * skip; + f32 wxw = wxw_start + dwxw * skip; + f32 wyw = wyw_start + dwyw * skip; + f32 wzw = wzw_start + dwzw * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; + + const i32 SUBDIV = 16; + i32 x = x_start; + + while (x <= x_end) { + i32 span_end = x + SUBDIV - 1; + if (span_end > x_end) span_end = x_end; + i32 span_len = span_end - x + 1; + + f32 pw_start = 1.0f / wr; + f32 pw_end = 1.0f / (wr + dwr * (f32)span_len); + + f32 u_start = uw * pw_start; + f32 v_start = vw * pw_start; + f32 u_end = (uw + duw * (f32)span_len) * pw_end; + f32 v_end = (vw + dvw * (f32)span_len) * pw_end; + + f32 wx_start = wxw * pw_start; + f32 wy_start = wyw * pw_start; + f32 wz_start = wzw * pw_start; + f32 wx_end = (wxw + dwxw * (f32)span_len) * pw_end; + f32 wy_end = (wyw + dwyw * (f32)span_len) * pw_end; + f32 wz_end = (wzw + dwzw * (f32)span_len) * pw_end; + + f32 l_start = base_light; + f32 l_end = base_light; + f32 tint_start = 0.0f; + f32 tint_end = 0.0f; + + if (facing_light && cpu->frame.lights_count > 0) { + f32 dx_s = light_pos.x - wx_start; + f32 dy_s = light_pos.y - wy_start; + f32 dz_s = light_pos.z - wz_start; + f32 dist_sq_s = dx_s * dx_s + dy_s * dy_s + dz_s * dz_s; + if (dist_sq_s < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_s * light_inv_radius_sq; + l_start += falloff; + tint_start = falloff; + } + + f32 dx_e = light_pos.x - wx_end; + f32 dy_e = light_pos.y - wy_end; + f32 dz_e = light_pos.z - wz_end; + f32 dist_sq_e = dx_e * dx_e + dy_e * dy_e + dz_e * dz_e; + if (dist_sq_e < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_e * light_inv_radius_sq; + l_end += falloff; + tint_end = falloff; + } + } + + if (l_start > 1.0f) l_start = 1.0f; + if (l_end > 1.0f) l_end = 1.0f; + l_start *= 255.0f; + l_end *= 255.0f; + + f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; + f32 du = (u_end - u_start) * inv_span; + f32 dv = (v_end - v_start) * inv_span; + f32 dl = (l_end - l_start) * inv_span; + f32 dt = (tint_end - tint_start) * inv_span; + + i32 u_fixed = (i32)(u_start * (f32)tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * (f32)tex_h * 65536.0f); + i32 du_fixed = (i32)(du * (f32)tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * (f32)tex_h * 65536.0f); + + f32 l_a = l_start; + f32 t_a = tint_start; + f32 z_a = z; + + for (i32 px = x; px <= span_end; px++) { + i32 tx = (u_fixed >> 16) & tex_mask_w; + i32 ty = (v_fixed >> 16) & tex_mask_h; + u8 tex_idx = tex_pixels ? tex_pixels[tex_base + (u32)ty * (u32)tex_stride + (u32)tx] : 1; + + if (tex_idx != 0) { + u8 light = (u8)l_a; + if (dither) { + light = pxl8_dither_light(light, (u32)px, (u32)y); + } + u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx; + + if (mat_alpha >= 128) { + prow[px] = src_idx; + 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; + u16 z16 = (u16)(depth_norm * 65535.0f); + if (z16 < zrow[px]) zrow[px] = z16; + if (light_accum && t_a > 0.01f) { + u32 alpha = (u32)(t_a * 255.0f); + if (alpha > 255) alpha = 255; + u32 tint = (cpu->light_tint & 0x00FFFFFF) | (alpha << 24); + light_accum[row_start + (u32)px] = tint; + } + } + } + + u_fixed += du_fixed; + v_fixed += dv_fixed; + l_a += dl; + t_a += dt; + z_a += dz; + } + + wr += dwr * (f32)span_len; + uw += duw * (f32)span_len; + vw += dvw * (f32)span_len; + wxw += dwxw * (f32)span_len; + wyw += dwyw * (f32)span_len; + wzw += dwzw * (f32)span_len; + z += dz * (f32)span_len; + x = span_end + 1; + } + } +} + +static void rasterize_triangle_wireframe( + pxl8_cpu_backend* cpu, + const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, + u8 color, bool double_sided +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + f32 hw = (f32)render_target->width * 0.5f; + f32 hh = (f32)render_target->height * 0.5f; + + i32 x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw); + i32 y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh); + i32 x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw); + i32 y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh); + i32 x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw); + i32 y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh); + + if (!double_sided) { + i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0); + if (cross >= 0) return; + } + + pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color); + pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color); + pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color); +} + +static void dispatch_triangle( + pxl8_cpu_backend* cpu, + const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, + const pxl8_atlas* textures, const pxl8_gfx_material* material +) { + if (material->wireframe) { + rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, 15, material->double_sided); + return; + } + + pxl8_cpu_render_target* render_target = cpu->current_target; + tri_setup setup; + if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) { + return; + } + + bool alpha_blend = material->alpha < 255; + bool passthrough = material->vertex_color_passthrough; + + if (alpha_blend) { + rasterize_triangle_alpha(cpu, &setup, textures, material->texture_id, material->alpha, material->dither); + } else if (passthrough) { + rasterize_triangle_passthrough(cpu, &setup); + } else { + rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither); + } +} + +void pxl8_cpu_draw_mesh( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + const pxl8_mat4* model, + const pxl8_gfx_material* material, + const pxl8_atlas* textures +) { + if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return; + + pxl8_mat4 mv = pxl8_mat4_multiply(cpu->frame.view, *model); + pxl8_mat4 mvp = pxl8_mat4_multiply(cpu->frame.projection, mv); + + f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f; + + for (u32 i = 0; i < mesh->index_count; i += 3) { + u16 i0 = mesh->indices[i]; + u16 i1 = mesh->indices[i + 1]; + u16 i2 = mesh->indices[i + 2]; + + const pxl8_vertex* v0 = &mesh->vertices[i0]; + const pxl8_vertex* v1 = &mesh->vertices[i1]; + const pxl8_vertex* v2 = &mesh->vertices[i2]; + + vertex_output vo0, vo1, vo2; + + pxl8_vec4 p0 = {v0->position.x, v0->position.y, v0->position.z, 1.0f}; + pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f}; + pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f}; + + vo0.clip_pos = pxl8_mat4_multiply_vec4(mvp, p0); + vo1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1); + vo2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2); + + pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(*model, p0); + pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(*model, p1); + pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(*model, p2); + vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; + vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; + vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; + + vo0.normal = v0->normal; + vo1.normal = v1->normal; + vo2.normal = v2->normal; + + vo0.u = v0->u; vo0.v = v0->v; + vo1.u = v1->u; vo1.v = v1->v; + vo2.u = v2->u; vo2.v = v2->v; + + vo0.color = v0->color; + vo1.color = v1->color; + vo2.color = v2->color; + + if (material->dynamic_lighting) { + pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v0->normal)); + pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v1->normal)); + pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v2->normal)); + + pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame); + pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame); + pxl8_light_result lr2 = calc_vertex_light(vo2.world_pos, n2, &cpu->frame); + + vo0.light = lr0.light; + vo1.light = lr1.light; + vo2.light = lr2.light; + } else { + vo0.light = 255; + vo1.light = 255; + vo2.light = 255; + } + + vertex_output clipped[6]; + i32 clipped_count = clip_triangle_near(&vo0, &vo1, &vo2, near, clipped); + + for (i32 t = 0; t < clipped_count; t += 3) { + dispatch_triangle(cpu, &clipped[t], &clipped[t+1], &clipped[t+2], textures, material); + } + } +} + +u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->framebuffer; +} + +u32* pxl8_cpu_get_light_accum(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->light_accum; +} + +u16* pxl8_cpu_get_zbuffer(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->zbuffer; +} + +u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return 0; + return cpu->target_stack[0]->height; +} + +u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return 0; + return cpu->target_stack[0]->width; +} + +pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc) { + if (!desc) return NULL; + + pxl8_cpu_render_target* target = pxl8_calloc(1, sizeof(pxl8_cpu_render_target)); + if (!target) return NULL; + + u32 size = desc->width * desc->height; + target->width = desc->width; + target->height = desc->height; + target->framebuffer = pxl8_calloc(size, sizeof(u8)); + if (!target->framebuffer) { + pxl8_free(target); + return NULL; + } + + if (desc->with_depth) { + target->zbuffer = pxl8_calloc(size, sizeof(u16)); + if (!target->zbuffer) { + pxl8_free(target->framebuffer); + pxl8_free(target); + return NULL; + } + } + + if (desc->with_lighting) { + target->light_accum = pxl8_calloc(size, sizeof(u32)); + if (!target->light_accum) { + pxl8_free(target->zbuffer); + pxl8_free(target->framebuffer); + pxl8_free(target); + return NULL; + } + } + + return target; +} + +void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target) { + if (!target) return; + pxl8_free(target->light_accum); + pxl8_free(target->zbuffer); + pxl8_free(target->framebuffer); + pxl8_free(target); +} + +pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu) { + return cpu ? cpu->current_target : NULL; +} + +void pxl8_cpu_set_target(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* target) { + if (!cpu || !target) return; + cpu->current_target = target; +} + +void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i32 y, u8 transparent_idx) { + if (!cpu || !cpu->current_target || !src || !src->framebuffer) return; + pxl8_cpu_render_target* dst = cpu->current_target; + + i32 src_x0 = 0, src_y0 = 0; + i32 dst_x0 = x, dst_y0 = y; + i32 copy_w = (i32)src->width; + i32 copy_h = (i32)src->height; + + if (dst_x0 < 0) { src_x0 = -dst_x0; copy_w += dst_x0; dst_x0 = 0; } + if (dst_y0 < 0) { src_y0 = -dst_y0; copy_h += dst_y0; dst_y0 = 0; } + if (dst_x0 + copy_w > (i32)dst->width) copy_w = (i32)dst->width - dst_x0; + if (dst_y0 + copy_h > (i32)dst->height) copy_h = (i32)dst->height - dst_y0; + + if (copy_w <= 0 || copy_h <= 0) return; + + for (i32 row = 0; row < copy_h; row++) { + u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0; + u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0; + u32* light_row = dst->light_accum ? dst->light_accum + (dst_y0 + row) * dst->width + dst_x0 : NULL; + for (i32 col = 0; col < copy_w; col++) { + u8 pixel = src_row[col]; + if (pixel != transparent_idx) { + dst_row[col] = pixel; + if (light_row) light_row[col] = 0; + } + } + } +} + +bool pxl8_cpu_push_target(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false; + + pxl8_cpu_render_target* base = cpu->target_stack[0]; + pxl8_cpu_render_target_desc desc = { + .width = base->width, + .height = base->height, + .with_depth = false, + .with_lighting = false, + }; + pxl8_cpu_render_target* target = pxl8_cpu_create_render_target(&desc); + if (!target) return false; + + cpu->target_stack[cpu->target_stack_depth++] = target; + cpu->current_target = target; + return true; +} + +void pxl8_cpu_pop_target(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth <= 1) return; + + pxl8_cpu_render_target* popped = cpu->target_stack[--cpu->target_stack_depth]; + cpu->current_target = cpu->target_stack[cpu->target_stack_depth - 1]; + + pxl8_cpu_blit(cpu, popped, 0, 0, 0); + pxl8_cpu_destroy_render_target(popped); +} + +u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu) { + return cpu ? cpu->target_stack_depth : 0; +} + +u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target) { + return target ? target->framebuffer : NULL; +} + +u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target) { + return target ? target->height : 0; +} + +u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target) { + return target ? target->width : 0; +} + +u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target) { + return target ? target->light_accum : NULL; +} + +u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) { + return cpu ? cpu->output : NULL; +} + +void pxl8_cpu_render_glows( + pxl8_cpu_backend* cpu, + const pxl8_glow* glows, + u32 glow_count +) { + if (!cpu || cpu->target_stack_depth == 0) return; + + pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1]; + if (!target || !target->framebuffer) return; + + u32 width = target->width; + u32 height = target->height; + u8* pixels = target->framebuffer; + u16* zbuffer = target->zbuffer; + u32* light_accum = target->light_accum; + + for (u32 gi = 0; gi < glow_count; gi++) { + const pxl8_glow* glow = &glows[gi]; + i32 cx = glow->x; + i32 cy = glow->y; + i32 radius = glow->radius; + + if (radius <= 0 || glow->intensity == 0) continue; + + i32 x0 = cx - radius; + i32 y0 = cy - radius; + i32 x1 = cx + radius; + i32 y1 = cy + radius; + + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 >= (i32)width) x1 = (i32)width - 1; + if (y1 >= (i32)height) y1 = (i32)height - 1; + + if (x0 >= x1 || y0 >= y1) continue; + + u16 glow_depth = glow->depth; + u8 base_color = glow->color; + f32 base_intensity = glow->intensity / 255.0f; + f32 radius_f = (f32)radius; + f32 radius_sq = radius_f * radius_f; + f32 inv_radius_sq = 1.0f / radius_sq; + f32 inv_radius = 1.0f / radius_f; + + for (i32 y = y0; y <= y1; y++) { + u32 row = (u32)y * width; + f32 dy = (f32)(y - cy); + + for (i32 x = x0; x <= x1; x++) { + u32 idx = row + (u32)x; + + if (zbuffer && glow_depth > zbuffer[idx]) continue; + + f32 dx = (f32)(x - cx); + f32 intensity = 0.0f; + + switch (glow->shape) { + case PXL8_GLOW_CIRCLE: { + f32 dist_sq = dx * dx + dy * dy; + if (dist_sq >= radius_sq) continue; + f32 falloff = 1.0f - dist_sq * inv_radius_sq; + intensity = base_intensity * falloff * falloff; + break; + } + case PXL8_GLOW_DIAMOND: { + f32 abs_dx = dx < 0 ? -dx : dx; + f32 abs_dy = dy < 0 ? -dy : dy; + f32 manhattan = abs_dx + abs_dy; + if (manhattan >= radius_f) continue; + f32 falloff = 1.0f - manhattan * inv_radius; + intensity = base_intensity * falloff * falloff; + break; + } + case PXL8_GLOW_SHAFT: { + f32 abs_dx = dx < 0 ? -dx : dx; + f32 abs_dy = dy < 0 ? -dy : dy; + f32 height_f = glow->height > 0 ? (f32)glow->height : radius_f; + if (abs_dx >= radius_f || abs_dy >= height_f) continue; + f32 falloff_x = 1.0f - abs_dx * inv_radius; + f32 falloff_y = 1.0f - abs_dy / height_f; + intensity = base_intensity * falloff_x * falloff_x * falloff_y; + break; + } + } + + if (intensity < 0.02f) continue; + + u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255; + u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y); + + if (light_level < 8) continue; + + u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, PXL8_LIGHT_WHITE, light_level); + + if (cpu->palette_cube) { + u8 sr, sg, sb, dr, dg, db; + pxl8_palette_cube_get_rgb(cpu->palette_cube, shaded, &sr, &sg, &sb); + pxl8_palette_cube_get_rgb(cpu->palette_cube, pixels[idx], &dr, &dg, &db); + u32 r = (u32)sr + (u32)dr; + u32 g = (u32)sg + (u32)dg; + u32 b = (u32)sb + (u32)db; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + pixels[idx] = pxl8_palette_cube_lookup(cpu->palette_cube, (u8)r, (u8)g, (u8)b); + } else { + pixels[idx] = shaded; + } + + if (light_accum) { + light_accum[idx] = 0; + } + } + } + } +} + +void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) { + if (!cpu || !cpu->palette || cpu->target_stack_depth == 0) return; + + pxl8_cpu_render_target* target = cpu->target_stack[0]; + if (!target || !target->framebuffer) return; + + u32 pixel_count = target->width * target->height; + const u8* pixels = target->framebuffer; + const u32* light_accum = target->light_accum; + const u32* palette = cpu->palette; + u32* output = cpu->output; + + for (u32 i = 0; i < pixel_count; i++) { + u32 base = palette[pixels[i]]; + + if (light_accum && light_accum[i] != 0) { + u32 light_color = light_accum[i]; + u32 tint_alpha = (light_color >> 24) & 0xFF; + + if (tint_alpha == 255) { + output[i] = light_color; + continue; + } + + if (tint_alpha > 0) { + u32 lr = light_color & 0xFF; + u32 lg = (light_color >> 8) & 0xFF; + u32 lb = (light_color >> 16) & 0xFF; + + u32 br = base & 0xFF; + u32 bg = (base >> 8) & 0xFF; + u32 bb = (base >> 16) & 0xFF; + u32 ba = (base >> 24) & 0xFF; + + f32 t = (f32)tint_alpha / 255.0f; + f32 tint_r = (f32)lr / 255.0f; + f32 tint_g = (f32)lg / 255.0f; + f32 tint_b = (f32)lb / 255.0f; + + f32 or_f = (f32)br * (1.0f + t * tint_r); + f32 og_f = (f32)bg * (1.0f + t * tint_g * 0.6f); + f32 ob_f = (f32)bb * (1.0f + t * tint_b * 0.3f); + + u32 or = (u32)or_f; + u32 og = (u32)og_f; + u32 ob = (u32)ob_f; + + if (or > 255) or = 255; + if (og > 255) og = 255; + if (ob > 255) ob = 255; + + output[i] = or | (og << 8) | (ob << 16) | (ba << 24); + continue; + } + } + + output[i] = base; + } + + for (u32 t = 1; t < cpu->target_stack_depth; t++) { + pxl8_cpu_render_target* overlay = cpu->target_stack[t]; + if (!overlay || !overlay->framebuffer) continue; + + const u8* overlay_pixels = overlay->framebuffer; + for (u32 i = 0; i < pixel_count; i++) { + u8 idx = overlay_pixels[i]; + if (idx != 0) { + output[i] = palette[idx]; + } + } + } +} diff --git a/src/gfx/pxl8_cpu.h b/src/gfx/pxl8_cpu.h new file mode 100644 index 0000000..81f3852 --- /dev/null +++ b/src/gfx/pxl8_cpu.h @@ -0,0 +1,94 @@ +#pragma once + +#include "pxl8_atlas.h" +#include "pxl8_colormap.h" +#include "pxl8_gfx.h" +#include "pxl8_gfx3d.h" +#include "pxl8_lightmap.h" +#include "pxl8_math.h" +#include "pxl8_mesh.h" +#include "pxl8_palette.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct pxl8_cpu_backend pxl8_cpu_backend; +typedef struct pxl8_cpu_render_target pxl8_cpu_render_target; + +typedef struct pxl8_cpu_render_target_desc { + u32 width; + u32 height; + bool with_depth; + bool with_lighting; +} pxl8_cpu_render_target_desc; + +pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height); +void pxl8_cpu_destroy(pxl8_cpu_backend* cpu); + +void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame); +void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu); + +void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color); +void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu); + +void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm); +void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette); +void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube); + +u32 pxl8_cpu_add_lightmap(pxl8_cpu_backend* cpu, pxl8_lightmap* lm); +pxl8_lightmap* pxl8_cpu_get_lightmap(pxl8_cpu_backend* cpu, u32 id); + +void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color); +u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y); +void pxl8_cpu_draw_line_2d(pxl8_cpu_backend* cpu, i32 x0, i32 y0, i32 x1, i32 y1, u8 color); +void pxl8_cpu_draw_rect(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color); +void pxl8_cpu_draw_rect_fill(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color); +void pxl8_cpu_draw_circle(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color); +void pxl8_cpu_draw_circle_fill(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color); +void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); + +void pxl8_cpu_draw_mesh( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + const pxl8_mat4* model, + const pxl8_gfx_material* material, + const pxl8_atlas* textures +); + +u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); +u32* pxl8_cpu_get_light_accum(pxl8_cpu_backend* cpu); +u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu); +u16* pxl8_cpu_get_zbuffer(pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu); + +void pxl8_cpu_render_glows( + pxl8_cpu_backend* cpu, + const pxl8_glow* glows, + u32 glow_count +); + +void pxl8_cpu_resolve(pxl8_cpu_backend* cpu); + +pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc); +void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target); + +pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu); +void pxl8_cpu_set_target(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* target); + +void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i32 y, u8 transparent_idx); + +bool pxl8_cpu_push_target(pxl8_cpu_backend* cpu); +void pxl8_cpu_pop_target(pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu); + +u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target); +u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target); +u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target); +u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target); + +#ifdef __cplusplus +} +#endif diff --git a/src/gfx/pxl8_dither.h b/src/gfx/pxl8_dither.h index c22cf8e..3b5416a 100644 --- a/src/gfx/pxl8_dither.h +++ b/src/gfx/pxl8_dither.h @@ -8,16 +8,8 @@ extern "C" { extern const u8 PXL8_BAYER_4X4[16]; -static inline u8 pxl8_gfx_dither(f32 value, u32 x, u32 y) { - if (value <= 0.0f) return 0; - if (value >= 255.0f) return 255; - u8 base = (u8)value; - f32 frac = value - (f32)base; - f32 threshold = (PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)] + 0.5f) * (1.0f / 16.0f); - if (frac > threshold) { - return base < 255 ? (u8)(base + 1) : 255; - } - return base; +static inline i8 pxl8_bayer_offset(u32 x, u32 y) { + return (i8)(PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)]) - 8; } static inline u8 pxl8_ordered_dither(u8 value, u32 x, u32 y) { @@ -26,6 +18,24 @@ static inline u8 pxl8_ordered_dither(u8 value, u32 x, u32 y) { return result > 255 ? 255 : (u8)result; } +static inline u8 pxl8_dither_light(u8 light, u32 x, u32 y) { + i8 offset = pxl8_bayer_offset(x, y) >> 1; + i16 result = (i16)light + offset; + if (result < 0) return 0; + if (result > 255) return 255; + return (u8)result; +} + +static inline u8 pxl8_dither_float(f32 value, u32 x, u32 y) { + u8 floor_val = (u8)value; + f32 frac = value - (f32)floor_val; + f32 threshold = (PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)] + 0.5f) * (1.0f / 16.0f); + if (frac > threshold) { + return floor_val < 255 ? floor_val + 1 : 255; + } + return floor_val; +} + #ifdef __cplusplus } #endif diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index 1dadd12..4d2e364 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -5,6 +5,7 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" +#include "pxl8_backend.h" #include "pxl8_blit.h" #include "pxl8_color.h" #include "pxl8_colormap.h" @@ -14,77 +15,36 @@ #include "pxl8_macros.h" #include "pxl8_math.h" #include "pxl8_mem.h" -#include "pxl8_render.h" -#include "pxl8_shader_registry.h" #include "pxl8_sys.h" #include "pxl8_types.h" -#define PXL8_MAX_TARGET_STACK 8 - typedef struct pxl8_sprite_cache_entry { char path[256]; u32 sprite_id; bool active; } pxl8_sprite_cache_entry; -typedef struct pxl8_target_entry { - pxl8_gfx_texture color; - pxl8_gfx_texture depth; - pxl8_gfx_texture light; -} pxl8_target_entry; - -#define PXL8_MAX_FRAME_RESOURCES 512 -#define PXL8_STREAM_VB_SIZE (256 * 1024) -#define PXL8_STREAM_IB_SIZE (512 * 1024) - -typedef struct pxl8_frame_resources { - pxl8_gfx_texture textures[PXL8_MAX_FRAME_RESOURCES]; - pxl8_gfx_buffer buffers[PXL8_MAX_FRAME_RESOURCES]; - pxl8_gfx_pipeline pipelines[PXL8_MAX_FRAME_RESOURCES]; - pxl8_gfx_bindings bindings[PXL8_MAX_FRAME_RESOURCES]; - u32 texture_count; - u32 buffer_count; - u32 pipeline_count; - u32 bindings_count; -} pxl8_frame_resources; - struct pxl8_gfx { pxl8_atlas* atlas; - pxl8_renderer* renderer; - pxl8_gfx_texture color_target; - pxl8_gfx_texture depth_target; - pxl8_gfx_texture light_target; - pxl8_gfx_cmdbuf* cmdbuf; - pxl8_target_entry target_stack[PXL8_MAX_TARGET_STACK]; - u32 target_stack_depth; + pxl8_gfx_backend backend; const pxl8_bsp* bsp; pxl8_colormap* colormap; + u8* framebuffer; i32 framebuffer_height; i32 framebuffer_width; pxl8_frustum frustum; const pxl8_hal* hal; bool initialized; - u32* output; pxl8_palette* palette; pxl8_palette_cube* palette_cube; - pxl8_gfx_pass frame_pass; - pxl8_frame_resources frame_res; pxl8_pixel_mode pixel_mode; void* platform_data; + const pxl8_sdf* sdf; pxl8_sprite_cache_entry* sprite_cache; u32 sprite_cache_capacity; u32 sprite_cache_count; pxl8_viewport viewport; pxl8_mat4 view_proj; - pxl8_3d_frame frame; - - 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) { @@ -103,12 +63,15 @@ pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; - return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_framebuffer(gfx->backend.cpu); + } + return gfx->framebuffer; } u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; - return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); + return (u16*)gfx->framebuffer; } i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { @@ -117,30 +80,10 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx) { if (!gfx) return NULL; - return (u32*)pxl8_texture_get_data(gfx->renderer, gfx->light_target); -} - -u32* pxl8_gfx_get_output(pxl8_gfx* gfx) { - if (!gfx) return NULL; - return gfx->output; -} - -void pxl8_gfx_resolve(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized) return; - if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return; - - const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; - if (!pal) { - pxl8_error("resolve: no palette!"); - return; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_light_accum(gfx->backend.cpu); } - pxl8_resolve_to_rgba( - gfx->renderer, - gfx->color_target, - gfx->light_target, - pal, - gfx->output - ); + return NULL; } const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) { @@ -150,7 +93,10 @@ const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) { u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx) { if (!gfx) return NULL; - return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->depth_target); + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_zbuffer(gfx->backend.cpu); + } + return NULL; } i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { @@ -177,6 +123,9 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) { void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) { if (!gfx || !gfx->palette || !colors) return; pxl8_set_palette(gfx->palette, colors, count); + if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR && gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette)); + } } i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { @@ -187,6 +136,9 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { if (gfx->colormap) { pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette)); } + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette)); + } } return 0; } @@ -197,8 +149,6 @@ pxl8_gfx* pxl8_gfx_create( pxl8_pixel_mode mode, pxl8_resolution resolution ) { - pxl8_shader_registry_init(); - pxl8_gfx* gfx = (pxl8_gfx*)pxl8_calloc(1, sizeof(pxl8_gfx)); if (!gfx) { pxl8_error("Failed to allocate graphics context"); @@ -223,87 +173,29 @@ pxl8_gfx* pxl8_gfx_create( gfx->palette = pxl8_palette_create(); } - gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height); - if (!gfx->renderer) { - pxl8_error("Failed to create renderer"); + gfx->backend.type = PXL8_GFX_BACKEND_CPU; + gfx->backend.cpu = pxl8_cpu_create(gfx->framebuffer_width, gfx->framebuffer_height); + if (!gfx->backend.cpu) { + pxl8_error("Failed to create CPU backend"); pxl8_gfx_destroy(gfx); return NULL; } - pxl8_gfx_texture_desc color_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_INDEXED8, - .render_target = true, - }; - gfx->color_target = pxl8_create_texture(gfx->renderer, &color_desc); - - pxl8_gfx_texture_desc depth_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_DEPTH16, - .render_target = true, - }; - gfx->depth_target = pxl8_create_texture(gfx->renderer, &depth_desc); - - pxl8_gfx_texture_desc light_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_LIGHT_ACCUM, - .render_target = true, - }; - gfx->light_target = pxl8_create_texture(gfx->renderer, &light_desc); - - u32 pixel_count = (u32)gfx->framebuffer_width * (u32)gfx->framebuffer_height; - gfx->output = pxl8_calloc(pixel_count, sizeof(u32)); - if (!gfx->output) { - pxl8_error("Failed to allocate output buffer"); - pxl8_gfx_destroy(gfx); - return NULL; - } - - gfx->cmdbuf = pxl8_cmdbuf_create(1024); - if (!gfx->cmdbuf) { - pxl8_error("Failed to create command buffer"); - pxl8_gfx_destroy(gfx); - return NULL; - } + gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu); if (mode != PXL8_PIXEL_HICOLOR) { gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap)); + if (gfx->colormap) { + pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap); + } } - gfx->target_stack[0] = (pxl8_target_entry){ - .color = gfx->color_target, - .depth = gfx->depth_target, - .light = gfx->light_target, - }; - gfx->target_stack_depth = 1; - gfx->viewport.offset_x = 0; gfx->viewport.offset_y = 0; gfx->viewport.scaled_width = gfx->framebuffer_width; gfx->viewport.scaled_height = gfx->framebuffer_height; gfx->viewport.scale = 1.0f; - pxl8_gfx_buffer_desc vb_desc = { - .capacity = PXL8_STREAM_VB_SIZE, - .type = PXL8_GFX_BUFFER_VERTEX, - .usage = PXL8_GFX_USAGE_STREAM, - }; - gfx->stream_vb = pxl8_create_buffer(gfx->renderer, &vb_desc); - gfx->stream_vb_capacity = PXL8_STREAM_VB_SIZE; - gfx->stream_vb_offset = 0; - - pxl8_gfx_buffer_desc ib_desc = { - .capacity = PXL8_STREAM_IB_SIZE, - .type = PXL8_GFX_BUFFER_INDEX, - .usage = PXL8_GFX_USAGE_STREAM, - }; - gfx->stream_ib = pxl8_create_buffer(gfx->renderer, &ib_desc); - gfx->stream_ib_capacity = PXL8_STREAM_IB_SIZE; - gfx->stream_ib_offset = 0; - gfx->initialized = true; return gfx; } @@ -312,13 +204,13 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) { if (!gfx) return; pxl8_atlas_destroy(gfx->atlas); - pxl8_cmdbuf_destroy(gfx->cmdbuf); + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_destroy(gfx->backend.cpu); + } pxl8_free(gfx->colormap); - pxl8_free(gfx->output); pxl8_palette_cube_destroy(gfx->palette_cube); pxl8_palette_destroy(gfx->palette); pxl8_free(gfx->sprite_cache); - pxl8_renderer_destroy(gfx->renderer); pxl8_free(gfx); } @@ -438,24 +330,26 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized || !gfx->hal) return; - if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) { - pxl8_gfx_resolve(gfx); - gfx->hal->upload_texture( - gfx->platform_data, - gfx->output, - gfx->framebuffer_width, - gfx->framebuffer_height, - PXL8_PIXEL_RGBA, - NULL - ); - return; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU && gfx->pixel_mode == PXL8_PIXEL_INDEXED) { + pxl8_cpu_resolve(gfx->backend.cpu); + u32* output = pxl8_cpu_get_output(gfx->backend.cpu); + if (output) { + gfx->hal->upload_texture( + gfx->platform_data, + output, + gfx->framebuffer_width, + gfx->framebuffer_height, + PXL8_PIXEL_RGBA, + NULL + ); + return; + } } u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; - u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); gfx->hal->upload_texture( gfx->platform_data, - framebuffer, + gfx->framebuffer, gfx->framebuffer_width, gfx->framebuffer_height, gfx->pixel_mode, @@ -469,16 +363,6 @@ 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); @@ -498,99 +382,115 @@ void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { (void)gfx; (void)left; (void)right; (void)top; (void)bottom; } -static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) { - if (gfx->target_stack_depth == 0) return gfx->color_target; - return gfx->target_stack[gfx->target_stack_depth - 1].color; -} - -static pxl8_gfx_texture gfx_current_depth(pxl8_gfx* gfx) { - if (gfx->target_stack_depth == 0) return gfx->depth_target; - return gfx->target_stack[gfx->target_stack_depth - 1].depth; -} - -static pxl8_gfx_texture gfx_current_light(pxl8_gfx* gfx) { - if (gfx->target_stack_depth == 0) return gfx->light_target; - return gfx->target_stack[gfx->target_stack_depth - 1].light; -} - -static void gfx_composite_over(pxl8_gfx* gfx, const pxl8_target_entry* src, pxl8_target_entry* dst) { - if (!gfx || !src || !dst) return; - - u8* src_color = (u8*)pxl8_texture_get_data(gfx->renderer, src->color); - u8* dst_color = (u8*)pxl8_texture_get_data(gfx->renderer, dst->color); - if (!src_color || !dst_color) return; - - u32 width = pxl8_texture_get_width(gfx->renderer, src->color); - u32 height = pxl8_texture_get_height(gfx->renderer, src->color); - if (width == 0 || height == 0) return; - - u32 dst_width = pxl8_texture_get_width(gfx->renderer, dst->color); - u32 dst_height = pxl8_texture_get_height(gfx->renderer, dst->color); - if (dst_width != width || dst_height != height) return; - - u32* src_light = (u32*)pxl8_texture_get_data(gfx->renderer, src->light); - u32* dst_light = (u32*)pxl8_texture_get_data(gfx->renderer, dst->light); - - u32 count = width * height; - for (u32 i = 0; i < count; i++) { - u8 sc = src_color[i]; - if (sc == 0) continue; - - dst_color[i] = sc; - if (dst_light) { - dst_light[i] = src_light ? src_light[i] : 0; - } - } -} - void pxl8_2d_clear(pxl8_gfx* gfx, u32 color) { if (!gfx) return; - pxl8_clear(gfx->renderer, gfx_current_color(gfx), (u8)color); - pxl8_clear_light(gfx->renderer, gfx_current_light(gfx)); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear(gfx->backend.cpu, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { if (!gfx) return; - pxl8_draw_pixel(gfx->renderer, gfx_current_color(gfx), x, y, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_pixel(gfx->backend.cpu, x, y, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) { if (!gfx) return 0; - return pxl8_get_pixel(gfx->renderer, gfx_current_color(gfx), x, y); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_get_pixel(gfx->backend.cpu, x, y); + case PXL8_GFX_BACKEND_GPU: + return 0; + } + return 0; } void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { if (!gfx) return; - pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_line_2d(gfx->backend.cpu, x0, y0, x1, y1, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!gfx) return; - pxl8_draw_rect(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_rect(gfx->backend.cpu, x, y, w, h, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!gfx) return; - pxl8_draw_rect_fill(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_rect_fill(gfx->backend.cpu, x, y, w, h, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { if (!gfx) return; - pxl8_draw_circle(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_circle(gfx->backend.cpu, cx, cy, radius, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { if (!gfx) return; - pxl8_draw_circle_fill(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_circle_fill(gfx->backend.cpu, cx, cy, radius, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { if (!gfx || !text) return; - pxl8_gfx_texture target = gfx_current_color(gfx); - u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, target); - i32 fb_width = (i32)pxl8_texture_get_width(gfx->renderer, target); - i32 fb_height = (i32)pxl8_texture_get_height(gfx->renderer, target); + u8* framebuffer = NULL; + u32* light_accum = NULL; + i32 fb_width = 0; + i32 fb_height = 0; + + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: { + pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); + if (!render_target) return; + framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); + light_accum = pxl8_cpu_render_target_get_light_accum(render_target); + fb_width = (i32)pxl8_cpu_render_target_get_width(render_target); + fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); + break; + } + case PXL8_GFX_BACKEND_GPU: + return; + } if (!framebuffer) return; @@ -623,6 +523,7 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { if (pixel_bit) { i32 idx = py * fb_width + px; framebuffer[idx] = (u8)color; + if (light_accum) light_accum[idx] = 0; } } } @@ -635,10 +536,24 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) { if (!gfx || !gfx->atlas) return; - pxl8_gfx_texture target = gfx_current_color(gfx); - u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, target); - i32 fb_width = (i32)pxl8_texture_get_width(gfx->renderer, target); - i32 fb_height = (i32)pxl8_texture_get_height(gfx->renderer, target); + u8* framebuffer = NULL; + u32* light_accum = NULL; + i32 fb_width = 0; + i32 fb_height = 0; + + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: { + pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); + if (!render_target) return; + framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); + light_accum = pxl8_cpu_render_target_get_light_accum(render_target); + fb_width = (i32)pxl8_cpu_render_target_get_width(render_target); + fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); + break; + } + case PXL8_GFX_BACKEND_GPU: + return; + } if (!framebuffer) return; @@ -668,6 +583,11 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo if (is_1to1_scale && is_unclipped && !is_flipped) { const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); + if (light_accum) { + for (i32 py = 0; py < h; py++) { + memset(light_accum + (y + py) * fb_width + x, 0, w * sizeof(u32)); + } + } } else { for (i32 py = 0; py < draw_height; py++) { for (i32 px = 0; px < draw_width; px++) { @@ -681,6 +601,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); + if (light_accum) light_accum[dest_idx] = 0; } } } @@ -695,7 +616,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { } } -pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) { +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) { pxl8_3d_frame frame = {0}; if (!camera) return frame; @@ -718,37 +639,32 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp) { gfx->bsp = bsp; } -void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms) { +void pxl8_3d_set_sdf(pxl8_gfx* gfx, const pxl8_sdf* sdf) { + if (!gfx) return; + gfx->sdf = sdf; +} + +void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms) { if (!gfx || !camera) return; pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); frame.bsp = gfx->bsp; - frame.uniforms.lights = lights ? pxl8_lights_data(lights) : NULL; - frame.uniforms.lights_count = lights ? pxl8_lights_count(lights) : 0; + frame.lights = lights ? pxl8_lights_data(lights) : NULL; + frame.lights_count = lights ? pxl8_lights_count(lights) : 0; + frame.sdf = gfx->sdf; pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view); gfx->frustum = pxl8_frustum_from_matrix(vp); gfx->view_proj = vp; - gfx->frame = frame; - gfx->frame_res.texture_count = 0; - gfx->frame_res.buffer_count = 0; - gfx->frame_res.pipeline_count = 0; - gfx->frame_res.bindings_count = 0; - - gfx->stream_vb_offset = 0; - gfx->stream_ib_offset = 0; - - pxl8_cmdbuf_reset(gfx->cmdbuf); - - pxl8_gfx_pass_desc pass_desc = { - .color = { .texture = gfx_current_color(gfx), .load = PXL8_GFX_LOAD_CLEAR, .clear_value = 0 }, - .depth = { .texture = gfx_current_depth(gfx), .load = PXL8_GFX_LOAD_CLEAR, .clear_value = 0xFFFF }, - .light_accum = { .texture = gfx_current_light(gfx), .load = PXL8_GFX_LOAD_CLEAR }, - }; - gfx->frame_pass = pxl8_create_pass(gfx->renderer, &pass_desc); - pxl8_begin_pass(gfx->cmdbuf, gfx->frame_pass); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_begin_frame(gfx->backend.cpu, &frame); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) { @@ -790,249 +706,90 @@ u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) { if (!gfx) return; - pxl8_clear(gfx->renderer, gfx_current_color(gfx), color); - pxl8_clear_light(gfx->renderer, gfx_current_light(gfx)); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear(gfx->backend.cpu, color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_3d_clear_depth(pxl8_gfx* gfx) { if (!gfx) return; - pxl8_clear_depth(gfx->renderer, gfx_current_depth(gfx)); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear_depth(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { if (!gfx) return; - - pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(gfx->view_proj, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f}); - pxl8_vec4 c1 = pxl8_mat4_multiply_vec4(gfx->view_proj, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f}); - - if (c0.w <= 0.0f && c1.w <= 0.0f) return; - - f32 hw = (f32)gfx->framebuffer_width * 0.5f; - f32 hh = (f32)gfx->framebuffer_height * 0.5f; - - i32 x0 = (i32)(hw + c0.x / c0.w * hw); - i32 y0 = (i32)(hh - c0.y / c0.w * hh); - i32 x1 = (i32)(hw + c1.x / c1.w * hw); - i32 y1 = (i32)(hh - c1.y / c1.w * hh); - - pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_line_3d(gfx->backend.cpu, v0, v1, color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) { if (!gfx || !mesh || !model || !material) return; - if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return; - - pxl8_frame_resources* res = &gfx->frame_res; - bool is_wireframe = gfx->wireframe; - - const char* shader_name = "lit"; - if (material->emissive || (!material->dynamic_lighting && !material->per_pixel)) { - shader_name = "unlit"; - } - - pxl8_gfx_pipeline_desc pipe_desc = { - .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, - .emissive = material->emissive, - .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; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas); 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: + case PXL8_GFX_BACKEND_GPU: 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 = { - .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) { - res->bindings[res->bindings_count++] = bindings; - } - - u32 vb_size = mesh->vertex_count * sizeof(pxl8_vertex); - u32 ib_size = mesh->index_count * sizeof(u16); - - u32 base_vertex = 0; - u32 first_index = 0; - pxl8_gfx_buffer vb, ib; - - bool use_stream = pxl8_gfx_handle_valid(gfx->stream_vb) && - pxl8_gfx_handle_valid(gfx->stream_ib) && - (gfx->stream_vb_offset + vb_size <= gfx->stream_vb_capacity) && - (gfx->stream_ib_offset + ib_size <= gfx->stream_ib_capacity); - - if (use_stream) { - pxl8_gfx_range vb_data = { .ptr = mesh->vertices, .size = vb_size }; - pxl8_gfx_range ib_data = { .ptr = mesh->indices, .size = ib_size }; - i32 vb_offset = pxl8_append_buffer(gfx->renderer, gfx->stream_vb, &vb_data); - i32 ib_offset = pxl8_append_buffer(gfx->renderer, gfx->stream_ib, &ib_data); - - if (vb_offset >= 0 && ib_offset >= 0) { - gfx->stream_vb_offset += vb_size; - gfx->stream_ib_offset += ib_size; - - base_vertex = (u32)vb_offset / sizeof(pxl8_vertex); - first_index = (u32)ib_offset / sizeof(u16); - - vb = gfx->stream_vb; - ib = gfx->stream_ib; - } else { - use_stream = false; - } - } - - if (!use_stream) { - pxl8_gfx_buffer_desc vb_desc = { - .type = PXL8_GFX_BUFFER_VERTEX, - .data = { .ptr = mesh->vertices, .size = vb_size }, - }; - vb = pxl8_create_buffer(gfx->renderer, &vb_desc); - if (res->buffer_count < PXL8_MAX_FRAME_RESOURCES) { - res->buffers[res->buffer_count++] = vb; - } - - pxl8_gfx_buffer_desc ib_desc = { - .type = PXL8_GFX_BUFFER_INDEX, - .data = { .ptr = mesh->indices, .size = ib_size }, - }; - ib = pxl8_create_buffer(gfx->renderer, &ib_desc); - if (res->buffer_count < PXL8_MAX_FRAME_RESOURCES) { - res->buffers[res->buffer_count++] = ib; - } - } - - pxl8_gfx_cmd_draw_params draw_params = { - .model = *model, - .view = gfx->frame.view, - .projection = gfx->frame.projection, - .shader = gfx->frame.uniforms, - }; - - pxl8_set_pipeline(gfx->cmdbuf, pipeline); - pxl8_set_bindings(gfx->cmdbuf, bindings); - pxl8_set_draw_params(gfx->cmdbuf, &draw_params); - pxl8_draw(gfx->cmdbuf, vb, ib, first_index, mesh->index_count, base_vertex); } void pxl8_3d_end_frame(pxl8_gfx* gfx) { - if (!gfx) { pxl8_error("end_frame: gfx is NULL"); return; } - if (!pxl8_gfx_handle_valid(gfx->frame_pass)) { pxl8_error("end_frame: frame_pass invalid (id=%u)", gfx->frame_pass.id); return; } - - pxl8_end_pass(gfx->cmdbuf); - pxl8_gfx_submit(gfx->renderer, gfx->cmdbuf); - - pxl8_frame_resources* res = &gfx->frame_res; - - for (u32 i = 0; i < res->buffer_count; i++) { - pxl8_destroy_buffer(gfx->renderer, res->buffers[i]); + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_end_frame(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; } - for (u32 i = 0; i < res->pipeline_count; i++) { - pxl8_destroy_pipeline(gfx->renderer, res->pipelines[i]); - } - for (u32 i = 0; i < res->bindings_count; i++) { - pxl8_destroy_bindings(gfx->renderer, res->bindings[i]); - } - for (u32 i = 0; i < res->texture_count; i++) { - pxl8_destroy_texture(gfx->renderer, res->textures[i]); - } - - pxl8_destroy_pass(gfx->renderer, gfx->frame_pass); - gfx->frame_pass = (pxl8_gfx_pass){PXL8_GFX_INVALID_ID}; - - res->buffer_count = 0; - res->pipeline_count = 0; - res->bindings_count = 0; - res->texture_count = 0; } u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) { if (!gfx) return NULL; - return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx)); + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_get_framebuffer(gfx->backend.cpu); + case PXL8_GFX_BACKEND_GPU: + return NULL; + } + return NULL; } bool pxl8_gfx_push_target(pxl8_gfx* gfx) { - if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false; - - pxl8_gfx_texture_desc color_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_INDEXED8, - .render_target = true, - }; - pxl8_gfx_texture new_color = pxl8_create_texture(gfx->renderer, &color_desc); - - pxl8_gfx_texture_desc depth_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_DEPTH16, - .render_target = true, - }; - pxl8_gfx_texture new_depth = pxl8_create_texture(gfx->renderer, &depth_desc); - - pxl8_gfx_texture_desc light_desc = { - .width = (u32)gfx->framebuffer_width, - .height = (u32)gfx->framebuffer_height, - .format = PXL8_GFX_FORMAT_LIGHT_ACCUM, - .render_target = true, - }; - pxl8_gfx_texture new_light = pxl8_create_texture(gfx->renderer, &light_desc); - - gfx->target_stack[gfx->target_stack_depth] = (pxl8_target_entry){ - .color = new_color, - .depth = new_depth, - .light = new_light, - }; - gfx->target_stack_depth++; - - return true; + if (!gfx) return false; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_push_target(gfx->backend.cpu); + case PXL8_GFX_BACKEND_GPU: + return false; + } + return false; } void pxl8_gfx_pop_target(pxl8_gfx* gfx) { - if (!gfx || gfx->target_stack_depth <= 1) return; - - u32 top = gfx->target_stack_depth - 1; - pxl8_target_entry* entry = &gfx->target_stack[top]; - pxl8_target_entry* parent = &gfx->target_stack[top - 1]; - - gfx_composite_over(gfx, entry, parent); - - gfx->target_stack_depth--; - - pxl8_destroy_texture(gfx->renderer, entry->color); - pxl8_destroy_texture(gfx->renderer, entry->depth); - pxl8_destroy_texture(gfx->renderer, entry->light); + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_pop_target(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } } void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { @@ -1040,6 +797,9 @@ void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { if (!gfx->palette_cube) { gfx->palette_cube = pxl8_palette_cube_create(gfx->palette); + if (gfx->backend.cpu) { + pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube); + } } } @@ -1050,6 +810,9 @@ void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) { if (gfx->palette_cube) { pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette); + if (gfx->backend.cpu) { + pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube); + } } } @@ -1075,14 +838,20 @@ u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) { 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; -} +void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) { + if (!gfx || !params || count == 0) return; -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; + switch (effect) { + case PXL8_GFX_EFFECT_GLOWS: { + const pxl8_glow* glows = (const pxl8_glow*)params; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_render_glows(gfx->backend.cpu, glows, count); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } + break; + } + } } diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index 8a09a33..cd53b5c 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -10,7 +10,6 @@ #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, @@ -26,8 +25,6 @@ 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); @@ -65,11 +62,6 @@ 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_gfx3d.h b/src/gfx/pxl8_gfx3d.h index d5e2689..bf964c2 100644 --- a/src/gfx/pxl8_gfx3d.h +++ b/src/gfx/pxl8_gfx3d.h @@ -4,20 +4,39 @@ #include "pxl8_lights.h" #include "pxl8_math.h" #include "pxl8_mesh.h" -#include "pxl8_shader.h" #include "pxl8_types.h" typedef struct pxl8_bsp pxl8_bsp; typedef struct pxl8_gfx pxl8_gfx; +typedef struct pxl8_3d_uniforms { + u8 ambient; + pxl8_vec3 celestial_dir; + f32 celestial_intensity; + u8 fog_color; + f32 fog_density; + f32 time; +} pxl8_3d_uniforms; + +typedef struct pxl8_3d_frame_desc { + const pxl8_bsp* bsp; + const pxl8_3d_camera* camera; + const pxl8_lights* lights; + const pxl8_sdf* sdf; + pxl8_3d_uniforms uniforms; +} pxl8_3d_frame_desc; + typedef struct pxl8_3d_frame { const pxl8_bsp* bsp; pxl8_vec3 camera_dir; pxl8_vec3 camera_pos; f32 far_clip; + const pxl8_light* lights; + u32 lights_count; f32 near_clip; pxl8_mat4 projection; - pxl8_shader_uniforms uniforms; + const pxl8_sdf* sdf; + pxl8_3d_uniforms uniforms; pxl8_mat4 view; } pxl8_3d_frame; @@ -26,7 +45,8 @@ extern "C" { #endif void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp); -void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms); +void pxl8_3d_set_sdf(pxl8_gfx* gfx, const pxl8_sdf* sdf); +void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms); void pxl8_3d_clear(pxl8_gfx* gfx, u8 color); void pxl8_3d_clear_depth(pxl8_gfx* gfx); void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); @@ -37,7 +57,7 @@ const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx); u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform); -pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms); +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms); #ifdef __cplusplus } diff --git a/src/gfx/pxl8_lights.c b/src/gfx/pxl8_lights.c index 17bffc5..429ef14 100644 --- a/src/gfx/pxl8_lights.c +++ b/src/gfx/pxl8_lights.c @@ -48,36 +48,6 @@ void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, l->radius = radius; l->radius_sq = radius_sq; l->inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f; - - l->constant = 1.0f; - if (radius <= 7.0f) { - l->linear = 0.7f; - l->quadratic = 1.8f; - } else if (radius <= 13.0f) { - l->linear = 0.35f; - l->quadratic = 0.44f; - } else if (radius <= 20.0f) { - l->linear = 0.22f; - l->quadratic = 0.20f; - } else if (radius <= 32.0f) { - l->linear = 0.14f; - l->quadratic = 0.07f; - } else if (radius <= 50.0f) { - l->linear = 0.09f; - l->quadratic = 0.032f; - } else if (radius <= 65.0f) { - l->linear = 0.07f; - l->quadratic = 0.017f; - } else if (radius <= 100.0f) { - l->linear = 0.045f; - l->quadratic = 0.0075f; - } else if (radius <= 160.0f) { - l->linear = 0.027f; - l->quadratic = 0.0028f; - } else { - l->linear = 0.022f; - l->quadratic = 0.0019f; - } } void pxl8_lights_clear(pxl8_lights* lights) { diff --git a/src/gfx/pxl8_lights.h b/src/gfx/pxl8_lights.h index 2be0c6b..4a445bd 100644 --- a/src/gfx/pxl8_lights.h +++ b/src/gfx/pxl8_lights.h @@ -12,9 +12,6 @@ typedef struct pxl8_light { u8 intensity; f32 radius; f32 radius_sq; - f32 constant; - f32 linear; - f32 quadratic; } pxl8_light; typedef struct pxl8_lights pxl8_lights; diff --git a/src/gfx/pxl8_mesh.h b/src/gfx/pxl8_mesh.h index ddc8b4f..cb85ab0 100644 --- a/src/gfx/pxl8_mesh.h +++ b/src/gfx/pxl8_mesh.h @@ -31,8 +31,10 @@ typedef struct pxl8_gfx_material { bool dither; bool double_sided; bool dynamic_lighting; - bool emissive; bool per_pixel; + bool vertex_color_passthrough; + bool wireframe; + f32 emissive_intensity; } pxl8_gfx_material; typedef struct pxl8_vertex { diff --git a/src/gfx/pxl8_render.c b/src/gfx/pxl8_render.c deleted file mode 100644 index 3a5e355..0000000 --- a/src/gfx/pxl8_render.c +++ /dev/null @@ -1,1502 +0,0 @@ -#include "pxl8_render.h" -#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" -#include "pxl8_shader.h" - -#include -#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; - pxl8_vec3 normal; - f32 u, v; - u8 color; - u8 light; -} raster_vertex; - -typedef struct { - pxl8_vec3 p0, p1, p2; - pxl8_vec3 w_recip; - pxl8_vec3 u_w, v_w; - pxl8_vec3 l_w; - pxl8_vec3 c_w; - pxl8_vec3 world0_w, world1_w, world2_w; - pxl8_vec3 normal; - 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) { - 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( - const raster_vertex* v0, const raster_vertex* v1, const raster_vertex* v2, - f32 near, raster_vertex out[6] -) { - bool in0 = v0->clip_pos.w >= near; - bool in1 = v1->clip_pos.w >= near; - bool in2 = v2->clip_pos.w >= near; - i32 count = in0 + in1 + in2; - - if (count == 0) return 0; - if (count == 3) { - out[0] = *v0; out[1] = *v1; out[2] = *v2; - return 3; - } - - if (count == 1) { - const raster_vertex *inside, *out_a, *out_b; - if (in0) { inside = v0; out_a = v1; out_b = v2; } - else if (in1) { inside = v1; out_a = v2; out_b = v0; } - else { inside = v2; out_a = v0; out_b = v1; } - - f32 t_a = (near - out_a->clip_pos.w) / (inside->clip_pos.w - out_a->clip_pos.w); - f32 t_b = (near - out_b->clip_pos.w) / (inside->clip_pos.w - out_b->clip_pos.w); - - out[0] = *inside; - out[1] = lerp_raster_vertex(out_a, inside, t_a); - out[2] = lerp_raster_vertex(out_b, inside, t_b); - return 3; - } - - const raster_vertex *outside, *in_a, *in_b; - if (!in0) { outside = v0; in_a = v1; in_b = v2; } - else if (!in1) { outside = v1; in_a = v2; in_b = v0; } - else { outside = v2; in_a = v0; in_b = v1; } - - f32 t_a = (near - outside->clip_pos.w) / (in_a->clip_pos.w - outside->clip_pos.w); - f32 t_b = (near - outside->clip_pos.w) / (in_b->clip_pos.w - outside->clip_pos.w); - - raster_vertex new_a = lerp_raster_vertex(outside, in_a, t_a); - raster_vertex new_b = lerp_raster_vertex(outside, in_b, t_b); - - out[0] = *in_a; out[1] = *in_b; out[2] = new_b; - out[3] = *in_a; out[4] = new_b; out[5] = new_a; - return 6; -} - -static bool setup_tri( - tri_setup* setup, - const raster_vertex* vo0, const raster_vertex* vo1, const raster_vertex* vo2, - 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 -) { - if (viewport_w == 0 || viewport_h == 0) return false; - - 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 = (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 = (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) { - 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}; - - if (setup->p0.y > setup->p1.y) { - pxl8_vec3 t = setup->p0; setup->p0 = setup->p1; setup->p1 = t; - const raster_vertex* tv = sorted[0]; sorted[0] = sorted[1]; sorted[1] = tv; - } - if (setup->p0.y > setup->p2.y) { - pxl8_vec3 t = setup->p0; setup->p0 = setup->p2; setup->p2 = t; - const raster_vertex* tv = sorted[0]; sorted[0] = sorted[2]; sorted[2] = tv; - } - if (setup->p1.y > setup->p2.y) { - pxl8_vec3 t = setup->p1; setup->p1 = setup->p2; setup->p2 = t; - const raster_vertex* tv = sorted[1]; sorted[1] = sorted[2]; sorted[2] = tv; - } - - f32 total_height = setup->p2.y - setup->p0.y; - if (total_height < 1.0f) return false; - - i32 y0_int = (i32)floorf(setup->p0.y); - i32 y2_int = (i32)ceilf(setup->p2.y) - 1; - 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; - setup->w_recip.z = 1.0f / sorted[2]->clip_pos.w; - - setup->u_w.x = sorted[0]->u * setup->w_recip.x; - setup->v_w.x = sorted[0]->v * setup->w_recip.x; - setup->u_w.y = sorted[1]->u * setup->w_recip.y; - setup->v_w.y = sorted[1]->v * setup->w_recip.y; - setup->u_w.z = sorted[2]->u * setup->w_recip.z; - setup->v_w.z = sorted[2]->v * setup->w_recip.z; - - setup->l_w.x = sorted[0]->light * setup->w_recip.x; - setup->l_w.y = sorted[1]->light * setup->w_recip.y; - setup->l_w.z = sorted[2]->light * setup->w_recip.z; - - setup->c_w.x = (f32)sorted[0]->color * setup->w_recip.x; - setup->c_w.y = (f32)sorted[1]->color * setup->w_recip.y; - setup->c_w.z = (f32)sorted[2]->color * setup->w_recip.z; - - setup->world0_w = pxl8_vec3_scale(sorted[0]->world_pos, setup->w_recip.x); - setup->world1_w = pxl8_vec3_scale(sorted[1]->world_pos, setup->w_recip.y); - setup->world2_w = pxl8_vec3_scale(sorted[2]->world_pos, setup->w_recip.z); - - setup->normal = sorted[0]->normal; - - setup->inv_total = 1.0f / total_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; -} - -static void rasterize_triangle( - const tri_setup* setup, - u8* fb, - u16* zb, - 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, - 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; - - f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; - f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; - f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; - f32 a_uw = setup->u_w.x + (setup->u_w.z - setup->u_w.x) * alpha; - f32 a_vw = setup->v_w.x + (setup->v_w.z - setup->v_w.x) * alpha; - f32 a_lw = setup->l_w.x + (setup->l_w.z - setup->l_w.x) * alpha; - f32 a_cw = setup->c_w.x + (setup->c_w.z - setup->c_w.x) * alpha; - f32 a_wxw = setup->world0_w.x + (setup->world2_w.x - setup->world0_w.x) * alpha; - f32 a_wyw = setup->world0_w.y + (setup->world2_w.y - setup->world0_w.y) * alpha; - f32 a_wzw = setup->world0_w.z + (setup->world2_w.z - setup->world0_w.z) * alpha; - - f32 bx, bz, b_wr, b_uw, b_vw, b_lw, b_cw, b_wxw, b_wyw, b_wzw; - bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; - f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); - if (segment_height < 0.001f) segment_height = 0.001f; - - f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; - if (beta < 0.0f) beta = 0.0f; - if (beta > 1.0f) beta = 1.0f; - - if (second_half) { - bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; - bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; - b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; - b_uw = setup->u_w.y + (setup->u_w.z - setup->u_w.y) * beta; - b_vw = setup->v_w.y + (setup->v_w.z - setup->v_w.y) * beta; - b_lw = setup->l_w.y + (setup->l_w.z - setup->l_w.y) * beta; - b_cw = setup->c_w.y + (setup->c_w.z - setup->c_w.y) * beta; - b_wxw = setup->world1_w.x + (setup->world2_w.x - setup->world1_w.x) * beta; - b_wyw = setup->world1_w.y + (setup->world2_w.y - setup->world1_w.y) * beta; - b_wzw = setup->world1_w.z + (setup->world2_w.z - setup->world1_w.z) * beta; - } else { - bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; - bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; - b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; - b_uw = setup->u_w.x + (setup->u_w.y - setup->u_w.x) * beta; - b_vw = setup->v_w.x + (setup->v_w.y - setup->v_w.x) * beta; - b_lw = setup->l_w.x + (setup->l_w.y - setup->l_w.x) * beta; - b_cw = setup->c_w.x + (setup->c_w.y - setup->c_w.x) * beta; - b_wxw = setup->world0_w.x + (setup->world1_w.x - setup->world0_w.x) * beta; - b_wyw = setup->world0_w.y + (setup->world1_w.y - setup->world0_w.y) * beta; - b_wzw = setup->world0_w.z + (setup->world1_w.z - setup->world0_w.z) * beta; - } - - f32 x_start_fp, x_end_fp, z_start, z_end; - f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end, lw_start, lw_end, cw_start, cw_end; - f32 wxw_start, wxw_end, wyw_start, wyw_end, wzw_start, wzw_end; - - if (ax <= bx) { - x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; - wr_start = a_wr; wr_end = b_wr; - uw_start = a_uw; uw_end = b_uw; - vw_start = a_vw; vw_end = b_vw; - lw_start = a_lw; lw_end = b_lw; - cw_start = a_cw; cw_end = b_cw; - wxw_start = a_wxw; wxw_end = b_wxw; - wyw_start = a_wyw; wyw_end = b_wyw; - wzw_start = a_wzw; wzw_end = b_wzw; - } else { - x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; - wr_start = b_wr; wr_end = a_wr; - uw_start = b_uw; uw_end = a_uw; - vw_start = b_vw; vw_end = a_vw; - lw_start = b_lw; lw_end = a_lw; - cw_start = b_cw; cw_end = a_cw; - wxw_start = b_wxw; wxw_end = a_wxw; - wyw_start = b_wyw; wyw_end = a_wyw; - wzw_start = b_wzw; wzw_end = a_wzw; - } - - i32 x_start = (i32)floorf(x_start_fp); - i32 x_end = (i32)ceilf(x_end_fp) - 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; - if (span_width < 1.0f) span_width = 1.0f; - f32 inv_width = 1.0f / span_width; - - f32 dz = (z_end - z_start) * inv_width; - f32 dwr = (wr_end - wr_start) * inv_width; - f32 duw = (uw_end - uw_start) * inv_width; - f32 dvw = (vw_end - vw_start) * inv_width; - f32 dlw = (lw_end - lw_start) * inv_width; - f32 dcw = (cw_end - cw_start) * inv_width; - f32 dwxw = (wxw_end - wxw_start) * inv_width; - f32 dwyw = (wyw_end - wyw_start) * inv_width; - f32 dwzw = (wzw_end - wzw_start) * inv_width; - - f32 skip = (f32)x_start + 0.5f - x_start_fp; - f32 z = z_start + dz * skip; - f32 wr = wr_start + dwr * skip; - f32 uw = uw_start + duw * skip; - f32 vw = vw_start + dvw * skip; - f32 lw = lw_start + dlw * skip; - f32 cw = cw_start + dcw * skip; - f32 wxw = wxw_start + dwxw * skip; - f32 wyw = wyw_start + dwyw * skip; - f32 wzw = wzw_start + dwzw * skip; - - u32 row_start = (u32)y * fb_width; - u8* prow = fb + row_start; - u16* zrow = zb + row_start; - u32* lrow = light_accum ? light_accum + row_start : NULL; - - i32 x = x_start; - while (x <= x_end) { - i32 span_end = x + SUBDIV - 1; - if (span_end > x_end) span_end = x_end; - i32 span_len = span_end - x + 1; - - f32 pw_start = pxl8_fast_rcp(wr); - f32 pw_end = pxl8_fast_rcp(wr + dwr * (f32)span_len); - - f32 u_start = uw * pw_start; - f32 v_start = vw * pw_start; - f32 u_end = (uw + duw * (f32)span_len) * pw_end; - f32 v_end = (vw + dvw * (f32)span_len) * pw_end; - - 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; - f32 wz_start = wzw * pw_start; - f32 wx_end = (wxw + dwxw * (f32)span_len) * pw_end; - f32 wy_end = (wyw + dwyw * (f32)span_len) * pw_end; - f32 wz_end = (wzw + dwzw * (f32)span_len) * pw_end; - - f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; - f32 du = (u_end - u_start) * inv_span; - f32 dv = (v_end - v_start) * inv_span; - f32 dl = (l_end_fp - l_start_fp) * inv_span; - f32 dc = (c_end_fp - c_start_fp) * inv_span; - f32 dwx = (wx_end - wx_start) * inv_span; - f32 dwy = (wy_end - wy_start) * inv_span; - f32 dwz = (wz_end - wz_start) * inv_span; - - f32 u_a = u_start; - f32 v_a = v_start; - f32 l_a = l_start_fp; - f32 c_a = c_start_fp; - f32 z_a = z; - f32 wx_a = wx_start; - f32 wy_a = wy_start; - f32 wz_a = wz_start; - - for (i32 px = x; px <= span_end; px++) { - f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f); - u16 z16 = (u16)(depth_norm * 65535.0f); - - 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_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, - .out_light_color = 0, - }; - - 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); - } - } - } - } - - u_a += du; - v_a += dv; - l_a += dl; - c_a += dc; - z_a += dz; - wx_a += dwx; - wy_a += dwy; - wz_a += dwz; - } - - wr += dwr * (f32)span_len; - uw += duw * (f32)span_len; - vw += dvw * (f32)span_len; - lw += dlw * (f32)span_len; - cw += dcw * (f32)span_len; - z += dz * (f32)span_len; - wxw += dwxw * (f32)span_len; - wyw += dwyw * (f32)span_len; - wzw += dwzw * (f32)span_len; - x = span_end + 1; - } - } -} - -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; - u16 generation; - bool active; -} bindings_slot; - -typedef struct { - void* data; - u32 size; - u32 append_pos; - pxl8_gfx_buffer_type type; - pxl8_gfx_usage usage; - u16 generation; - bool active; -} buffer_slot; - -typedef struct { - pxl8_gfx_pass_desc desc; - u16 generation; - 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 { - void* data; - u32 width; - u32 height; - pxl8_gfx_texture_format format; - pxl8_gfx_usage usage; - u16 generation; - bool active; -} texture_slot; - -struct pxl8_renderer { - u32 width; - u32 height; - - texture_slot textures[PXL8_GFX_MAX_TEXTURES]; - buffer_slot buffers[PXL8_GFX_MAX_BUFFERS]; - 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; - pxl8_gfx_cmd_draw_params current_draw_params; - - i32 viewport_x, viewport_y; - u32 viewport_w, viewport_h; - i32 scissor_x, scissor_y; - u32 scissor_w, scissor_h; - - pxl8_shader_fn shader; - pxl8_gfx_stats stats; -}; - -struct pxl8_gfx_cmdbuf { - pxl8_gfx_cmd* commands; - u32 capacity; - u32 count; -}; - -pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) { - pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer)); - r->width = width; - r->height = height; - r->viewport_w = width; - r->viewport_h = height; - r->scissor_w = width; - r->scissor_h = height; - pxl8_renderer_reset_stats(r); - return r; -} - -void pxl8_renderer_destroy(pxl8_renderer* r) { - if (!r) return; - for (u32 i = 0; i < PXL8_GFX_MAX_TEXTURES; i++) { - if (r->textures[i].data) pxl8_free(r->textures[i].data); - } - for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) { - if (r->buffers[i].data) pxl8_free(r->buffers[i].data); - } - pxl8_free(r); -} - -u32 pxl8_renderer_get_width(const pxl8_renderer* r) { - return r ? r->width : 0; -} - -u32 pxl8_renderer_get_height(const pxl8_renderer* r) { - return r ? r->height : 0; -} - -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; - case PXL8_GFX_FORMAT_DEPTH16: return w * h * 2; - case PXL8_GFX_FORMAT_LIGHT_ACCUM: return w * h * 4; - } - return 0; -} - -pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_desc* desc) { - for (u32 i = 0; i < PXL8_GFX_MAX_TEXTURES; i++) { - if (!r->textures[i].active) { - texture_slot* s = &r->textures[i]; - u32 size = texture_byte_size(desc->format, desc->width, desc->height); - s->data = pxl8_malloc(size); - if (desc->data.ptr && desc->data.size >= size) { - memcpy(s->data, desc->data.ptr, size); - } else { - memset(s->data, 0, size); - } - s->width = desc->width; - s->height = desc->height; - s->format = desc->format; - s->usage = desc->usage; - s->generation = NEXT_GEN(s->generation); - s->active = true; - return (pxl8_gfx_texture){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of texture slots"); - return (pxl8_gfx_texture){ PXL8_GFX_INVALID_ID }; -} - -pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc) { - for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) { - if (!r->buffers[i].active) { - buffer_slot* s = &r->buffers[i]; - u32 capacity = desc->capacity > 0 ? desc->capacity : desc->data.size; - s->data = pxl8_malloc(capacity); - if (desc->data.ptr && desc->data.size > 0) { - memcpy(s->data, desc->data.ptr, desc->data.size); - if (capacity > desc->data.size) { - memset((u8*)s->data + desc->data.size, 0, capacity - desc->data.size); - } - } else { - memset(s->data, 0, capacity); - } - s->size = capacity; - s->append_pos = 0; - s->type = desc->type; - s->usage = desc->usage; - s->generation = NEXT_GEN(s->generation); - s->active = true; - return (pxl8_gfx_buffer){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of buffer slots"); - return (pxl8_gfx_buffer){ 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 = NEXT_GEN(s->generation); - s->active = true; - return (pxl8_gfx_pipeline){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of pipeline slots"); - return (pxl8_gfx_pipeline){ PXL8_GFX_INVALID_ID }; -} - -pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc) { - for (u32 i = 0; i < PXL8_GFX_MAX_BINDINGS; i++) { - if (!r->bindings[i].active) { - bindings_slot* s = &r->bindings[i]; - s->desc = *desc; - s->generation = NEXT_GEN(s->generation); - s->active = true; - return (pxl8_gfx_bindings){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of bindings slots"); - return (pxl8_gfx_bindings){ PXL8_GFX_INVALID_ID }; -} - -pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc) { - for (u32 i = 0; i < PXL8_GFX_MAX_PASSES; i++) { - if (!r->passes[i].active) { - pass_slot* s = &r->passes[i]; - s->desc = *desc; - s->generation = NEXT_GEN(s->generation); - s->active = true; - return (pxl8_gfx_pass){ MAKE_ID(i, s->generation) }; - } - } - pxl8_error("Out of pass slots"); - return (pxl8_gfx_pass){ PXL8_GFX_INVALID_ID }; -} - -#define VALID_TEX(r, h) (SLOT_INDEX((h).id) < PXL8_GFX_MAX_TEXTURES && \ - r->textures[SLOT_INDEX((h).id)].active && \ - r->textures[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id)) - -#define VALID_BUF(r, h) (SLOT_INDEX((h).id) < PXL8_GFX_MAX_BUFFERS && \ - r->buffers[SLOT_INDEX((h).id)].active && \ - r->buffers[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id)) - -#define VALID_PASS(r, h) (SLOT_INDEX((h).id) < PXL8_GFX_MAX_PASSES && \ - r->passes[SLOT_INDEX((h).id)].active && \ - r->passes[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id)) - -#define VALID_PIPELINE(r, h) (SLOT_INDEX((h).id) < PXL8_GFX_MAX_PIPELINES && \ - r->pipelines[SLOT_INDEX((h).id)].active && \ - r->pipelines[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id)) - -#define VALID_BINDINGS(r, h) (SLOT_INDEX((h).id) < PXL8_GFX_MAX_BINDINGS && \ - r->bindings[SLOT_INDEX((h).id)].active && \ - r->bindings[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id)) - -void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex) { - if (!VALID_TEX(r, tex)) return; - texture_slot* s = &r->textures[SLOT_INDEX(tex.id)]; - pxl8_free(s->data); - s->data = NULL; - s->active = false; -} - -void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf) { - if (!VALID_BUF(r, buf)) return; - buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)]; - pxl8_free(s->data); - s->data = NULL; - s->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)) { - r->pipelines[idx].active = false; - } -} - -void pxl8_destroy_bindings(pxl8_renderer* r, pxl8_gfx_bindings bnd) { - u32 idx = SLOT_INDEX(bnd.id); - if (idx < PXL8_GFX_MAX_BINDINGS && r->bindings[idx].generation == SLOT_GEN(bnd.id)) { - r->bindings[idx].active = false; - } -} - -void pxl8_destroy_pass(pxl8_renderer* r, pxl8_gfx_pass pass) { - u32 idx = SLOT_INDEX(pass.id); - if (idx < PXL8_GFX_MAX_PASSES && r->passes[idx].generation == SLOT_GEN(pass.id)) { - r->passes[idx].active = false; - } -} - -void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) { - if (!VALID_BUF(r, buf)) return; - buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)]; - u32 copy_size = data->size < s->size ? data->size : s->size; - memcpy(s->data, data->ptr, copy_size); -} - -i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) { - if (!VALID_BUF(r, buf)) return -1; - buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)]; - if (s->append_pos + data->size > s->size) return -1; - i32 offset = (i32)s->append_pos; - memcpy((u8*)s->data + s->append_pos, data->ptr, data->size); - s->append_pos += data->size; - return offset; -} - -void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h) { - if (!VALID_TEX(r, tex)) return; - texture_slot* s = &r->textures[SLOT_INDEX(tex.id)]; - u32 bpp = (s->format == PXL8_GFX_FORMAT_INDEXED8) ? 1 : - (s->format == PXL8_GFX_FORMAT_DEPTH16) ? 2 : 4; - const u8* src = data->ptr; - u8* dst = (u8*)s->data + (y * s->width + x) * bpp; - for (u32 row = 0; row < h; row++) { - memcpy(dst, src, w * bpp); - src += w * bpp; - dst += s->width * bpp; - } -} - -void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf) { - if (!VALID_BUF(r, buf)) return NULL; - return r->buffers[SLOT_INDEX(buf.id)].data; -} - -u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) { - if (!VALID_BUF(r, buf)) return 0; - return r->buffers[SLOT_INDEX(buf.id)].size; -} - -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_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_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) { - if (!VALID_TEX(r, tex)) return 0; - return r->textures[SLOT_INDEX(tex.id)].height; -} - -pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex) { - if (!VALID_TEX(r, tex)) return PXL8_GFX_FORMAT_INDEXED8; - return r->textures[SLOT_INDEX(tex.id)].format; -} - -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)); - cb->capacity = capacity; - cb->count = 0; - return cb; -} - -void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb) { - if (!cb) return; - pxl8_free(cb->commands); - pxl8_free(cb); -} - -void pxl8_cmdbuf_reset(pxl8_gfx_cmdbuf* cb) { - if (cb) { - cb->count = 0; - } -} - -static pxl8_gfx_cmd* cmd_alloc(pxl8_gfx_cmdbuf* cb) { - if (cb->count >= cb->capacity) { - cb->capacity *= 2; - cb->commands = pxl8_realloc(cb->commands, cb->capacity * sizeof(pxl8_gfx_cmd)); - } - return &cb->commands[cb->count++]; -} - -void pxl8_begin_pass(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pass pass) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_BEGIN_PASS; - cmd->begin_pass.pass = pass; -} - -void pxl8_end_pass(pxl8_gfx_cmdbuf* cb) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_END_PASS; -} - -void pxl8_set_pipeline(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pipeline pipeline) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_SET_PIPELINE; - cmd->set_pipeline.pipeline = pipeline; -} - -void pxl8_set_bindings(pxl8_gfx_cmdbuf* cb, pxl8_gfx_bindings bindings) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_SET_BINDINGS; - cmd->set_bindings.bindings = bindings; -} - -void pxl8_set_viewport(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_SET_VIEWPORT; - cmd->set_viewport.x = x; - cmd->set_viewport.y = y; - cmd->set_viewport.w = w; - cmd->set_viewport.h = h; -} - -void pxl8_set_scissor(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_SET_SCISSOR; - cmd->set_scissor.x = x; - cmd->set_scissor.y = y; - cmd->set_scissor.w = w; - cmd->set_scissor.h = h; -} - -void pxl8_set_draw_params(pxl8_gfx_cmdbuf* cb, const pxl8_gfx_cmd_draw_params* p) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_SET_DRAW_PARAMS; - cmd->draw_params = *p; -} - -void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32 first, u32 count, u32 base_vertex) { - pxl8_gfx_cmd* cmd = cmd_alloc(cb); - cmd->type = PXL8_GFX_CMD_DRAW; - cmd->draw.vertex_buffer = vb; - cmd->draw.index_buffer = ib; - cmd->draw.first_index = first; - cmd->draw.index_count = count; - cmd->draw.base_vertex = base_vertex; -} - -static void execute_draw( - pxl8_renderer* r, - const pxl8_gfx_cmd_draw* cmd -) { - if (!VALID_BUF(r, cmd->vertex_buffer)) return; - bool use_indices = pxl8_gfx_handle_valid(cmd->index_buffer) && VALID_BUF(r, cmd->index_buffer); - 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)]; - pipeline_slot* pip = &r->pipelines[SLOT_INDEX(r->current_pipeline.id)]; - - 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; - } - - texture_slot* color_tex = &r->textures[SLOT_INDEX(pass->desc.color.texture.id)]; - texture_slot* depth_tex = &r->textures[SLOT_INDEX(pass->desc.depth.texture.id)]; - - u8* fb = color_tex->data; - u16* zb = depth_tex->data; - 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)]; - light_accum = light_tex->data; - } - - const pxl8_vertex* vertices = vb->data; - const u16* indices = use_indices ? ib->data : NULL; - - pxl8_mat4 mv = pxl8_mat4_multiply(r->current_draw_params.view, r->current_draw_params.model); - pxl8_mat4 mvp = pxl8_mat4_multiply(r->current_draw_params.projection, mv); - f32 near = 0.1f; - - pxl8_shader_fn shader = pip->desc.shader; - 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; - shader_uniforms.dither = pip->desc.dither; - shader_uniforms.emissive = pip->desc.emissive; - - if (VALID_BINDINGS(r, r->current_bindings)) { - bindings_slot* bnd = &r->bindings[SLOT_INDEX(r->current_bindings.id)]; - shader_bindings.colormap = (const u8*)bnd->desc.colormap; - shader_bindings.palette = bnd->desc.palette; - - 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; - i0 = indices[i] + cmd->base_vertex; - i1 = indices[i + 1] + cmd->base_vertex; - i2 = indices[i + 2] + cmd->base_vertex; - } else { - i0 = (u16)(i + cmd->base_vertex); - i1 = (u16)(i + 1 + cmd->base_vertex); - i2 = (u16)(i + 2 + cmd->base_vertex); - } - - if (i0 >= vb->size / sizeof(pxl8_vertex)) continue; - if (i1 >= vb->size / sizeof(pxl8_vertex)) continue; - if (i2 >= vb->size / sizeof(pxl8_vertex)) continue; - - const pxl8_vertex* v0 = &vertices[i0]; - const pxl8_vertex* v1 = &vertices[i1]; - const pxl8_vertex* v2 = &vertices[i2]; - - raster_vertex rv0, rv1, rv2; - - pxl8_vec4 p0 = {v0->position.x, v0->position.y, v0->position.z, 1.0f}; - pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f}; - pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f}; - - rv0.clip_pos = pxl8_mat4_multiply_vec4(mvp, p0); - rv1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1); - rv2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2); - - 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.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.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)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)((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; - 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], - 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; - } - - 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_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) { - case PXL8_GFX_CMD_BEGIN_PASS: - r->current_pass = cmd->begin_pass.pass; - if (VALID_PASS(r, cmd->begin_pass.pass)) { - pass_slot* p = &r->passes[SLOT_INDEX(cmd->begin_pass.pass.id)]; - if (p->desc.color.load == PXL8_GFX_LOAD_CLEAR) { - pxl8_clear(r, p->desc.color.texture, p->desc.color.clear_value); - } - if (p->desc.depth.load == PXL8_GFX_LOAD_CLEAR) { - pxl8_clear_depth(r, p->desc.depth.texture); - } - if (p->desc.light_accum.load == PXL8_GFX_LOAD_CLEAR) { - pxl8_clear_light(r, p->desc.light_accum.texture); - } - } - break; - case PXL8_GFX_CMD_END_PASS: - r->current_pass = (pxl8_gfx_pass){ PXL8_GFX_INVALID_ID }; - break; - case PXL8_GFX_CMD_SET_PIPELINE: - r->current_pipeline = cmd->set_pipeline.pipeline; - break; - case PXL8_GFX_CMD_SET_BINDINGS: - r->current_bindings = cmd->set_bindings.bindings; - break; - case PXL8_GFX_CMD_SET_VIEWPORT: - r->viewport_x = cmd->set_viewport.x; - r->viewport_y = cmd->set_viewport.y; - r->viewport_w = cmd->set_viewport.w; - r->viewport_h = cmd->set_viewport.h; - break; - case PXL8_GFX_CMD_SET_SCISSOR: - r->scissor_x = cmd->set_scissor.x; - r->scissor_y = cmd->set_scissor.y; - r->scissor_w = cmd->set_scissor.w; - r->scissor_h = cmd->set_scissor.h; - break; - case PXL8_GFX_CMD_SET_DRAW_PARAMS: - r->current_draw_params = cmd->draw_params; - break; - case PXL8_GFX_CMD_DRAW: - execute_draw(r, &cmd->draw); - break; - case PXL8_GFX_CMD_RESOLVE: - break; - } - } - - for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) { - if (r->buffers[i].active && r->buffers[i].usage == PXL8_GFX_USAGE_STREAM) { - r->buffers[i].append_pos = 0; - } - } - STATS_ADD(&r->stats, submit_ns, submit_start); -} - -void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format == PXL8_GFX_FORMAT_INDEXED8) { - memset(s->data, color, s->width * s->height); - } -} - -void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format == PXL8_GFX_FORMAT_DEPTH16) { - memset(s->data, 0xFF, s->width * s->height * 2); - } -} - -void pxl8_clear_light(pxl8_renderer* r, pxl8_gfx_texture target) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format == PXL8_GFX_FORMAT_LIGHT_ACCUM) { - memset(s->data, 0, s->width * s->height * 4); - } -} - -void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (x < 0 || y < 0 || (u32)x >= s->width || (u32)y >= s->height) return; - if (s->format == PXL8_GFX_FORMAT_INDEXED8) { - ((u8*)s->data)[y * s->width + x] = color; - } -} - -u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y) { - if (!VALID_TEX(r, target)) return 0; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (x < 0 || y < 0 || (u32)x >= s->width || (u32)y >= s->height) return 0; - if (s->format == PXL8_GFX_FORMAT_INDEXED8) { - return ((u8*)s->data)[y * s->width + x]; - } - return 0; -} - -void pxl8_draw_line(pxl8_renderer* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format != PXL8_GFX_FORMAT_INDEXED8) return; - - u8* fb = s->data; - i32 w = (i32)s->width; - i32 h = (i32)s->height; - - 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 >= 0 && x0 < w && y0 >= 0 && y0 < h) { - fb[y0 * w + x0] = color; - } - if (x0 == x1 && y0 == y1) break; - i32 e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } -} - -void pxl8_draw_rect(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) { - pxl8_draw_line(r, target, x, y, x + w - 1, y, color); - pxl8_draw_line(r, target, x + w - 1, y, x + w - 1, y + h - 1, color); - pxl8_draw_line(r, target, x + w - 1, y + h - 1, x, y + h - 1, color); - pxl8_draw_line(r, target, x, y + h - 1, x, y, color); -} - -void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format != PXL8_GFX_FORMAT_INDEXED8) return; - - u8* fb = s->data; - i32 tw = (i32)s->width; - i32 th = (i32)s->height; - - i32 x0 = x < 0 ? 0 : x; - i32 y0 = y < 0 ? 0 : y; - i32 x1 = x + w > tw ? tw : x + w; - i32 y1 = y + h > th ? th : y + h; - - for (i32 py = y0; py < y1; py++) { - memset(&fb[py * tw + x0], color, (size_t)(x1 - x0)); - } -} - -void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format != PXL8_GFX_FORMAT_INDEXED8) return; - - u8* fb = s->data; - i32 w = (i32)s->width; - i32 h = (i32)s->height; - - i32 px = 0, py = radius; - i32 d = 3 - 2 * radius; - - #define PLOT(xx, yy) if ((xx) >= 0 && (xx) < w && (yy) >= 0 && (yy) < h) fb[(yy) * w + (xx)] = color - - while (py >= px) { - PLOT(cx + px, cy + py); PLOT(cx - px, cy + py); - PLOT(cx + px, cy - py); PLOT(cx - px, cy - py); - PLOT(cx + py, cy + px); PLOT(cx - py, cy + px); - PLOT(cx + py, cy - px); PLOT(cx - py, cy - px); - px++; - if (d > 0) { py--; d += 4 * (px - py) + 10; } - else { d += 4 * px + 6; } - } - #undef PLOT -} - -void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) { - if (!VALID_TEX(r, target)) return; - texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; - if (s->format != PXL8_GFX_FORMAT_INDEXED8) return; - - u8* fb = s->data; - i32 w = (i32)s->width; - i32 h = (i32)s->height; - - i32 r2 = radius * radius; - for (i32 y = -radius; y <= radius; y++) { - i32 py = cy + y; - if (py < 0 || py >= h) continue; - i32 hspan = (i32)sqrtf((f32)(r2 - y * y)); - i32 x0 = cx - hspan; - i32 x1 = cx + hspan; - if (x0 < 0) x0 = 0; - if (x1 >= w) x1 = w - 1; - if (x0 <= x1) { - memset(&fb[py * w + x0], color, (size_t)(x1 - x0 + 1)); - } - } -} - -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)]; - - u8* fb = cs->data; - u32 w = cs->width; - u32 h = cs->height; - - u32* light_data = NULL; - if (light_accum.id != PXL8_GFX_INVALID_ID && VALID_TEX(r, light_accum)) { - light_data = r->textures[SLOT_INDEX(light_accum.id)].data; - } - - for (u32 i = 0; i < w * h; i++) { - u8 idx = fb[i]; - u32 base = palette[idx]; - - if (light_data) { - u32 lv = light_data[i]; - u32 la = lv >> 24; - if (la > 0) { - i32 br = base & 0xFF; - i32 bg = (base >> 8) & 0xFF; - i32 bb = (base >> 16) & 0xFF; - - i32 lr = lv & 0xFF; - i32 lg = (lv >> 8) & 0xFF; - i32 lb = (lv >> 16) & 0xFF; - - f32 t = (f32)la / 255.0f; - br += (i32)((f32)(lr - 128) * t * 2.0f); - bg += (i32)((f32)(lg - 128) * t * 2.0f); - bb += (i32)((f32)(lb - 128) * t * 2.0f); - - 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; - } - } - - output[i] = base | 0xFF000000; - } -} diff --git a/src/gfx/pxl8_render.h b/src/gfx/pxl8_render.h deleted file mode 100644 index 906def2..0000000 --- a/src/gfx/pxl8_render.h +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include "pxl8_colormap.h" -#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 - -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); - -pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc); -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_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_texture(pxl8_renderer* r, pxl8_gfx_texture tex); - -void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data); -i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data); -void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h); - -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_get_data(pxl8_renderer* r, pxl8_gfx_texture tex); -u32 pxl8_texture_get_width(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); - -pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity); -void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb); -void pxl8_cmdbuf_reset(pxl8_gfx_cmdbuf* cb); - -void pxl8_begin_pass(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pass pass); -void pxl8_end_pass(pxl8_gfx_cmdbuf* cb); -void pxl8_set_bindings(pxl8_gfx_cmdbuf* cb, pxl8_gfx_bindings bindings); -void pxl8_set_draw_params(pxl8_gfx_cmdbuf* cb, const pxl8_gfx_cmd_draw_params* p); -void pxl8_set_pipeline(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pipeline pipeline); -void pxl8_set_scissor(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h); -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_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb); - -void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color); -void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target); -void pxl8_clear_light(pxl8_renderer* r, pxl8_gfx_texture target); - -void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color); -u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y); -void pxl8_draw_line(pxl8_renderer* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color); -void pxl8_draw_rect(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color); -void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color); -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_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, pxl8_gfx_texture light_accum, - const u32* palette, u32* output); - -#ifdef __cplusplus -} -#endif diff --git a/src/gfx/pxl8_render_types.h b/src/gfx/pxl8_render_types.h deleted file mode 100644 index d100c26..0000000 --- a/src/gfx/pxl8_render_types.h +++ /dev/null @@ -1,242 +0,0 @@ -#pragma once - -#include "pxl8_atlas.h" -#include "pxl8_colormap.h" -#include "pxl8_lights.h" -#include "pxl8_math.h" -#include "pxl8_shader.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define PXL8_GFX_MAX_BINDINGS 256 -#define PXL8_GFX_MAX_BUFFERS 512 -#define PXL8_GFX_MAX_PASSES 32 -#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_pipeline; -typedef struct { u32 id; } pxl8_gfx_bindings; -typedef struct { u32 id; } pxl8_gfx_pass; - -#define PXL8_GFX_INVALID_ID (0) -#define pxl8_gfx_handle_valid(h) ((h).id != PXL8_GFX_INVALID_ID) - -typedef struct pxl8_gfx_range { - const void* ptr; - u32 size; -} pxl8_gfx_range; - -#define PXL8_GFX_RANGE(x) ((pxl8_gfx_range){ .ptr = &(x), .size = sizeof(x) }) -#define PXL8_GFX_RANGE_REF(p, sz) ((pxl8_gfx_range){ .ptr = (p), .size = (sz) }) - -typedef enum pxl8_gfx_buffer_type { - PXL8_GFX_BUFFER_VERTEX, - PXL8_GFX_BUFFER_INDEX, -} pxl8_gfx_buffer_type; - -typedef enum pxl8_gfx_usage { - PXL8_GFX_USAGE_IMMUTABLE, - PXL8_GFX_USAGE_DYNAMIC, - PXL8_GFX_USAGE_STREAM, -} pxl8_gfx_usage; - -typedef enum pxl8_gfx_texture_format { - PXL8_GFX_FORMAT_INDEXED8, - PXL8_GFX_FORMAT_DEPTH16, - PXL8_GFX_FORMAT_LIGHT_ACCUM, -} pxl8_gfx_texture_format; - -typedef enum pxl8_gfx_cull_mode { - PXL8_GFX_CULL_NONE, - PXL8_GFX_CULL_BACK, - PXL8_GFX_CULL_FRONT, -} pxl8_gfx_cull_mode; - -typedef enum pxl8_gfx_fill_mode { - PXL8_GFX_FILL_SOLID, - PXL8_GFX_FILL_WIREFRAME, -} pxl8_gfx_fill_mode; - -typedef enum pxl8_gfx_compare_func { - PXL8_GFX_COMPARE_NEVER, - PXL8_GFX_COMPARE_LESS, - PXL8_GFX_COMPARE_EQUAL, - PXL8_GFX_COMPARE_LEQUAL, - PXL8_GFX_COMPARE_GREATER, - PXL8_GFX_COMPARE_NOTEQUAL, - PXL8_GFX_COMPARE_GEQUAL, - PXL8_GFX_COMPARE_ALWAYS, -} pxl8_gfx_compare_func; - -typedef enum pxl8_gfx_blend_factor { - PXL8_GFX_BLEND_ZERO, - PXL8_GFX_BLEND_ONE, - PXL8_GFX_BLEND_SRC_ALPHA, - PXL8_GFX_BLEND_INV_SRC_ALPHA, - PXL8_GFX_BLEND_DST_ALPHA, - PXL8_GFX_BLEND_INV_DST_ALPHA, -} pxl8_gfx_blend_factor; - -typedef enum pxl8_gfx_load_op { - PXL8_GFX_LOAD_CLEAR, - PXL8_GFX_LOAD_LOAD, - PXL8_GFX_LOAD_DONT_CARE, -} pxl8_gfx_load_op; - -typedef enum pxl8_gfx_store_op { - PXL8_GFX_STORE_STORE, - PXL8_GFX_STORE_DONT_CARE, -} pxl8_gfx_store_op; - -typedef struct pxl8_gfx_buffer_desc { - pxl8_gfx_range data; - u32 capacity; - pxl8_gfx_buffer_type type; - pxl8_gfx_usage usage; -} pxl8_gfx_buffer_desc; - -typedef struct pxl8_gfx_texture_desc { - u32 width; - u32 height; - pxl8_gfx_texture_format format; - pxl8_gfx_range data; - pxl8_gfx_usage usage; - bool render_target; -} pxl8_gfx_texture_desc; - -typedef pxl8_shader_fn pxl8_gfx_shader; - -typedef struct pxl8_gfx_pipeline_desc { - struct { - bool enabled; - pxl8_gfx_blend_factor src; - pxl8_gfx_blend_factor dst; - bool alpha_test; - u8 alpha_ref; - } blend; - - struct { - bool test; - bool write; - pxl8_gfx_compare_func compare; - } depth; - - bool dither; - bool double_sided; - bool emissive; - - struct { - pxl8_gfx_cull_mode cull; - pxl8_gfx_fill_mode fill; - } rasterizer; - - pxl8_gfx_shader shader; -} pxl8_gfx_pipeline_desc; - -typedef struct pxl8_gfx_bindings_desc { - 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 { - pxl8_gfx_texture texture; - pxl8_gfx_load_op load; - pxl8_gfx_store_op store; - u8 clear_value; -} pxl8_gfx_pass_color_attachment; - -typedef struct pxl8_gfx_pass_depth_attachment { - pxl8_gfx_texture texture; - pxl8_gfx_load_op load; - u16 clear_value; -} pxl8_gfx_pass_depth_attachment; - -typedef struct pxl8_gfx_pass_light_attachment { - pxl8_gfx_texture texture; - pxl8_gfx_load_op load; -} pxl8_gfx_pass_light_attachment; - -typedef struct pxl8_gfx_pass_desc { - pxl8_gfx_pass_color_attachment color; - pxl8_gfx_pass_depth_attachment depth; - pxl8_gfx_pass_light_attachment light_accum; -} pxl8_gfx_pass_desc; - -typedef struct pxl8_gfx_cmd_draw_params { - pxl8_mat4 model; - pxl8_mat4 projection; - pxl8_shader_uniforms shader; - pxl8_mat4 view; -} pxl8_gfx_cmd_draw_params; - -typedef enum pxl8_gfx_cmd_type { - PXL8_GFX_CMD_BEGIN_PASS, - PXL8_GFX_CMD_END_PASS, - PXL8_GFX_CMD_SET_PIPELINE, - PXL8_GFX_CMD_SET_BINDINGS, - PXL8_GFX_CMD_SET_VIEWPORT, - PXL8_GFX_CMD_SET_SCISSOR, - PXL8_GFX_CMD_SET_DRAW_PARAMS, - PXL8_GFX_CMD_DRAW, - PXL8_GFX_CMD_RESOLVE, -} pxl8_gfx_cmd_type; - -typedef struct pxl8_gfx_cmd_begin_pass { - pxl8_gfx_pass pass; -} pxl8_gfx_cmd_begin_pass; - -typedef struct pxl8_gfx_cmd_set_pipeline { - pxl8_gfx_pipeline pipeline; -} pxl8_gfx_cmd_set_pipeline; - -typedef struct pxl8_gfx_cmd_set_bindings { - pxl8_gfx_bindings bindings; -} pxl8_gfx_cmd_set_bindings; - -typedef struct pxl8_gfx_cmd_set_viewport { - i32 x, y; - u32 w, h; -} pxl8_gfx_cmd_set_viewport; - -typedef struct pxl8_gfx_cmd_set_scissor { - i32 x, y; - u32 w, h; -} pxl8_gfx_cmd_set_scissor; - -typedef struct pxl8_gfx_cmd_draw { - pxl8_gfx_buffer vertex_buffer; - pxl8_gfx_buffer index_buffer; - u32 base_vertex; - u32 first_index; - u32 index_count; -} pxl8_gfx_cmd_draw; - -typedef struct pxl8_gfx_cmd_resolve { - pxl8_gfx_texture src; - u32* output; -} pxl8_gfx_cmd_resolve; - -typedef struct pxl8_gfx_cmd { - pxl8_gfx_cmd_type type; - union { - pxl8_gfx_cmd_begin_pass begin_pass; - pxl8_gfx_cmd_set_pipeline set_pipeline; - pxl8_gfx_cmd_set_bindings set_bindings; - pxl8_gfx_cmd_set_viewport set_viewport; - pxl8_gfx_cmd_set_scissor set_scissor; - pxl8_gfx_cmd_draw draw; - pxl8_gfx_cmd_draw_params draw_params; - pxl8_gfx_cmd_resolve resolve; - }; -} pxl8_gfx_cmd; - -#ifdef __cplusplus -} -#endif diff --git a/src/gfx/pxl8_shader.h b/src/gfx/pxl8_shader.h deleted file mode 100644 index 3241a21..0000000 --- a/src/gfx/pxl8_shader.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include "pxl8_lights.h" -#include "pxl8_math.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct pxl8_vertex_in { - pxl8_vec3 a_position; - pxl8_vec3 a_normal; - pxl8_vec2 a_uv; - u8 a_color; - u8 a_light; -} pxl8_vertex_in; - -typedef struct pxl8_vertex_out { - pxl8_vec4 gl_Position; - pxl8_vec2 v_uv; - pxl8_vec3 v_world; - pxl8_vec3 v_normal; - f32 v_light; - f32 v_depth; -} pxl8_vertex_out; - -typedef struct pxl8_shader_uniforms { - u8 ambient; - bool baked_lighting; - pxl8_vec3 celestial_dir; - f32 celestial_intensity; - bool dither; - bool dynamic_lighting; - bool emissive; - u8 fog_color; - f32 fog_density; - const pxl8_light* lights; - u32 lights_count; - bool textures; - f32 time; -} pxl8_shader_uniforms; - -typedef struct pxl8_shader_bindings { - const u8* colormap; - const u32* palette; - 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; - u32 out_light_color; -} pxl8_shader_ctx; - -typedef u8 (*pxl8_shader_fn)( - pxl8_shader_ctx* ctx, - const pxl8_shader_bindings* bindings, - const pxl8_shader_uniforms* uniforms -); - -#ifdef __cplusplus -} -#endif diff --git a/src/gfx/pxl8_shader_builtins.h b/src/gfx/pxl8_shader_builtins.h deleted file mode 100644 index abde7a5..0000000 --- a/src/gfx/pxl8_shader_builtins.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include "pxl8_dither.h" -#include "pxl8_math.h" -#include "pxl8_shader.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -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}; } -static inline pxl8_vec2 pxl8_swizzle_yz(pxl8_vec4 v) { return (pxl8_vec2){v.y, v.z}; } -static inline pxl8_vec2 pxl8_swizzle_yw(pxl8_vec4 v) { return (pxl8_vec2){v.y, v.w}; } -static inline pxl8_vec2 pxl8_swizzle_zw(pxl8_vec4 v) { return (pxl8_vec2){v.z, v.w}; } - -static inline pxl8_vec3 pxl8_swizzle_xyz(pxl8_vec4 v) { return (pxl8_vec3){v.x, v.y, v.z}; } -static inline pxl8_vec3 pxl8_swizzle_xyw(pxl8_vec4 v) { return (pxl8_vec3){v.x, v.y, v.w}; } -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 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_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(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; - f32 dist_sq = dx * dx + dy * dy + dz * dz; - if (dist_sq >= light->radius_sq) return 0.0f; - return 1.0f - dist_sq * light->inv_radius_sq; -} - -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); -} - -static inline void pxl8_set_light_tint(pxl8_shader_ctx* ctx, u32 color, f32 strength) { - if (strength <= 0.0f) return; - if (strength > 1.0f) strength = 1.0f; - u32 alpha = (u32)(strength * 255.0f); - ctx->out_light_color = (color & 0x00FFFFFF) | (alpha << 24); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/gfx/pxl8_shader_registry.c b/src/gfx/pxl8_shader_registry.c deleted file mode 100644 index 370f183..0000000 --- a/src/gfx/pxl8_shader_registry.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "pxl8_shader_registry.h" - -#include - -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) {} - -pxl8_shader_fn pxl8_shader_registry_get(const char* name) { - if (strcmp(name, "lit") == 0) return (pxl8_shader_fn)pxl8_shader_lit; - if (strcmp(name, "unlit") == 0) return (pxl8_shader_fn)pxl8_shader_unlit; - return NULL; -} diff --git a/src/gfx/pxl8_shader_registry.h b/src/gfx/pxl8_shader_registry.h deleted file mode 100644 index 473afc6..0000000 --- a/src/gfx/pxl8_shader_registry.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "pxl8_shader.h" - -void pxl8_shader_registry_init(void); -void pxl8_shader_registry_reload(void); -pxl8_shader_fn pxl8_shader_registry_get(const char* name); diff --git a/src/gfx/pxl8_shader_runtime.c b/src/gfx/pxl8_shader_runtime.c deleted file mode 100644 index 14de4f7..0000000 --- a/src/gfx/pxl8_shader_runtime.c +++ /dev/null @@ -1,66 +0,0 @@ -#include "pxl8_shader_runtime.h" -#include "pxl8_log.h" - -#include -#include -#include -#include - -struct pxl8_shader_lib { - void* handle; - char path[256]; -}; - -pxl8_shader_lib* pxl8_shader_lib_load(const char* path) { - void* handle = dlopen(path, RTLD_NOW); - if (!handle) { - pxl8_error("Failed to load shader library: %s - %s", path, dlerror()); - return NULL; - } - - pxl8_shader_lib* lib = malloc(sizeof(pxl8_shader_lib)); - lib->handle = handle; - strncpy(lib->path, path, sizeof(lib->path) - 1); - lib->path[sizeof(lib->path) - 1] = '\0'; - - return lib; -} - -void pxl8_shader_lib_unload(pxl8_shader_lib* lib) { - if (!lib) return; - if (lib->handle) { - dlclose(lib->handle); - } - free(lib); -} - -bool pxl8_shader_lib_reload(pxl8_shader_lib* lib) { - if (!lib) return false; - - if (lib->handle) { - dlclose(lib->handle); - } - - lib->handle = dlopen(lib->path, RTLD_NOW); - if (!lib->handle) { - pxl8_error("Failed to reload shader library: %s - %s", lib->path, dlerror()); - return false; - } - - return true; -} - -pxl8_shader_fn pxl8_shader_lib_get(pxl8_shader_lib* lib, const char* name) { - if (!lib || !lib->handle) return NULL; - - char symbol[128]; - snprintf(symbol, sizeof(symbol), "pxl8_shader_%s", name); - - void* fn = dlsym(lib->handle, symbol); - if (!fn) { - pxl8_error("Failed to find shader: %s - %s", symbol, dlerror()); - return NULL; - } - - return (pxl8_shader_fn)fn; -} diff --git a/src/gfx/pxl8_shader_runtime.h b/src/gfx/pxl8_shader_runtime.h deleted file mode 100644 index 4650f65..0000000 --- a/src/gfx/pxl8_shader_runtime.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "pxl8_shader.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct pxl8_shader_lib pxl8_shader_lib; - -pxl8_shader_lib* pxl8_shader_lib_load(const char* path); -void pxl8_shader_lib_unload(pxl8_shader_lib* lib); -bool pxl8_shader_lib_reload(pxl8_shader_lib* lib); - -pxl8_shader_fn pxl8_shader_lib_get(pxl8_shader_lib* lib, const char* name); - -#ifdef __cplusplus -} -#endif diff --git a/src/gfx/shaders/cpu/lit.c b/src/gfx/shaders/cpu/lit.c deleted file mode 100644 index b9d55c9..0000000 --- a/src/gfx/shaders/cpu/lit.c +++ /dev/null @@ -1,106 +0,0 @@ -#include "pxl8_macros.h" -#include "pxl8_shader.h" -#include "pxl8_shader_builtins.h" - -u8 pxl8_shader_lit( - pxl8_shader_ctx* ctx, - const pxl8_shader_bindings* bindings, - const pxl8_shader_uniforms* uniforms -) { - u8 tex_idx = 0; - if (bindings && bindings->atlas) { - tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv); - if (pxl8_unlikely(tex_idx == 0)) return 0; - } else { - 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); - tex_idx = (u8)(clamped); - } - } - - f32 light = ctx->v_light; - - if (uniforms) { - f32 ambient = (f32)uniforms->ambient / 255.0f; - if (ambient > light) light = ambient; - - 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; - } - } - - f32 dyn_strength = 0.0f; - f32 dyn_r = 0.0f; - f32 dyn_g = 0.0f; - f32 dyn_b = 0.0f; - - 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 inv_dist = pxl8_fast_inv_sqrt(dist_sq); - f32 nx = lx * inv_dist; - f32 ny = ly * inv_dist; - f32 nz = lz * inv_dist; - - f32 ndotl = ctx->v_normal.x * nx + ctx->v_normal.y * ny + ctx->v_normal.z * nz; - if (ndotl <= 0.0f) continue; - - 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; - - dyn_strength += strength; - dyn_r += strength * (f32)l->r; - dyn_g += strength * (f32)l->g; - dyn_b += strength * (f32)l->b; - } - - if (dyn_strength > 0.0f) { - 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); - u8 a = (u8)pxl8_clamp(dyn_strength * 255.0f, 0.0f, 255.0f); - ctx->out_light_color = (u32)r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); - light += dyn_strength; - } - } - - if (light > 1.0f) light = 1.0f; - if (light < 0.0f) light = 0.0f; - - f32 light_f = light * 255.0f; - u8 light_u8 = (u8)light_f; - if (uniforms && uniforms->dither) { - light_u8 = pxl8_gfx_dither(light_f, (u32)ctx->x, (u32)ctx->y); - } - - u8 shaded = pxl8_colormap_lookup(bindings, tex_idx, light_u8); - - if (uniforms && uniforms->emissive) { - u32 rgb = 0x00FFFFFF; - if (bindings && bindings->palette) { - rgb = bindings->palette[tex_idx] & 0x00FFFFFF; - } - pxl8_set_light_tint(ctx, rgb, 1.0f); - } - - return shaded; -} diff --git a/src/gfx/shaders/cpu/unlit.c b/src/gfx/shaders/cpu/unlit.c deleted file mode 100644 index b3ff4c6..0000000 --- a/src/gfx/shaders/cpu/unlit.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "pxl8_shader.h" -#include "pxl8_shader_builtins.h" - -u8 pxl8_shader_unlit( - pxl8_shader_ctx* ctx, - const pxl8_shader_bindings* bindings, - const pxl8_shader_uniforms* uniforms -) { - u8 tex_idx = 0; - if (bindings && bindings->atlas) { - tex_idx = pxl8_sample_indexed(bindings, ctx->v_uv); - if (tex_idx == 0) return 0; - } else { - 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); - tex_idx = (u8)(clamped); - } - } - - if (uniforms && uniforms->emissive) { - u32 rgb = 0x00FFFFFF; - if (bindings && bindings->palette) { - rgb = bindings->palette[tex_idx] & 0x00FFFFFF; - } - pxl8_set_light_tint(ctx, rgb, 1.0f); - } - - return tex_idx; -} diff --git a/src/gui/pxl8_gui.c b/src/gui/pxl8_gui.c index 766091a..e3f667a 100644 --- a/src/gui/pxl8_gui.c +++ b/src/gui/pxl8_gui.c @@ -20,19 +20,19 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state) { } void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx) { - (void)gfx; if (!state) return; state->hot_id = 0; + if (gfx) pxl8_gfx_push_target(gfx); } void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx) { - (void)gfx; if (!state) return; if (!state->cursor_down) { state->active_id = 0; } state->cursor_clicked = false; + if (gfx) pxl8_gfx_pop_target(gfx); } void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) { diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 0f5c486..a4617d4 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -2,7 +2,8 @@ local anim = require("pxl8.anim") local bytes = require("pxl8.bytes") local core = require("pxl8.core") local effects = require("pxl8.effects") -local gfx = require("pxl8.gfx") +local gfx2d = require("pxl8.gfx2d") +local gfx3d = require("pxl8.gfx3d") local gui = require("pxl8.gui") local input = require("pxl8.input") local math = require("pxl8.math") @@ -43,27 +44,25 @@ pxl8.set_palette = core.set_palette pxl8.set_palette_rgb = core.set_palette_rgb pxl8.update_palette_deps = core.update_palette_deps -pxl8.clear = gfx.clear -pxl8.pixel = gfx.pixel -pxl8.line = gfx.line -pxl8.rect = gfx.rect -pxl8.rect_fill = gfx.rect_fill -pxl8.circle = gfx.circle -pxl8.circle_fill = gfx.circle_fill -pxl8.text = gfx.text -pxl8.sprite = gfx.sprite -pxl8.load_palette = gfx.load_palette -pxl8.load_sprite = gfx.load_sprite -pxl8.create_texture = gfx.create_texture -pxl8.gfx_color_ramp = gfx.color_ramp -pxl8.gfx_fade_palette = gfx.fade_palette -pxl8.gfx_cycle_palette = gfx.cycle_palette -pxl8.add_palette_cycle = gfx.add_palette_cycle -pxl8.remove_palette_cycle = gfx.remove_palette_cycle -pxl8.set_palette_cycle_speed = gfx.set_palette_cycle_speed -pxl8.clear_palette_cycles = gfx.clear_palette_cycles -pxl8.push_target = gfx.push_target -pxl8.pop_target = gfx.pop_target +pxl8.clear = gfx2d.clear +pxl8.pixel = gfx2d.pixel +pxl8.line = gfx2d.line +pxl8.rect = gfx2d.rect +pxl8.rect_fill = gfx2d.rect_fill +pxl8.circle = gfx2d.circle +pxl8.circle_fill = gfx2d.circle_fill +pxl8.text = gfx2d.text +pxl8.sprite = gfx2d.sprite +pxl8.load_palette = gfx2d.load_palette +pxl8.load_sprite = gfx2d.load_sprite +pxl8.create_texture = gfx2d.create_texture +pxl8.gfx_color_ramp = gfx2d.color_ramp +pxl8.gfx_fade_palette = gfx2d.fade_palette +pxl8.gfx_cycle_palette = gfx2d.cycle_palette +pxl8.add_palette_cycle = gfx2d.add_palette_cycle +pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle +pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed +pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles pxl8.key_down = input.key_down pxl8.key_pressed = input.key_pressed @@ -87,23 +86,26 @@ pxl8.create_anim_from_ase = anim.Anim.from_ase pxl8.bounds = math.bounds -pxl8.Camera3D = gfx.Camera3D -pxl8.Material = gfx.Material -pxl8.Mesh = gfx.Mesh -pxl8.begin_frame_3d = gfx.begin_frame_3d -pxl8.clear_3d = gfx.clear_3d -pxl8.clear_depth = gfx.clear_depth -pxl8.create_camera_3d = gfx.Camera3D.new -pxl8.create_material = gfx.create_material -pxl8.create_mesh = gfx.Mesh.new -pxl8.create_vec3_array = gfx.create_vec3_array -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.Camera3D = gfx3d.Camera3D +pxl8.Material = gfx3d.Material +pxl8.Mesh = gfx3d.Mesh +pxl8.begin_frame_3d = gfx3d.begin_frame +pxl8.clear_3d = gfx3d.clear +pxl8.clear_depth = gfx3d.clear_depth +pxl8.create_camera_3d = gfx3d.Camera3D.new +pxl8.create_material = gfx3d.create_material +pxl8.create_mesh = gfx3d.Mesh.new +pxl8.create_vec3_array = gfx3d.create_vec3_array +pxl8.draw_line_3d = gfx3d.draw_line +pxl8.draw_mesh = gfx3d.draw_mesh +pxl8.end_frame_3d = gfx3d.end_frame +pxl8.project_points = gfx3d.project_points +pxl8.GLOW_CIRCLE = effects.GLOW_CIRCLE +pxl8.GLOW_DIAMOND = effects.GLOW_DIAMOND +pxl8.GLOW_SHAFT = effects.GLOW_SHAFT +pxl8.Glows = effects.Glows +pxl8.create_glows = effects.Glows.new pxl8.Lights = effects.Lights pxl8.create_lights = effects.Lights.new diff --git a/src/lua/pxl8/effects.lua b/src/lua/pxl8/effects.lua index f38381d..a6221b5 100644 --- a/src/lua/pxl8/effects.lua +++ b/src/lua/pxl8/effects.lua @@ -1,8 +1,49 @@ local ffi = require("ffi") local C = ffi.C +local core = require("pxl8.core") local effects = {} +effects.GLOW_CIRCLE = 0 +effects.GLOW_DIAMOND = 1 +effects.GLOW_SHAFT = 2 + +local Glows = {} +Glows.__index = Glows + +function Glows.new(capacity) + local ptr = C.pxl8_glows_create(capacity or 1000) + if ptr == nil then + return nil + end + return setmetatable({ _ptr = ptr }, Glows) +end + +function Glows:add(x, y, radius, intensity, color, shape) + C.pxl8_glows_add(self._ptr, x, y, radius or 8, intensity or 255, color or 15, shape or 0) +end + +function Glows:clear() + C.pxl8_glows_clear(self._ptr) +end + +function Glows:count() + return C.pxl8_glows_count(self._ptr) +end + +function Glows:destroy() + if self._ptr then + C.pxl8_glows_destroy(self._ptr) + self._ptr = nil + end +end + +function Glows:render() + C.pxl8_glows_render(self._ptr, core.gfx) +end + +effects.Glows = Glows + local Lights = {} Lights.__index = Lights diff --git a/src/lua/pxl8/gfx2d.lua b/src/lua/pxl8/gfx2d.lua new file mode 100644 index 0000000..16ae850 --- /dev/null +++ b/src/lua/pxl8/gfx2d.lua @@ -0,0 +1,108 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local graphics = {} + +function graphics.clear(color) + C.pxl8_2d_clear(core.gfx, color or 0) +end + +function graphics.pixel(x, y, color) + if color then + C.pxl8_2d_pixel(core.gfx, x, y, color) + else + return C.pxl8_2d_get_pixel(core.gfx, x, y) + end +end + +function graphics.line(x0, y0, x1, y1, color) + C.pxl8_2d_line(core.gfx, x0, y0, x1, y1, color) +end + +function graphics.rect(x, y, w, h, color) + C.pxl8_2d_rect(core.gfx, x, y, w, h, color) +end + +function graphics.rect_fill(x, y, w, h, color) + C.pxl8_2d_rect_fill(core.gfx, x, y, w, h, color) +end + +function graphics.circle(x, y, r, color) + C.pxl8_2d_circle(core.gfx, x, y, r, color) +end + +function graphics.circle_fill(x, y, r, color) + C.pxl8_2d_circle_fill(core.gfx, x, y, r, color) +end + +function graphics.text(str, x, y, color) + C.pxl8_2d_text(core.gfx, str, x or 0, y or 0, color or 15) +end + +function graphics.sprite(id, x, y, w, h, flip_x, flip_y) + C.pxl8_2d_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false) +end + +function graphics.load_palette(filepath) + return C.pxl8_gfx_load_palette(core.gfx, filepath) +end + +function graphics.load_sprite(filepath) + local result = C.pxl8_gfx_load_sprite(core.gfx, filepath) + if result >= 0 and result < 100 then + return result + else + return nil, result + end +end + +function graphics.create_texture(pixels, width, height) + local pixel_data = ffi.new("u8[?]", width * height) + for i = 0, width * height - 1 do + pixel_data[i] = pixels[i + 1] or 0 + end + local result = C.pxl8_gfx_create_texture(core.gfx, pixel_data, width, height) + if result < 0 then + return nil + end + return result +end + +function graphics.color_ramp(start, count, from_color, to_color) + C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color) +end + +function graphics.fade_palette(start, count, amount, target_color) + C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color) +end + +function graphics.cycle_palette(start, count, step) + C.pxl8_gfx_cycle_palette(core.gfx, start, count, step or 1) +end + +function graphics.add_palette_cycle(start_index, end_index, speed) + return C.pxl8_gfx_add_palette_cycle(core.gfx, start_index, end_index, speed or 1.0) +end + +function graphics.remove_palette_cycle(cycle_id) + C.pxl8_gfx_remove_palette_cycle(core.gfx, cycle_id) +end + +function graphics.set_palette_cycle_speed(cycle_id, speed) + C.pxl8_gfx_set_palette_cycle_speed(core.gfx, cycle_id, speed) +end + +function graphics.clear_palette_cycles() + C.pxl8_gfx_clear_palette_cycles(core.gfx) +end + +function graphics.push_target() + return C.pxl8_gfx_push_target(core.gfx) +end + +function graphics.pop_target() + C.pxl8_gfx_pop_target(core.gfx) +end + +return graphics diff --git a/src/lua/pxl8/gfx.lua b/src/lua/pxl8/gfx3d.lua similarity index 53% rename from src/lua/pxl8/gfx.lua rename to src/lua/pxl8/gfx3d.lua index 64cbf87..fc30fc2 100644 --- a/src/lua/pxl8/gfx.lua +++ b/src/lua/pxl8/gfx3d.lua @@ -2,108 +2,7 @@ local ffi = require("ffi") local C = ffi.C local core = require("pxl8.core") -local gfx = {} - -function gfx.clear(color) - C.pxl8_2d_clear(core.gfx, color or 0) -end - -function gfx.pixel(x, y, color) - if color then - C.pxl8_2d_pixel(core.gfx, x, y, color) - else - return C.pxl8_2d_get_pixel(core.gfx, x, y) - end -end - -function gfx.line(x0, y0, x1, y1, color) - C.pxl8_2d_line(core.gfx, x0, y0, x1, y1, color) -end - -function gfx.rect(x, y, w, h, color) - C.pxl8_2d_rect(core.gfx, x, y, w, h, color) -end - -function gfx.rect_fill(x, y, w, h, color) - C.pxl8_2d_rect_fill(core.gfx, x, y, w, h, color) -end - -function gfx.circle(x, y, r, color) - C.pxl8_2d_circle(core.gfx, x, y, r, color) -end - -function gfx.circle_fill(x, y, r, color) - C.pxl8_2d_circle_fill(core.gfx, x, y, r, color) -end - -function gfx.text(str, x, y, color) - C.pxl8_2d_text(core.gfx, str, x or 0, y or 0, color or 15) -end - -function gfx.sprite(id, x, y, w, h, flip_x, flip_y) - C.pxl8_2d_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false) -end - -function gfx.load_palette(filepath) - return C.pxl8_gfx_load_palette(core.gfx, filepath) -end - -function gfx.load_sprite(filepath) - local result = C.pxl8_gfx_load_sprite(core.gfx, filepath) - if result >= 0 and result < 100 then - return result - else - return nil, result - end -end - -function gfx.create_texture(pixels, width, height) - local pixel_data = ffi.new("u8[?]", width * height) - for i = 0, width * height - 1 do - pixel_data[i] = pixels[i + 1] or 0 - end - local result = C.pxl8_gfx_create_texture(core.gfx, pixel_data, width, height) - if result < 0 then - return nil - end - return result -end - -function gfx.color_ramp(start, count, from_color, to_color) - C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color) -end - -function gfx.fade_palette(start, count, amount, target_color) - C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color) -end - -function gfx.cycle_palette(start, count, step) - C.pxl8_gfx_cycle_palette(core.gfx, start, count, step or 1) -end - -function gfx.add_palette_cycle(start_index, end_index, speed) - return C.pxl8_gfx_add_palette_cycle(core.gfx, start_index, end_index, speed or 1.0) -end - -function gfx.remove_palette_cycle(cycle_id) - C.pxl8_gfx_remove_palette_cycle(core.gfx, cycle_id) -end - -function gfx.set_palette_cycle_speed(cycle_id, speed) - C.pxl8_gfx_set_palette_cycle_speed(core.gfx, cycle_id, speed) -end - -function gfx.clear_palette_cycles() - C.pxl8_gfx_clear_palette_cycles(core.gfx) -end - -function gfx.push_target() - return C.pxl8_gfx_push_target(core.gfx) -end - -function gfx.pop_target() - C.pxl8_gfx_pop_target(core.gfx) -end +local gfx3d = {} local Camera3D = {} Camera3D.__index = Camera3D @@ -185,6 +84,8 @@ function Camera3D:world_to_screen(x, y, z, width, height) return nil end +gfx3d.Camera3D = Camera3D + local Mesh = {} Mesh.__index = Mesh @@ -226,43 +127,35 @@ function Mesh:clear() end end -gfx.Camera3D = Camera3D +gfx3d.Mesh = Mesh -gfx.Mesh = Mesh - -function gfx.draw_mesh(mesh, opts) - if not mesh or not mesh._ptr then - return - end +function gfx3d.draw_mesh(mesh, opts) opts = opts or {} - local model - if opts.transform then - model = opts.transform - else - model = ffi.new("pxl8_mat4") - local s = opts.scale or 1 - model.m[0] = s - model.m[5] = s - model.m[10] = s - model.m[15] = 1 - if opts.x then model.m[12] = opts.x end - if opts.y then model.m[13] = opts.y end - if opts.z then model.m[14] = opts.z end - end + local model = ffi.new("pxl8_mat4") + local s = opts.scale or 1 + model.m[0] = s + model.m[5] = s + model.m[10] = s + model.m[15] = 1 + if opts.x then model.m[12] = opts.x end + if opts.y then model.m[13] = opts.y end + if opts.z then model.m[14] = opts.z end local material = ffi.new("pxl8_gfx_material", { + texture_id = opts.texture or 0, alpha = opts.alpha or 255, blend_mode = opts.blend_mode or 0, dither = opts.dither ~= false, double_sided = opts.double_sided or false, dynamic_lighting = opts.lighting or false, - emissive = opts.emissive or false, per_pixel = opts.per_pixel or false, - texture_id = opts.texture or 0xFFFFFFFF, + vertex_color_passthrough = opts.passthrough or false, + wireframe = opts.wireframe or false, + emissive_intensity = opts.emissive or 0.0, }) C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) end -function gfx.begin_frame_3d(camera, lights, uniforms) +function gfx3d.begin_frame(camera, lights, uniforms) uniforms = uniforms or {} local u = ffi.new("pxl8_3d_uniforms") @@ -271,82 +164,68 @@ 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 - cx = uniforms.celestial_dir[1] or 0 - cy = uniforms.celestial_dir[2] or -1 - cz = uniforms.celestial_dir[3] or 0 + 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 else - cx, cy, cz = 0, -1, 0 + u.celestial_dir.x = 0 + u.celestial_dir.y = -1 + u.celestial_dir.z = 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 C.pxl8_3d_begin_frame(core.gfx, camera._ptr, lights_ptr, u) end -function gfx.clear_3d(color) +function gfx3d.clear(color) C.pxl8_3d_clear(core.gfx, color or 0) end -function gfx.clear_depth() +function gfx3d.clear_depth() C.pxl8_3d_clear_depth(core.gfx) end -function gfx.draw_line_3d(p0, p1, color) +function gfx3d.draw_line(p0, p1, color) local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]}) local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]}) C.pxl8_3d_draw_line(core.gfx, vec0, vec1, color) end -function gfx.end_frame_3d() +function gfx3d.end_frame() C.pxl8_3d_end_frame(core.gfx) end -function gfx.project_points(input, output, count, transform) - return C.pxl8_3d_project_points(core.gfx, input, output, count, transform) +function gfx3d.project_points(input, output, count, transform) + C.pxl8_3d_project_points(core.gfx, input, output, count, transform) end -function gfx.create_vec3_array(count) +function gfx3d.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 function Material.new(opts) opts = opts or {} local mat = ffi.new("pxl8_gfx_material", { + texture_id = opts.texture or 0, alpha = opts.alpha or 255, blend_mode = opts.blend_mode or 0, dither = opts.dither ~= false, double_sided = opts.double_sided or false, dynamic_lighting = opts.lighting or false, - emissive = opts.emissive or false, per_pixel = opts.per_pixel or false, - texture_id = opts.texture or 0xFFFFFFFF, + vertex_color_passthrough = opts.passthrough or false, + wireframe = opts.wireframe or false, + emissive_intensity = opts.emissive or 0.0, }) return setmetatable({ _ptr = mat }, Material) end -gfx.Material = Material +gfx3d.Material = Material +gfx3d.create_material = Material.new -gfx.create_material = Material.new - -return gfx +return gfx3d diff --git a/src/lua/pxl8/shader.lua b/src/lua/pxl8/shader.lua new file mode 100644 index 0000000..2dab54b --- /dev/null +++ b/src/lua/pxl8/shader.lua @@ -0,0 +1,281 @@ +local ffi = require("ffi") +local bit = require("bit") +local core = require("pxl8.core") + +ffi.cdef[[ + u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); + u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx); + const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx); + u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx); +]] + +local C = ffi.C +local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs +local sin, cos, fmod = math.sin, math.cos, math.fmod + +local shader = {} + +local fb = ffi.new("u8*") +local light = ffi.new("u32*") +local pal = ffi.new("const u32*") +local zbuf = ffi.new("u16*") +local w, h, count = 0, 0, 0 + +function shader.begin_frame() + fb = C.pxl8_gfx_get_framebuffer_indexed(core.gfx) + light = C.pxl8_gfx_get_light_accum(core.gfx) + pal = C.pxl8_gfx_palette_colors(core.gfx) + zbuf = C.pxl8_gfx_get_zbuffer(core.gfx) + w = C.pxl8_gfx_get_width(core.gfx) + h = C.pxl8_gfx_get_height(core.gfx) + count = w * h +end + +function shader.get_buffers() + return fb, light, pal, zbuf, w, h +end + +local function clamp(x, lo, hi) + if x < lo then return lo end + if x > hi then return hi end + return x +end + +shader.resolve_tint = function() + if fb == nil or light == nil or pal == nil then return end + local fb_l, light_l, pal_l = fb, light, pal + local count_l = count + local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift + local floor_l = floor + + for i = 0, count_l - 1 do + local lv = light_l[i] + if lv ~= 0 then + local a = rshift_l(lv, 24) + if a > 0 then + local base = pal_l[fb_l[i]] + local br = band_l(base, 0xFF) + local bg = band_l(rshift_l(base, 8), 0xFF) + local bb = band_l(rshift_l(base, 16), 0xFF) + + local lr = band_l(lv, 0xFF) + local lg = band_l(rshift_l(lv, 8), 0xFF) + local lb = band_l(rshift_l(lv, 16), 0xFF) + + local t = a * 0.00392156862 + local r = floor_l(br + (lr - 128) * t * 2) + local g = floor_l(bg + (lg - 128) * t * 2) + local b = floor_l(bb + (lb - 128) * t * 2) + + if r < 0 then r = 0 elseif r > 255 then r = 255 end + if g < 0 then g = 0 elseif g > 255 then g = 255 end + if b < 0 then b = 0 elseif b > 255 then b = 255 end + + light_l[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000) + end + end + end +end + +shader.compile = function(source) + local header = [[ +local ffi = require("ffi") +local bit = require("bit") +local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs +local sin, cos, fmod = math.sin, math.cos, math.fmod +local clamp = function(x, lo, hi) if x < lo then return lo elseif x > hi then return hi else return x end end +local mix = function(a, b, t) return a + (b - a) * t end +local smoothstep = function(e0, e1, x) local t = clamp((x - e0) / (e1 - e0), 0, 1); return t * t * (3 - 2 * t) end +local saturate = function(x) if x < 0 then return 0 elseif x > 1 then return 1 else return x end end +local length2 = function(x, y) return sqrt(x*x + y*y) end +local length3 = function(x, y, z) return sqrt(x*x + y*y + z*z) end +local dot2 = function(ax, ay, bx, by) return ax*bx + ay*by end +local dot3 = function(ax, ay, az, bx, by, bz) return ax*bx + ay*by + az*bz end +local fract = function(x) return x - floor(x) end + +return function(fb, light, pal, zbuf, w, h, uniforms) + uniforms = uniforms or {} + local count = w * h + local time = uniforms.time or 0 + local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift + local floor_l, sqrt_l, max_l, min_l = floor, sqrt, max, min + + for i = 0, count - 1 do + local x = i % w + local y = floor_l(i / w) + local uv_x = x / w + local uv_y = y / h + + local idx = fb[i] + local base = pal[idx] + local br = band_l(base, 0xFF) + local bg = band_l(rshift_l(base, 8), 0xFF) + local bb = band_l(rshift_l(base, 16), 0xFF) + + local lv = light[i] + local lr = band_l(lv, 0xFF) + local lg = band_l(rshift_l(lv, 8), 0xFF) + local lb = band_l(rshift_l(lv, 16), 0xFF) + local la = rshift_l(lv, 24) + + local depth = zbuf and zbuf[i] or 0 + local depth_n = depth / 65535.0 + + local r, g, b = br, bg, bb + +]] + + local footer = [[ + + r = floor_l(clamp(r, 0, 255)) + g = floor_l(clamp(g, 0, 255)) + b = floor_l(clamp(b, 0, 255)) + light[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000) + end +end +]] + + local code = header .. source .. footer + local fn, err = loadstring(code) + if not fn then + error("Shader compile error: " .. tostring(err)) + end + return fn() +end + +shader.run = function(compiled_shader, uniforms) + if fb == nil or light == nil or pal == nil then return end + compiled_shader(fb, light, pal, zbuf, w, h, uniforms) +end + +shader.presets = {} + +shader.presets.passthrough = shader.compile([[ + -- passthrough: just use base color +]]) + +shader.presets.light_tint = shader.compile([[ + if la > 0 then + local t = la / 255.0 + r = br + (lr - 128) * t * 2 + g = bg + (lg - 128) * t * 2 + b = bb + (lb - 128) * t * 2 + end +]]) + +shader.presets.vignette = shader.compile([[ + local cx = uv_x - 0.5 + local cy = uv_y - 0.5 + local dist = sqrt_l(cx*cx + cy*cy) + local vig = 1.0 - saturate(dist * 1.5) + vig = vig * vig + r = br * vig + g = bg * vig + b = bb * vig +]]) + +shader.presets.scanlines = shader.compile([[ + local scan = 0.8 + 0.2 * (y % 2) + r = br * scan + g = bg * scan + b = bb * scan +]]) + +shader.presets.crt = shader.compile([[ + -- Scanlines + local scan = 0.85 + 0.15 * (y % 2) + -- Vignette + local cx = uv_x - 0.5 + local cy = uv_y - 0.5 + local dist = sqrt_l(cx*cx + cy*cy) + local vig = 1.0 - saturate(dist * 1.2) + -- RGB shift based on x position + local shift = (uv_x - 0.5) * 0.02 + local mult = scan * vig + r = br * mult * (1.0 + shift) + g = bg * mult + b = bb * mult * (1.0 - shift) +]]) + +shader.presets.dither_fade = shader.compile([[ + local threshold = fract(sin(x * 12.9898 + y * 78.233) * 43758.5453) + local fade = uniforms.fade or 0.5 + if threshold > fade then + r, g, b = 0, 0, 0 + else + r, g, b = br, bg, bb + end +]]) + +shader.presets.fog = shader.compile([[ + local fog_color_r = uniforms.fog_r or 32 + local fog_color_g = uniforms.fog_g or 32 + local fog_color_b = uniforms.fog_b or 48 + local fog_start = uniforms.fog_start or 0.3 + local fog_end = uniforms.fog_end or 0.9 + local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start)) + fog_t = fog_t * fog_t + r = mix(br, fog_color_r, fog_t) + g = mix(bg, fog_color_g, fog_t) + b = mix(bb, fog_color_b, fog_t) +]]) + +shader.presets.light_with_fog = shader.compile([[ + -- Apply light tint first + if la > 0 then + local t = la / 255.0 + r = br + (lr - 128) * t * 2 + g = bg + (lg - 128) * t * 2 + b = bb + (lb - 128) * t * 2 + else + r, g, b = br, bg, bb + end + -- Then fog + local fog_r = uniforms.fog_r or 16 + local fog_g = uniforms.fog_g or 16 + local fog_b = uniforms.fog_b or 24 + local fog_start = uniforms.fog_start or 0.2 + local fog_end = uniforms.fog_end or 0.95 + local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start)) + fog_t = fog_t * fog_t + r = mix(r, fog_r, fog_t) + g = mix(g, fog_g, fog_t) + b = mix(b, fog_b, fog_t) +]]) + +shader.presets.posterize = shader.compile([[ + local levels = uniforms.levels or 4 + local step = 255 / levels + r = floor_l(br / step) * step + g = floor_l(bg / step) * step + b = floor_l(bb / step) * step +]]) + +shader.presets.chromatic = shader.compile([=[ + local amount = uniforms.amount or 2 + local ox = floor_l(amount * (uv_x - 0.5)) + local r_i = clamp(i - ox, 0, count - 1) + local b_i = clamp(i + ox, 0, count - 1) + local r_base = pal[fb[r_i]] + local b_base = pal[fb[b_i]] + r = band_l(r_base, 0xFF) + g = bg + b = band_l(rshift_l(b_base, 16), 0xFF) +]=]) + +shader.clear_light = function() + if light == nil then return end + ffi.fill(light, count * 4, 0) +end + +shader.fill_output = function(r, g, b) + if light == nil then return end + local color = bor(r, lshift(g, 8), lshift(b, 16), 0xFF000000) + for i = 0, count - 1 do + light[i] = color + end +end + +return shader diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index 990e53b..bb93c62 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -117,6 +117,10 @@ 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 fb605a4..2efb8f5 100644 --- a/src/math/pxl8_math.c +++ b/src/math/pxl8_math.c @@ -1,50 +1,5 @@ #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; @@ -63,6 +18,10 @@ 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); } @@ -97,11 +56,11 @@ f32 pxl8_vec2_length(pxl8_vec2 v) { } pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) { - f32 len_sq = pxl8_vec2_dot(v, v); + f32 len = pxl8_vec2_length(v); - if (len_sq < 1e-12f) return (pxl8_vec2){0}; + if (len < 1e-6f) return (pxl8_vec2){0}; - return pxl8_vec2_scale(v, pxl8_fast_inv_sqrt(len_sq)); + return pxl8_vec2_scale(v, 1.0f / len); } pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) { @@ -153,11 +112,11 @@ pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) { } pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) { - f32 len_sq = pxl8_vec3_dot(v, v); + f32 len = pxl8_vec3_length(v); - if (len_sq < 1e-12f) return (pxl8_vec3){0}; + if (len < 1e-6f) return (pxl8_vec3){0}; - return pxl8_vec3_scale(v, pxl8_fast_inv_sqrt(len_sq)); + return pxl8_vec3_scale(v, 1.0f / len); } pxl8_mat4 pxl8_mat4_identity(void) { diff --git a/src/math/pxl8_math.h b/src/math/pxl8_math.h index 6cf314b..1290217 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -15,43 +15,64 @@ #define PXL8_PI 3.14159265358979323846f #define PXL8_TAU (PXL8_PI * 2.0f) -#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_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_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)) +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 +} -f32 pxl8_fast_inv_sqrt(f32 x); -f32 pxl8_fast_rcp(f32 x); - -typedef union pxl8_vec2 { - struct { f32 x, y; }; - struct { f32 yaw, pitch; }; - f32 v[2]; +typedef struct pxl8_vec2 { + f32 x, y; } pxl8_vec2; -typedef union pxl8_vec3 { - struct { f32 x, y, z; }; - f32 v[3]; +typedef struct pxl8_vec3 { + f32 x, y, z; } pxl8_vec3; -typedef union pxl8_vec4 { - struct { f32 x, y, z, w; }; - f32 v[4]; +typedef struct pxl8_vec4 { + f32 x, y, z, w; } pxl8_vec4; -typedef union pxl8_mat4 { +typedef struct pxl8_mat4 { f32 m[16]; } pxl8_mat4; @@ -78,6 +99,18 @@ typedef struct pxl8_ray { bool hit; } pxl8_ray; +#define PXL8_SDF_X 32 +#define PXL8_SDF_Y 16 +#define PXL8_SDF_Z 32 +#define PXL8_SDF_SIZE (PXL8_SDF_X * PXL8_SDF_Y * PXL8_SDF_Z) +#define PXL8_SDF_CELL 16.0f + +typedef struct pxl8_sdf { + i8 data[PXL8_SDF_SIZE]; + pxl8_vec3 origin; + f32 cell; +} pxl8_sdf; + #ifdef __cplusplus extern "C" { #endif @@ -85,6 +118,7 @@ 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 b6e9ed9..df3fa1f 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(c00, c10, tx); - f32 b = pxl8_lerp(c01, c11, tx); - return pxl8_lerp(a, b, tz); + f32 a = pxl8_lerp_f32(c00, c10, tx); + f32 b = pxl8_lerp_f32(c01, c11, tx); + return pxl8_lerp_f32(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(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 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 b0 = pxl8_lerp(a00, a10, ty); - f32 b1 = pxl8_lerp(a01, a11, ty); + f32 b0 = pxl8_lerp_f32(a00, a10, ty); + f32 b1 = pxl8_lerp_f32(a01, a11, ty); - return pxl8_lerp(b0, b1, tz); + return pxl8_lerp_f32(b0, b1, tz); } f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves) { diff --git a/src/script/pxl8_script.c b/src/script/pxl8_script.c index 3685650..4df3301 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -1152,11 +1152,6 @@ bool pxl8_script_check_reload(pxl8_script* script) { return false; } - static u32 frame_counter = 0; - if (++frame_counter % 60 != 0) { - return false; - } - time_t current_mod_time = get_latest_script_mod_time(script->watch_dir); if (current_mod_time > script->latest_mod_time && current_mod_time != 0) { pxl8_info("Script files modified, reloading: %s", script->main_path); diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index 036da56..7ec6877 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -44,8 +44,6 @@ 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" @@ -199,7 +197,6 @@ static const char* pxl8_ffi_cdefs = "void pxl8_anim_stop(pxl8_anim* anim);\n" "void pxl8_anim_update(pxl8_anim* anim, f32 dt);\n" "\n" -"typedef union { struct { float x, y; }; struct { float yaw, pitch; }; float v[2]; } pxl8_vec2;\n" "typedef struct { float x, y, z; } pxl8_vec3;\n" "typedef struct { float x, y, z, w; } pxl8_vec4;\n" "typedef struct { float m[16]; } pxl8_mat4;\n" @@ -247,7 +244,31 @@ static const char* pxl8_ffi_cdefs = "typedef struct pxl8_projected_point { i32 x; i32 y; f32 depth; bool visible; } pxl8_projected_point;\n" "pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);\n" "\n" +"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n" +"\n" +"typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n" +"\n" +"typedef struct pxl8_glow {\n" +" u8 color;\n" +" u16 depth;\n" +" u8 height;\n" +" u16 intensity;\n" +" u8 radius;\n" +" pxl8_glow_shape shape;\n" +" i16 x;\n" +" i16 y;\n" +"} pxl8_glow;\n" +"\n" +"void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);\n" "void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);\n" +"\n" +"typedef struct pxl8_glows pxl8_glows;\n" +"pxl8_glows* pxl8_glows_create(u32 capacity);\n" +"void pxl8_glows_destroy(pxl8_glows* glows);\n" +"void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape);\n" +"void pxl8_glows_clear(pxl8_glows* glows);\n" +"u32 pxl8_glows_count(const pxl8_glows* glows);\n" +"void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx);\n" "void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\n" "\n" "void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n" @@ -272,8 +293,10 @@ static const char* pxl8_ffi_cdefs = " bool dither;\n" " bool double_sided;\n" " bool dynamic_lighting;\n" -" bool emissive;\n" " bool per_pixel;\n" +" bool vertex_color_passthrough;\n" +" bool wireframe;\n" +" f32 emissive_intensity;\n" "} pxl8_gfx_material;\n" "\n" "typedef struct pxl8_vertex {\n" @@ -423,6 +446,7 @@ 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 06e5264..a4ebbdc 100644 --- a/src/vxl/pxl8_vxl_render.c +++ b/src/vxl/pxl8_vxl_render.c @@ -569,3 +569,8 @@ 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 6e5b6cf..2762c46 100644 --- a/src/vxl/pxl8_vxl_render.h +++ b/src/vxl/pxl8_vxl_render.h @@ -9,11 +9,12 @@ extern "C" { #endif typedef struct pxl8_vxl_render_state { - u8 _unused; + bool wireframe; } 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 48913c0..353b8b0 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -30,14 +30,13 @@ struct pxl8_world { pxl8_entity_pool* entities; pxl8_bsp_render_state* bsp_render_state; pxl8_vxl_render_state* vxl_render_state; + pxl8_sdf sdf; i32 render_distance; i32 sim_distance; pxl8_sim_entity local_player; u64 client_tick; - pxl8_vec2 pointer_motion; - #ifdef PXL8_ASYNC_THREADS pxl8_sim_entity render_state[2]; atomic_uint active_buffer; @@ -188,6 +187,100 @@ static void userdata_to_entity(const u8* userdata, pxl8_sim_entity* ent) { memcpy(&ent->kind, p, 2); } +static void update_sdf(pxl8_world* world, pxl8_vec3 center, f32 cell_size) { + if (!world) return; + + world->sdf.cell = cell_size; + world->sdf.origin.x = center.x - (PXL8_SDF_X / 2) * cell_size; + world->sdf.origin.y = center.y - (PXL8_SDF_Y / 2) * cell_size; + world->sdf.origin.z = center.z - (PXL8_SDF_Z / 2) * cell_size; + + i16 seed_x[PXL8_SDF_SIZE]; + i16 seed_y[PXL8_SDF_SIZE]; + i16 seed_z[PXL8_SDF_SIZE]; + + for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) { + f32 wy = world->sdf.origin.y + (iy + 0.5f) * cell_size; + for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) { + f32 wz = world->sdf.origin.z + (iz + 0.5f) * cell_size; + for (i32 ix = 0; ix < PXL8_SDF_X; ix++) { + f32 wx = world->sdf.origin.x + (ix + 0.5f) * cell_size; + i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix; + + if (pxl8_world_point_solid(world, wx, wy, wz)) { + seed_x[idx] = (i16)ix; + seed_y[idx] = (i16)iy; + seed_z[idx] = (i16)iz; + } else { + seed_x[idx] = -1; + seed_y[idx] = -1; + seed_z[idx] = -1; + } + } + } + } + + for (i32 step = 16; step >= 1; step /= 2) { + for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) { + for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) { + for (i32 ix = 0; ix < PXL8_SDF_X; ix++) { + i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix; + i32 best_dist_sq = (seed_x[idx] >= 0) + ? (ix - seed_x[idx]) * (ix - seed_x[idx]) + + (iy - seed_y[idx]) * (iy - seed_y[idx]) + + (iz - seed_z[idx]) * (iz - seed_z[idx]) + : 0x7FFFFFFF; + + for (i32 dy = -step; dy <= step; dy += step) { + i32 ny = iy + dy; + if (ny < 0 || ny >= PXL8_SDF_Y) continue; + for (i32 dz = -step; dz <= step; dz += step) { + i32 nz = iz + dz; + if (nz < 0 || nz >= PXL8_SDF_Z) continue; + for (i32 dx = -step; dx <= step; dx += step) { + if (dx == 0 && dy == 0 && dz == 0) continue; + i32 nx = ix + dx; + if (nx < 0 || nx >= PXL8_SDF_X) continue; + + i32 nidx = ny * PXL8_SDF_Z * PXL8_SDF_X + nz * PXL8_SDF_X + nx; + if (seed_x[nidx] < 0) continue; + + i32 dist_sq = (ix - seed_x[nidx]) * (ix - seed_x[nidx]) + + (iy - seed_y[nidx]) * (iy - seed_y[nidx]) + + (iz - seed_z[nidx]) * (iz - seed_z[nidx]); + + if (dist_sq < best_dist_sq) { + best_dist_sq = dist_sq; + seed_x[idx] = seed_x[nidx]; + seed_y[idx] = seed_y[nidx]; + seed_z[idx] = seed_z[nidx]; + } + } + } + } + } + } + } + } + + for (i32 i = 0; i < PXL8_SDF_SIZE; i++) { + if (seed_x[i] < 0) { + world->sdf.data[i] = 127; + } else { + i32 idx_x = i % PXL8_SDF_X; + i32 idx_z = (i / PXL8_SDF_X) % PXL8_SDF_Z; + i32 idx_y = i / (PXL8_SDF_X * PXL8_SDF_Z); + i32 dx = idx_x - seed_x[i]; + i32 dy = idx_y - seed_y[i]; + i32 dz = idx_z - seed_z[i]; + f32 dist = sqrtf((f32)(dx * dx + dy * dy + dz * dz)); + i32 d = (i32)(dist * cell_size); + if (d > 127) d = 127; + world->sdf.data[i] = (i8)d; + } + } +} + bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z) { if (!world) return false; @@ -302,22 +395,18 @@ void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt) msg.look_dx = (f32)pxl8_mouse_dx(input); msg.look_dy = (f32)pxl8_mouse_dy(input); msg.buttons = pxl8_key_down(input, "space") ? 1 : 0; - msg.tick = world->client_tick; - msg.timestamp = pxl8_get_ticks_ns(); - - if (world->net) { - pxl8_net_send_input(world->net, &msg); - } pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); pxl8_sim_move_player(&world->local_player, &msg, &sim, dt); - world->client_tick++; } } void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { if (!world || !gfx) return; + update_sdf(world, camera_pos, PXL8_SDF_CELL); + pxl8_3d_set_sdf(gfx, &world->sdf); + if (world->active_chunk) { if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) { pxl8_3d_set_bsp(gfx, world->active_chunk->bsp); @@ -332,10 +421,13 @@ 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, + .vertex_color_passthrough = true, .alpha = 255, + .wireframe = wireframe, }; i32 r = world->render_distance; @@ -389,27 +481,16 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) { world->active_chunk = chunk; pxl8_debug("[CLIENT] Synced BSP chunk id=%u as active (verts=%u faces=%u)", chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces); - - if (world->bsp_render_state) { - pxl8_bsp_render_state_destroy(world->bsp_render_state); - world->bsp_render_state = NULL; - } - world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces); } } } else if (chunk_id == 0 && world->active_chunk != NULL) { world->active_chunk = NULL; - if (world->bsp_render_state) { - pxl8_bsp_render_state_destroy(world->bsp_render_state); - world->bsp_render_state = NULL; - } } } static void ensure_bsp_render_state(pxl8_world* world) { if (!world || world->bsp_render_state) return; - if (!world->active_chunk) return; - if (world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return; + if (!world->active_chunk || world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return; if (!world->active_chunk->bsp) return; world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces); @@ -424,6 +505,18 @@ 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; @@ -466,15 +559,8 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) { if (!world) return NULL; -#ifdef PXL8_ASYNC_THREADS - const pxl8_sim_entity* state = pxl8_world_get_render_state(world); - if (!state) return NULL; - if (!(state->flags & PXL8_SIM_FLAG_ALIVE)) return NULL; - return (pxl8_sim_entity*)state; -#else if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return NULL; return &world->local_player; -#endif } void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt) { @@ -499,14 +585,7 @@ void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) { const u8* server_state = pxl8_net_entity_userdata(net, player_id); if (!server_state) return; - pxl8_sim_entity server_player = {0}; - userdata_to_entity(server_state, &server_player); - - if (!(server_player.flags & PXL8_SIM_FLAG_ALIVE)) { - return; - } - - world->local_player = server_player; + userdata_to_entity(server_state, &world->local_player); const pxl8_snapshot_header* snap = pxl8_net_snapshot(net); u64 server_tick = snap ? snap->tick : 0; @@ -527,9 +606,11 @@ void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) { #define SIM_TIMESTEP (1.0f / 60.0f) static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) { - bool alive = (world->local_player.flags & PXL8_SIM_FLAG_ALIVE) != 0; + if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return; + pxl8_input_msg merged = {0}; pxl8_input_msg* input = NULL; + bool has_input = false; while ((input = pxl8_queue_pop(&world->input_queue))) { merged.look_dx += input->look_dx; @@ -537,31 +618,16 @@ static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) { merged.move_x = input->move_x; merged.move_y = input->move_y; merged.buttons |= input->buttons; + has_input = true; pxl8_free(input); } - if (!alive) { - return; - } - const f32 MAX_LOOK_DELTA = 100.0f; if (merged.look_dx > MAX_LOOK_DELTA) merged.look_dx = MAX_LOOK_DELTA; if (merged.look_dx < -MAX_LOOK_DELTA) merged.look_dx = -MAX_LOOK_DELTA; if (merged.look_dy > MAX_LOOK_DELTA) merged.look_dy = MAX_LOOK_DELTA; if (merged.look_dy < -MAX_LOOK_DELTA) merged.look_dy = -MAX_LOOK_DELTA; - merged.tick = world->client_tick; - merged.timestamp = pxl8_get_ticks_ns(); - merged.yaw = world->pointer_motion.yaw; - if (world->net) { - pxl8_net_send_input(world->net, &merged); - } - - world->local_player.yaw = world->pointer_motion.yaw; - world->local_player.pitch = world->pointer_motion.pitch; - merged.look_dx = 0; - merged.look_dy = 0; - pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); pxl8_sim_move_player(&world->local_player, &merged, &sim, dt); @@ -571,6 +637,7 @@ static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) { } world->client_tick++; + (void)has_input; } static void pxl8_world_swap_buffers(pxl8_world* world) { @@ -670,12 +737,6 @@ void pxl8_world_pause_sim(pxl8_world* world, bool paused) { void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input) { if (!world || !input) return; - world->pointer_motion.yaw -= input->look_dx * 0.008f; - f32 pitch = world->pointer_motion.pitch - input->look_dy * 0.008f; - if (pitch > PXL8_SIM_MAX_PITCH) pitch = PXL8_SIM_MAX_PITCH; - if (pitch < -PXL8_SIM_MAX_PITCH) pitch = -PXL8_SIM_MAX_PITCH; - world->pointer_motion.pitch = pitch; - pxl8_input_msg* copy = pxl8_malloc(sizeof(pxl8_input_msg)); if (copy) { *copy = *input; diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index 11f31fd..a9024d9 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -35,6 +35,7 @@ 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); diff --git a/src/world/pxl8_world_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c index 89f5e38..3fd1177 100644 --- a/src/world/pxl8_world_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -409,7 +409,8 @@ pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache, (hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) || (hdr->chunk_type == PXL8_CHUNK_TYPE_VXL && (a->cx != hdr->cx || a->cy != hdr->cy || a->cz != hdr->cz)) || - a->version != hdr->version; + a->version != hdr->version || + hdr->fragment_idx == 0; if (new_assembly) { assembly_init(a, hdr); diff --git a/src/world/pxl8_world_chunk_cache.h b/src/world/pxl8_world_chunk_cache.h index 756caa7..ed40b01 100644 --- a/src/world/pxl8_world_chunk_cache.h +++ b/src/world/pxl8_world_chunk_cache.h @@ -11,7 +11,7 @@ extern "C" { #endif #define PXL8_WORLD_CHUNK_CACHE_SIZE 512 -#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 255 +#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 64 #define PXL8_WORLD_CHUNK_MAX_DATA_SIZE 131072 typedef struct pxl8_world_chunk_cache_entry {