diff --git a/demo/main.fnl b/demo/main.fnl index e4cc30c..9fe84cf 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -107,4 +107,6 @@ (transition:render)) (when (menu.is-paused) - (menu.draw)))) + (pxl8.push_target) + (menu.draw) + (pxl8.pop_target)))) diff --git a/demo/mod/entities.fnl b/demo/mod/entities.fnl index 829957b..1a17c01 100644 --- a/demo/mod/entities.fnl +++ b/demo/mod/entities.fnl @@ -168,9 +168,8 @@ (fn render-fireball [x y z wireframe] (when fireball-mesh (pxl8.draw_mesh fireball-mesh {:x x :y y :z z - :passthrough true - :wireframe wireframe - :emissive 1.0}))) + :emissive true + :wireframe wireframe}))) {: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 c231e0b..fefa7ec 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -1,7 +1,6 @@ (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)) @@ -124,9 +123,7 @@ (set lights (pxl8.create_lights))) (entities.init textures) - - (sky.generate-stars 12345) - + (sky.generate-stars) (preload) (when (not ceiling-tex) @@ -136,22 +133,17 @@ (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) ceiling-tex floor-tex trim-tex wall-tex) + (when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex) (let [chunk (world:active_chunk)] (when (and chunk (chunk:ready)) - (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})] - + (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})] (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)))))) @@ -199,7 +191,7 @@ (when (> portal-cooldown 0) (set portal-cooldown (- portal-cooldown dt))) - (when (and network (<= portal-cooldown 0)) + (when (and world network (<= portal-cooldown 0)) (let [chunk (world:active_chunk) in-bsp (not= chunk nil) (door-x door-z) (entities.get-door-position) @@ -240,7 +232,7 @@ expecting-bsp (> chunk-id 0) voxel-space (and (not chunk) (= chunk-id 0)) ready (or voxel-space (and chunk (chunk:ready)))] - (when ready + (when world (let [input (sample-input) grid-max (if voxel-space 100000 (* grid-size chunk-size)) movement-yaw cam-yaw] @@ -322,10 +314,14 @@ 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 for world data..." 5 30 12)) + (pxl8.text (.. "Waiting... chunk=" (tostring chunk) " voxel=" (tostring voxel-space)) 5 50 12)) - (when (and camera world ready) + (when (and camera world) (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)) @@ -353,19 +349,22 @@ 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 0xFF8C32 light-intensity light-radius) + (lights:add light-x light-y light-z 0xFFB888 light-intensity light-radius) + + (pxl8.push_target) (pxl8.begin_frame_3d camera lights { - :ambient 30 + :ambient 25 :fog_density 0.0 :celestial_dir [0.5 -0.8 0.3] - :celestial_intensity 0.5}) + :celestial_intensity 0.3}) (pxl8.clear_depth) (sky.update-gradient 1 2 6 6 10 18) (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) - (world:set_wireframe (menu.is-wireframe)) + (pxl8.set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) (when chunk @@ -373,16 +372,10 @@ (entities.render-door (menu.is-wireframe) (if voxel-space 128 0)) - (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.end_frame_3d) + (pxl8.pop_target)) + (pxl8.push_target) (let [cx (/ (pxl8.get_width) 2) cy (/ (pxl8.get_height) 2) crosshair-size 4 @@ -394,7 +387,8 @@ (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)))))) + (string.format "%.0f" cam-z)) 5 15 text-color)) + (pxl8.pop_target))))) {:preload preload :is-connected is-connected diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 5bee354..4219e13 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -4,7 +4,6 @@ (local net-mod (require :pxl8.net)) (var paused false) -(var wireframe false) (var gui nil) (var render-distance 3) (var sim-distance 4) @@ -13,6 +12,11 @@ (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)] @@ -100,24 +104,20 @@ (or clicked (and is-selected (= pending-action label)))))) (fn draw-main-menu [] - (pxl8.gui_window 200 80 240 235 "pxl8 demo") + (pxl8.gui_window 200 80 240 200 "pxl8 demo") (when (menu-button 1 215 127 210 30 "Resume") (hide)) - (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") + (when (menu-button 5 215 162 210 30 "GFX") (set current-panel :gfx) (set selected-item nil)) - (when (menu-button 6 215 232 210 30 "SFX") + (when (menu-button 6 215 197 210 30 "SFX") (set current-panel :sfx) (set selected-item nil)) - (when (menu-button 2 215 267 210 30 "Quit") + (when (menu-button 2 215 232 210 30 "Quit") (pxl8.quit))) (fn draw-sfx-panel [] @@ -136,10 +136,18 @@ (set selected-item nil))) (fn draw-gfx-panel [] - (pxl8.gui_window 200 100 240 180 "GFX") + (pxl8.gui_window 200 60 240 220 "GFX") - (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)] + (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)] (when changed (set render-distance new-val) (let [w (world-mod.World.get) @@ -147,16 +155,15 @@ (when w (w:set_render_distance new-val)) (when n (n:set_chunk_settings new-val sim-distance))))) - (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 [tex-label (if textures "Textures: On" "Textures: Off")] + (when (menu-button 42 215 194 210 24 tex-label) + (set textures (not textures)))) - (when (menu-button 32 215 240 210 30 "Back") + (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") (set current-panel :main) (set selected-item nil))) @@ -178,6 +185,9 @@ {: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 f1727fe..eb6d2d0 100644 --- a/demo/mod/sky.fnl +++ b/demo/mod/sky.fnl @@ -176,7 +176,8 @@ (set star-count (+ (length tiny-stars) (length random-stars))) (set star-directions (pxl8.create_vec3_array star-count)) - (set star-glows (pxl8.create_glows 10000)) + (when pxl8.create_glows + (set star-glows (pxl8.create_glows 10000))) (set star-projected (pxl8.create_vec3_array star-count)) (var idx 0) @@ -247,7 +248,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 :passthrough true :wireframe wireframe}))) + (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :wireframe wireframe}))) {:render render :render-stars render-stars diff --git a/demo/res/textures/door.ase b/demo/res/textures/door.ase new file mode 100644 index 0000000..ecd2ad4 Binary files /dev/null and b/demo/res/textures/door.ase differ diff --git a/pxl8.sh b/pxl8.sh index 8bc63c0..605e1eb 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -8,7 +8,7 @@ if command -v ccache >/dev/null 2>&1; then CC="ccache $CC" fi -CFLAGS="-std=c23 -Wall -Wextra" +CFLAGS="-std=c23 -Wall -Wextra -Wno-missing-braces" LIBS="-lm" MODE="debug" BUILDDIR=".build" @@ -23,6 +23,7 @@ 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)" @@ -32,6 +33,7 @@ case "$(uname)" in ;; *) LINKER_FLAGS="$LINKER_FLAGS -rdynamic" + LIBS="$LIBS -ldl" ;; esac @@ -59,6 +61,12 @@ 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 @@ -70,6 +78,8 @@ 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" @@ -78,14 +88,7 @@ build_server() { } start_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 - print_info "Server mode: $mode, binary: $server_bin" + local server_bin="$BINDIR/pxl8d" if [[ -f "$server_bin" ]]; then print_info "Starting server..." ./$server_bin & @@ -94,7 +97,7 @@ start_server() { sleep 0.5 else print_error "pxl8d binary not found: $server_bin" - print_error "Build pxl8d first with: cd pxl8d && cargo build" + print_error "Build first with: ./pxl8.sh build" fi } @@ -128,8 +131,17 @@ 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 @@ -155,6 +167,54 @@ 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 @@ -180,15 +240,17 @@ print_usage() { echo " clean Remove build artifacts" echo " help Show this help message" echo " install Install pxl8 to ~/.local/bin" + echo " profile Profile with perf and generate flamegraph (Linux)" echo " run Build and run pxl8 (optional: cart.pxc or folder)" echo " update Download/update all dependencies" echo " vendor Fetch source for dependencies (ex. SDL3)" echo echo -e "${BOLD}OPTIONS:${NC}" - echo " --all Clean both build artifacts and dependencies" - echo " --cache Clear ccache (use with clean)" - echo " --deps Clean only dependencies" - echo " --release Build/run/clean in release mode (default: debug)" + echo " --all Clean both build artifacts and dependencies" + echo " --cache Clear ccache (use with clean)" + echo " --deps Clean only dependencies" + echo " --duration=N Profile duration in seconds (default: 30)" + echo " --release Build/run/clean in release mode (default: debug)" } setup_sdl3() { @@ -264,6 +326,20 @@ update_fennel() { fi } +update_flamegraph() { + print_info "Fetching FlameGraph" + + if [[ -d "lib/FlameGraph/.git" ]]; then + cd lib/FlameGraph && git pull --quiet origin master + cd - > /dev/null + else + rm -rf lib/FlameGraph + git clone --quiet https://github.com/brendangregg/FlameGraph.git lib/FlameGraph + fi + + print_info "Updated FlameGraph" +} + update_linenoise() { print_info "Fetching linenoise" @@ -328,6 +404,7 @@ update_sdl() { print_info "Updated SDL3" } + COMMAND="$1" shift || true @@ -399,7 +476,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/sfx -Isrc/sim -Isrc/vxl -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz" + 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" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" @@ -424,11 +501,12 @@ 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 @@ -461,11 +539,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" @@ -477,7 +555,9 @@ 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" @@ -509,7 +589,7 @@ case "$COMMAND" in if [[ "$LUAJIT_LIB" -nt "$EXECUTABLE" ]] || [[ "$NEED_LINK" == true ]]; then print_info "Linking executable" - if ! $CC $LINKER_FLAGS $OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then + if ! $CC $LINKER_FLAGS $OBJECTS $SHADER_OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then print_error "Linking failed" exit 1 fi @@ -578,7 +658,7 @@ case "$COMMAND" in if [[ "$CLEAN_ALL" == true ]]; then print_info "Removing build artifacts and dependencies" - rm -rf "$BUILD_PATH" "$BIN_PATH" lib + rm -rf "$BUILD_PATH" "$BIN_PATH" .build/shaders lib clean_server print_info "Cleaned all" elif [[ "$CLEAN_DEPS" == true ]]; then @@ -587,7 +667,7 @@ case "$COMMAND" in print_info "Cleaned dependencies" else print_info "Removing build artifacts" - rm -rf "$BUILD_PATH" "$BIN_PATH" + rm -rf "$BUILD_PATH" "$BIN_PATH" .build/shaders clean_server print_info "Cleaned" fi @@ -626,6 +706,72 @@ case "$COMMAND" in bash tools/aseprite/pxl8-ase.sh "$@" ;; + profile) + if [[ "$(uname)" != "Linux" ]]; then + print_error "Profiling with perf is only supported on Linux" + exit 1 + fi + + if ! command -v perf >/dev/null 2>&1; then + print_error "perf not found. Install linux-tools or perf package." + exit 1 + fi + + if [[ ! -d "lib/FlameGraph" ]]; then + mkdir -p lib + update_flamegraph + fi + + "$0" build || exit 1 + + PROFILE_DIR=".build/debug/profile" + mkdir -p "$PROFILE_DIR" + + CART="" + PERF_DURATION=30 + for arg in "$@"; do + if [[ "$arg" =~ ^--duration=([0-9]+)$ ]]; then + PERF_DURATION="${BASH_REMATCH[1]}" + elif [[ "$arg" != "--release" ]] && [[ -z "$CART" ]]; then + CART="$arg" + fi + done + + [[ -z "$CART" ]] && CART="demo" + + TIMESTAMP=$(date +"%Y%m%d_%H%M%S") + PERF_DATA="$PROFILE_DIR/perf_${TIMESTAMP}.data" + PERF_SCRIPT="$PROFILE_DIR/perf_${TIMESTAMP}.perf" + FOLDED="$PROFILE_DIR/perf_${TIMESTAMP}.folded" + SVG="$PROFILE_DIR/flamegraph_${TIMESTAMP}.svg" + + print_info "Starting server..." + ./bin/debug/pxl8d & + SERVER_PID=$! + sleep 0.5 + + trap "kill $SERVER_PID 2>/dev/null; wait $SERVER_PID 2>/dev/null" EXIT + + print_info "Profiling pxl8 for ${PERF_DURATION}s (Ctrl+C to stop early)..." + perf record -F 99 -g --call-graph dwarf -o "$PERF_DATA" -- \ + timeout "${PERF_DURATION}s" ./bin/debug/pxl8 "$CART" 2>/dev/null || true + + print_info "Processing profile data..." + perf script -i "$PERF_DATA" > "$PERF_SCRIPT" + + print_info "Generating flamegraph..." + lib/FlameGraph/stackcollapse-perf.pl "$PERF_SCRIPT" > "$FOLDED" + lib/FlameGraph/flamegraph.pl --cp --colors orange --title "pxl8 profile" "$FOLDED" > "$SVG" + + rm -f "$PERF_DATA" "$PERF_SCRIPT" "$FOLDED" + + print_info "Flamegraph: $SVG" + + if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$SVG" 2>/dev/null & + fi + ;; + help|--help|-h|"") print_usage ;; diff --git a/pxl8d/build.rs b/pxl8d/build.rs index 6b45bbf..2ff503d 100644 --- a/pxl8d/build.rs +++ b/pxl8d/build.rs @@ -36,9 +36,8 @@ fn main() { let bindings = bindgen::Builder::default() .header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap()) - .header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap()) - .header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap()) .header(pxl8_src.join("math/pxl8_noise.h").to_str().unwrap()) + .header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap()) .clang_arg(format!("-I{}", pxl8_src.join("bsp").display())) .clang_arg(format!("-I{}", pxl8_src.join("core").display())) .clang_arg(format!("-I{}", pxl8_src.join("math").display())) @@ -50,6 +49,11 @@ fn main() { .blocklist_item("FP_ZERO") .blocklist_item("FP_SUBNORMAL") .blocklist_item("FP_NORMAL") + .blocklist_type("pxl8_vec2") + .blocklist_type("pxl8_vec3") + .blocklist_type("pxl8_vec4") + .blocklist_type("pxl8_mat4") + .raw_line("pub use crate::math::{pxl8_vec2, pxl8_vec3, pxl8_vec4, pxl8_mat4};") .clang_arg("-DPXL8_NO_SIMD") .use_core() .rustified_enum(".*") diff --git a/pxl8d/src/bsp.rs b/pxl8d/src/bsp.rs index 0ef5fa0..8d36245 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -101,6 +101,38 @@ impl Default for CellPortals { } } +impl Clone for Face { + fn clone(&self) -> Self { + Self { + first_edge: self.first_edge, + lightmap_offset: self.lightmap_offset, + num_edges: self.num_edges, + plane_id: self.plane_id, + side: self.side, + styles: self.styles, + material_id: self.material_id, + aabb_min: self.aabb_min, + aabb_max: self.aabb_max, + } + } +} + +impl Clone for Plane { + fn clone(&self) -> Self { + Self { + dist: self.dist, + normal: self.normal, + type_: self.type_, + } + } +} + +impl Clone for Vertex { + fn clone(&self) -> Self { + Self { position: self.position } + } +} + pub struct Bsp { inner: pxl8_bsp, pub cell_portals: Box<[CellPortals]>, diff --git a/pxl8d/src/math.rs b/pxl8d/src/math.rs index ea2b272..250ec0f 100644 --- a/pxl8d/src/math.rs +++ b/pxl8d/src/math.rs @@ -1,8 +1,43 @@ use core::ops::{Add, Mul, Sub}; -use crate::pxl8::pxl8_vec3; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} -pub type Vec3 = pxl8_vec3; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Vec4 { + pub x: f32, + pub y: f32, + pub z: f32, + pub w: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Mat4 { + pub m: [f32; 16], +} + +#[allow(non_camel_case_types)] +pub type pxl8_vec2 = Vec2; +#[allow(non_camel_case_types)] +pub type pxl8_vec3 = Vec3; +#[allow(non_camel_case_types)] +pub type pxl8_vec4 = Vec4; +#[allow(non_camel_case_types)] +pub type pxl8_mat4 = Mat4; pub const VEC3_ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 }; pub const VEC3_Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 }; diff --git a/pxl8d/src/procgen.rs b/pxl8d/src/procgen.rs index 0da79f0..1df3441 100644 --- a/pxl8d/src/procgen.rs +++ b/pxl8d/src/procgen.rs @@ -6,6 +6,7 @@ use libm::sqrtf; use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex}; use crate::math::{Vec3, Vec3Ext}; +use crate::pxl8::{pxl8_vec3_cross, pxl8_vec3_dot, pxl8_vec3_normalize, pxl8_vec3_scale, pxl8_vec3_add, pxl8_vec3_sub}; pub const CELL_SIZE: f32 = 64.0; pub const WALL_HEIGHT: f32 = 128.0; @@ -402,36 +403,139 @@ fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) { bsp.visdata = visdata; } +const AO_NUM_SAMPLES: usize = 16; +const AO_RAY_LENGTH: f32 = 48.0; + +fn generate_hemisphere_samples(normal: Vec3) -> [Vec3; AO_NUM_SAMPLES] { + let tangent = if normal.y.abs() < 0.9 { + unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(0.0, 1.0, 0.0))) } + } else { + unsafe { pxl8_vec3_normalize(pxl8_vec3_cross(normal, Vec3::new(1.0, 0.0, 0.0))) } + }; + let bitangent = unsafe { pxl8_vec3_cross(normal, tangent) }; + + let mut samples = [Vec3::new(0.0, 0.0, 0.0); AO_NUM_SAMPLES]; + for i in 0..AO_NUM_SAMPLES { + let phi = (i as f32 / AO_NUM_SAMPLES as f32) * core::f32::consts::TAU; + let theta = ((i as f32 + 0.5) / AO_NUM_SAMPLES as f32) * (core::f32::consts::PI * 0.45); + let (sin_phi, cos_phi) = (libm::sinf(phi), libm::cosf(phi)); + let (sin_theta, cos_theta) = (libm::sinf(theta), libm::cosf(theta)); + + let local_x = sin_theta * cos_phi; + let local_y = cos_theta; + let local_z = sin_theta * sin_phi; + + unsafe { + let t_contrib = pxl8_vec3_scale(tangent, local_x); + let n_contrib = pxl8_vec3_scale(normal, local_y); + let b_contrib = pxl8_vec3_scale(bitangent, local_z); + samples[i] = pxl8_vec3_normalize(pxl8_vec3_add(pxl8_vec3_add(t_contrib, n_contrib), b_contrib)); + } + } + samples +} + +fn ray_triangle_intersect(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3, max_dist: f32) -> bool { + let edge1 = unsafe { pxl8_vec3_sub(v1, v0) }; + let edge2 = unsafe { pxl8_vec3_sub(v2, v0) }; + let h = unsafe { pxl8_vec3_cross(dir, edge2) }; + let a = unsafe { pxl8_vec3_dot(edge1, h) }; + + if a > -0.0001 && a < 0.0001 { + return false; + } + + let f = 1.0 / a; + let s = unsafe { pxl8_vec3_sub(origin, v0) }; + let u = f * unsafe { pxl8_vec3_dot(s, h) }; + if u < 0.0 || u > 1.0 { + return false; + } + + let q = unsafe { pxl8_vec3_cross(s, edge1) }; + let v = f * unsafe { pxl8_vec3_dot(dir, q) }; + if v < 0.0 || u + v > 1.0 { + return false; + } + + let t = f * unsafe { pxl8_vec3_dot(edge2, q) }; + t > 0.001 && t < max_dist +} + +fn compute_vertex_ao(bsp: &BspBuilder, pos: Vec3, normal: Vec3) -> f32 { + let samples = generate_hemisphere_samples(normal); + let offset_pos = unsafe { pxl8_vec3_add(pos, pxl8_vec3_scale(normal, 0.5)) }; + + let mut occluded = 0; + + for sample_dir in &samples { + 'face_loop: for face in &bsp.faces { + if face.num_edges < 3 { + continue; + } + + let mut verts = [Vec3::new(0.0, 0.0, 0.0); 4]; + let mut num_verts = 0usize; + + for e in 0..face.num_edges.min(4) { + let surfedge_idx = (face.first_edge + e as u32) as usize; + if surfedge_idx >= bsp.surfedges.len() { + continue; + } + + let edge_idx = bsp.surfedges[surfedge_idx]; + let vert_idx = if edge_idx >= 0 { + let ei = edge_idx as usize; + if ei >= bsp.edges.len() { continue; } + bsp.edges[ei].vertex[0] as usize + } else { + let ei = (-edge_idx) as usize; + if ei >= bsp.edges.len() { continue; } + bsp.edges[ei].vertex[1] as usize + }; + + if vert_idx < bsp.vertices.len() { + verts[num_verts] = bsp.vertices[vert_idx].position; + num_verts += 1; + } + } + + if num_verts >= 3 { + if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[1], verts[2], AO_RAY_LENGTH) { + occluded += 1; + break 'face_loop; + } + if num_verts == 4 { + if ray_triangle_intersect(offset_pos, *sample_dir, verts[0], verts[2], verts[3], AO_RAY_LENGTH) { + occluded += 1; + break 'face_loop; + } + } + } + } + } + + 1.0 - (occluded as f32 / AO_NUM_SAMPLES as f32) +} + fn compute_vertex_light( pos: Vec3, normal: Vec3, lights: &[LightSource], - ambient: f32, ) -> f32 { - let mut total = ambient; + let mut total = 0.0; for light in lights { - let to_light = Vec3::new( - light.position.x - pos.x, - light.position.y - pos.y, - light.position.z - pos.z, - ); - - let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z; - let dist = sqrtf(dist_sq).max(1.0); + let to_light = unsafe { pxl8_vec3_sub(light.position, pos) }; + let dist = unsafe { pxl8_vec3_dot(to_light, to_light) }; + let dist = sqrtf(dist).max(1.0); if dist > light.radius { continue; } - let inv_dist = 1.0 / dist; - let light_dir = Vec3::new( - to_light.x * inv_dist, - to_light.y * inv_dist, - to_light.z * inv_dist, - ); - - let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0); + let light_dir = unsafe { pxl8_vec3_normalize(to_light) }; + let ndotl = unsafe { pxl8_vec3_dot(normal, light_dir) }.max(0.0); let attenuation = (1.0 - dist / light.radius).max(0.0); let attenuation = attenuation * attenuation; @@ -442,12 +546,13 @@ fn compute_vertex_light( total.min(1.0) } -fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) { +fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) { if bsp.vertices.is_empty() { return; } bsp.vertex_lights = vec![0u32; bsp.vertices.len()]; + let mut vertex_normals: Vec> = vec![None; bsp.vertices.len()]; for f in 0..bsp.faces.len() { let face = &bsp.faces[f]; @@ -478,13 +583,27 @@ fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], amb continue; } - let pos = bsp.vertices[vert_idx].position; - let light = compute_vertex_light(pos, normal, lights, ambient); + vertex_normals[vert_idx] = Some(normal); - let light_byte = (light * 255.0) as u8; - bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF; + let pos = bsp.vertices[vert_idx].position; + let direct = compute_vertex_light(pos, normal, lights); + let direct_byte = ((direct * 255.0).min(255.0)) as u8; + + bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0x00FFFFFF) | ((direct_byte as u32) << 24); } } + + for vert_idx in 0..bsp.vertices.len() { + let normal = match vertex_normals[vert_idx] { + Some(n) => n, + None => continue, + }; + let pos = bsp.vertices[vert_idx].position; + let ao = compute_vertex_ao(bsp, pos, normal); + let ao_byte = ((ao * 255.0).min(255.0)) as u8; + + bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0xFF00FFFF) | ((ao_byte as u32) << 16); + } } fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { @@ -926,17 +1045,27 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; - let lights: Vec = rooms.iter().map(|room| { + let fireball_pos = Vec3::new(384.0, light_height, 324.0); + let fireball_exclusion_radius = 150.0; + + let lights: Vec = rooms.iter().filter_map(|room| { let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; - LightSource { - position: Vec3::new(cx, light_height, cy), - intensity: 0.8, - radius: 300.0, + let cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; + + let dx = cx - fireball_pos.x; + let dz = cz - fireball_pos.z; + if dx * dx + dz * dz < fireball_exclusion_radius * fireball_exclusion_radius { + return None; } + + Some(LightSource { + position: Vec3::new(cx, light_height, cz), + intensity: 1.8, + radius: 160.0, + }) }).collect(); - compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1); + compute_bsp_vertex_lighting(&mut bsp, &lights); bsp.into() } diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index e3dc5c1..e916861 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -28,6 +28,20 @@ impl Default for Entity { } } +impl Clone for Entity { + fn clone(&self) -> Self { + Self { + pos: self.pos, + vel: self.vel, + yaw: self.yaw, + pitch: self.pitch, + flags: self.flags, + kind: self.kind, + _pad: self._pad, + } + } +} + pub struct Simulation { pub entities: Vec, pub free_list: Vec, diff --git a/src/asset/pxl8_embed.h b/src/asset/pxl8_embed.h index 7139fa4..b33b40d 100644 --- a/src/asset/pxl8_embed.h +++ b/src/asset/pxl8_embed.h @@ -27,12 +27,8 @@ static const char embed_pxl8_effects[] = { #embed "src/lua/pxl8/effects.lua" , 0 }; -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" +static const char embed_pxl8_gfx[] = { +#embed "src/lua/pxl8/gfx.lua" , 0 }; static const char embed_pxl8_gui[] = { @@ -86,8 +82,7 @@ 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_gfx2d, "pxl8.gfx2d"), - PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"), + PXL8_EMBED_ENTRY(embed_pxl8_gfx, "pxl8.gfx"), 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 5f85a65..8afe551 100644 --- a/src/bsp/pxl8_bsp_render.c +++ b/src/bsp/pxl8_bsp_render.c @@ -118,7 +118,7 @@ static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const } static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state, - u32 face_id, pxl8_mesh* mesh) { + u32 face_id, pxl8_mesh* mesh, u8 ambient) { const pxl8_bsp_face* face = &bsp->faces[face_id]; if (face->num_edges < 3) return; @@ -179,7 +179,11 @@ static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_stat u8 light = 255; if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) { - light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; + u32 packed = bsp->vertex_lights[vert_idx]; + u8 direct = (packed >> 24) & 0xFF; + u8 ao = (packed >> 16) & 0xFF; + f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao; + light = (u8)(combined > 255.0f ? 255.0f : combined); } pxl8_vertex vtx = { @@ -230,7 +234,7 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_mesh* mesh = pxl8_mesh_create(64, 192); if (!mesh) return; - collect_face_to_mesh(bsp, NULL, face_id, mesh); + collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx)); if (mesh->index_count > 0) { pxl8_mat4 identity = pxl8_mat4_identity(); @@ -242,15 +246,8 @@ 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 (!bsp->cell_portals || bsp->num_cell_portals == 0) { - return; - } - if (!state->materials || state->num_materials == 0) { - return; - } + if (!gfx || !bsp || !state || bsp->num_faces == 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); @@ -259,6 +256,38 @@ 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; @@ -370,7 +399,7 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, } current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh); + collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); } } @@ -420,10 +449,3 @@ void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const state->materials[material_id].u_offset = u_offset; state->materials[material_id].v_offset = v_offset; } - -void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe) { - if (!state || !state->materials) return; - for (u32 i = 0; i < state->num_materials; i++) { - state->materials[i].wireframe = wireframe; - } -} diff --git a/src/bsp/pxl8_bsp_render.h b/src/bsp/pxl8_bsp_render.h index 04b1cb3..3c4cb4e 100644 --- a/src/bsp/pxl8_bsp_render.h +++ b/src/bsp/pxl8_bsp_render.h @@ -23,7 +23,6 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material); void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material); -void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe); #ifdef __cplusplus } diff --git a/src/core/pxl8.c b/src/core/pxl8.c index 642bc0e..ac06e7d 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -393,13 +393,16 @@ pxl8_result pxl8_update(pxl8* sys) { } #ifdef PXL8_ASYNC_THREADS - if (game->world && (pxl8_world_local_player(game->world))) { + if (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); - msg.look_dx = (f32)pxl8_mouse_dx(&game->input); - msg.look_dy = (f32)pxl8_mouse_dy(&game->input); + 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.buttons = pxl8_key_down(&game->input, "space") ? 1 : 0; + pxl8_world_push_input(game->world, &msg); } pxl8_net_update(game->net, dt); @@ -409,7 +412,6 @@ 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 @@ -563,9 +565,6 @@ 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 ed3509d..f289461 100644 --- a/src/core/pxl8_macros.h +++ b/src/core/pxl8_macros.h @@ -2,6 +2,14 @@ #include +#if defined(__GNUC__) || defined(__clang__) +#define pxl8_likely(x) __builtin_expect(!!(x), 1) +#define pxl8_unlikely(x) __builtin_expect(!!(x), 0) +#else +#define pxl8_likely(x) (x) +#define pxl8_unlikely(x) (x) +#endif + #ifndef pxl8_min #define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) #endif diff --git a/src/gfx/pxl8_atlas.h b/src/gfx/pxl8_atlas.h index 2a429f9..94e0a95 100644 --- a/src/gfx/pxl8_atlas.h +++ b/src/gfx/pxl8_atlas.h @@ -6,10 +6,10 @@ typedef struct pxl8_atlas pxl8_atlas; typedef struct pxl8_atlas_entry { bool active; + i32 h, w, x, y; + u8 log2_h, log2_w; u32 texture_id; - i32 x, y, w, h; u32 tiled_base; - u8 log2_w; } pxl8_atlas_entry; static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) { diff --git a/src/gfx/pxl8_backend.h b/src/gfx/pxl8_backend.h deleted file mode 100644 index d7741b5..0000000 --- a/src/gfx/pxl8_backend.h +++ /dev/null @@ -1,19 +0,0 @@ -#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 04f8fd0..fb9f714 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_dither_light(intensity, x, y); + u8 dithered = pxl8_gfx_dither((f32)intensity + 0.5f, 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 deleted file mode 100644 index 2989d42..0000000 --- a/src/gfx/pxl8_cpu.c +++ /dev/null @@ -1,1669 +0,0 @@ -#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 deleted file mode 100644 index 81f3852..0000000 --- a/src/gfx/pxl8_cpu.h +++ /dev/null @@ -1,94 +0,0 @@ -#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 3b5416a..c22cf8e 100644 --- a/src/gfx/pxl8_dither.h +++ b/src/gfx/pxl8_dither.h @@ -8,8 +8,16 @@ extern "C" { extern const u8 PXL8_BAYER_4X4[16]; -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_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 u8 pxl8_ordered_dither(u8 value, u32 x, u32 y) { @@ -18,24 +26,6 @@ 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 4d2e364..1dadd12 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -5,7 +5,6 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" -#include "pxl8_backend.h" #include "pxl8_blit.h" #include "pxl8_color.h" #include "pxl8_colormap.h" @@ -15,36 +14,77 @@ #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_gfx_backend backend; + 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; 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) { @@ -63,15 +103,12 @@ 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; - if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { - return pxl8_cpu_get_framebuffer(gfx->backend.cpu); - } - return gfx->framebuffer; + return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); } u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; - return (u16*)gfx->framebuffer; + return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); } i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { @@ -80,10 +117,30 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx) { if (!gfx) return NULL; - if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { - return pxl8_cpu_get_light_accum(gfx->backend.cpu); + 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; } - return NULL; + pxl8_resolve_to_rgba( + gfx->renderer, + gfx->color_target, + gfx->light_target, + pal, + gfx->output + ); } const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) { @@ -93,10 +150,7 @@ const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) { u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx) { if (!gfx) return NULL; - if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { - return pxl8_cpu_get_zbuffer(gfx->backend.cpu); - } - return NULL; + return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->depth_target); } i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { @@ -123,9 +177,6 @@ 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) { @@ -136,9 +187,6 @@ 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; } @@ -149,6 +197,8 @@ 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"); @@ -173,29 +223,87 @@ pxl8_gfx* pxl8_gfx_create( gfx->palette = pxl8_palette_create(); } - 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"); + gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height); + if (!gfx->renderer) { + pxl8_error("Failed to create renderer"); pxl8_gfx_destroy(gfx); return NULL; } - gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu); + 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; + } 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; } @@ -204,13 +312,13 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) { if (!gfx) return; pxl8_atlas_destroy(gfx->atlas); - if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { - pxl8_cpu_destroy(gfx->backend.cpu); - } + pxl8_cmdbuf_destroy(gfx->cmdbuf); 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); } @@ -330,26 +438,24 @@ 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->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; - } + 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; } 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, - gfx->framebuffer, + framebuffer, gfx->framebuffer_width, gfx->framebuffer_height, gfx->pixel_mode, @@ -363,6 +469,16 @@ void pxl8_gfx_present(pxl8_gfx* gfx) { gfx->hal->present(gfx->platform_data); } +void pxl8_gfx_reset_stats(pxl8_gfx* gfx) { + if (!gfx || !gfx->renderer) return; + pxl8_renderer_reset_stats(gfx->renderer); +} + +const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx) { + if (!gfx || !gfx->renderer) return NULL; + return pxl8_renderer_get_stats(gfx->renderer); +} + pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { pxl8_viewport vp = {0}; vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height); @@ -382,115 +498,99 @@ 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; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_clear(gfx->backend.cpu, (u8)color); - break; - case PXL8_GFX_BACKEND_GPU: - break; - } + pxl8_clear(gfx->renderer, gfx_current_color(gfx), (u8)color); + pxl8_clear_light(gfx->renderer, gfx_current_light(gfx)); } void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_pixel(gfx->renderer, gfx_current_color(gfx), x, y, (u8)color); } u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) { if (!gfx) return 0; - 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; + return pxl8_get_pixel(gfx->renderer, gfx_current_color(gfx), x, y); } void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, (u8)color); } void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_rect(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color); } void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_rect_fill(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color); } void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_circle(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color); } void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { if (!gfx) return; - 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; - } + pxl8_draw_circle_fill(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color); } void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { if (!gfx || !text) return; - 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; - } + 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); if (!framebuffer) return; @@ -523,7 +623,6 @@ 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; } } } @@ -536,24 +635,10 @@ 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; - 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; - } + 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); if (!framebuffer) return; @@ -583,11 +668,6 @@ 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++) { @@ -601,7 +681,6 @@ 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; } } } @@ -616,7 +695,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_3d_uniforms* uniforms) { +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) { pxl8_3d_frame frame = {0}; if (!camera) return frame; @@ -639,32 +718,37 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp) { gfx->bsp = bsp; } -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) { +void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms) { if (!gfx || !camera) return; pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); frame.bsp = gfx->bsp; - frame.lights = lights ? pxl8_lights_data(lights) : NULL; - frame.lights_count = lights ? pxl8_lights_count(lights) : 0; - frame.sdf = gfx->sdf; + frame.uniforms.lights = lights ? pxl8_lights_data(lights) : NULL; + frame.uniforms.lights_count = lights ? pxl8_lights_count(lights) : 0; pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view); gfx->frustum = pxl8_frustum_from_matrix(vp); gfx->view_proj = vp; + gfx->frame = frame; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_begin_frame(gfx->backend.cpu, &frame); - break; - case PXL8_GFX_BACKEND_GPU: - break; - } + 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); } const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) { @@ -706,90 +790,249 @@ 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; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_clear(gfx->backend.cpu, color); - break; - case PXL8_GFX_BACKEND_GPU: - break; - } + pxl8_clear(gfx->renderer, gfx_current_color(gfx), color); + pxl8_clear_light(gfx->renderer, gfx_current_light(gfx)); } void pxl8_3d_clear_depth(pxl8_gfx* gfx) { if (!gfx) return; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_clear_depth(gfx->backend.cpu); - break; - case PXL8_GFX_BACKEND_GPU: - break; - } + pxl8_clear_depth(gfx->renderer, gfx_current_depth(gfx)); } void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { if (!gfx) return; - 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; - } + + 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); } 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; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas); + 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; break; - case PXL8_GFX_BACKEND_GPU: + case PXL8_BLEND_ALPHA: + pipe_desc.blend.enabled = true; + pipe_desc.blend.src = PXL8_GFX_BLEND_SRC_ALPHA; + pipe_desc.blend.dst = PXL8_GFX_BLEND_INV_SRC_ALPHA; + break; + case PXL8_BLEND_ADDITIVE: + pipe_desc.blend.enabled = true; + pipe_desc.blend.src = PXL8_GFX_BLEND_ONE; + pipe_desc.blend.dst = PXL8_GFX_BLEND_ONE; + break; + case PXL8_BLEND_OPAQUE: + default: break; } + pxl8_gfx_pipeline pipeline = pxl8_create_pipeline(gfx->renderer, &pipe_desc); + if (res->pipeline_count < PXL8_MAX_FRAME_RESOURCES) { + res->pipelines[res->pipeline_count++] = pipeline; + } + + pxl8_gfx_bindings_desc bind_desc = { + .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) return; - switch (gfx->backend.type) { - case PXL8_GFX_BACKEND_CPU: - pxl8_cpu_end_frame(gfx->backend.cpu); - break; - case PXL8_GFX_BACKEND_GPU: - break; + 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]); } + 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; - 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; + return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx)); } bool pxl8_gfx_push_target(pxl8_gfx* gfx) { - 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; + 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; } void pxl8_gfx_pop_target(pxl8_gfx* gfx) { - 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; - } + 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); } void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { @@ -797,9 +1040,6 @@ 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); - } } } @@ -810,9 +1050,6 @@ 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); - } } } @@ -838,20 +1075,14 @@ u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) { return pxl8_palette_find_closest(gfx->palette, r, g, b); } -void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) { - if (!gfx || !params || count == 0) return; - - 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; - } - } +void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled) { + if (gfx) gfx->wireframe = enabled; +} + +bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx) { + return gfx ? gfx->wireframe : false; +} + +u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx) { + return gfx ? gfx->frame.uniforms.ambient : 0; } diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index cd53b5c..8a09a33 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -10,6 +10,7 @@ #include "pxl8_gui_palette.h" typedef struct pxl8_gfx pxl8_gfx; +typedef struct pxl8_gfx_stats pxl8_gfx_stats; typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0, @@ -25,6 +26,8 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx); void pxl8_gfx_present(pxl8_gfx* gfx); void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt); void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); +void pxl8_gfx_reset_stats(pxl8_gfx* gfx); +const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx); u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color); @@ -62,6 +65,11 @@ void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx); u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b); u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index); +void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled); +bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx); + +u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx); + #ifdef __cplusplus } #endif diff --git a/src/gfx/pxl8_gfx3d.h b/src/gfx/pxl8_gfx3d.h index bf964c2..d5e2689 100644 --- a/src/gfx/pxl8_gfx3d.h +++ b/src/gfx/pxl8_gfx3d.h @@ -4,39 +4,20 @@ #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; - const pxl8_sdf* sdf; - pxl8_3d_uniforms uniforms; + pxl8_shader_uniforms uniforms; pxl8_mat4 view; } pxl8_3d_frame; @@ -45,8 +26,7 @@ extern "C" { #endif void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp); -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_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_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); @@ -57,7 +37,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_3d_uniforms* uniforms); +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms); #ifdef __cplusplus } diff --git a/src/gfx/pxl8_lights.c b/src/gfx/pxl8_lights.c index 429ef14..17bffc5 100644 --- a/src/gfx/pxl8_lights.c +++ b/src/gfx/pxl8_lights.c @@ -48,6 +48,36 @@ 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 4a445bd..2be0c6b 100644 --- a/src/gfx/pxl8_lights.h +++ b/src/gfx/pxl8_lights.h @@ -12,6 +12,9 @@ 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 cb85ab0..ddc8b4f 100644 --- a/src/gfx/pxl8_mesh.h +++ b/src/gfx/pxl8_mesh.h @@ -31,10 +31,8 @@ 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 new file mode 100644 index 0000000..3a5e355 --- /dev/null +++ b/src/gfx/pxl8_render.c @@ -0,0 +1,1502 @@ +#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 new file mode 100644 index 0000000..906def2 --- /dev/null +++ b/src/gfx/pxl8_render.h @@ -0,0 +1,98 @@ +#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 new file mode 100644 index 0000000..d100c26 --- /dev/null +++ b/src/gfx/pxl8_render_types.h @@ -0,0 +1,242 @@ +#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 new file mode 100644 index 0000000..3241a21 --- /dev/null +++ b/src/gfx/pxl8_shader.h @@ -0,0 +1,81 @@ +#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 new file mode 100644 index 0000000..abde7a5 --- /dev/null +++ b/src/gfx/pxl8_shader_builtins.h @@ -0,0 +1,80 @@ +#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 new file mode 100644 index 0000000..370f183 --- /dev/null +++ b/src/gfx/pxl8_shader_registry.c @@ -0,0 +1,15 @@ +#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 new file mode 100644 index 0000000..473afc6 --- /dev/null +++ b/src/gfx/pxl8_shader_registry.h @@ -0,0 +1,7 @@ +#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 new file mode 100644 index 0000000..14de4f7 --- /dev/null +++ b/src/gfx/pxl8_shader_runtime.c @@ -0,0 +1,66 @@ +#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 new file mode 100644 index 0000000..4650f65 --- /dev/null +++ b/src/gfx/pxl8_shader_runtime.h @@ -0,0 +1,20 @@ +#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 new file mode 100644 index 0000000..b9d55c9 --- /dev/null +++ b/src/gfx/shaders/cpu/lit.c @@ -0,0 +1,106 @@ +#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 new file mode 100644 index 0000000..b3ff4c6 --- /dev/null +++ b/src/gfx/shaders/cpu/unlit.c @@ -0,0 +1,31 @@ +#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 e3f667a..766091a 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 a4617d4..0f5c486 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -2,8 +2,7 @@ local anim = require("pxl8.anim") local bytes = require("pxl8.bytes") local core = require("pxl8.core") local effects = require("pxl8.effects") -local gfx2d = require("pxl8.gfx2d") -local gfx3d = require("pxl8.gfx3d") +local gfx = require("pxl8.gfx") local gui = require("pxl8.gui") local input = require("pxl8.input") local math = require("pxl8.math") @@ -44,25 +43,27 @@ pxl8.set_palette = core.set_palette pxl8.set_palette_rgb = core.set_palette_rgb pxl8.update_palette_deps = core.update_palette_deps -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.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.key_down = input.key_down pxl8.key_pressed = input.key_pressed @@ -86,26 +87,23 @@ pxl8.create_anim_from_ase = anim.Anim.from_ase pxl8.bounds = math.bounds -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.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.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 a6221b5..f38381d 100644 --- a/src/lua/pxl8/effects.lua +++ b/src/lua/pxl8/effects.lua @@ -1,49 +1,8 @@ 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/gfx3d.lua b/src/lua/pxl8/gfx.lua similarity index 53% rename from src/lua/pxl8/gfx3d.lua rename to src/lua/pxl8/gfx.lua index fc30fc2..64cbf87 100644 --- a/src/lua/pxl8/gfx3d.lua +++ b/src/lua/pxl8/gfx.lua @@ -2,7 +2,108 @@ local ffi = require("ffi") local C = ffi.C local core = require("pxl8.core") -local gfx3d = {} +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 Camera3D = {} Camera3D.__index = Camera3D @@ -84,8 +185,6 @@ function Camera3D:world_to_screen(x, y, z, width, height) return nil end -gfx3d.Camera3D = Camera3D - local Mesh = {} Mesh.__index = Mesh @@ -127,35 +226,43 @@ function Mesh:clear() end end -gfx3d.Mesh = Mesh +gfx.Camera3D = Camera3D -function gfx3d.draw_mesh(mesh, opts) +gfx.Mesh = Mesh + +function gfx.draw_mesh(mesh, opts) + if not mesh or not mesh._ptr then + return + end opts = opts or {} - 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 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 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, - vertex_color_passthrough = opts.passthrough or false, - wireframe = opts.wireframe or false, - emissive_intensity = opts.emissive or 0.0, + texture_id = opts.texture or 0xFFFFFFFF, }) C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) end -function gfx3d.begin_frame(camera, lights, uniforms) +function gfx.begin_frame_3d(camera, lights, uniforms) uniforms = uniforms or {} local u = ffi.new("pxl8_3d_uniforms") @@ -164,68 +271,82 @@ function gfx3d.begin_frame(camera, lights, uniforms) u.fog_density = uniforms.fog_density or 0.0 u.time = uniforms.time or 0.0 + local cx, cy, cz if uniforms.celestial_dir then - u.celestial_dir.x = uniforms.celestial_dir[1] or 0 - u.celestial_dir.y = uniforms.celestial_dir[2] or -1 - u.celestial_dir.z = uniforms.celestial_dir[3] or 0 + cx = uniforms.celestial_dir[1] or 0 + cy = uniforms.celestial_dir[2] or -1 + cz = uniforms.celestial_dir[3] or 0 else - u.celestial_dir.x = 0 - u.celestial_dir.y = -1 - u.celestial_dir.z = 0 + cx, cy, cz = 0, -1, 0 end + local len = math.sqrt(cx * cx + cy * cy + cz * cz) + if len > 0.0001 then + local inv = 1.0 / len + cx, cy, cz = cx * inv, cy * inv, cz * inv + end + u.celestial_dir.x = cx + u.celestial_dir.y = cy + u.celestial_dir.z = cz u.celestial_intensity = uniforms.celestial_intensity or 0.0 local lights_ptr = lights and lights._ptr or nil C.pxl8_3d_begin_frame(core.gfx, camera._ptr, lights_ptr, u) end -function gfx3d.clear(color) +function gfx.clear_3d(color) C.pxl8_3d_clear(core.gfx, color or 0) end -function gfx3d.clear_depth() +function gfx.clear_depth() C.pxl8_3d_clear_depth(core.gfx) end -function gfx3d.draw_line(p0, p1, color) +function gfx.draw_line_3d(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 gfx3d.end_frame() +function gfx.end_frame_3d() C.pxl8_3d_end_frame(core.gfx) end -function gfx3d.project_points(input, output, count, transform) - C.pxl8_3d_project_points(core.gfx, input, output, count, transform) +function gfx.project_points(input, output, count, transform) + return C.pxl8_3d_project_points(core.gfx, input, output, count, transform) end -function gfx3d.create_vec3_array(count) +function gfx.create_vec3_array(count) return ffi.new("pxl8_vec3[?]", count) end +function gfx.set_wireframe(enabled) + C.pxl8_gfx_set_wireframe(core.gfx, enabled) +end + +function gfx.get_wireframe() + return C.pxl8_gfx_get_wireframe(core.gfx) +end + local Material = {} Material.__index = Material 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, - vertex_color_passthrough = opts.passthrough or false, - wireframe = opts.wireframe or false, - emissive_intensity = opts.emissive or 0.0, + texture_id = opts.texture or 0xFFFFFFFF, }) return setmetatable({ _ptr = mat }, Material) end -gfx3d.Material = Material -gfx3d.create_material = Material.new +gfx.Material = Material -return gfx3d +gfx.create_material = Material.new + +return gfx diff --git a/src/lua/pxl8/gfx2d.lua b/src/lua/pxl8/gfx2d.lua deleted file mode 100644 index 16ae850..0000000 --- a/src/lua/pxl8/gfx2d.lua +++ /dev/null @@ -1,108 +0,0 @@ -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/shader.lua b/src/lua/pxl8/shader.lua deleted file mode 100644 index 2dab54b..0000000 --- a/src/lua/pxl8/shader.lua +++ /dev/null @@ -1,281 +0,0 @@ -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 bb93c62..990e53b 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -117,10 +117,6 @@ function World:set_sim_distance(distance) C.pxl8_world_set_sim_distance(self._ptr, distance) end -function World:set_wireframe(enabled) - C.pxl8_world_set_wireframe(self._ptr, enabled) -end - function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius) local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) diff --git a/src/math/pxl8_math.c b/src/math/pxl8_math.c index 2efb8f5..fb605a4 100644 --- a/src/math/pxl8_math.c +++ b/src/math/pxl8_math.c @@ -1,5 +1,50 @@ #include "pxl8_math.h" +f32 pxl8_fast_inv_sqrt(f32 x) { +#if defined(PXL8_NO_SIMD) + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(f32*)&i; + x = x * (1.5f - half * x * x); + return x; +#elif defined(__x86_64__) || defined(_M_X64) + __m128 v = _mm_set_ss(x); + v = _mm_rsqrt_ss(v); + return _mm_cvtss_f32(v); +#elif defined(__aarch64__) || defined(_M_ARM64) + float32x2_t v = vdup_n_f32(x); + float32x2_t est = vrsqrte_f32(v); + est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est)); + return vget_lane_f32(est, 0); +#else + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(f32*)&i; + x = x * (1.5f - half * x * x); + return x; +#endif +} + +f32 pxl8_fast_rcp(f32 x) { +#if defined(PXL8_NO_SIMD) + return 1.0f / x; +#elif defined(__x86_64__) || defined(_M_X64) + __m128 v = _mm_set_ss(x); + __m128 rcp = _mm_rcp_ss(v); + rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp)))); + return _mm_cvtss_f32(rcp); +#elif defined(__aarch64__) || defined(_M_ARM64) + float32x2_t v = vdup_n_f32(x); + float32x2_t est = vrecpe_f32(v); + est = vmul_f32(est, vrecps_f32(v, est)); + return vget_lane_f32(est, 0); +#else + return 1.0f / x; +#endif +} + u32 pxl8_hash32(u32 x) { x ^= x >> 16; x *= 0x85EBCA6Bu; @@ -18,10 +63,6 @@ u64 pxl8_hash64(u64 x) { return x; } -f32 pxl8_lerp_f32(f32 a, f32 b, f32 t) { - return a + (b - a) * t; -} - f32 pxl8_smoothstep(f32 t) { return t * t * (3.0f - 2.0f * t); } @@ -56,11 +97,11 @@ f32 pxl8_vec2_length(pxl8_vec2 v) { } pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) { - f32 len = pxl8_vec2_length(v); + f32 len_sq = pxl8_vec2_dot(v, v); - if (len < 1e-6f) return (pxl8_vec2){0}; + if (len_sq < 1e-12f) return (pxl8_vec2){0}; - return pxl8_vec2_scale(v, 1.0f / len); + return pxl8_vec2_scale(v, pxl8_fast_inv_sqrt(len_sq)); } pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) { @@ -112,11 +153,11 @@ pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) { } pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) { - f32 len = pxl8_vec3_length(v); + f32 len_sq = pxl8_vec3_dot(v, v); - if (len < 1e-6f) return (pxl8_vec3){0}; + if (len_sq < 1e-12f) return (pxl8_vec3){0}; - return pxl8_vec3_scale(v, 1.0f / len); + return pxl8_vec3_scale(v, pxl8_fast_inv_sqrt(len_sq)); } pxl8_mat4 pxl8_mat4_identity(void) { diff --git a/src/math/pxl8_math.h b/src/math/pxl8_math.h index 1290217..6cf314b 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -15,64 +15,43 @@ #define PXL8_PI 3.14159265358979323846f #define PXL8_TAU (PXL8_PI * 2.0f) -static inline f32 pxl8_fast_inv_sqrt(f32 x) { -#if defined(PXL8_NO_SIMD) - f32 half = 0.5f * x; - i32 i = *(i32*)&x; - i = 0x5f3759df - (i >> 1); - x = *(f32*)&i; - x = x * (1.5f - half * x * x); - return x; -#elif defined(__x86_64__) || defined(_M_X64) - __m128 v = _mm_set_ss(x); - v = _mm_rsqrt_ss(v); - return _mm_cvtss_f32(v); -#elif defined(__aarch64__) || defined(_M_ARM64) - float32x2_t v = vdup_n_f32(x); - float32x2_t est = vrsqrte_f32(v); - est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est)); - return vget_lane_f32(est, 0); -#else - f32 half = 0.5f * x; - i32 i = *(i32*)&x; - i = 0x5f3759df - (i >> 1); - x = *(f32*)&i; - x = x * (1.5f - half * x * x); - return x; -#endif -} +#define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) +#define pxl8_max(a, b) ((a) > (b) ? (a) : (b)) +#define pxl8_clamp(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x))) +#define pxl8_clamp_byte(x) pxl8_clamp(x, 0, 255) -static inline f32 pxl8_fast_rcp(f32 x) { -#if defined(PXL8_NO_SIMD) - return 1.0f / x; -#elif defined(__x86_64__) || defined(_M_X64) - __m128 v = _mm_set_ss(x); - __m128 rcp = _mm_rcp_ss(v); - rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp)))); - return _mm_cvtss_f32(rcp); -#elif defined(__aarch64__) || defined(_M_ARM64) - float32x2_t v = vdup_n_f32(x); - float32x2_t est = vrecpe_f32(v); - est = vmul_f32(est, vrecps_f32(v, est)); - return vget_lane_f32(est, 0); -#else - return 1.0f / x; -#endif -} +#define pxl8_abs(x) fabsf(x) +#define pxl8_ceil(x) ceilf(x) +#define pxl8_floor(x) floorf(x) +#define pxl8_sqrt(x) sqrtf(x) +#define pxl8_sin(x) sinf(x) +#define pxl8_cos(x) cosf(x) +#define pxl8_tan(x) tanf(x) +#define pxl8_exp(x) expf(x) +#define pxl8_log(x) logf(x) +#define pxl8_pow(x, y) powf(x, y) +#define pxl8_lerp(a, b, t) ((a) + ((b) - (a)) * (t)) -typedef struct pxl8_vec2 { - f32 x, y; +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]; } pxl8_vec2; -typedef struct pxl8_vec3 { - f32 x, y, z; +typedef union pxl8_vec3 { + struct { f32 x, y, z; }; + f32 v[3]; } pxl8_vec3; -typedef struct pxl8_vec4 { - f32 x, y, z, w; +typedef union pxl8_vec4 { + struct { f32 x, y, z, w; }; + f32 v[4]; } pxl8_vec4; -typedef struct pxl8_mat4 { +typedef union pxl8_mat4 { f32 m[16]; } pxl8_mat4; @@ -99,18 +78,6 @@ 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 @@ -118,7 +85,6 @@ extern "C" { u32 pxl8_hash32(u32 x); u64 pxl8_hash64(u64 x); -f32 pxl8_lerp_f32(f32 a, f32 b, f32 t); f32 pxl8_smoothstep(f32 t); pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b); diff --git a/src/math/pxl8_noise.c b/src/math/pxl8_noise.c index df3fa1f..b6e9ed9 100644 --- a/src/math/pxl8_noise.c +++ b/src/math/pxl8_noise.c @@ -24,9 +24,9 @@ f32 pxl8_value_noise(f32 x, f32 z, u64 seed) { f32 c01 = pxl8_noise2d(x0, z1, seed); f32 c11 = pxl8_noise2d(x1, z1, seed); - f32 a = pxl8_lerp_f32(c00, c10, tx); - f32 b = pxl8_lerp_f32(c01, c11, tx); - return pxl8_lerp_f32(a, b, tz); + f32 a = pxl8_lerp(c00, c10, tx); + f32 b = pxl8_lerp(c01, c11, tx); + return pxl8_lerp(a, b, tz); } f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) { @@ -50,15 +50,15 @@ f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) { f32 c011 = pxl8_noise3d(x0, y1, z1, seed); f32 c111 = pxl8_noise3d(x1, y1, z1, seed); - f32 a00 = pxl8_lerp_f32(c000, c100, tx); - f32 a10 = pxl8_lerp_f32(c010, c110, tx); - f32 a01 = pxl8_lerp_f32(c001, c101, tx); - f32 a11 = pxl8_lerp_f32(c011, c111, tx); + f32 a00 = pxl8_lerp(c000, c100, tx); + f32 a10 = pxl8_lerp(c010, c110, tx); + f32 a01 = pxl8_lerp(c001, c101, tx); + f32 a11 = pxl8_lerp(c011, c111, tx); - f32 b0 = pxl8_lerp_f32(a00, a10, ty); - f32 b1 = pxl8_lerp_f32(a01, a11, ty); + f32 b0 = pxl8_lerp(a00, a10, ty); + f32 b1 = pxl8_lerp(a01, a11, ty); - return pxl8_lerp_f32(b0, b1, tz); + return pxl8_lerp(b0, b1, tz); } f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves) { diff --git a/src/script/pxl8_script.c b/src/script/pxl8_script.c index 4df3301..3685650 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -1152,6 +1152,11 @@ 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 7ec6877..036da56 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -44,6 +44,8 @@ static const char* pxl8_ffi_cdefs = "void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);\n" "void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);\n" "i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n" +"void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled);\n" +"bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx);\n" "void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_clear(pxl8_gfx* ctx, u32 color);\n" @@ -197,6 +199,7 @@ 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" @@ -244,31 +247,7 @@ 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" @@ -293,10 +272,8 @@ 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" @@ -446,7 +423,6 @@ static const char* pxl8_ffi_cdefs = "pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" "void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n" -"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n" "i32 pxl8_world_get_render_distance(const pxl8_world* world);\n" "void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);\n" "i32 pxl8_world_get_sim_distance(const pxl8_world* world);\n" diff --git a/src/vxl/pxl8_vxl_render.c b/src/vxl/pxl8_vxl_render.c index a4ebbdc..06e5264 100644 --- a/src/vxl/pxl8_vxl_render.c +++ b/src/vxl/pxl8_vxl_render.c @@ -569,8 +569,3 @@ pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) { void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) { pxl8_free(state); } - -void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe) { - if (!state) return; - state->wireframe = wireframe; -} diff --git a/src/vxl/pxl8_vxl_render.h b/src/vxl/pxl8_vxl_render.h index 2762c46..6e5b6cf 100644 --- a/src/vxl/pxl8_vxl_render.h +++ b/src/vxl/pxl8_vxl_render.h @@ -9,12 +9,11 @@ extern "C" { #endif typedef struct pxl8_vxl_render_state { - bool wireframe; + u8 _unused; } pxl8_vxl_render_state; pxl8_vxl_render_state* pxl8_vxl_render_state_create(void); void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state); -void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe); typedef struct pxl8_vxl_mesh_config { bool ambient_occlusion; diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index 353b8b0..48913c0 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -30,13 +30,14 @@ 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; @@ -187,100 +188,6 @@ 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; @@ -395,18 +302,22 @@ 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); @@ -421,13 +332,10 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); - bool wireframe = world->vxl_render_state && world->vxl_render_state->wireframe; pxl8_gfx_material mat = { .texture_id = 0, .dynamic_lighting = true, - .vertex_color_passthrough = true, .alpha = 255, - .wireframe = wireframe, }; i32 r = world->render_distance; @@ -481,16 +389,27 @@ 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 || world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return; + if (!world->active_chunk) return; + if (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); @@ -505,18 +424,6 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_ pxl8_bsp_set_material(world->bsp_render_state, material_id, material); } -void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) { - if (!world) return; - - ensure_bsp_render_state(world); - if (world->bsp_render_state) { - pxl8_bsp_set_wireframe(world->bsp_render_state, enabled); - } - if (world->vxl_render_state) { - pxl8_vxl_set_wireframe(world->vxl_render_state, enabled); - } -} - i32 pxl8_world_get_render_distance(const pxl8_world* world) { if (!world) return 3; return world->render_distance; @@ -559,8 +466,15 @@ 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) { @@ -585,7 +499,14 @@ 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; - userdata_to_entity(server_state, &world->local_player); + 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; const pxl8_snapshot_header* snap = pxl8_net_snapshot(net); u64 server_tick = snap ? snap->tick : 0; @@ -606,11 +527,9 @@ 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) { - if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return; - + bool alive = (world->local_player.flags & PXL8_SIM_FLAG_ALIVE) != 0; 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; @@ -618,16 +537,31 @@ 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); @@ -637,7 +571,6 @@ 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) { @@ -737,6 +670,12 @@ 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 a9024d9..11f31fd 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -35,7 +35,6 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); void pxl8_world_sync(pxl8_world* world, pxl8_net* net); void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material); -void pxl8_world_set_wireframe(pxl8_world* world, bool enabled); i32 pxl8_world_get_render_distance(const pxl8_world* world); void pxl8_world_set_render_distance(pxl8_world* world, i32 distance); diff --git a/src/world/pxl8_world_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c index 3fd1177..89f5e38 100644 --- a/src/world/pxl8_world_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -409,8 +409,7 @@ 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 || - hdr->fragment_idx == 0; + a->version != hdr->version; 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 ed40b01..756caa7 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 64 +#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 255 #define PXL8_WORLD_CHUNK_MAX_DATA_SIZE 131072 typedef struct pxl8_world_chunk_cache_entry {