improve sw renderer

This commit is contained in:
asrael 2026-01-21 23:19:50 -06:00
parent 415d424057
commit 39ee0fefb7
89 changed files with 9380 additions and 2307 deletions

View file

@ -6,11 +6,10 @@
(var time 0) (var time 0)
(var active-demo :logo) (var active-demo :logo)
(var particles nil) (var particles nil)
(var particles2 nil)
(var fire-init? false) (var fire-init? false)
(var rain-init? false) (var rain-init? false)
(var snow-init? false) (var snow-init? false)
(var snow-init2? false) (var first_person3d-init? false)
(var use-famicube-palette? false) (var use-famicube-palette? false)
(var logo-x 256) (var logo-x 256)
@ -32,9 +31,7 @@
(pxl8.load_palette "res/sprites/pxl8_logo.ase") (pxl8.load_palette "res/sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase"))
(set particles (pxl8.create_particles 1000)) (set particles (pxl8.create_particles 1000))
(set particles2 (pxl8.create_particles 500)) (music.init)))
(music.init)
(first_person3d.init)))
(global update (fn [dt] (global update (fn [dt]
(when (pxl8.key_pressed "escape") (when (pxl8.key_pressed "escape")
@ -57,7 +54,8 @@
(set transition-pending nil) (set transition-pending nil)
(when (= active-demo :fire) (set fire-init? false)) (when (= active-demo :fire) (set fire-init? false))
(when (= active-demo :rain) (set rain-init? false)) (when (= active-demo :rain) (set rain-init? false))
(when (= active-demo :snow) (set snow-init? false) (set snow-init2? false))) (when (= active-demo :snow) (set snow-init? false))
(when (= active-demo :first_person3d) (set first_person3d-init? false)))
(transition:destroy) (transition:destroy)
(set transition nil))) (set transition nil)))
@ -92,12 +90,14 @@
(when (> logo-y 296) (when (> logo-y 296)
(set logo-y 296) (set logo-y 296)
(set logo-dy (- (math.abs logo-dy))))) (set logo-dy (- (math.abs logo-dy)))))
:first_person3d (first_person3d.update dt)) :first_person3d (do
(when (not first_person3d-init?)
(first_person3d.init)
(set first_person3d-init? true))
(first_person3d.update dt)))
(when particles (when particles
(particles:update dt)) (particles:update dt)))
(when particles2
(particles2:update dt)))
(when (menu.is-paused) (when (menu.is-paused)
(menu.update)))) (menu.update))))
@ -174,6 +174,7 @@
:first_person3d (first_person3d.frame) :first_person3d (first_person3d.frame)
_ (pxl8.clear 0)) _ (pxl8.clear 0))
(when transition (when transition

4103
demo/mod/blendtable.fnl Normal file

File diff suppressed because it is too large Load diff

1031
demo/mod/colormap.fnl Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local effects (require :pxl8.effects)) (local effects (require :pxl8.effects))
(local net (require :pxl8.net)) (local net (require :pxl8.net))
(local colormap (require :mod.colormap))
(local menu (require :mod.menu))
(local palette (require :mod.palette))
(local sky (require :mod.sky)) (local sky (require :mod.sky))
(local textures (require :mod.textures))
(local bob-amount 4.0) (local bob-amount 4.0)
(local bob-speed 8.0) (local bob-speed 8.0)
(local cam-smoothing 0.25) (local cam-smoothing 0.25)
(local cell-size 64) (local cell-size 64)
(local cursor-sensitivity 0.008) (local cursor-sensitivity 0.010)
(local gravity -800) (local gravity -800)
(local grid-size 64) (local grid-size 64)
(local ground-y 64) (local ground-y 64)
@ -16,7 +21,7 @@
(local land-squash-amount -4) (local land-squash-amount -4)
(local max-pitch 1.5) (local max-pitch 1.5)
(local move-speed 200) (local move-speed 200)
(local turn-speed 2.0) (local turn-speed 4.0)
(local sim-tick-rate 60) (local sim-tick-rate 60)
(local sim-dt (/ 1.0 sim-tick-rate)) (local sim-dt (/ 1.0 sim-tick-rate))
@ -32,10 +37,10 @@
(var cam-yaw 0) (var cam-yaw 0)
(var cam-z 1000) (var cam-z 1000)
(var camera nil) (var camera nil)
(var cursor-look? true)
(var grounded? true) (var grounded? true)
(var land-squash 0) (var land-squash 0)
(var light-time 0) (var light-time 0)
(var real-time 0)
(var network nil) (var network nil)
(var smooth-cam-x 1000) (var smooth-cam-x 1000)
(var smooth-cam-z 1000) (var smooth-cam-z 1000)
@ -43,16 +48,148 @@
(var world nil) (var world nil)
(var fps-avg 0) (var fps-avg 0)
(var fps-sample-count 0) (var fps-sample-count 0)
(var fireball-mesh nil)
(var last-dt 0.016)
(var lights nil)
(local FIREBALL_COLOR 184) (local cursor-look? true)
(local FIREBALL_COLOR 218)
(local STONE_FLOOR_START 37)
(local STONE_WALL_START 2)
(local MOSS_COLOR 200)
(fn init-fireball-palette [] (local trail-positions [])
(for [i 0 7] (local TRAIL_LENGTH 8)
(let [t (/ i 7)
r (math.floor (+ 0xFF (* t 0))) (fn create-fireball-mesh []
g (math.floor (+ 0x60 (* t (- 0xE0 0x60)))) (let [verts []
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))] indices []
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))) radius 5
rings 4
segments 6
core-color (+ FIREBALL_COLOR 6)
spike-color (+ FIREBALL_COLOR 2)]
;; top pole
(table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255})
;; sphere rings
(for [ring 1 (- rings 1)]
(let [phi (* (/ ring rings) math.pi)
sin-phi (math.sin phi)
cos-phi (math.cos phi)
y (* radius cos-phi)
ring-radius (* radius sin-phi)]
(for [seg 0 (- segments 1)]
(let [theta (* (/ seg segments) math.pi 2)
x (* ring-radius (math.cos theta))
z (* ring-radius (math.sin theta))
nx (* sin-phi (math.cos theta))
nz (* sin-phi (math.sin theta))]
(table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255})))))
;; bottom pole
(let [bottom-idx (length verts)]
(table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255})
;; top cap triangles
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)]
(table.insert indices 0)
(table.insert indices (+ 1 next-seg))
(table.insert indices (+ 1 seg))))
;; middle quads
(for [ring 0 (- rings 3)]
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)
curr-row (+ 1 (* ring segments))
next-row (+ 1 (* (+ ring 1) segments))]
(table.insert indices (+ curr-row seg))
(table.insert indices (+ curr-row next-seg))
(table.insert indices (+ next-row seg))
(table.insert indices (+ curr-row next-seg))
(table.insert indices (+ next-row next-seg))
(table.insert indices (+ next-row seg)))))
;; bottom cap triangles
(let [last-ring-start (+ 1 (* (- rings 2) segments))]
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)]
(table.insert indices bottom-idx)
(table.insert indices (+ last-ring-start seg))
(table.insert indices (+ last-ring-start next-seg))))))
;; add spikes - evenly distributed using golden ratio
(let [num-spikes 12
spike-len 8
base-size 1.2
golden-ratio (/ (+ 1 (math.sqrt 5)) 2)]
(for [i 0 (- num-spikes 1)]
(let [;; fibonacci sphere distribution
y (- 1 (* (/ i (- num-spikes 1)) 2))
r-at-y (math.sqrt (- 1 (* y y)))
theta (* math.pi 2 i golden-ratio)
nx (* r-at-y (math.cos theta))
ny y
nz (* r-at-y (math.sin theta))
;; tangent vectors for base
tx (if (> (math.abs ny) 0.9) 1 0)
ty (if (> (math.abs ny) 0.9) 0 1)
tz 0
;; cross product for perpendicular
px (- (* ty nz) (* tz ny))
py (- (* tz nx) (* tx nz))
pz (- (* tx ny) (* ty nx))
pl (math.sqrt (+ (* px px) (* py py) (* pz pz)))
px (/ px pl) py (/ py pl) pz (/ pz pl)
;; second perpendicular
qx (- (* ny pz) (* nz py))
qy (- (* nz px) (* nx pz))
qz (- (* nx py) (* ny px))
;; base center inside sphere
bx (* radius 0.8 nx)
by (* radius 0.8 ny)
bz (* radius 0.8 nz)
;; spike tip
sx (* (+ radius spike-len) nx)
sy (* (+ radius spike-len) ny)
sz (* (+ radius spike-len) nz)
base-idx (length verts)]
;; 4 base vertices forming a square
(table.insert verts {:x (+ bx (* base-size px) (* base-size qx))
:y (+ by (* base-size py) (* base-size qy))
:z (+ bz (* base-size pz) (* base-size qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* base-size px) (* (- base-size) qx))
:y (+ by (* base-size py) (* (- base-size) qy))
:z (+ bz (* base-size pz) (* (- base-size) qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* (- base-size) px) (* (- base-size) qx))
:y (+ by (* (- base-size) py) (* (- base-size) qy))
:z (+ bz (* (- base-size) pz) (* (- base-size) qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* (- base-size) px) (* base-size qx))
:y (+ by (* (- base-size) py) (* base-size qy))
:z (+ bz (* (- base-size) pz) (* base-size qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
;; spike tip
(table.insert verts {:x sx :y sy :z sz :nx nx :ny ny :nz nz :color spike-color :light 255})
;; 4 triangular faces of pyramid
(table.insert indices base-idx)
(table.insert indices (+ base-idx 1))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 1))
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 3))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 3))
(table.insert indices base-idx)
(table.insert indices (+ base-idx 4)))))
(set fireball-mesh (pxl8.create_mesh verts indices))))
(var client-tick 0) (var client-tick 0)
(var last-processed-tick 0) (var last-processed-tick 0)
@ -99,10 +236,22 @@
(values new-x new-z)) (values new-x new-z))
(fn init [] (fn init []
(pxl8.set_relative_mouse_mode true)
(pxl8.set_palette palette 256)
(pxl8.set_colormap colormap 16384)
(for [i 0 7]
(let [t (/ i 7)
r 0xFF
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))
(sky.update-gradient 1 2 6 6 10 18)
(pxl8.update_palette_deps)
(set camera (pxl8.create_camera_3d)) (set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world)) (set world (pxl8.create_world))
(set lights (pxl8.create_lights))
(sky.generate-stars 12345) (sky.generate-stars 12345)
(init-fireball-palette) (create-fireball-mesh)
(set network (net.Net.new {:port 7777})) (set network (net.Net.new {:port 7777}))
(when network (when network
@ -119,16 +268,8 @@
:num_rooms 20})] :num_rooms 20})]
(if (< result 0) (if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result)) (pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor" (let [floor-tex (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR)
:seed 11111 wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR)
:width 64
:height 64
:base_color 19})
wall-tex (pxl8.procgen_tex {:name "wall"
:seed 12345
:width 64
:height 64
:base_color 4})
sky-tex (pxl8.create_texture [0] 1 1)] sky-tex (pxl8.create_texture [0] 1 1)]
(let [result (world:apply_textures [ (let [result (world:apply_textures [
@ -193,6 +334,7 @@
(store-position t cam-x cam-z hist.yaw)))))))))) (store-position t cam-x cam-z hist.yaw))))))))))
(fn update [dt] (fn update [dt]
(set last-dt dt)
(let [fps (pxl8.get_fps)] (let [fps (pxl8.get_fps)]
(set fps-sample-count (+ fps-sample-count 1)) (set fps-sample-count (+ fps-sample-count 1))
(set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count)) (set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count))
@ -234,9 +376,9 @@
(when (and (not cursor-look?) (pxl8.key_down "down")) (when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "left")) (when (and (not cursor-look?) (pxl8.key_down "left"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (- cam-yaw (* turn-speed dt)))) (set cam-yaw (- cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when network (when network
(let [(ok err) (pcall (fn [] (let [(ok err) (pcall (fn []
@ -287,7 +429,8 @@
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))) (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
(set light-time (+ light-time (* dt 0.15)))))) (set light-time (+ light-time (* dt 0.5)))
(set real-time (+ real-time dt)))))
(fn frame [] (fn frame []
(pxl8.clear 1) (pxl8.clear 1)
@ -316,77 +459,57 @@
[0 1 0]) [0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0) (camera:set_perspective 1.047 aspect 1.0 4096.0)
(let [light-pulse (+ 0.7 (* 0.3 (math.sin (* light-time 2)))) (let [light-x (+ 1000 (* 50 (math.cos light-time)))
forward-x (- (math.sin cam-yaw)) light-z (+ 940 (* 50 (math.sin light-time)))
forward-z (- (math.cos cam-yaw)) light-y 80
light-x (+ smooth-cam-x (* 150 forward-x) (* 50 (math.cos light-time))) phase (+ (* light-x 0.01) 1.7)
light-z (+ smooth-cam-z (* 150 forward-z) (* 50 (math.sin light-time))) f1 (* 0.08 (math.sin (+ (* real-time 2.5) phase)))
light-y (+ eye-y 30)] f2 (* 0.05 (math.sin (+ (* real-time 4.1) (* phase 0.7))))
(pxl8.begin_frame_3d camera { f3 (* 0.03 (math.sin (+ (* real-time 7.3) (* phase 1.2))))
:ambient 80 flicker (+ 0.92 f1 f2 f3)
light-intensity (math.floor (math.max 0 (math.min 255 (* 255 flicker))))
r1 (* 0.06 (math.sin (+ (* real-time 1.8) (* phase 0.5))))
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 255 200 150 light-intensity light-radius)
(pxl8.begin_frame_3d camera lights {
:ambient 30
:fog_density 0.0
:celestial_dir [0.5 -0.8 0.3] :celestial_dir [0.5 -0.8 0.3]
:celestial_intensity 0.5 :celestial_intensity 0.5})
:lights [{:x light-x :y light-y :z light-z
:r 255 :g 200 :b 150
:intensity (* 255 light-pulse)
:radius 400}]})
(pxl8.clear_depth) (pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18) (sky.update-gradient 1 2 6 6 10 18)
(sky.render smooth-cam-x eye-y smooth-cam-z) (sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
(pxl8.clear_depth) (pxl8.clear_depth)
(world:set_wireframe (menu.is-wireframe) 15)
(world:render [smooth-cam-x eye-y smooth-cam-z]) (world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d) (when fireball-mesh
(let [wire (menu.is-wireframe)]
(pxl8.draw_mesh fireball-mesh {:x light-x :y light-y :z light-z
:passthrough true
:wireframe wire
:emissive 1.0})))
(let [dx (- light-x smooth-cam-x) (pxl8.end_frame_3d))
dy (- light-y eye-y)
dz (- light-z smooth-cam-z)
dist (math.sqrt (+ (* dx dx) (* dy dy) (* dz dz)))]
(when (> dist 1)
(let [inv-dist (/ 1 dist)
dir-x (* dx inv-dist)
dir-y (* dy inv-dist)
dir-z (* dz inv-dist)
cos-yaw (math.cos cam-yaw)
sin-yaw (math.sin cam-yaw)
cos-pitch (math.cos cam-pitch)
sin-pitch (math.sin cam-pitch)
rx (+ (* dir-x cos-yaw) (* dir-z sin-yaw))
rz (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw))
ry (- (* dir-y cos-pitch) (* rz sin-pitch))
fz (+ (* dir-y sin-pitch) (* rz cos-pitch))]
(when (> fz 0.01)
(let [width (pxl8.get_width)
height (pxl8.get_height)
fov 1.047
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rx (* fz half-fov-tan aspect))
ndc-y (/ ry (* fz half-fov-tan))
sx (math.floor (* (+ 1 ndc-x) 0.5 width))
sy (math.floor (* (- 1 ndc-y) 0.5 height))
screen-size (/ 400 dist)
base-radius (math.max 2 (math.min 12 (math.floor screen-size)))
pulse-int (math.floor (* 180 light-pulse))]
(when (and (>= sx 0) (< sx width) (>= sy 0) (< sy height))
(effects.glows [
{:x sx :y sy :radius (+ base-radius 2) :intensity (/ pulse-int 5) :color (+ FIREBALL_COLOR 1) :shape effects.GLOW_CIRCLE}
{:x sx :y sy :radius base-radius :intensity pulse-int :color (+ FIREBALL_COLOR 5) :shape effects.GLOW_DIAMOND}]))))))))
(sky.render-stars cam-yaw cam-pitch 1.0) (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt)
(let [cx (/ (pxl8.get_width) 2) (let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2) cy (/ (pxl8.get_height) 2)
crosshair-size 4 crosshair-size 4
red-color 18] crosshair-color 240
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color) text-color 251]
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color)) (pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy crosshair-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) crosshair-color)
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 12) (pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 text-color)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) "," (string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12)))) (string.format "%.0f" cam-z)) 5 15 text-color)))))
{:init init {:init init
:update update :update update

View file

@ -2,6 +2,7 @@
(local music (require :mod.music)) (local music (require :mod.music))
(var paused false) (var paused false)
(var wireframe false)
(var gui nil) (var gui nil)
(fn init [] (fn init []
@ -37,18 +38,22 @@
(when gui (when gui
(gui:begin_frame) (gui:begin_frame)
(pxl8.gui_window 200 100 240 180 "pxl8 demo") (pxl8.gui_window 200 100 240 200 "pxl8 demo")
(when (gui:button 1 215 145 210 32 "Resume") (when (gui:button 1 215 147 210 30 "Resume")
(hide)) (hide))
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")] (let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 185 210 32 music-label) (when (gui:button 3 215 182 210 30 music-label)
(if (music.is-playing) (if (music.is-playing)
(music.stop) (music.stop)
(music.start)))) (music.start))))
(when (gui:button 2 215 225 210 32 "Quit") (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
(when (gui:button 4 215 217 210 30 wire-label)
(set wireframe (not wireframe))))
(when (gui:button 2 215 252 210 30 "Quit")
(pxl8.quit)) (pxl8.quit))
(if (gui:is_hovering) (if (gui:is_hovering)
@ -58,6 +63,7 @@
(gui:end_frame))) (gui:end_frame)))
{:is-paused (fn [] paused) {:is-paused (fn [] paused)
:is-wireframe (fn [] wireframe)
:toggle toggle :toggle toggle
:show show :show show
:hide hide :hide hide

263
demo/mod/palette.fnl Normal file
View file

@ -0,0 +1,263 @@
(require :pxl8)
(local ffi (require :ffi))
(local data (ffi.new "u32[256]" [
0x080602
0x14120E
0x23211E
0x31302C
0x403E3B
0x4B4946
0x595755
0x676664
0x767573
0x858382
0x939290
0xA2A09F
0xB0AFAE
0xBEBDBC
0xCDCCCC
0xDADAD9
0x594625
0x544023
0x4F3C24
0x4C3A22
0x453821
0x40321F
0x3E2F20
0x382D1D
0x33291E
0x30271F
0x2F251D
0x2D231E
0x28211C
0x251F1D
0x23201A
0x221F1B
0x646269
0x5F5C61
0x5C545A
0x584F55
0x5B514F
0x554A47
0x4B413F
0x423C36
0x463D31
0x3E352A
0x362E25
0x2D2922
0x26221D
0x1C1916
0x151310
0x100F0D
0x8C6F52
0x7E6045
0x73553B
0x715134
0xBA8346
0xA1723B
0x815C2E
0x745226
0xCC8926
0xBB7E22
0x9F6B1F
0x875A1C
0x6E4918
0x553712
0x3B250D
0x24180A
0xA34331
0x9B3728
0x923220
0x882E18
0x842B16
0x772312
0x69200D
0x5A1C06
0x541C04
0x4C1A03
0x411701
0x371000
0x2E0D00
0x250B00
0x1B0600
0x130500
0x7D5741
0x76503A
0x6E4C37
0x684833
0x5D3F2F
0x553A2C
0x4F3628
0x483024
0x4A3126
0x483025
0x432D22
0x3C2C22
0x352922
0x2C241F
0x221C1B
0x1A1916
0x6E4626
0x5F4025
0x523924
0x433322
0x352B1E
0x28231A
0x1A1A14
0x1C1815
0x96544B
0xAC7369
0xB48C86
0xBCA7A4
0xB1BCC2
0x9DB0B9
0x8A9FAA
0x77929F
0x738995
0x5E7C8B
0x4A6C7D
0x345E72
0x1F4C64
0x19445C
0x143C51
0x10384B
0x183748
0x1A3341
0x192F39
0x152B34
0x13262E
0x101E23
0x0E1519
0x0B0E10
0x896463
0x815C5B
0x785352
0x6F4C4D
0x664444
0x5F3C3D
0x573738
0x523233
0x442929
0x392324
0x2D1D1D
0x241414
0x1A0E0E
0x100909
0x070403
0x000000
0x98936F
0x918B68
0x887F60
0x807759
0x797055
0x73684D
0x6B6146
0x63593F
0x5B523A
0x504834
0x423D2D
0x373226
0x2E2B1F
0x222018
0x161511
0x0E0F0A
0x9A554F
0x904D48
0x87453F
0x7D4037
0x743831
0x693329
0x612C24
0x572720
0x4F231A
0x441E16
0x391914
0x2D150F
0x22110D
0x1A0B06
0x0D0403
0x040202
0x7F77C0
0x7770B5
0x6E68A8
0x686099
0x60588C
0x575381
0x4E4C72
0x454263
0x3D3957
0x34324A
0x2C2940
0x242135
0x1E1928
0x16121D
0x0C0A12
0x050306
0x88AF7B
0x81A473
0x7B9A67
0x728E5D
0x6D8553
0x61794A
0x5B7144
0x61734B
0x586A3D
0x4D5E2D
0x465422
0x3F4D17
0x36420E
0x2F3507
0x272804
0x211F02
0x1EF708
0x3CE10D
0x51CC1B
0x64B621
0x6DA12C
0x69882B
0x727F3B
0xE4DDCE
0xEEE6BA
0xEAE290
0xE9E26D
0xE5DE43
0xE3DB20
0xE1CC18
0xDFB911
0xDCA60B
0xE8A306
0xDF9312
0xE17B05
0xC86815
0xBC5908
0xB14805
0xA63D07
0xB6431E
0xAA381A
0x9A2E12
0x8C270F
0x892B17
0x762311
0x5F1F0D
0x491B09
0x3B1809
0xE50F19
0x6A34C4
0xE00B28
0x2B08C8
0x322A33
0x281C0E
0x2F1E15
0xD48067
0xC26B4C
0x974928
0x814123
0xD5B3A9
0xBE9D93
0x9A7B6C
0x7F5F51
0x8E504C
]))
data

View file

@ -1,23 +1,32 @@
(local ffi (require :ffi))
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local SKY_GRADIENT_START 144) (local SKY_GRADIENT_START 144)
(local SKY_GRADIENT_COUNT 16) (local SKY_GRADIENT_COUNT 16)
(local sky-radius 900) (local sky-radius 900)
(local sky-segments 16) (local sky-segments 16)
(local sky-rings 16) (local sky-rings 16)
(local max-theta (* math.pi 0.55))
(local STAR_COUNT 200) (local NUM_RANDOM_STARS 300)
(local TINY_STAR_COUNT 5000) (local NUM_TINY_STARS 7000)
(local STAR_SILVER_START 160) (local STAR_SEED 0xDEADBEEF)
(local STAR_BLUE_START 168) (local STAR_CYCLE_PERIOD 7200)
(local STAR_RED_START 176)
;; Use existing bright palette colors
;; Silver/white: indices 14-15 (brightest grays)
(local IDX_SILVER 14)
;; Warm/torch: indices 216-218 (bright creams/yellows)
(local IDX_TORCH 216)
;; Blue/magic: indices 176-178 (purples - brightest of the range)
(local IDX_MAGIC 176)
(var sky-mesh nil)
(var last-gradient-key nil) (var last-gradient-key nil)
(var stars []) (var random-stars [])
(var sky-mesh nil)
(var star-count 0)
(var star-directions nil)
(var star-glows nil)
(var star-projected nil)
(var star-time 0)
(var tiny-stars []) (var tiny-stars [])
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] (fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
@ -33,8 +42,8 @@
indices []] indices []]
(for [i 0 (- sky-rings 1)] (for [i 0 (- sky-rings 1)]
(let [theta0 (* (/ i sky-rings) max-theta) (let [theta0 (* (/ i sky-rings) math.pi 0.5)
theta1 (* (/ (+ i 1) sky-rings) max-theta) theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
sin-t0 (math.sin theta0) sin-t0 (math.sin theta0)
cos-t0 (math.cos theta0) cos-t0 (math.cos theta0)
sin-t1 (math.sin theta1) sin-t1 (math.sin theta1)
@ -67,22 +76,17 @@
(if (= i 0) (if (= i 0)
(do (do
;; First ring is degenerate - just a triangle from pole
;; Vertices: v00 (pole, c0), v11 (bottom-right, c1), v10 (bottom-left, c1)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; Triangle: base, base+2, base+1
(table.insert indices base-idx) (table.insert indices base-idx)
(table.insert indices (+ base-idx 2)) (table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1))) (table.insert indices (+ base-idx 1)))
(do (do
;; Regular quad: v00 (top-left), v01 (top-right), v11 (bottom-right), v10 (bottom-left)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255}) (table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; push_quad(base, base+3, base+2, base+1) = triangles (base,base+3,base+2) and (base,base+2,base+1)
(table.insert indices base-idx) (table.insert indices base-idx)
(table.insert indices (+ base-idx 3)) (table.insert indices (+ base-idx 3))
(table.insert indices (+ base-idx 2)) (table.insert indices (+ base-idx 2))
@ -98,149 +102,142 @@
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b) (generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
(set last-gradient-key key)))) (set last-gradient-key key))))
(fn palette-ramp [start c0 c1] (fn galactic-band-factor [dx dy dz]
(let [r0 (bit.rshift (bit.band c0 0xFF0000) 16) (let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
g0 (bit.rshift (bit.band c0 0x00FF00) 8) bx (/ 0.6 band-len)
b0 (bit.band c0 0x0000FF) by (/ 0.3 band-len)
r1 (bit.rshift (bit.band c1 0xFF0000) 16) bz (/ 0.742 band-len)
g1 (bit.rshift (bit.band c1 0x00FF00) 8) dist-from-band (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
b1 (bit.band c1 0x0000FF)] in-band (- 1 (math.min (* dist-from-band 3) 1))]
(for [i 0 7] (* in-band in-band)))
(let [t (/ i 7)
r (math.floor (+ r0 (* t (- r1 r0))))
g (math.floor (+ g0 (* t (- g1 g0))))
b (math.floor (+ b0 (* t (- b1 b0))))]
(pxl8.set_palette_rgb (+ start i) r g b)))))
(fn init-star-palette [] (fn generate-stars []
(palette-ramp STAR_SILVER_START 0x707888 0xFFFFFF) ;; silver (set random-stars [])
(palette-ramp STAR_BLUE_START 0x5070B0 0xD0E8FF) ;; blue
(palette-ramp STAR_RED_START 0x802020 0xFF9090)) ;; red
(fn generate-stars [seed]
(set stars [])
(set tiny-stars []) (set tiny-stars [])
(init-star-palette)
(pxl8.rng_seed seed)
(for [i 1 STAR_COUNT] ;; Generate random stars - use full upper hemisphere (dy > -0.1)
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.85))) (for [i 0 (- NUM_RANDOM_STARS 1)]
phi (* (pxl8.rng_f32) math.pi 2) (let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
brightness (pxl8.rng_range 1 4) h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
color-type (pxl8.rng_range 0 100) h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
shade (pxl8.rng_range 0 5) h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 3))
color (if (< color-type 3) (+ STAR_RED_START shade) theta (* (/ h1 0xFFFFFFFF) math.pi 2)
(< color-type 15) (+ STAR_BLUE_START shade) phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
(+ STAR_SILVER_START shade)) sin-phi (math.sin phi)
sin-t (math.sin theta) cos-phi (math.cos phi)
cos-t (math.cos theta)] dx (* sin-phi (math.cos theta))
(table.insert stars {:dx (* sin-t (math.cos phi)) dy cos-phi
:dy cos-t dz (* sin-phi (math.sin theta))
:dz (* sin-t (math.sin phi)) brightness-raw (/ (% h3 256) 255)
:brightness brightness brightness (math.floor (+ 60 (* brightness-raw brightness-raw 195)))
:color color}))) color-type (% h4 100)
color (if (< color-type 8) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-type 16) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(pxl8.rng_seed (+ seed 0xCAFEBABE)) (when (> dy -0.1)
(for [i 1 TINY_STAR_COUNT] (table.insert random-stars {:dx dx :dy dy :dz dz
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.95)))
phi (* (pxl8.rng_f32) math.pi 2)
brightness (+ 25 (pxl8.rng_range 0 40))
shade (pxl8.rng_range 0 3)
color-type (pxl8.rng_range 0 100)
color (if (< color-type 15) (+ STAR_BLUE_START shade)
(+ STAR_SILVER_START shade))
sin-t (math.sin theta)
cos-t (math.cos theta)]
(table.insert tiny-stars {:dx (* sin-t (math.cos phi))
:dy cos-t
:dz (* sin-t (math.sin phi))
:brightness brightness :brightness brightness
:color color})))) :color color}))))
(fn project-direction [dir-x dir-y dir-z yaw pitch width height] (let [tiny-seed (+ STAR_SEED 0xCAFEBABE)]
(let [cos-yaw (math.cos yaw) (for [i 0 (- NUM_TINY_STARS 1)]
sin-yaw (math.sin yaw) (let [h1 (pxl8.hash32 (+ tiny-seed (* i 4)))
cos-pitch (math.cos pitch) h2 (pxl8.hash32 (+ tiny-seed (* i 4) 1))
sin-pitch (math.sin pitch) h3 (pxl8.hash32 (+ tiny-seed (* i 4) 2))
rotated-x (+ (* dir-x cos-yaw) (* dir-z sin-yaw)) h4 (pxl8.hash32 (+ tiny-seed (* i 4) 3))
rotated-z (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw)) theta (* (/ h1 0xFFFFFFFF) math.pi 2)
rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch)) phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))] sin-phi (math.sin phi)
(when (> final-z 0.01) cos-phi (math.cos phi)
(let [fov 1.047 dx (* sin-phi (math.cos theta))
aspect (/ width height) dy cos-phi
half-fov-tan (math.tan (* fov 0.5)) dz (* sin-phi (math.sin theta))
ndc-x (/ rotated-x (* final-z half-fov-tan aspect)) band-boost (galactic-band-factor dx dy dz)
ndc-y (/ rotated-y (* final-z half-fov-tan))] base-bright (+ 40 (% h3 50))
(when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1)) brightness (+ base-bright (math.floor (* band-boost 40)))
{:x (math.floor (* (+ 1 ndc-x) 0.5 width)) color-shift (% h4 100)
:y (math.floor (* (- 1 ndc-y) 0.5 height))}))))) color (if (< color-shift 3) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-shift 12) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(when (> dy -0.1)
(table.insert tiny-stars {:dx dx :dy dy :dz dz
:brightness brightness
:color color})))))
(fn render-stars [yaw pitch intensity] (set star-count (+ (length tiny-stars) (length random-stars)))
(when (> intensity 0) (set star-directions (pxl8.create_vec3_array star-count))
(let [width (pxl8.get_width) (set star-glows (pxl8.create_glows 10000))
height (pxl8.get_height) (set star-projected (pxl8.create_vec3_array star-count))
glows []
fade-sq (* intensity intensity)]
(var idx 0)
(each [_ star (ipairs tiny-stars)] (each [_ star (ipairs tiny-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] (let [dir (. star-directions idx)]
(when screen (set dir.x star.dx)
(let [int (math.floor (* star.brightness fade-sq))] (set dir.y star.dy)
(set dir.z star.dz))
(set idx (+ idx 1)))
(each [_ star (ipairs random-stars)]
(let [dir (. star-directions idx)]
(set dir.x star.dx)
(set dir.y star.dy)
(set dir.z star.dz))
(set idx (+ idx 1))))
(fn render-stars [cam-x cam-y cam-z intensity dt]
(set star-time (+ star-time (or dt 0)))
(when (and (> intensity 0) (> star-count 0) star-glows)
(let [fade-in (* intensity intensity)
time-factor (/ star-time 60)
star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
t (pxl8.mat4_translate cam-x cam-y cam-z)
r (pxl8.mat4_rotate_y star-rotation)
s (pxl8.mat4_scale sky-radius sky-radius sky-radius)
transform (pxl8.mat4_multiply t (pxl8.mat4_multiply r s))
tiny-count (length tiny-stars)]
(star-glows:clear)
(pxl8.project_points star-directions star-projected star-count transform)
(for [i 0 (- tiny-count 1)]
(let [screen (. star-projected i)]
(when (> screen.z 0)
(let [star (. tiny-stars (+ i 1))
int (math.floor (* star.brightness fade-in))]
(when (> int 8) (when (> int 8)
(table.insert glows {:x screen.x :y screen.y (star-glows:add (math.floor screen.x) (math.floor screen.y)
:radius 1 1 int star.color pxl8.GLOW_CIRCLE))))))
:intensity int
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(each [_ star (ipairs stars)] (for [i 0 (- (length random-stars) 1)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] (let [screen (. star-projected (+ tiny-count i))]
(when screen (when (> screen.z 0)
(let [base-int (math.floor (* star.brightness 50 fade-sq 1.5))] (let [star (. random-stars (+ i 1))
(if (>= star.brightness 4) phase (+ (* (+ i 1) 2.137) (* time-factor 3.0))
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
int (math.floor (* star.brightness fade-in twinkle))
sx (math.floor screen.x)
sy (math.floor screen.y)]
(if (> star.brightness 220)
(do (do
(table.insert glows {:x screen.x :y screen.y (star-glows:add sx sy 3 (math.floor (* int 1.5)) star.color pxl8.GLOW_DIAMOND)
:radius 4 (star-glows:add sx sy 5 (math.floor (/ int 2)) star.color pxl8.GLOW_CIRCLE))
:intensity (math.floor (/ base-int 4)) (> star.brightness 180)
:color star.color
:shape effects.GLOW_CIRCLE})
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 3)
(do (do
(table.insert glows {:x screen.x :y screen.y (star-glows:add sx sy 2 int star.color pxl8.GLOW_DIAMOND)
:radius 3 (star-glows:add sx sy 4 (math.floor (/ int 3)) star.color pxl8.GLOW_CIRCLE))
:intensity (math.floor (/ base-int 4)) (> star.brightness 120)
:color star.color (do
:shape effects.GLOW_CIRCLE}) (star-glows:add sx sy 2 (math.floor (* int 0.67)) star.color pxl8.GLOW_DIAMOND)
(table.insert glows {:x screen.x :y screen.y (star-glows:add sx sy 3 (math.floor (/ int 4)) star.color pxl8.GLOW_CIRCLE))
:radius 2 (star-glows:add sx sy 2 (math.floor (* int 0.5)) star.color pxl8.GLOW_CIRCLE))))))
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 2)
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity (math.floor (* base-int 0.7))
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0) (when (> (star-glows:count) 0)
(effects.glows glows))))) (star-glows:render)))))
(fn render [cam-x cam-y cam-z] (fn render [cam-x cam-y cam-z wireframe]
(when (not sky-mesh) (create-sky-dome)) (when (not sky-mesh) (create-sky-dome))
(when sky-mesh (when sky-mesh
(pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true}))) (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true :wireframe wireframe})))
{:render render {:render render
:render-stars render-stars :render-stars render-stars

168
demo/mod/textures.fnl Normal file
View file

@ -0,0 +1,168 @@
(local pxl8 (require :pxl8))
(local procgen (require :pxl8.procgen))
(local textures {})
(fn build-graph [seed builder]
(let [g (pxl8.create_graph 128)
x (g:add_node procgen.OP_INPUT_X 0 0 0 0 0)
y (g:add_node procgen.OP_INPUT_Y 0 0 0 0 0)
ctx {:graph g :x x :y y}]
(g:set_seed seed)
(let [output (builder ctx)]
(g:set_output output)
g)))
(fn const [ctx val]
(ctx.graph:add_node procgen.OP_CONST 0 0 0 0 val))
(fn add [ctx a b]
(ctx.graph:add_node procgen.OP_ADD a b 0 0 0))
(fn sub [ctx a b]
(ctx.graph:add_node procgen.OP_SUB a b 0 0 0))
(fn mul [ctx a b]
(ctx.graph:add_node procgen.OP_MUL a b 0 0 0))
(fn div [ctx a b]
(ctx.graph:add_node procgen.OP_DIV a b 0 0 0))
(fn min-op [ctx a b]
(ctx.graph:add_node procgen.OP_MIN a b 0 0 0))
(fn max-op [ctx a b]
(ctx.graph:add_node procgen.OP_MAX a b 0 0 0))
(fn abs [ctx a]
(ctx.graph:add_node procgen.OP_ABS a 0 0 0 0))
(fn floor [ctx a]
(ctx.graph:add_node procgen.OP_FLOOR a 0 0 0 0))
(fn fract [ctx a]
(ctx.graph:add_node procgen.OP_FRACT a 0 0 0 0))
(fn lerp [ctx a b t]
(ctx.graph:add_node procgen.OP_LERP a b t 0 0))
(fn clamp [ctx val lo hi]
(ctx.graph:add_node procgen.OP_CLAMP val lo hi 0 0))
(fn select [ctx cond a b]
(ctx.graph:add_node procgen.OP_SELECT a b cond 0 0))
(fn smoothstep [ctx edge0 edge1 x]
(ctx.graph:add_node procgen.OP_SMOOTHSTEP edge0 edge1 x 0 0))
(fn noise-value [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0)))
(fn noise-perlin [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0)))
(fn noise-fbm [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves)))
(fn noise-ridged [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_RIDGED ctx.x ctx.y s p octaves)))
(fn noise-turbulence [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_TURBULENCE ctx.x ctx.y s p octaves)))
(fn voronoi-cell [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_CELL ctx.x ctx.y s 0 0)))
(fn voronoi-edge [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_EDGE ctx.x ctx.y s 0 0)))
(fn voronoi-id [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_ID ctx.x ctx.y s 0 0)))
(fn gradient-linear [ctx angle]
(let [a (const ctx angle)]
(ctx.graph:add_node procgen.OP_GRADIENT_LINEAR ctx.x ctx.y a 0 0)))
(fn gradient-radial [ctx cx cy]
(let [center-x (const ctx cx)
center-y (const ctx cy)]
(ctx.graph:add_node procgen.OP_GRADIENT_RADIAL ctx.x ctx.y center-x center-y 0)))
(fn quantize [ctx val base range]
(let [r (const ctx range)]
(ctx.graph:add_node procgen.OP_QUANTIZE val r 0 0 base)))
(fn textures.mossy-cobblestone [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 6)
edge (voronoi-edge ctx 6)
mortar-threshold (const ctx 0.05)
is-mortar (sub ctx mortar-threshold edge)
mortar-color (const ctx (- base-color 2))
stone-detail (noise-value ctx 48)
stone-base (mul ctx cell (const ctx 0.6))
stone-combined (add ctx stone-base (mul ctx stone-detail (const ctx 0.4)))
stone-quant (quantize ctx stone-combined base-color 8)
moss-pattern (noise-fbm ctx 4 10 0.5)
moss-detail (noise-value ctx 64)
moss-var (add ctx (mul ctx moss-pattern (const ctx 0.7))
(mul ctx moss-detail (const ctx 0.3)))
moss-threshold (const ctx 0.55)
has-moss (sub ctx moss-pattern moss-threshold)
moss-quant (quantize ctx moss-var moss-color 6)
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
(select ctx is-mortar mortar-color stone-or-moss))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
(fn textures.ashlar-wall [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 5)
edge (voronoi-edge ctx 5)
cell-id (voronoi-id ctx 5)
mortar-threshold (const ctx 0.12)
is-mortar (sub ctx mortar-threshold edge)
stone-detail (noise-fbm ctx 3 32 0.5)
stone-tint (mul ctx cell-id (const ctx 0.4))
stone-shade (mul ctx cell (const ctx 0.3))
stone-combined (add ctx stone-detail (add ctx stone-tint stone-shade))
stone-quant (quantize ctx stone-combined base-color 10)
crack-moss (noise-fbm ctx 3 16 0.5)
moss-in-crack (mul ctx crack-moss (sub ctx (const ctx 0.2) edge))
moss-threshold (const ctx 0.06)
has-moss (sub ctx moss-in-crack moss-threshold)
moss-quant (quantize ctx crack-moss moss-color 4)
mortar-color (const ctx (+ base-color 1))
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
(select ctx is-mortar mortar-color stone-or-moss))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
(fn textures.gradient-sky [seed zenith-color horizon-color]
(let [g (build-graph seed
(fn [ctx]
(let [grad (gradient-linear ctx (* math.pi 0.5))
zenith (const ctx zenith-color)
horizon (const ctx horizon-color)
range (const ctx (- horizon-color zenith-color))]
(quantize ctx grad zenith-color (- horizon-color zenith-color)))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
textures

5
demo/profile_3d.fnl Normal file
View file

@ -0,0 +1,5 @@
(local first_person3d (require :mod.first_person3d))
(global init first_person3d.init)
(global update first_person3d.update)
(global frame first_person3d.frame)

41
pxl8.sh
View file

@ -62,12 +62,17 @@ build_server() {
print_info "Building server ($mode mode)" print_info "Building server ($mode mode)"
cd server cd server
if [[ "$mode" == "release" ]]; then if [[ "$mode" == "release" ]]; then
cargo build --release > /dev/null 2>&1 cargo build --release
else else
cargo build > /dev/null 2>&1 cargo build
fi fi
local status=$?
cd - > /dev/null cd - > /dev/null
if [[ $status -eq 0 ]]; then
print_info "Built server" print_info "Built server"
else
print_error "Server build failed"
fi
fi fi
} }
@ -79,11 +84,16 @@ start_server() {
else else
server_bin="server/target/debug/pxl8-server" server_bin="server/target/debug/pxl8-server"
fi fi
print_info "Server mode: $mode, binary: $server_bin"
if [[ -f "$server_bin" ]]; then if [[ -f "$server_bin" ]]; then
print_info "Starting server" print_info "Starting server..."
./$server_bin & ./$server_bin &
SERVER_PID=$! SERVER_PID=$!
sleep 0.2 print_info "Server started with PID $SERVER_PID"
sleep 0.5
else
print_error "Server binary not found: $server_bin"
print_error "Build the server first with: cd server && cargo build"
fi fi
} }
@ -213,7 +223,7 @@ timestamp() {
update_fennel() { update_fennel() {
print_info "Fetching Fennel" print_info "Fetching Fennel"
local version="1.6.0" local version="1.6.1"
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
@ -304,7 +314,8 @@ for arg in "$@"; do
done done
if [ "$MODE" = "release" ]; then if [ "$MODE" = "release" ]; then
CFLAGS="$CFLAGS -O3 -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables" CFLAGS="$CFLAGS -O3 -flto -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables"
LINKER_FLAGS="$LINKER_FLAGS -flto"
BUILDDIR="$BUILDDIR/release" BUILDDIR="$BUILDDIR/release"
BINDIR="$BINDIR/release" BINDIR="$BINDIR/release"
else else
@ -313,7 +324,7 @@ else
BINDIR="$BINDIR/debug" BINDIR="$BINDIR/debug"
fi fi
DEP_CFLAGS="-O3 -ffast-math -funroll-loops" DEP_CFLAGS="-O3 -funroll-loops"
case "$COMMAND" in case "$COMMAND" in
build) build)
@ -360,7 +371,7 @@ case "$COMMAND" in
print_info "Compiler cache: ccache enabled" print_info "Compiler cache: ccache enabled"
fi fi
INCLUDES="-Isrc/core -Isrc/math -Isrc/gfx -Isrc/sfx -Isrc/script -Isrc/hal -Isrc/world -Isrc/asset -Isrc/game -Isrc/net -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" INCLUDES="-Isrc/asset -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz"
COMPILE_FLAGS="$CFLAGS $INCLUDES" COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
@ -377,7 +388,6 @@ case "$COMMAND" in
src/math/pxl8_math.c src/math/pxl8_math.c
src/gfx/pxl8_anim.c src/gfx/pxl8_anim.c
src/gfx/pxl8_atlas.c src/gfx/pxl8_atlas.c
src/gfx/pxl8_blend.c
src/gfx/pxl8_blit.c src/gfx/pxl8_blit.c
src/gfx/pxl8_3d_camera.c src/gfx/pxl8_3d_camera.c
src/gfx/pxl8_colormap.c src/gfx/pxl8_colormap.c
@ -385,6 +395,9 @@ case "$COMMAND" in
src/gfx/pxl8_dither.c src/gfx/pxl8_dither.c
src/gfx/pxl8_font.c src/gfx/pxl8_font.c
src/gfx/pxl8_gfx.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 src/gfx/pxl8_mesh.c
src/gfx/pxl8_palette.c src/gfx/pxl8_palette.c
src/gfx/pxl8_particles.c src/gfx/pxl8_particles.c
@ -394,15 +407,17 @@ case "$COMMAND" in
src/sfx/pxl8_sfx.c src/sfx/pxl8_sfx.c
src/script/pxl8_repl.c src/script/pxl8_repl.c
src/script/pxl8_script.c src/script/pxl8_script.c
src/hal/pxl8_sdl3.c src/hal/pxl8_hal_sdl3.c
src/hal/pxl8_mem_sdl3.c
src/world/pxl8_bsp.c src/world/pxl8_bsp.c
src/world/pxl8_gen.c src/world/pxl8_gen.c
src/world/pxl8_world.c src/world/pxl8_world.c
src/procgen/pxl8_graph.c
src/asset/pxl8_ase.c src/asset/pxl8_ase.c
src/asset/pxl8_cart.c src/asset/pxl8_cart.c
src/asset/pxl8_save.c src/asset/pxl8_save.c
src/game/pxl8_gui.c src/gui/pxl8_gui.c
src/game/pxl8_replay.c src/core/pxl8_replay.c
src/net/pxl8_net.c src/net/pxl8_net.c
src/net/pxl8_protocol.c src/net/pxl8_protocol.c
" "
@ -477,7 +492,7 @@ case "$COMMAND" in
RUN_SERVER=false RUN_SERVER=false
for arg in "$@"; do for arg in "$@"; do
if [[ "$arg" == "--release" ]]; then if [[ "$arg" == "--release" ]]; then
continue MODE="release"
elif [[ "$arg" == "--repl" ]]; then elif [[ "$arg" == "--repl" ]]; then
EXTRA_ARGS="$EXTRA_ARGS --repl" EXTRA_ARGS="$EXTRA_ARGS --repl"
elif [[ "$arg" == "--server" ]]; then elif [[ "$arg" == "--server" ]]; then

View file

@ -3,14 +3,14 @@ use std::path::PathBuf;
fn main() { fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let client_src = PathBuf::from(&manifest_dir).join("../client/src"); let pxl8_src = PathBuf::from(&manifest_dir).join("../src");
println!("cargo:rerun-if-changed=../client/src/net/pxl8_protocol.h"); println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h");
println!("cargo:rerun-if-changed=../client/src/core/pxl8_types.h"); println!("cargo:rerun-if-changed=../src/core/pxl8_types.h");
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header(client_src.join("net/pxl8_protocol.h").to_str().unwrap()) .header(pxl8_src.join("net/pxl8_protocol.h").to_str().unwrap())
.clang_arg(format!("-I{}", client_src.join("core").display())) .clang_arg(format!("-I{}", pxl8_src.join("core").display()))
.use_core() .use_core()
.rustified_enum(".*") .rustified_enum(".*")
.generate() .generate()

View file

@ -14,6 +14,7 @@
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) { static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
header->file_size = pxl8_read_u32(stream); header->file_size = pxl8_read_u32(stream);
@ -58,7 +59,7 @@ static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette
palette->entry_count = total_colors; palette->entry_count = total_colors;
palette->first_color = 0; palette->first_color = 0;
palette->last_color = total_colors - 1; palette->last_color = total_colors - 1;
palette->colors = (u32*)malloc(total_colors * sizeof(u32)); palette->colors = (u32*)pxl8_malloc(total_colors * sizeof(u32));
if (!palette->colors) { if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -97,7 +98,7 @@ static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer)
u16 name_len = pxl8_read_u16(stream); u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) { if (name_len > 0) {
layer->name = (char*)malloc(name_len + 1); layer->name = (char*)pxl8_malloc(name_len + 1);
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY; if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, layer->name, name_len); pxl8_read_bytes(stream, layer->name, name_len);
layer->name[name_len] = '\0'; layer->name[name_len] = '\0';
@ -120,7 +121,7 @@ static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* pa
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
palette->colors = (u32*)malloc(color_count * sizeof(u32)); palette->colors = (u32*)pxl8_malloc(color_count * sizeof(u32));
if (!palette->colors) { if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -154,7 +155,7 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
if (user_data->has_text) { if (user_data->has_text) {
u16 text_len = pxl8_read_u16(stream); u16 text_len = pxl8_read_u16(stream);
if (text_len > 0) { if (text_len > 0) {
user_data->text = (char*)malloc(text_len + 1); user_data->text = (char*)pxl8_malloc(text_len + 1);
if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY; if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->text, text_len); pxl8_read_bytes(stream, user_data->text, text_len);
user_data->text[text_len] = '\0'; user_data->text[text_len] = '\0';
@ -174,14 +175,14 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
u32 num_properties = pxl8_read_u32(stream); u32 num_properties = pxl8_read_u32(stream);
if (num_properties > 0) { if (num_properties > 0) {
user_data->properties = (pxl8_ase_property*)calloc(num_properties, sizeof(pxl8_ase_property)); user_data->properties = (pxl8_ase_property*)pxl8_calloc(num_properties, sizeof(pxl8_ase_property));
if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY; if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY;
user_data->property_count = num_properties; user_data->property_count = num_properties;
for (u32 i = 0; i < num_properties; i++) { for (u32 i = 0; i < num_properties; i++) {
u16 name_len = pxl8_read_u16(stream); u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) { if (name_len > 0) {
user_data->properties[i].name = (char*)malloc(name_len + 1); user_data->properties[i].name = (char*)pxl8_malloc(name_len + 1);
if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY; if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->properties[i].name, name_len); pxl8_read_bytes(stream, user_data->properties[i].name, name_len);
user_data->properties[i].name[name_len] = '\0'; user_data->properties[i].name[name_len] = '\0';
@ -207,7 +208,7 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data
case 8: { case 8: {
u16 str_len = pxl8_read_u16(stream); u16 str_len = pxl8_read_u16(stream);
if (str_len > 0) { if (str_len > 0) {
user_data->properties[i].string_val = (char*)malloc(str_len + 1); user_data->properties[i].string_val = (char*)pxl8_malloc(str_len + 1);
if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY; if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len); pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len);
user_data->properties[i].string_val[str_len] = '\0'; user_data->properties[i].string_val[str_len] = '\0';
@ -236,7 +237,7 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
u16 name_len = pxl8_read_u16(stream); u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) { if (name_len > 0) {
tileset->name = (char*)malloc(name_len + 1); tileset->name = (char*)pxl8_malloc(name_len + 1);
if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY; if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY;
pxl8_read_bytes(stream, tileset->name, name_len); pxl8_read_bytes(stream, tileset->name, name_len);
tileset->name[name_len] = '\0'; tileset->name[name_len] = '\0';
@ -249,7 +250,7 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
if (tileset->flags & 2) { if (tileset->flags & 2) {
u32 compressed_size = pxl8_read_u32(stream); u32 compressed_size = pxl8_read_u32(stream);
tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count; tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count;
tileset->pixels = (u8*)malloc(tileset->pixels_size); tileset->pixels = (u8*)pxl8_malloc(tileset->pixels_size);
if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY; if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size); const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -257,13 +258,13 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size); i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) { if (result != MZ_OK) {
pxl8_error("Failed to decompress tileset data: miniz error %d", result); pxl8_error("Failed to decompress tileset data: miniz error %d", result);
free(tileset->pixels); pxl8_free(tileset->pixels);
tileset->pixels = NULL; tileset->pixels = NULL;
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
} }
tileset->tile_user_data = (pxl8_ase_user_data*)calloc(tileset->tile_count, sizeof(pxl8_ase_user_data)); tileset->tile_user_data = (pxl8_ase_user_data*)pxl8_calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY; if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY;
return PXL8_OK; return PXL8_OK;
@ -288,7 +289,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
u32 pixels_size = cel->image.width * cel->image.height; u32 pixels_size = cel->image.width * cel->image.height;
u32 compressed_size = chunk_size - 20; u32 compressed_size = chunk_size - 20;
cel->image.pixels = (u8*)malloc(pixels_size); cel->image.pixels = (u8*)pxl8_malloc(pixels_size);
if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY; if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size); const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -296,7 +297,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size); i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) { if (result != MZ_OK) {
pxl8_error("Failed to decompress cel data: miniz error %d", result); pxl8_error("Failed to decompress cel data: miniz error %d", result);
free(cel->image.pixels); pxl8_free(cel->image.pixels);
cel->image.pixels = NULL; cel->image.pixels = NULL;
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
@ -321,7 +322,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
u32 uncompressed_size = tile_count * bytes_per_tile; u32 uncompressed_size = tile_count * bytes_per_tile;
u32 compressed_size = chunk_size - 36; u32 compressed_size = chunk_size - 36;
u8* temp_buffer = (u8*)malloc(uncompressed_size); u8* temp_buffer = (u8*)pxl8_malloc(uncompressed_size);
if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY; if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size); const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
@ -329,13 +330,13 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size); i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size);
if (result != MZ_OK) { if (result != MZ_OK) {
pxl8_error("Failed to decompress tilemap data: miniz error %d", result); pxl8_error("Failed to decompress tilemap data: miniz error %d", result);
free(temp_buffer); pxl8_free(temp_buffer);
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
cel->tilemap.tiles = (u32*)calloc(tile_count, sizeof(u32)); cel->tilemap.tiles = (u32*)pxl8_calloc(tile_count, sizeof(u32));
if (!cel->tilemap.tiles) { if (!cel->tilemap.tiles) {
free(temp_buffer); pxl8_free(temp_buffer);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -353,7 +354,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase
} }
} }
free(temp_buffer); pxl8_free(temp_buffer);
} }
return PXL8_OK; return PXL8_OK;
@ -367,7 +368,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
memset(ase_file, 0, sizeof(pxl8_ase_file)); memset(ase_file, 0, sizeof(pxl8_ase_file));
u8* file_data; u8* file_data;
size_t file_size; usize file_size;
pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size); pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size);
if (result != PXL8_OK) { if (result != PXL8_OK) {
return result; return result;
@ -387,7 +388,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
} }
ase_file->frame_count = ase_file->header.frames; ase_file->frame_count = ase_file->header.frames;
ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame)); ase_file->frames = (pxl8_ase_frame*)pxl8_calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
if (!ase_file->frames) { if (!ase_file->frames) {
pxl8_io_free_binary_data(file_data); pxl8_io_free_binary_data(file_data);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -425,7 +426,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
frame->duration = frame_header.duration; frame->duration = frame_header.duration;
u32 pixel_count = frame->width * frame->height; u32 pixel_count = frame->width * frame->height;
frame->pixels = (u8*)calloc(pixel_count, sizeof(u8)); frame->pixels = (u8*)pxl8_calloc(pixel_count, sizeof(u8));
if (!frame->pixels) { if (!frame->pixels) {
result = PXL8_ERROR_OUT_OF_MEMORY; result = PXL8_ERROR_OUT_OF_MEMORY;
break; break;
@ -457,7 +458,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
case PXL8_ASE_CHUNK_LAYER: { case PXL8_ASE_CHUNK_LAYER: {
pxl8_ase_layer* new_layers = pxl8_ase_layer* new_layers =
(pxl8_ase_layer*)realloc(ase_file->layers, (pxl8_ase_layer*)pxl8_realloc(ase_file->layers,
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer)); (ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
if (!new_layers) { if (!new_layers) {
result = PXL8_ERROR_OUT_OF_MEMORY; result = PXL8_ERROR_OUT_OF_MEMORY;
@ -476,7 +477,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
pxl8_ase_cel cel = {0}; pxl8_ase_cel cel = {0};
result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel); result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
if (result == PXL8_OK) { if (result == PXL8_OK) {
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel)); pxl8_ase_cel* new_cels = (pxl8_ase_cel*)pxl8_realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
if (!new_cels) { if (!new_cels) {
result = PXL8_ERROR_OUT_OF_MEMORY; result = PXL8_ERROR_OUT_OF_MEMORY;
break; break;
@ -515,14 +516,14 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
case PXL8_ASE_CHUNK_PALETTE: case PXL8_ASE_CHUNK_PALETTE:
if (ase_file->palette.colors) { if (ase_file->palette.colors) {
free(ase_file->palette.colors); pxl8_free(ase_file->palette.colors);
ase_file->palette.colors = NULL; ase_file->palette.colors = NULL;
} }
result = parse_palette_chunk(&stream, &ase_file->palette); result = parse_palette_chunk(&stream, &ase_file->palette);
break; break;
case PXL8_ASE_CHUNK_TILESET: { case PXL8_ASE_CHUNK_TILESET: {
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets, pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)pxl8_realloc(ase_file->tilesets,
(ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset)); (ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
if (!new_tilesets) { if (!new_tilesets) {
result = PXL8_ERROR_OUT_OF_MEMORY; result = PXL8_ERROR_OUT_OF_MEMORY;
@ -579,57 +580,57 @@ void pxl8_ase_destroy(pxl8_ase_file* ase_file) {
if (ase_file->frames) { if (ase_file->frames) {
for (u32 i = 0; i < ase_file->frame_count; i++) { for (u32 i = 0; i < ase_file->frame_count; i++) {
if (ase_file->frames[i].pixels) free(ase_file->frames[i].pixels); if (ase_file->frames[i].pixels) pxl8_free(ase_file->frames[i].pixels);
if (ase_file->frames[i].cels) { if (ase_file->frames[i].cels) {
for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) { for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) {
pxl8_ase_cel* cel = &ase_file->frames[i].cels[j]; pxl8_ase_cel* cel = &ase_file->frames[i].cels[j];
if (cel->cel_type == 2 && cel->image.pixels) { if (cel->cel_type == 2 && cel->image.pixels) {
free(cel->image.pixels); pxl8_free(cel->image.pixels);
} else if (cel->cel_type == 3 && cel->tilemap.tiles) { } else if (cel->cel_type == 3 && cel->tilemap.tiles) {
free(cel->tilemap.tiles); pxl8_free(cel->tilemap.tiles);
} }
} }
free(ase_file->frames[i].cels); pxl8_free(ase_file->frames[i].cels);
} }
} }
free(ase_file->frames); pxl8_free(ase_file->frames);
} }
if (ase_file->palette.colors) { if (ase_file->palette.colors) {
free(ase_file->palette.colors); pxl8_free(ase_file->palette.colors);
} }
if (ase_file->layers) { if (ase_file->layers) {
for (u32 i = 0; i < ase_file->layer_count; i++) { for (u32 i = 0; i < ase_file->layer_count; i++) {
if (ase_file->layers[i].name) { if (ase_file->layers[i].name) {
free(ase_file->layers[i].name); pxl8_free(ase_file->layers[i].name);
} }
} }
free(ase_file->layers); pxl8_free(ase_file->layers);
} }
if (ase_file->tilesets) { if (ase_file->tilesets) {
for (u32 i = 0; i < ase_file->tileset_count; i++) { for (u32 i = 0; i < ase_file->tileset_count; i++) {
if (ase_file->tilesets[i].name) free(ase_file->tilesets[i].name); if (ase_file->tilesets[i].name) pxl8_free(ase_file->tilesets[i].name);
if (ase_file->tilesets[i].pixels) free(ase_file->tilesets[i].pixels); if (ase_file->tilesets[i].pixels) pxl8_free(ase_file->tilesets[i].pixels);
if (ase_file->tilesets[i].tile_user_data) { if (ase_file->tilesets[i].tile_user_data) {
for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) { for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) {
pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j]; pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j];
if (ud->text) free(ud->text); if (ud->text) pxl8_free(ud->text);
if (ud->properties) { if (ud->properties) {
for (u32 k = 0; k < ud->property_count; k++) { for (u32 k = 0; k < ud->property_count; k++) {
if (ud->properties[k].name) free(ud->properties[k].name); if (ud->properties[k].name) pxl8_free(ud->properties[k].name);
if (ud->properties[k].type == 8 && ud->properties[k].string_val) { if (ud->properties[k].type == 8 && ud->properties[k].string_val) {
free(ud->properties[k].string_val); pxl8_free(ud->properties[k].string_val);
} }
} }
free(ud->properties); pxl8_free(ud->properties);
} }
} }
free(ase_file->tilesets[i].tile_user_data); pxl8_free(ase_file->tilesets[i].tile_user_data);
} }
} }
free(ase_file->tilesets); pxl8_free(ase_file->tilesets);
} }
memset(ase_file, 0, sizeof(pxl8_ase_file)); memset(ase_file, 0, sizeof(pxl8_ase_file));

View file

@ -8,6 +8,7 @@
#include <unistd.h> #include <unistd.h>
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_CART_MAGIC 0x43585850 #define PXL8_CART_MAGIC 0x43585850
#define PXL8_CART_VERSION 1 #define PXL8_CART_VERSION 1
@ -62,7 +63,7 @@ static bool is_directory(const char* path) {
} }
static bool is_pxc_file(const char* path) { static bool is_pxc_file(const char* path) {
size_t len = strlen(path); usize len = strlen(path);
return len > 4 && strcmp(path + len - 4, ".pxc") == 0; return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
} }
@ -97,7 +98,7 @@ static void collect_files_recursive(const char* dir_path, const char* prefix,
} else { } else {
if (*count >= *capacity) { if (*count >= *capacity) {
*capacity = (*capacity == 0) ? 64 : (*capacity * 2); *capacity = (*capacity == 0) ? 64 : (*capacity * 2);
*paths = realloc(*paths, *capacity * sizeof(char*)); *paths = pxl8_realloc(*paths, *capacity * sizeof(char*));
} }
(*paths)[(*count)++] = strdup(rel_path); (*paths)[(*count)++] = strdup(rel_path);
} }
@ -123,13 +124,13 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT; if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT;
if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT; if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT;
cart->data = malloc(size); cart->data = pxl8_malloc(size);
if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY; if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(cart->data, data, size); memcpy(cart->data, data, size);
cart->data_size = size; cart->data_size = size;
cart->file_count = header->file_count; cart->file_count = header->file_count;
cart->files = calloc(cart->file_count, sizeof(pxl8_cart_file)); cart->files = pxl8_calloc(cart->file_count, sizeof(pxl8_cart_file));
if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY; if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY;
const u8* toc = cart->data + sizeof(pxl8_cart_header); const u8* toc = cart->data + sizeof(pxl8_cart_header);
@ -137,7 +138,7 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc; const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc;
toc += sizeof(pxl8_cart_entry); toc += sizeof(pxl8_cart_entry);
cart->files[i].path = malloc(entry->path_len + 1); cart->files[i].path = pxl8_malloc(entry->path_len + 1);
memcpy(cart->files[i].path, toc, entry->path_len); memcpy(cart->files[i].path, toc, entry->path_len);
cart->files[i].path[entry->path_len] = '\0'; cart->files[i].path[entry->path_len] = '\0';
toc += entry->path_len; toc += entry->path_len;
@ -151,7 +152,7 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
} }
pxl8_cart* pxl8_cart_create(void) { pxl8_cart* pxl8_cart_create(void) {
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart)); pxl8_cart* cart = pxl8_calloc(1, sizeof(pxl8_cart));
if (cart) { if (cart) {
cart->resolution = PXL8_RESOLUTION_640x360; cart->resolution = PXL8_RESOLUTION_640x360;
cart->window_size = (pxl8_size){1280, 720}; cart->window_size = (pxl8_size){1280, 720};
@ -167,7 +168,7 @@ pxl8_cart* pxl8_get_cart(void) {
void pxl8_cart_destroy(pxl8_cart* cart) { void pxl8_cart_destroy(pxl8_cart* cart) {
if (!cart) return; if (!cart) return;
pxl8_cart_unload(cart); pxl8_cart_unload(cart);
free(cart); pxl8_free(cart);
} }
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
@ -202,16 +203,16 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
u32 size = (u32)ftell(file); u32 size = (u32)ftell(file);
fseek(file, 0, SEEK_SET); fseek(file, 0, SEEK_SET);
u8* data = malloc(size); u8* data = pxl8_malloc(size);
if (!data || fread(data, 1, size, file) != size) { if (!data || fread(data, 1, size, file) != size) {
free(data); pxl8_free(data);
fclose(file); fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
fclose(file); fclose(file);
pxl8_result result = load_packed_cart(cart, data, size); pxl8_result result = load_packed_cart(cart, data, size);
free(data); pxl8_free(data);
if (result == PXL8_OK) { if (result == PXL8_OK) {
pxl8_info("Loaded cart"); pxl8_info("Loaded cart");
@ -242,16 +243,16 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
} }
fseek(file, trailer.cart_offset, SEEK_SET); fseek(file, trailer.cart_offset, SEEK_SET);
u8* data = malloc(trailer.cart_size); u8* data = pxl8_malloc(trailer.cart_size);
if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) { if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) {
free(data); pxl8_free(data);
fclose(file); fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
fclose(file); fclose(file);
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size); pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
free(data); pxl8_free(data);
if (result == PXL8_OK) { if (result == PXL8_OK) {
pxl8_info("Loaded embedded cart"); pxl8_info("Loaded embedded cart");
@ -265,7 +266,7 @@ void pxl8_cart_unload(pxl8_cart* cart) {
if (cart->title) { if (cart->title) {
pxl8_info("Unloaded cart: %s", cart->title); pxl8_info("Unloaded cart: %s", cart->title);
pxl8_cart_unmount(cart); pxl8_cart_unmount(cart);
free(cart->title); pxl8_free(cart->title);
cart->title = NULL; cart->title = NULL;
} else if (cart->base_path || cart->data) { } else if (cart->base_path || cart->data) {
pxl8_info("Unloaded cart"); pxl8_info("Unloaded cart");
@ -274,18 +275,18 @@ void pxl8_cart_unload(pxl8_cart* cart) {
if (cart->files) { if (cart->files) {
for (u32 i = 0; i < cart->file_count; i++) { for (u32 i = 0; i < cart->file_count; i++) {
free(cart->files[i].path); pxl8_free(cart->files[i].path);
} }
free(cart->files); pxl8_free(cart->files);
cart->files = NULL; cart->files = NULL;
} }
cart->file_count = 0; cart->file_count = 0;
free(cart->data); pxl8_free(cart->data);
cart->data = NULL; cart->data = NULL;
cart->data_size = 0; cart->data_size = 0;
free(cart->base_path); pxl8_free(cart->base_path);
cart->base_path = NULL; cart->base_path = NULL;
cart->is_folder = false; cart->is_folder = false;
} }
@ -302,7 +303,7 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
pxl8_original_cwd = getcwd(NULL, 0); pxl8_original_cwd = getcwd(NULL, 0);
if (chdir(cart->base_path) != 0) { if (chdir(cart->base_path) != 0) {
pxl8_error("Failed to change to cart directory: %s", cart->base_path); pxl8_error("Failed to change to cart directory: %s", cart->base_path);
free(pxl8_original_cwd); pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL; pxl8_original_cwd = NULL;
return PXL8_ERROR_FILE_NOT_FOUND; return PXL8_ERROR_FILE_NOT_FOUND;
} }
@ -323,7 +324,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) {
if (pxl8_original_cwd) { if (pxl8_original_cwd) {
chdir(pxl8_original_cwd); chdir(pxl8_original_cwd);
free(pxl8_original_cwd); pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL; pxl8_original_cwd = NULL;
} }
@ -343,7 +344,7 @@ const char* pxl8_cart_get_title(const pxl8_cart* cart) {
void pxl8_cart_set_title(pxl8_cart* cart, const char* title) { void pxl8_cart_set_title(pxl8_cart* cart, const char* title) {
if (!cart || !title) return; if (!cart || !title) return;
free(cart->title); pxl8_free(cart->title);
cart->title = strdup(title); cart->title = strdup(title);
} }
@ -399,16 +400,16 @@ bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
return find_file(cart, path) != NULL; return find_file(cart, path) != NULL;
} }
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) { bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, usize out_size) {
if (!cart || !relative_path || !out_path || out_size == 0) return false; if (!cart || !relative_path || !out_path || out_size == 0) return false;
if (cart->is_folder && cart->base_path) { if (cart->is_folder && cart->base_path) {
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path); i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
return written >= 0 && (size_t)written < out_size; return written >= 0 && (usize)written < out_size;
} }
i32 written = snprintf(out_path, out_size, "%s", relative_path); i32 written = snprintf(out_path, out_size, "%s", relative_path);
return written >= 0 && (size_t)written < out_size; return written >= 0 && (usize)written < out_size;
} }
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) { pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) {
@ -427,9 +428,9 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
*size_out = (u32)ftell(file); *size_out = (u32)ftell(file);
fseek(file, 0, SEEK_SET); fseek(file, 0, SEEK_SET);
*data_out = malloc(*size_out); *data_out = pxl8_malloc(*size_out);
if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) { if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) {
free(*data_out); pxl8_free(*data_out);
*data_out = NULL; *data_out = NULL;
fclose(file); fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
@ -442,7 +443,7 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
if (!cf) return PXL8_ERROR_FILE_NOT_FOUND; if (!cf) return PXL8_ERROR_FILE_NOT_FOUND;
*size_out = cf->size; *size_out = cf->size;
*data_out = malloc(cf->size); *data_out = pxl8_malloc(cf->size);
if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY; if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(*data_out, cart->data + cf->offset, cf->size); memcpy(*data_out, cart->data + cf->offset, cf->size);
@ -450,7 +451,7 @@ pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** da
} }
void pxl8_cart_free_file(u8* data) { void pxl8_cart_free_file(u8* data) {
free(data); pxl8_free(data);
} }
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) { pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
@ -483,7 +484,7 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
u32 data_offset = sizeof(pxl8_cart_header) + toc_size; u32 data_offset = sizeof(pxl8_cart_header) + toc_size;
u32 total_size = data_offset; u32 total_size = data_offset;
u32* file_sizes = malloc(count * sizeof(u32)); u32* file_sizes = pxl8_malloc(count * sizeof(u32));
for (u32 i = 0; i < count; i++) { for (u32 i = 0; i < count; i++) {
char full_path[1024]; char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]); snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
@ -496,11 +497,11 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
} }
} }
u8* buffer = calloc(1, total_size); u8* buffer = pxl8_calloc(1, total_size);
if (!buffer) { if (!buffer) {
free(file_sizes); pxl8_free(file_sizes);
for (u32 i = 0; i < count; i++) free(paths[i]); for (u32 i = 0; i < count; i++) pxl8_free(paths[i]);
free(paths); pxl8_free(paths);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -536,20 +537,20 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
} }
file_offset += file_sizes[i]; file_offset += file_sizes[i];
free(paths[i]); pxl8_free(paths[i]);
} }
free(paths); pxl8_free(paths);
free(file_sizes); pxl8_free(file_sizes);
FILE* out = fopen(output_path, "wb"); FILE* out = fopen(output_path, "wb");
if (!out) { if (!out) {
free(buffer); pxl8_free(buffer);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
fwrite(buffer, 1, total_size, out); fwrite(buffer, 1, total_size, out);
fclose(out); fclose(out);
free(buffer); pxl8_free(buffer);
pxl8_info("Cart packed: %u files, %u bytes", count, total_size); pxl8_info("Cart packed: %u files, %u bytes", count, total_size);
return PXL8_OK; return PXL8_OK;
@ -573,7 +574,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
cart_size = (u32)ftell(f); cart_size = (u32)ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
cart_data = malloc(cart_size); cart_data = pxl8_malloc(cart_size);
fread(cart_data, 1, cart_size, f); fread(cart_data, 1, cart_size, f);
fclose(f); fclose(f);
unlink(temp_pxc); unlink(temp_pxc);
@ -584,7 +585,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
cart_size = (u32)ftell(f); cart_size = (u32)ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
cart_data = malloc(cart_size); cart_data = pxl8_malloc(cart_size);
fread(cart_data, 1, cart_size, f); fread(cart_data, 1, cart_size, f);
fclose(f); fclose(f);
free_cart = true; free_cart = true;
@ -594,30 +595,30 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
FILE* exe = fopen(exe_path, "rb"); FILE* exe = fopen(exe_path, "rb");
if (!exe) { if (!exe) {
if (free_cart) free(cart_data); if (free_cart) pxl8_free(cart_data);
return PXL8_ERROR_FILE_NOT_FOUND; return PXL8_ERROR_FILE_NOT_FOUND;
} }
fseek(exe, 0, SEEK_END); fseek(exe, 0, SEEK_END);
u32 exe_size = (u32)ftell(exe); u32 exe_size = (u32)ftell(exe);
fseek(exe, 0, SEEK_SET); fseek(exe, 0, SEEK_SET);
u8* exe_data = malloc(exe_size); u8* exe_data = pxl8_malloc(exe_size);
fread(exe_data, 1, exe_size, exe); fread(exe_data, 1, exe_size, exe);
fclose(exe); fclose(exe);
FILE* out = fopen(output_path, "wb"); FILE* out = fopen(output_path, "wb");
if (!out) { if (!out) {
free(exe_data); pxl8_free(exe_data);
if (free_cart) free(cart_data); if (free_cart) pxl8_free(cart_data);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
fwrite(exe_data, 1, exe_size, out); fwrite(exe_data, 1, exe_size, out);
free(exe_data); pxl8_free(exe_data);
u32 cart_offset = exe_size; u32 cart_offset = exe_size;
fwrite(cart_data, 1, cart_size, out); fwrite(cart_data, 1, cart_size, out);
if (free_cart) free(cart_data); if (free_cart) pxl8_free(cart_data);
pxl8_cart_trailer trailer = { pxl8_cart_trailer trailer = {
.magic = PXL8_CART_TRAILER_MAGIC, .magic = PXL8_CART_TRAILER_MAGIC,

View file

@ -34,7 +34,7 @@ bool pxl8_cart_is_packed(const pxl8_cart* cart);
bool pxl8_cart_has_embedded(const char* exe_path); bool pxl8_cart_has_embedded(const char* exe_path);
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path); bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size); bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, usize out_size);
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out); pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out);
void pxl8_cart_free_file(u8* data); void pxl8_cart_free_file(u8* data);

View file

@ -17,6 +17,7 @@
#endif #endif
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
typedef struct { typedef struct {
u32 magic; u32 magic;
@ -40,7 +41,7 @@ static u32 pxl8_save_checksum(const u8* data, u32 size) {
return hash; return hash;
} }
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) { static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, usize path_size) {
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) { if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP); snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
} else { } else {
@ -68,7 +69,7 @@ static pxl8_result pxl8_save_ensure_directory(const char* path) {
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
if (!game_name) return NULL; if (!game_name) return NULL;
pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save)); pxl8_save* save = (pxl8_save*)pxl8_calloc(1, sizeof(pxl8_save));
if (!save) return NULL; if (!save) return NULL;
save->magic = magic; save->magic = magic;
@ -81,7 +82,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
snprintf(save->directory, sizeof(save->directory), snprintf(save->directory, sizeof(save->directory),
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name); "%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
} else { } else {
free(save); pxl8_free(save);
return NULL; return NULL;
} }
#else #else
@ -91,7 +92,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
if (pw) home = pw->pw_dir; if (pw) home = pw->pw_dir;
} }
if (!home) { if (!home) {
free(save); pxl8_free(save);
return NULL; return NULL;
} }
@ -106,7 +107,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
#endif #endif
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) { if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
free(save); pxl8_free(save);
return NULL; return NULL;
} }
@ -116,7 +117,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
void pxl8_save_destroy(pxl8_save* save) { void pxl8_save_destroy(pxl8_save* save) {
if (save) { if (save) {
free(save); pxl8_free(save);
} }
} }
@ -199,14 +200,14 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
u8* data = (u8*)malloc(header.size); u8* data = (u8*)pxl8_malloc(header.size);
if (!data) { if (!data) {
fclose(file); fclose(file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
if (fread(data, 1, header.size, file) != header.size) { if (fread(data, 1, header.size, file) != header.size) {
free(data); pxl8_free(data);
fclose(file); fclose(file);
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
@ -215,7 +216,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
u32 checksum = pxl8_save_checksum(data, header.size); u32 checksum = pxl8_save_checksum(data, header.size);
if (checksum != header.checksum) { if (checksum != header.checksum) {
free(data); pxl8_free(data);
pxl8_error("Save file checksum mismatch"); pxl8_error("Save file checksum mismatch");
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
@ -234,7 +235,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
void pxl8_save_free(u8* data) { void pxl8_save_free(u8* data) {
if (data) { if (data) {
free(data); pxl8_free(data);
} }
} }

View file

@ -6,7 +6,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@ -14,6 +13,7 @@
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_repl.h" #include "pxl8_repl.h"
#include "pxl8_replay.h" #include "pxl8_replay.h"
#include "pxl8_script.h" #include "pxl8_script.h"
@ -39,23 +39,23 @@ static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32
#endif #endif
pxl8* pxl8_create(const pxl8_hal* hal) { pxl8* pxl8_create(const pxl8_hal* hal) {
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8)); pxl8* sys = (pxl8*)pxl8_calloc(1, sizeof(pxl8));
if (!sys) return NULL; if (!sys) return NULL;
pxl8_log_init(&sys->log); pxl8_log_init(&sys->log);
if (!hal) { if (!hal) {
pxl8_error("hal cannot be null"); pxl8_error("hal cannot be null");
free(sys); pxl8_free(sys);
return NULL; return NULL;
} }
sys->hal = hal; sys->hal = hal;
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game)); sys->game = (pxl8_game*)pxl8_calloc(1, sizeof(pxl8_game));
if (!sys->game) { if (!sys->game) {
pxl8_error("failed to allocate game"); pxl8_error("failed to allocate game");
free(sys); pxl8_free(sys);
return NULL; return NULL;
} }
@ -65,11 +65,11 @@ pxl8* pxl8_create(const pxl8_hal* hal) {
void pxl8_destroy(pxl8* sys) { void pxl8_destroy(pxl8* sys) {
if (!sys) return; if (!sys) return;
if (sys->game) free(sys->game); if (sys->game) pxl8_free(sys->game);
if (sys->cart) pxl8_cart_destroy(sys->cart); if (sys->cart) pxl8_cart_destroy(sys->cart);
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data); if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
free(sys); pxl8_free(sys);
} }
static void pxl8_print_help(void) { static void pxl8_print_help(void) {
@ -171,7 +171,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
cart_path = "."; cart_path = ".";
} else { } else {
pxl8_error("no main.fnl or main.lua found in current directory"); pxl8_error("no main.fnl or main.lua found in current directory");
free(original_cwd); pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED; return PXL8_ERROR_INITIALIZATION_FAILED;
} }
} }
@ -190,7 +190,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : ""); pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
if (sys->cart) pxl8_cart_destroy(sys->cart); if (sys->cart) pxl8_cart_destroy(sys->cart);
sys->cart = NULL; sys->cart = NULL;
free(original_cwd); pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED; return PXL8_ERROR_INITIALIZATION_FAILED;
} }
@ -203,7 +203,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
} else if (script_arg) { } else if (script_arg) {
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path)); pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
} }
free(original_cwd); pxl8_free(original_cwd);
const char* window_title = pxl8_cart_get_title(sys->cart); const char* window_title = pxl8_cart_get_title(sys->cart);
if (!window_title) window_title = "pxl8"; if (!window_title) window_title = "pxl8";
@ -310,7 +310,7 @@ pxl8_result pxl8_update(pxl8* sys) {
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) { if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
const char* err_msg = pxl8_script_get_last_error(game->script); const char* err_msg = pxl8_script_get_last_error(game->script);
pxl8_error("failed to setup pxl8 global: %s", err_msg); pxl8_error("failed to load pxl8 global: %s", err_msg);
} }
sys->repl = pxl8_repl_create(); sys->repl = pxl8_repl_create();

View file

@ -1,35 +1,35 @@
#include "pxl8_bytes.h" #include "pxl8_bytes.h"
#include <string.h> #include <string.h>
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) { void pxl8_pack_u8(u8* buf, usize offset, u8 val) {
buf[offset] = val; buf[offset] = val;
} }
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) { void pxl8_pack_u16_le(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val); buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8); buf[offset + 1] = (u8)(val >> 8);
} }
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) { void pxl8_pack_u16_be(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val >> 8); buf[offset] = (u8)(val >> 8);
buf[offset + 1] = (u8)(val); buf[offset + 1] = (u8)(val);
} }
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) { void pxl8_pack_u32_le(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val); buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8); buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16); buf[offset + 2] = (u8)(val >> 16);
buf[offset + 3] = (u8)(val >> 24); buf[offset + 3] = (u8)(val >> 24);
} }
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) { void pxl8_pack_u32_be(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val >> 24); buf[offset] = (u8)(val >> 24);
buf[offset + 1] = (u8)(val >> 16); buf[offset + 1] = (u8)(val >> 16);
buf[offset + 2] = (u8)(val >> 8); buf[offset + 2] = (u8)(val >> 8);
buf[offset + 3] = (u8)(val); buf[offset + 3] = (u8)(val);
} }
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) { void pxl8_pack_u64_le(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val); buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8); buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16); buf[offset + 2] = (u8)(val >> 16);
@ -40,7 +40,7 @@ void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val >> 56); buf[offset + 7] = (u8)(val >> 56);
} }
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) { void pxl8_pack_u64_be(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val >> 56); buf[offset] = (u8)(val >> 56);
buf[offset + 1] = (u8)(val >> 48); buf[offset + 1] = (u8)(val >> 48);
buf[offset + 2] = (u8)(val >> 40); buf[offset + 2] = (u8)(val >> 40);
@ -51,85 +51,85 @@ void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val); buf[offset + 7] = (u8)(val);
} }
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) { void pxl8_pack_i8(u8* buf, usize offset, i8 val) {
buf[offset] = (u8)val; buf[offset] = (u8)val;
} }
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) { void pxl8_pack_i16_le(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_le(buf, offset, (u16)val); pxl8_pack_u16_le(buf, offset, (u16)val);
} }
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) { void pxl8_pack_i16_be(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_be(buf, offset, (u16)val); pxl8_pack_u16_be(buf, offset, (u16)val);
} }
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) { void pxl8_pack_i32_le(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_le(buf, offset, (u32)val); pxl8_pack_u32_le(buf, offset, (u32)val);
} }
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) { void pxl8_pack_i32_be(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_be(buf, offset, (u32)val); pxl8_pack_u32_be(buf, offset, (u32)val);
} }
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) { void pxl8_pack_i64_le(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_le(buf, offset, (u64)val); pxl8_pack_u64_le(buf, offset, (u64)val);
} }
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) { void pxl8_pack_i64_be(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_be(buf, offset, (u64)val); pxl8_pack_u64_be(buf, offset, (u64)val);
} }
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) { void pxl8_pack_f32_le(u8* buf, usize offset, f32 val) {
u32 bits; u32 bits;
memcpy(&bits, &val, sizeof(bits)); memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_le(buf, offset, bits); pxl8_pack_u32_le(buf, offset, bits);
} }
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) { void pxl8_pack_f32_be(u8* buf, usize offset, f32 val) {
u32 bits; u32 bits;
memcpy(&bits, &val, sizeof(bits)); memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_be(buf, offset, bits); pxl8_pack_u32_be(buf, offset, bits);
} }
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) { void pxl8_pack_f64_le(u8* buf, usize offset, f64 val) {
u64 bits; u64 bits;
memcpy(&bits, &val, sizeof(bits)); memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_le(buf, offset, bits); pxl8_pack_u64_le(buf, offset, bits);
} }
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) { void pxl8_pack_f64_be(u8* buf, usize offset, f64 val) {
u64 bits; u64 bits;
memcpy(&bits, &val, sizeof(bits)); memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_be(buf, offset, bits); pxl8_pack_u64_be(buf, offset, bits);
} }
u8 pxl8_unpack_u8(const u8* buf, size_t offset) { u8 pxl8_unpack_u8(const u8* buf, usize offset) {
return buf[offset]; return buf[offset];
} }
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) { u16 pxl8_unpack_u16_le(const u8* buf, usize offset) {
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8); return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
} }
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) { u16 pxl8_unpack_u16_be(const u8* buf, usize offset) {
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1]; return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
} }
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) { u32 pxl8_unpack_u32_le(const u8* buf, usize offset) {
return (u32)buf[offset] | return (u32)buf[offset] |
((u32)buf[offset + 1] << 8) | ((u32)buf[offset + 1] << 8) |
((u32)buf[offset + 2] << 16) | ((u32)buf[offset + 2] << 16) |
((u32)buf[offset + 3] << 24); ((u32)buf[offset + 3] << 24);
} }
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) { u32 pxl8_unpack_u32_be(const u8* buf, usize offset) {
return ((u32)buf[offset] << 24) | return ((u32)buf[offset] << 24) |
((u32)buf[offset + 1] << 16) | ((u32)buf[offset + 1] << 16) |
((u32)buf[offset + 2] << 8) | ((u32)buf[offset + 2] << 8) |
(u32)buf[offset + 3]; (u32)buf[offset + 3];
} }
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) { u64 pxl8_unpack_u64_le(const u8* buf, usize offset) {
return (u64)buf[offset] | return (u64)buf[offset] |
((u64)buf[offset + 1] << 8) | ((u64)buf[offset + 1] << 8) |
((u64)buf[offset + 2] << 16) | ((u64)buf[offset + 2] << 16) |
@ -140,7 +140,7 @@ u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
((u64)buf[offset + 7] << 56); ((u64)buf[offset + 7] << 56);
} }
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) { u64 pxl8_unpack_u64_be(const u8* buf, usize offset) {
return ((u64)buf[offset] << 56) | return ((u64)buf[offset] << 56) |
((u64)buf[offset + 1] << 48) | ((u64)buf[offset + 1] << 48) |
((u64)buf[offset + 2] << 40) | ((u64)buf[offset + 2] << 40) |
@ -151,56 +151,56 @@ u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
(u64)buf[offset + 7]; (u64)buf[offset + 7];
} }
i8 pxl8_unpack_i8(const u8* buf, size_t offset) { i8 pxl8_unpack_i8(const u8* buf, usize offset) {
return (i8)buf[offset]; return (i8)buf[offset];
} }
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) { i16 pxl8_unpack_i16_le(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_le(buf, offset); return (i16)pxl8_unpack_u16_le(buf, offset);
} }
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) { i16 pxl8_unpack_i16_be(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_be(buf, offset); return (i16)pxl8_unpack_u16_be(buf, offset);
} }
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) { i32 pxl8_unpack_i32_le(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_le(buf, offset); return (i32)pxl8_unpack_u32_le(buf, offset);
} }
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) { i32 pxl8_unpack_i32_be(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_be(buf, offset); return (i32)pxl8_unpack_u32_be(buf, offset);
} }
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) { i64 pxl8_unpack_i64_le(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_le(buf, offset); return (i64)pxl8_unpack_u64_le(buf, offset);
} }
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) { i64 pxl8_unpack_i64_be(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_be(buf, offset); return (i64)pxl8_unpack_u64_be(buf, offset);
} }
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) { f32 pxl8_unpack_f32_le(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_le(buf, offset); u32 bits = pxl8_unpack_u32_le(buf, offset);
f32 result; f32 result;
memcpy(&result, &bits, sizeof(result)); memcpy(&result, &bits, sizeof(result));
return result; return result;
} }
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) { f32 pxl8_unpack_f32_be(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_be(buf, offset); u32 bits = pxl8_unpack_u32_be(buf, offset);
f32 result; f32 result;
memcpy(&result, &bits, sizeof(result)); memcpy(&result, &bits, sizeof(result));
return result; return result;
} }
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) { f64 pxl8_unpack_f64_le(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_le(buf, offset); u64 bits = pxl8_unpack_u64_le(buf, offset);
f64 result; f64 result;
memcpy(&result, &bits, sizeof(result)); memcpy(&result, &bits, sizeof(result));
return result; return result;
} }
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) { f64 pxl8_unpack_f64_be(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_be(buf, offset); u64 bits = pxl8_unpack_u64_be(buf, offset);
f64 result; f64 result;
memcpy(&result, &bits, sizeof(result)); memcpy(&result, &bits, sizeof(result));

View file

@ -10,43 +10,43 @@ void pxl8_bit_set(u32* val, u8 bit);
bool pxl8_bit_test(u32 val, u8 bit); bool pxl8_bit_test(u32 val, u8 bit);
void pxl8_bit_toggle(u32* val, u8 bit); void pxl8_bit_toggle(u32* val, u8 bit);
void pxl8_pack_u8(u8* buf, size_t offset, u8 val); void pxl8_pack_u8(u8* buf, usize offset, u8 val);
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val); void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val); void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val); void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val); void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val); void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val); void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);
void pxl8_pack_i8(u8* buf, size_t offset, i8 val); void pxl8_pack_i8(u8* buf, usize offset, i8 val);
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val); void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val); void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val); void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val); void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val); void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val); void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val); void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val); void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val); void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val); void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);
u8 pxl8_unpack_u8(const u8* buf, size_t offset); u8 pxl8_unpack_u8(const u8* buf, usize offset);
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset); u16 pxl8_unpack_u16_be(const u8* buf, usize offset);
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset); u16 pxl8_unpack_u16_le(const u8* buf, usize offset);
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset); u32 pxl8_unpack_u32_be(const u8* buf, usize offset);
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset); u32 pxl8_unpack_u32_le(const u8* buf, usize offset);
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset); u64 pxl8_unpack_u64_be(const u8* buf, usize offset);
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset); u64 pxl8_unpack_u64_le(const u8* buf, usize offset);
i8 pxl8_unpack_i8(const u8* buf, size_t offset); i8 pxl8_unpack_i8(const u8* buf, usize offset);
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset); i16 pxl8_unpack_i16_be(const u8* buf, usize offset);
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset); i16 pxl8_unpack_i16_le(const u8* buf, usize offset);
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset); i32 pxl8_unpack_i32_be(const u8* buf, usize offset);
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset); i32 pxl8_unpack_i32_le(const u8* buf, usize offset);
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset); i64 pxl8_unpack_i64_be(const u8* buf, usize offset);
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset); i64 pxl8_unpack_i64_le(const u8* buf, usize offset);
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset); f32 pxl8_unpack_f32_be(const u8* buf, usize offset);
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset); f32 pxl8_unpack_f32_le(const u8* buf, usize offset);
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset); f64 pxl8_unpack_f64_be(const u8* buf, usize offset);
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset); f64 pxl8_unpack_f64_le(const u8* buf, usize offset);
typedef struct { typedef struct {
const u8* bytes; const u8* bytes;

View file

@ -1,4 +1,5 @@
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_mem.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -10,7 +11,7 @@ static inline char pxl8_to_lower(char c) {
return (c >= 'A' && c <= 'Z') ? c + 32 : c; return (c >= 'A' && c <= 'Z') ? c + 32 : c;
} }
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) { pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size) {
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER; if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
pxl8_cart* cart = pxl8_get_cart(); pxl8_cart* cart = pxl8_get_cart();
@ -19,7 +20,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
u32 cart_size = 0; u32 cart_size = 0;
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size); pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
if (result == PXL8_OK) { if (result == PXL8_OK) {
*content = realloc(data, cart_size + 1); *content = pxl8_realloc(data, cart_size + 1);
if (!*content) { if (!*content) {
pxl8_cart_free_file(data); pxl8_cart_free_file(data);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -44,13 +45,13 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
*content = malloc(file_size + 1); *content = pxl8_malloc(file_size + 1);
if (!*content) { if (!*content) {
fclose(file); fclose(file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
size_t bytes_read = fread(*content, 1, file_size, file); usize bytes_read = fread(*content, 1, file_size, file);
(*content)[bytes_read] = '\0'; (*content)[bytes_read] = '\0';
*size = bytes_read; *size = bytes_read;
@ -58,7 +59,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_OK; return PXL8_OK;
} }
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) { pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size) {
if (!path || !content) return PXL8_ERROR_NULL_POINTER; if (!path || !content) return PXL8_ERROR_NULL_POINTER;
FILE* file = fopen(path, "wb"); FILE* file = fopen(path, "wb");
@ -66,17 +67,17 @@ pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t siz
return PXL8_ERROR_SYSTEM_FAILURE; return PXL8_ERROR_SYSTEM_FAILURE;
} }
size_t bytes_written = fwrite(content, 1, size, file); usize bytes_written = fwrite(content, 1, size, file);
fclose(file); fclose(file);
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE; return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
} }
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) { pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size) {
return pxl8_io_read_file(path, (char**)data, size); return pxl8_io_read_file(path, (char**)data, size);
} }
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) { pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size) {
return pxl8_io_write_file(path, (const char*)data, size); return pxl8_io_write_file(path, (const char*)data, size);
} }
@ -112,13 +113,13 @@ pxl8_result pxl8_io_create_directory(const char* path) {
void pxl8_io_free_file_content(char* content) { void pxl8_io_free_file_content(char* content) {
if (content) { if (content) {
free(content); pxl8_free(content);
} }
} }
void pxl8_io_free_binary_data(u8* data) { void pxl8_io_free_binary_data(u8* data) {
if (data) { if (data) {
free(data); pxl8_free(data);
} }
} }
@ -144,7 +145,7 @@ static i32 pxl8_key_code(const char* key_name) {
}; };
char lower_name[64]; char lower_name[64];
size_t i; usize i;
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) { for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
lower_name[i] = pxl8_to_lower(key_name[i]); lower_name[i] = pxl8_to_lower(key_name[i]);
} }

View file

@ -15,10 +15,10 @@ bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data); void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content); void pxl8_io_free_file_content(char* content);
f64 pxl8_io_get_file_modified_time(const char* path); f64 pxl8_io_get_file_modified_time(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size); pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size); pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size); pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size); pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size);
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name); bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name); bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);

View file

@ -34,7 +34,7 @@ void pxl8_log_set_level(pxl8_log_level level) {
if (g_log) g_log->level = level; if (g_log) g_log->level = level;
} }
static void log_timestamp(char* buffer, size_t size) { static void log_timestamp(char* buffer, usize size) {
time_t now = time(NULL); time_t now = time(NULL);
struct tm* tm_info = localtime(&now); struct tm* tm_info = localtime(&now);
strftime(buffer, size, "%H:%M:%S", tm_info); strftime(buffer, size, "%H:%M:%S", tm_info);

View file

@ -1,5 +1,6 @@
#include "pxl8_replay.h" #include "pxl8_replay.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
#include <stdio.h> #include <stdio.h>
@ -42,8 +43,8 @@ struct pxl8_replay {
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) { static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
while (chunk) { while (chunk) {
pxl8_replay_chunk* next = chunk->next; pxl8_replay_chunk* next = chunk->next;
free(chunk->data); pxl8_free(chunk->data);
free(chunk); pxl8_free(chunk);
chunk = next; chunk = next;
} }
} }
@ -52,7 +53,7 @@ static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
while (entry) { while (entry) {
pxl8_keyframe_entry* next = entry->next; pxl8_keyframe_entry* next = entry->next;
pxl8_replay_chunk_free(entry->input_deltas); pxl8_replay_chunk_free(entry->input_deltas);
free(entry); pxl8_free(entry);
entry = next; entry = next;
} }
} }
@ -64,7 +65,7 @@ pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
return NULL; return NULL;
} }
pxl8_replay* r = calloc(1, sizeof(pxl8_replay)); pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) { if (!r) {
fclose(f); fclose(f);
return NULL; return NULL;
@ -85,7 +86,7 @@ pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
} }
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) { pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
pxl8_replay* r = calloc(1, sizeof(pxl8_replay)); pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) return NULL; if (!r) return NULL;
r->recording = true; r->recording = true;
@ -125,7 +126,7 @@ pxl8_replay* pxl8_replay_open(const char* path) {
return NULL; return NULL;
} }
pxl8_replay* r = calloc(1, sizeof(pxl8_replay)); pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) { if (!r) {
fclose(f); fclose(f);
return NULL; return NULL;
@ -146,14 +147,14 @@ pxl8_replay* pxl8_replay_open(const char* path) {
if (fread(size_bytes, 3, 1, f) != 1) break; if (fread(size_bytes, 3, 1, f) != 1) break;
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16); u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
u8* data = malloc(size); u8* data = pxl8_malloc(size);
if (!data || fread(data, size, 1, f) != 1) { if (!data || fread(data, size, 1, f) != 1) {
free(data); pxl8_free(data);
break; break;
} }
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) { if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry)); pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (entry && size >= sizeof(pxl8_keyframe)) { if (entry && size >= sizeof(pxl8_keyframe)) {
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe)); memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
entry->prev = r->current_keyframe; entry->prev = r->current_keyframe;
@ -166,10 +167,10 @@ pxl8_replay* pxl8_replay_open(const char* path) {
} }
r->keyframe_count++; r->keyframe_count++;
} }
free(data); pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) { } else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
if (r->current_keyframe) { if (r->current_keyframe) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk)); pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) { if (chunk) {
chunk->type = chunk_type; chunk->type = chunk_type;
chunk->size = size; chunk->size = size;
@ -185,9 +186,9 @@ pxl8_replay* pxl8_replay_open(const char* path) {
} }
} }
} }
free(data); pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) { } else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk)); pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) { if (chunk) {
chunk->type = chunk_type; chunk->type = chunk_type;
chunk->size = size; chunk->size = size;
@ -202,9 +203,9 @@ pxl8_replay* pxl8_replay_open(const char* path) {
r->audio_events_tail = chunk; r->audio_events_tail = chunk;
} }
} }
free(data); pxl8_free(data);
} else { } else {
free(data); pxl8_free(data);
} }
} }
@ -231,7 +232,7 @@ void pxl8_replay_destroy(pxl8_replay* r) {
pxl8_replay_chunk_free(r->pending_inputs); pxl8_replay_chunk_free(r->pending_inputs);
pxl8_replay_chunk_free(r->audio_events); pxl8_replay_chunk_free(r->audio_events);
free(r); pxl8_free(r);
} }
bool pxl8_replay_is_recording(pxl8_replay* r) { bool pxl8_replay_is_recording(pxl8_replay* r) {
@ -262,14 +263,14 @@ static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
} }
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) { static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk)); pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return; if (!chunk) return;
chunk->type = type; chunk->type = type;
chunk->size = size; chunk->size = size;
chunk->data = malloc(size); chunk->data = pxl8_malloc(size);
if (!chunk->data) { if (!chunk->data) {
free(chunk); pxl8_free(chunk);
return; return;
} }
memcpy(chunk->data, data, size); memcpy(chunk->data, data, size);
@ -327,7 +328,7 @@ void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* r
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf)); write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
fflush(r->file); fflush(r->file);
} else { } else {
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry)); pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (!entry) return; if (!entry) return;
entry->keyframe = kf; entry->keyframe = kf;
@ -342,7 +343,7 @@ void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* r
r->keyframes->prev = NULL; r->keyframes->prev = NULL;
} }
pxl8_replay_chunk_free(oldest->input_deltas); pxl8_replay_chunk_free(oldest->input_deltas);
free(oldest); pxl8_free(oldest);
r->keyframe_count--; r->keyframe_count--;
} }
@ -446,14 +447,14 @@ void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8
if (r->file) { if (r->file) {
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt)); write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
} else { } else {
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk)); pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return; if (!chunk) return;
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT; chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
chunk->size = sizeof(evt); chunk->size = sizeof(evt);
chunk->data = malloc(sizeof(evt)); chunk->data = pxl8_malloc(sizeof(evt));
if (!chunk->data) { if (!chunk->data) {
free(chunk); pxl8_free(chunk);
return; return;
} }
memcpy(chunk->data, &evt, sizeof(evt)); memcpy(chunk->data, &evt, sizeof(evt));

View file

@ -23,6 +23,9 @@ typedef uint16_t u16;
typedef uint32_t u32; typedef uint32_t u32;
typedef uint64_t u64; typedef uint64_t u64;
typedef size_t usize;
typedef ptrdiff_t isize;
#if defined(__SIZEOF_INT128__) #if defined(__SIZEOF_INT128__)
typedef __int128_t i128; typedef __int128_t i128;
typedef __uint128_t u128; typedef __uint128_t u128;

View file

@ -1,4 +1,5 @@
#include "pxl8_3d_camera.h" #include "pxl8_3d_camera.h"
#include "pxl8_mem.h"
#include <math.h> #include <math.h>
#include <stdlib.h> #include <stdlib.h>
@ -28,7 +29,7 @@ struct pxl8_3d_camera {
}; };
pxl8_3d_camera* pxl8_3d_camera_create(void) { pxl8_3d_camera* pxl8_3d_camera_create(void) {
pxl8_3d_camera* cam = calloc(1, sizeof(pxl8_3d_camera)); pxl8_3d_camera* cam = pxl8_calloc(1, sizeof(pxl8_3d_camera));
if (!cam) return NULL; if (!cam) return NULL;
cam->position = (pxl8_vec3){0, 0, 0}; cam->position = (pxl8_vec3){0, 0, 0};
@ -46,7 +47,7 @@ pxl8_3d_camera* pxl8_3d_camera_create(void) {
} }
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) { void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) {
free(cam); pxl8_free(cam);
} }
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) { void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) {
@ -56,8 +57,8 @@ void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target,
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
cam->roll = 0; cam->roll = 0;
(void)up; (void)up;
@ -104,9 +105,9 @@ pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
f32 sy = sinf(cam->yaw); f32 sy = sinf(cam->yaw);
return (pxl8_vec3){ return (pxl8_vec3){
cp * sy, -sy * cp,
-sp, sp,
cp * cy -cy * cp
}; };
} }
@ -121,7 +122,7 @@ pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) { if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) {
return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far); return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far);
} else { } else {
return pxl8_mat4_ortho( return pxl8_mat4_orthographic(
cam->ortho_left, cam->ortho_right, cam->ortho_left, cam->ortho_right,
cam->ortho_bottom, cam->ortho_top, cam->ortho_bottom, cam->ortho_top,
cam->near, cam->far cam->near, cam->far
@ -183,8 +184,8 @@ void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offs
cam->position = pxl8_vec3_lerp(cam->position, desired, t); cam->position = pxl8_vec3_lerp(cam->position, desired, t);
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
} }
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) { void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
@ -212,3 +213,30 @@ void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
} }
} }
} }
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point result = {0, 0, 0.0f, false};
if (!cam) return result;
pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_multiply(proj, view);
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});
if (clip.w <= 0.0f) return result;
f32 inv_w = 1.0f / clip.w;
f32 ndc_x = clip.x * inv_w;
f32 ndc_y = clip.y * inv_w;
f32 ndc_z = clip.z * inv_w;
if (ndc_x < -1.0f || ndc_x > 1.0f || ndc_y < -1.0f || ndc_y > 1.0f) return result;
result.x = (i32)((ndc_x + 1.0f) * 0.5f * (f32)screen_width);
result.y = (i32)((1.0f - ndc_y) * 0.5f * (f32)screen_height);
result.depth = ndc_z;
result.visible = true;
return result;
}

View file

@ -32,6 +32,7 @@ pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t); void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt); void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration); void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt); void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);

View file

@ -7,6 +7,7 @@
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_ANIM_MAX_STATES 32 #define PXL8_ANIM_MAX_STATES 32
@ -36,36 +37,36 @@ pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u1
return NULL; return NULL;
} }
pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim)); pxl8_anim* anim = (pxl8_anim*)pxl8_calloc(1, sizeof(pxl8_anim));
if (!anim) { if (!anim) {
pxl8_error("Failed to allocate animation"); pxl8_error("Failed to allocate animation");
return NULL; return NULL;
} }
anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32)); anim->frame_ids = (u32*)pxl8_malloc(frame_count * sizeof(u32));
if (!anim->frame_ids) { if (!anim->frame_ids) {
pxl8_error("Failed to allocate frame IDs"); pxl8_error("Failed to allocate frame IDs");
free(anim); pxl8_free(anim);
return NULL; return NULL;
} }
memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32)); memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));
if (frame_durations) { if (frame_durations) {
anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16)); anim->frame_durations = (u16*)pxl8_malloc(frame_count * sizeof(u16));
if (!anim->frame_durations) { if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations"); pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids); pxl8_free(anim->frame_ids);
free(anim); pxl8_free(anim);
return NULL; return NULL;
} }
memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16)); memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
} else { } else {
anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16)); anim->frame_durations = (u16*)pxl8_calloc(frame_count, sizeof(u16));
if (!anim->frame_durations) { if (!anim->frame_durations) {
pxl8_error("Failed to allocate frame durations"); pxl8_error("Failed to allocate frame durations");
free(anim->frame_ids); pxl8_free(anim->frame_ids);
free(anim); pxl8_free(anim);
return NULL; return NULL;
} }
for (u16 i = 0; i < frame_count; i++) { for (u16 i = 0; i < frame_count; i++) {
@ -107,12 +108,12 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
return NULL; return NULL;
} }
u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32)); u32* frame_ids = (u32*)pxl8_malloc(ase_file.frame_count * sizeof(u32));
u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16)); u16* frame_durations = (u16*)pxl8_malloc(ase_file.frame_count * sizeof(u16));
if (!frame_ids || !frame_durations) { if (!frame_ids || !frame_durations) {
pxl8_error("Failed to allocate frame arrays"); pxl8_error("Failed to allocate frame arrays");
free(frame_ids); pxl8_free(frame_ids);
free(frame_durations); pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return NULL; return NULL;
} }
@ -122,8 +123,8 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height); result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
if (result != PXL8_OK) { if (result != PXL8_OK) {
pxl8_error("Failed to create texture for frame %u", i); pxl8_error("Failed to create texture for frame %u", i);
free(frame_ids); pxl8_free(frame_ids);
free(frame_durations); pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return NULL; return NULL;
} }
@ -133,8 +134,8 @@ pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count); pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);
free(frame_ids); pxl8_free(frame_ids);
free(frame_durations); pxl8_free(frame_durations);
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return anim; return anim;
@ -145,15 +146,15 @@ void pxl8_anim_destroy(pxl8_anim* anim) {
if (anim->state_machine) { if (anim->state_machine) {
for (u16 i = 0; i < anim->state_machine->state_count; i++) { for (u16 i = 0; i < anim->state_machine->state_count; i++) {
free(anim->state_machine->states[i].name); pxl8_free(anim->state_machine->states[i].name);
pxl8_anim_destroy(anim->state_machine->states[i].anim); pxl8_anim_destroy(anim->state_machine->states[i].anim);
} }
free(anim->state_machine); pxl8_free(anim->state_machine);
} }
free(anim->frame_ids); pxl8_free(anim->frame_ids);
free(anim->frame_durations); pxl8_free(anim->frame_durations);
free(anim); pxl8_free(anim);
} }
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) { pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
@ -162,7 +163,7 @@ pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* st
} }
if (!anim->state_machine) { if (!anim->state_machine) {
anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine)); anim->state_machine = (pxl8_anim_state_machine*)pxl8_calloc(1, sizeof(pxl8_anim_state_machine));
if (!anim->state_machine) { if (!anim->state_machine) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }

View file

@ -7,6 +7,7 @@
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
typedef struct pxl8_skyline_fit { typedef struct pxl8_skyline_fit {
bool found; bool found;
@ -27,6 +28,8 @@ typedef struct pxl8_skyline {
struct pxl8_atlas { struct pxl8_atlas {
u32 height, width; u32 height, width;
u8* pixels; u8* pixels;
u8* pixels_tiled;
u32 tiled_capacity, tiled_size;
bool dirty; bool dirty;
@ -103,7 +106,7 @@ static bool pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) { if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2; u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2;
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)realloc( pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)pxl8_realloc(
skyline->nodes, skyline->nodes,
new_capacity * sizeof(pxl8_skyline_node) new_capacity * sizeof(pxl8_skyline_node)
); );
@ -142,44 +145,44 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
} }
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) { pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas)); pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas));
if (!atlas) return NULL; if (!atlas) return NULL;
atlas->height = height; atlas->height = height;
atlas->width = width; atlas->width = width;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel); atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel);
if (!atlas->pixels) { if (!atlas->pixels) {
free(atlas); pxl8_free(atlas);
return NULL; return NULL;
} }
atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY; atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY;
atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry)); atlas->entries = (pxl8_atlas_entry*)pxl8_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
if (!atlas->entries) { if (!atlas->entries) {
free(atlas->pixels); pxl8_free(atlas->pixels);
free(atlas); pxl8_free(atlas);
return NULL; return NULL;
} }
atlas->free_capacity = 16; atlas->free_capacity = 16;
atlas->free_list = (u32*)calloc(atlas->free_capacity, sizeof(u32)); atlas->free_list = (u32*)pxl8_calloc(atlas->free_capacity, sizeof(u32));
if (!atlas->free_list) { if (!atlas->free_list) {
free(atlas->entries); pxl8_free(atlas->entries);
free(atlas->pixels); pxl8_free(atlas->pixels);
free(atlas); pxl8_free(atlas);
return NULL; return NULL;
} }
atlas->skyline.capacity = 16; atlas->skyline.capacity = 16;
atlas->skyline.nodes = atlas->skyline.nodes =
(pxl8_skyline_node*)calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node)); (pxl8_skyline_node*)pxl8_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
if (!atlas->skyline.nodes) { if (!atlas->skyline.nodes) {
free(atlas->free_list); pxl8_free(atlas->free_list);
free(atlas->entries); pxl8_free(atlas->entries);
free(atlas->pixels); pxl8_free(atlas->pixels);
free(atlas); pxl8_free(atlas);
return NULL; return NULL;
} }
@ -192,11 +195,12 @@ pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode)
void pxl8_atlas_destroy(pxl8_atlas* atlas) { void pxl8_atlas_destroy(pxl8_atlas* atlas) {
if (!atlas) return; if (!atlas) return;
free(atlas->entries); pxl8_free(atlas->entries);
free(atlas->free_list); pxl8_free(atlas->free_list);
free(atlas->pixels); pxl8_free(atlas->pixels);
free(atlas->skyline.nodes); pxl8_free(atlas->pixels_tiled);
free(atlas); pxl8_free(atlas->skyline.nodes);
pxl8_free(atlas);
} }
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) { void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
@ -209,6 +213,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
atlas->entry_count = preserve_count; atlas->entry_count = preserve_count;
atlas->free_count = 0; atlas->free_count = 0;
if (preserve_count == 0) {
atlas->tiled_size = 0;
} else {
pxl8_atlas_entry* last = &atlas->entries[preserve_count - 1];
atlas->tiled_size = last->tiled_base + (u32)(last->w * last->h);
}
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width}; atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
atlas->skyline.count = 1; atlas->skyline.count = 1;
@ -222,13 +233,13 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
u32 new_size = atlas->width * 2; u32 new_size = atlas->width * 2;
u32 old_width = atlas->width; u32 old_width = atlas->width;
u8* new_pixels = (u8*)calloc(new_size * new_size, bytes_per_pixel); u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel);
if (!new_pixels) return false; if (!new_pixels) return false;
pxl8_skyline new_skyline; pxl8_skyline new_skyline;
new_skyline.nodes = (pxl8_skyline_node*)calloc(16, sizeof(pxl8_skyline_node)); new_skyline.nodes = (pxl8_skyline_node*)pxl8_calloc(16, sizeof(pxl8_skyline_node));
if (!new_skyline.nodes) { if (!new_skyline.nodes) {
free(new_pixels); pxl8_free(new_pixels);
return false; return false;
} }
@ -248,8 +259,8 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
); );
if (!fit.found) { if (!fit.found) {
free(new_skyline.nodes); pxl8_free(new_skyline.nodes);
free(new_pixels); pxl8_free(new_pixels);
return false; return false;
} }
@ -269,15 +280,15 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
atlas->entries[i].y = fit.pos.y; atlas->entries[i].y = fit.pos.y;
if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) { if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) {
free(new_skyline.nodes); pxl8_free(new_skyline.nodes);
free(new_pixels); pxl8_free(new_pixels);
return false; return false;
} }
pxl8_skyline_compact(&new_skyline); pxl8_skyline_compact(&new_skyline);
} }
free(atlas->pixels); pxl8_free(atlas->pixels);
free(atlas->skyline.nodes); pxl8_free(atlas->skyline.nodes);
atlas->pixels = new_pixels; atlas->pixels = new_pixels;
atlas->skyline = new_skyline; atlas->skyline = new_skyline;
@ -316,7 +327,7 @@ u32 pxl8_atlas_add_texture(
} else { } else {
if (atlas->entry_count >= atlas->entry_capacity) { if (atlas->entry_count >= atlas->entry_capacity) {
u32 new_capacity = atlas->entry_capacity * 2; u32 new_capacity = atlas->entry_capacity * 2;
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)realloc( pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)pxl8_realloc(
atlas->entries, atlas->entries,
new_capacity * sizeof(pxl8_atlas_entry) new_capacity * sizeof(pxl8_atlas_entry)
); );
@ -334,6 +345,7 @@ u32 pxl8_atlas_add_texture(
entry->y = fit.pos.y; entry->y = fit.pos.y;
entry->w = w; entry->w = w;
entry->h = h; entry->h = h;
entry->log2_w = pxl8_log2(w);
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
for (u32 y = 0; y < h; y++) { for (u32 y = 0; y < h; y++) {
@ -349,6 +361,30 @@ u32 pxl8_atlas_add_texture(
} }
} }
u32 tiled_tex_size = w * h;
u32 new_tiled_size = atlas->tiled_size + tiled_tex_size;
if (new_tiled_size > atlas->tiled_capacity) {
u32 new_cap = atlas->tiled_capacity ? atlas->tiled_capacity * 2 : 4096;
while (new_cap < new_tiled_size) new_cap *= 2;
u8* new_tiled = (u8*)pxl8_realloc(atlas->pixels_tiled, new_cap);
if (!new_tiled) {
entry->active = false;
return UINT32_MAX;
}
atlas->pixels_tiled = new_tiled;
atlas->tiled_capacity = new_cap;
}
entry->tiled_base = atlas->tiled_size;
u8* tiled_dst = atlas->pixels_tiled + entry->tiled_base;
for (u32 ty = 0; ty < h; ty++) {
for (u32 tx = 0; tx < w; tx++) {
u32 tiled_offset = pxl8_tile_addr(tx, ty, entry->log2_w);
tiled_dst[tiled_offset] = pixels[ty * w + tx];
}
}
atlas->tiled_size = new_tiled_size;
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) { if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
entry->active = false; entry->active = false;
return UINT32_MAX; return UINT32_MAX;
@ -377,6 +413,10 @@ const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels : NULL; return atlas ? atlas->pixels : NULL;
} }
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels_tiled : NULL;
}
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) { u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
return atlas ? atlas->width : 0; return atlas ? atlas->width : 0;
} }

View file

@ -8,28 +8,42 @@ typedef struct pxl8_atlas_entry {
bool active; bool active;
u32 texture_id; u32 texture_id;
i32 x, y, w, h; i32 x, y, w, h;
u32 tiled_base;
u8 log2_w;
} pxl8_atlas_entry; } pxl8_atlas_entry;
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_log2(u32 v) {
u8 r = 0;
while (v >>= 1) r++;
return r;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode); pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_destroy(pxl8_atlas* atlas); void pxl8_atlas_destroy(pxl8_atlas* atlas);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas); const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas); u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas); bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
void pxl8_atlas_mark_clean(pxl8_atlas* atlas); void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -1,194 +0,0 @@
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include <stdlib.h>
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
struct pxl8_additive_table {
u8 table[PXL8_BLEND_TABLE_SIZE];
};
struct pxl8_overbright_table {
u8 table[PXL8_OVERBRIGHT_TABLE_SIZE];
};
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
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;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) {
pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table));
if (!table) return NULL;
pxl8_additive_table_rebuild(table, pal);
return table;
}
void pxl8_additive_table_destroy(pxl8_additive_table* table) {
free(table);
}
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 src = 0; src < 256; src++) {
u8 sr, sg, sb;
pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb);
for (u32 dst = 0; dst < 256; dst++) {
u32 idx = src * 256 + dst;
if (src == PXL8_TRANSPARENT) {
table->table[idx] = (u8)dst;
continue;
}
u8 dr, dg, db;
pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db);
u16 ar = (u16)sr + (u16)dr;
u16 ag = (u16)sg + (u16)dg;
u16 ab = (u16)sb + (u16)db;
if (ar > 255) ar = 255;
if (ag > 255) ag = 255;
if (ab > 255) ab = 255;
table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab);
}
}
}
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) {
return table->table[(u32)src * 256 + dst];
}
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) {
pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table));
if (!table) return NULL;
pxl8_overbright_table_rebuild(table, pal);
return table;
}
void pxl8_overbright_table_destroy(pxl8_overbright_table* table) {
free(table);
}
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) {
f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1);
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u32 idx = level * 256 + pal_idx;
if (pal_idx == PXL8_TRANSPARENT) {
table->table[idx] = PXL8_TRANSPARENT;
continue;
}
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * overbright);
u8 og = (u8)(g + (255 - g) * overbright);
u8 ob = (u8)(b + (255 - b) * overbright);
table->table[idx] = find_closest_stable(pal, or, og, ob);
}
}
}
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) {
u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1));
if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1;
return table->table[level * 256 + pal_idx];
}

View file

@ -1,39 +0,0 @@
#pragma once
#include "pxl8_palette.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
#define PXL8_BLEND_TABLE_SIZE (256 * 256)
#define PXL8_OVERBRIGHT_LEVELS 16
#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS)
typedef struct pxl8_additive_table pxl8_additive_table;
typedef struct pxl8_overbright_table pxl8_overbright_table;
typedef struct pxl8_palette_cube pxl8_palette_cube;
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal);
void pxl8_additive_table_destroy(pxl8_additive_table* table);
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal);
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst);
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal);
void pxl8_overbright_table_destroy(pxl8_overbright_table* table);
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal);
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus
}
#endif

View file

@ -1,42 +1,12 @@
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
#include <string.h> #include <string.h>
static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) {
i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b);
i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b);
i32 chroma = max - min;
*l = (max + min) / 2;
if (chroma == 0) {
*h = 0;
*s = 0;
} else {
i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l));
if (denom <= 0) denom = 1;
*s = (chroma * 255) / denom;
if (*s > 255) *s = 255;
if (max == (i32)r) {
*h = (((i32)g - (i32)b) * 60) / chroma;
if (*h < 0) *h += 360;
} else if (max == (i32)g) {
*h = 120 + (((i32)b - (i32)r) * 60) / chroma;
} else {
*h = 240 + (((i32)r - (i32)g) * 60) / chroma;
}
}
}
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) { static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1; u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF; u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT; u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
i32 th, ts, tl;
rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl);
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) { for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) { if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue; continue;
@ -47,17 +17,10 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
u8 pg = (c >> 8) & 0xFF; u8 pg = (c >> 8) & 0xFF;
u8 pb = (c >> 16) & 0xFF; u8 pb = (c >> 16) & 0xFF;
i32 ph, ps, pl; i32 dr = (i32)target_r - (i32)pr;
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl); i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
i32 dh = th - ph; u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dh > 180) dh -= 360;
if (dh < -180) dh += 360;
i32 ds = ts - ps;
i32 dl = tl - pl;
u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl);
if (dist < best_dist) { if (dist < best_dist) {
best_dist = dist; best_dist = dist;
@ -69,26 +32,19 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
return best_idx; return best_idx;
} }
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) { void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size) {
if (!cm || !palette) return; if (!cm || !data || size == 0) return;
u32 copy_size = size > PXL8_COLORMAP_SIZE ? PXL8_COLORMAP_SIZE : size;
u8 dark_r, dark_g, dark_b; memcpy(cm->table, data, copy_size);
if (tint && tint->tint_strength > 0.0f) {
f32 t = tint->tint_strength;
f32 inv = 1.0f - t;
dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t);
dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t);
dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t);
} else if (tint) {
dark_r = tint->dark_r;
dark_g = tint->dark_g;
dark_b = tint->dark_b;
} else {
dark_r = dark_g = dark_b = 0;
} }
for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) { static void generate_light_table(pxl8_colormap* cm, const u32* palette, pxl8_light_color light_color) {
f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1); pxl8_rgb light = pxl8_light_colors[light_color];
u32 base_row = (u32)light_color * PXL8_LIGHT_LEVELS;
for (u32 level = 0; level < PXL8_LIGHT_LEVELS; level++) {
f32 brightness = (f32)level / (f32)(PXL8_LIGHT_LEVELS - 1);
u32 row = base_row + level;
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) { for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u8 result_idx; u8 result_idx;
@ -103,14 +59,65 @@ void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_le
u8 g = (c >> 8) & 0xFF; u8 g = (c >> 8) & 0xFF;
u8 b = (c >> 16) & 0xFF; u8 b = (c >> 16) & 0xFF;
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness); f32 lr = (f32)light.r / 255.0f;
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness); f32 lg = (f32)light.g / 255.0f;
u8 target_b = (u8)(dark_b + (b - dark_b) * brightness); f32 lb = (f32)light.b / 255.0f;
u8 target_r = (u8)(r * brightness * lr);
u8 target_g = (u8)(g * brightness * lg);
u8 target_b = (u8)(b * brightness * lb);
result_idx = find_closest_color(palette, target_r, target_g, target_b); result_idx = find_closest_color(palette, target_r, target_g, target_b);
} }
cm->table[light * 256 + pal_idx] = result_idx; cm->table[row * 256 + pal_idx] = result_idx;
} }
} }
} }
static void generate_blend_table(pxl8_colormap* cm, const u32* palette) {
for (u32 src = 0; src < 256; src++) {
u32 row = PXL8_LIGHT_ROWS + src;
u8 sr, sg, sb;
if (src == PXL8_TRANSPARENT) {
sr = sg = sb = 0;
} else {
u32 sc = palette[src];
sr = sc & 0xFF;
sg = (sc >> 8) & 0xFF;
sb = (sc >> 16) & 0xFF;
}
for (u32 dst = 0; dst < 256; dst++) {
u8 result_idx;
if (src == PXL8_TRANSPARENT) {
result_idx = (u8)dst;
} else {
u32 dc = palette[dst];
u8 dr = dc & 0xFF;
u8 dg = (dc >> 8) & 0xFF;
u8 db = (dc >> 16) & 0xFF;
u8 blend_r = (u8)((sr + dr) / 2);
u8 blend_g = (u8)((sg + dg) / 2);
u8 blend_b = (u8)((sb + db) / 2);
result_idx = find_closest_color(palette, blend_r, blend_g, blend_b);
}
cm->table[row * 256 + dst] = result_idx;
}
}
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette) {
if (!cm || !palette) return;
for (u32 light = 0; light < PXL8_LIGHT_COLORS; light++) {
generate_light_table(cm, palette, (pxl8_light_color)light);
}
generate_blend_table(cm, palette);
}

View file

@ -7,35 +7,65 @@
extern "C" { extern "C" {
#endif #endif
#define PXL8_LIGHT_LEVELS 64 #define PXL8_LIGHT_COLORS 8
#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS) #define PXL8_LIGHT_LEVELS 8
#define PXL8_LIGHT_ROWS (PXL8_LIGHT_COLORS * PXL8_LIGHT_LEVELS)
#define PXL8_BLEND_ROWS 256
#define PXL8_COLORMAP_ROWS (PXL8_LIGHT_ROWS + PXL8_BLEND_ROWS)
#define PXL8_COLORMAP_SIZE (256 * PXL8_COLORMAP_ROWS)
#define PXL8_FULLBRIGHT_START 240 #define PXL8_FULLBRIGHT_START 240
#define PXL8_TRANSPARENT 0 #define PXL8_TRANSPARENT 0
#define PXL8_DYNAMIC_RANGE_START 144 #define PXL8_DYNAMIC_RANGE_START 144
#define PXL8_DYNAMIC_RANGE_COUNT 16 #define PXL8_DYNAMIC_RANGE_COUNT 16
typedef enum {
PXL8_LIGHT_WHITE = 0,
PXL8_LIGHT_RED = 1,
PXL8_LIGHT_ORANGE = 2,
PXL8_LIGHT_YELLOW = 3,
PXL8_LIGHT_GREEN = 4,
PXL8_LIGHT_CYAN = 5,
PXL8_LIGHT_BLUE = 6,
PXL8_LIGHT_PURPLE = 7,
} pxl8_light_color;
typedef struct {
u8 r, g, b;
} pxl8_rgb;
static const pxl8_rgb pxl8_light_colors[PXL8_LIGHT_COLORS] = {
{255, 255, 255},
{255, 64, 64},
{255, 160, 64},
{255, 255, 64},
{64, 255, 64},
{64, 255, 255},
{64, 64, 255},
{255, 64, 255},
};
typedef struct { typedef struct {
u8 table[PXL8_COLORMAP_SIZE]; u8 table[PXL8_COLORMAP_SIZE];
} pxl8_colormap; } pxl8_colormap;
typedef struct { void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette);
u8 dark_r, dark_g, dark_b; void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);
u8 tint_r, tint_g, tint_b;
f32 tint_strength;
} pxl8_level_tint;
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint); static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity) {
u32 light_row = ((u32)light_color << 3) + (intensity >> 5);
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) { return cm->table[(light_row << 8) + pal_idx];
u32 light_idx = light >> 2;
return cm->table[light_idx * 256 + pal_idx];
} }
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) { 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(light, x, y); u8 dithered = pxl8_dither_light(intensity, x, y);
u32 light_idx = dithered >> 2; u32 light_row = ((u32)light_color << 3) + (dithered >> 5);
return cm->table[light_idx * 256 + pal_idx]; return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_blend(const pxl8_colormap* cm, u8 src, u8 dst) {
u32 blend_row = PXL8_LIGHT_ROWS + src;
return cm->table[(blend_row << 8) + dst];
} }
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -1,10 +1,10 @@
#include "pxl8_cpu.h" #include "pxl8_cpu.h"
#include "pxl8_mem.h"
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_simd.h"
struct pxl8_cpu_render_target { struct pxl8_cpu_render_target {
u8* framebuffer; u8* framebuffer;
u32 height; u32 height;
@ -72,8 +72,8 @@ static pxl8_light_result calc_vertex_light(
if (sky_factor < 0.0f) sky_factor = 0.0f; if (sky_factor < 0.0f) sky_factor = 0.0f;
intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f; intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f;
for (u32 i = 0; i < frame->uniforms.num_lights; i++) { for (u32 i = 0; i < frame->lights_count; i++) {
const pxl8_light* light = &frame->uniforms.lights[i]; const pxl8_light* light = &frame->lights[i];
f32 contrib = calc_light_intensity(light, world_pos, normal); f32 contrib = calc_light_intensity(light, world_pos, normal);
if (contrib > 0.0f) { if (contrib > 0.0f) {
intensity += contrib; intensity += contrib;
@ -119,6 +119,7 @@ struct pxl8_cpu_backend {
pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK]; pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth; u32 target_stack_depth;
const pxl8_colormap* colormap; const pxl8_colormap* colormap;
const pxl8_palette_cube* palette_cube;
const u32* palette; const u32* palette;
pxl8_3d_frame frame; pxl8_3d_frame frame;
pxl8_mat4 mvp; pxl8_mat4 mvp;
@ -173,7 +174,7 @@ static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h
} }
pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) { pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
pxl8_cpu_backend* cpu = calloc(1, sizeof(pxl8_cpu_backend)); pxl8_cpu_backend* cpu = pxl8_calloc(1, sizeof(pxl8_cpu_backend));
if (!cpu) return NULL; if (!cpu) return NULL;
pxl8_cpu_render_target_desc desc = { pxl8_cpu_render_target_desc desc = {
@ -184,7 +185,7 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
}; };
pxl8_cpu_render_target* base_target = pxl8_cpu_create_render_target(&desc); pxl8_cpu_render_target* base_target = pxl8_cpu_create_render_target(&desc);
if (!base_target) { if (!base_target) {
free(cpu); pxl8_free(cpu);
return NULL; return NULL;
} }
@ -193,10 +194,10 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
cpu->current_target = base_target; cpu->current_target = base_target;
cpu->output_size = width * height; cpu->output_size = width * height;
cpu->output = calloc(cpu->output_size, sizeof(u32)); cpu->output = pxl8_calloc(cpu->output_size, sizeof(u32));
if (!cpu->output) { if (!cpu->output) {
pxl8_cpu_destroy_render_target(base_target); pxl8_cpu_destroy_render_target(base_target);
free(cpu); pxl8_free(cpu);
return NULL; return NULL;
} }
@ -208,14 +209,14 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) {
for (u32 i = 0; i < cpu->target_stack_depth; i++) { for (u32 i = 0; i < cpu->target_stack_depth; i++) {
pxl8_cpu_destroy_render_target(cpu->target_stack[i]); pxl8_cpu_destroy_render_target(cpu->target_stack[i]);
} }
free(cpu->output); pxl8_free(cpu->output);
free(cpu); pxl8_free(cpu);
} }
void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) { void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) {
if (!cpu || !frame) return; if (!cpu || !frame) return;
cpu->frame = *frame; cpu->frame = *frame;
cpu->mvp = pxl8_mat4_mul(frame->projection, frame->view); cpu->mvp = pxl8_mat4_multiply(frame->projection, frame->view);
} }
void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) { void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) {
@ -231,14 +232,12 @@ void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) {
void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) { void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->current_target) return; if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
if (render_target->zbuffer) {
u32 count = render_target->width * render_target->height; u32 count = render_target->width * render_target->height;
for (u32 i = 0; i < count; i++) { if (render_target->zbuffer) {
render_target->zbuffer[i] = 0xFFFF; memset(render_target->zbuffer, 0xFF, count * sizeof(u16));
}
} }
if (render_target->light_accum) { if (render_target->light_accum) {
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32)); memset(render_target->light_accum, 0, count * sizeof(u32));
} }
} }
@ -246,6 +245,10 @@ void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) {
if (cpu) cpu->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) { void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) {
if (cpu) cpu->palette = palette; if (cpu) cpu->palette = palette;
} }
@ -366,8 +369,8 @@ void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8
if (!cpu || !cpu->current_target) return; if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f}); pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.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; if (c0.w <= 0.0f || c1.w <= 0.0f) return;
@ -699,6 +702,22 @@ static void rasterize_triangle_opaque(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -727,7 +746,7 @@ static void rasterize_triangle_opaque(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
prow[px] = pal_idx; prow[px] = pal_idx;
zrow[px] = z16; zrow[px] = z16;
if (light_accum) { if (light_accum) {
@ -948,6 +967,22 @@ static void rasterize_triangle_alpha(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -974,7 +1009,7 @@ static void rasterize_triangle_alpha(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
if (mat_alpha >= 128) { if (mat_alpha >= 128) {
prow[px] = src_idx; prow[px] = src_idx;
@ -1002,11 +1037,42 @@ static void rasterize_triangle_alpha(
} }
} }
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( static void dispatch_triangle(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
const pxl8_atlas* textures, const pxl8_material* material 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; pxl8_cpu_render_target* render_target = cpu->current_target;
tri_setup setup; tri_setup setup;
if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) { if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) {
@ -1029,13 +1095,13 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh, const pxl8_mesh* mesh,
const pxl8_mat4* model, const pxl8_mat4* model,
const pxl8_material* material, const pxl8_gfx_material* material,
const pxl8_atlas* textures const pxl8_atlas* textures
) { ) {
if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return; if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return;
pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, *model); pxl8_mat4 mv = pxl8_mat4_multiply(cpu->frame.view, *model);
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv); pxl8_mat4 mvp = pxl8_mat4_multiply(cpu->frame.projection, mv);
f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f; f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f;
@ -1054,13 +1120,13 @@ void pxl8_cpu_draw_mesh(
pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->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}; pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f};
vo0.clip_pos = pxl8_mat4_mul_vec4(mvp, p0); vo0.clip_pos = pxl8_mat4_multiply_vec4(mvp, p0);
vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1); vo1.clip_pos = pxl8_mat4_multiply_vec4(mvp, p1);
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2); vo2.clip_pos = pxl8_mat4_multiply_vec4(mvp, p2);
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(*model, p0); pxl8_vec4 w0 = pxl8_mat4_multiply_vec4(*model, p0);
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(*model, p1); pxl8_vec4 w1 = pxl8_mat4_multiply_vec4(*model, p1);
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(*model, p2); pxl8_vec4 w2 = pxl8_mat4_multiply_vec4(*model, p2);
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z};
@ -1078,9 +1144,9 @@ void pxl8_cpu_draw_mesh(
vo2.color = v2->color; vo2.color = v2->color;
if (material->dynamic_lighting) { if (material->dynamic_lighting) {
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal)); pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v0->normal));
pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v1->normal)); pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_multiply_vec3(*model, v1->normal));
pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v2->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 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 lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame);
@ -1111,44 +1177,6 @@ void pxl8_cpu_draw_mesh(
} }
} }
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
) {
if (!cpu || !cpu->current_target || !mesh || mesh->index_count < 3) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, pxl8_mat4_mul(cpu->frame.view, model));
for (u32 i = 0; i < mesh->index_count; i += 3) {
const pxl8_vertex* v0 = &mesh->vertices[mesh->indices[i]];
const pxl8_vertex* v1 = &mesh->vertices[mesh->indices[i + 1]];
const pxl8_vertex* v2 = &mesh->vertices[mesh->indices[i + 2]];
pxl8_vec4 c0 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v0->position.x, v0->position.y, v0->position.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v1->position.x, v1->position.y, v1->position.z, 1.0f});
pxl8_vec4 c2 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v2->position.x, v2->position.y, v2->position.z, 1.0f});
if (c0.w <= 0.0f || c1.w <= 0.0f || c2.w <= 0.0f) continue;
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);
i32 x2 = (i32)(hw + c2.x / c2.w * hw);
i32 y2 = (i32)(hh - c2.y / c2.w * hh);
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);
}
}
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) { u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) {
if (!cpu || cpu->target_stack_depth == 0) return NULL; if (!cpu || cpu->target_stack_depth == 0) return NULL;
return cpu->target_stack[0]->framebuffer; return cpu->target_stack[0]->framebuffer;
@ -1167,33 +1195,33 @@ u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu) {
pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc) { pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc) {
if (!desc) return NULL; if (!desc) return NULL;
pxl8_cpu_render_target* target = calloc(1, sizeof(pxl8_cpu_render_target)); pxl8_cpu_render_target* target = pxl8_calloc(1, sizeof(pxl8_cpu_render_target));
if (!target) return NULL; if (!target) return NULL;
u32 size = desc->width * desc->height; u32 size = desc->width * desc->height;
target->width = desc->width; target->width = desc->width;
target->height = desc->height; target->height = desc->height;
target->framebuffer = calloc(size, sizeof(u8)); target->framebuffer = pxl8_calloc(size, sizeof(u8));
if (!target->framebuffer) { if (!target->framebuffer) {
free(target); pxl8_free(target);
return NULL; return NULL;
} }
if (desc->with_depth) { if (desc->with_depth) {
target->zbuffer = calloc(size, sizeof(u16)); target->zbuffer = pxl8_calloc(size, sizeof(u16));
if (!target->zbuffer) { if (!target->zbuffer) {
free(target->framebuffer); pxl8_free(target->framebuffer);
free(target); pxl8_free(target);
return NULL; return NULL;
} }
} }
if (desc->with_lighting) { if (desc->with_lighting) {
target->light_accum = calloc(size, sizeof(u32)); target->light_accum = pxl8_calloc(size, sizeof(u32));
if (!target->light_accum) { if (!target->light_accum) {
free(target->zbuffer); pxl8_free(target->zbuffer);
free(target->framebuffer); pxl8_free(target->framebuffer);
free(target); pxl8_free(target);
return NULL; return NULL;
} }
} }
@ -1203,10 +1231,10 @@ pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_targ
void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target) { void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target) {
if (!target) return; if (!target) return;
free(target->light_accum); pxl8_free(target->light_accum);
free(target->zbuffer); pxl8_free(target->zbuffer);
free(target->framebuffer); pxl8_free(target->framebuffer);
free(target); pxl8_free(target);
} }
pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu) { pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu) {
@ -1237,10 +1265,12 @@ void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i3
for (i32 row = 0; row < copy_h; row++) { for (i32 row = 0; row < copy_h; row++) {
u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0; u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0;
u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_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++) { for (i32 col = 0; col < copy_w; col++) {
u8 pixel = src_row[col]; u8 pixel = src_row[col];
if (pixel != transparent_idx) { if (pixel != transparent_idx) {
dst_row[col] = pixel; dst_row[col] = pixel;
if (light_row) light_row[col] = 0;
} }
} }
} }
@ -1300,13 +1330,9 @@ u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) {
void pxl8_cpu_render_glows( void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows, const pxl8_glow* glows,
u32 glow_count, u32 glow_count
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
) { ) {
(void)overbright;
if (!cpu || cpu->target_stack_depth == 0) return; if (!cpu || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1]; pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1];
@ -1319,7 +1345,7 @@ void pxl8_cpu_render_glows(
u32* light_accum = target->light_accum; u32* light_accum = target->light_accum;
for (u32 gi = 0; gi < glow_count; gi++) { for (u32 gi = 0; gi < glow_count; gi++) {
const pxl8_glow_source* glow = &glows[gi]; const pxl8_glow* glow = &glows[gi];
i32 cx = glow->x; i32 cx = glow->x;
i32 cy = glow->y; i32 cy = glow->y;
i32 radius = glow->radius; i32 radius = glow->radius;
@ -1392,25 +1418,21 @@ void pxl8_cpu_render_glows(
u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255; u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255;
u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y); u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y);
// Skip very dark pixels to create stippled transparency effect
if (light_level < 8) continue; if (light_level < 8) continue;
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, light_level); u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, PXL8_LIGHT_WHITE, light_level);
if (intensity > 1.0f && palette_cube) { if (cpu->palette_cube) {
f32 over = intensity - 1.0f; u8 sr, sg, sb, dr, dg, db;
if (over > 1.0f) over = 1.0f; pxl8_palette_cube_get_rgb(cpu->palette_cube, shaded, &sr, &sg, &sb);
u8 r, g, b; pxl8_palette_cube_get_rgb(cpu->palette_cube, pixels[idx], &dr, &dg, &db);
pxl8_palette_cube_get_rgb(palette_cube, shaded, &r, &g, &b); u32 r = (u32)sr + (u32)dr;
u8 or = (u8)(r + (255 - r) * over); u32 g = (u32)sg + (u32)dg;
u8 og = (u8)(g + (255 - g) * over); u32 b = (u32)sb + (u32)db;
u8 ob = (u8)(b + (255 - b) * over); if (r > 255) r = 255;
shaded = pxl8_palette_cube_lookup_stable(palette_cube, or, og, ob); 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);
u8 dst = pixels[idx];
if (additive) {
pixels[idx] = pxl8_additive_blend(additive, shaded, dst);
} else { } else {
pixels[idx] = shaded; pixels[idx] = shaded;
} }

View file

@ -1,12 +1,13 @@
#pragma once #pragma once
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_blend.h"
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_gfx3d.h" #include "pxl8_gfx3d.h"
#include "pxl8_lightmap.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include "pxl8_palette.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -34,6 +35,10 @@ 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_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(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); 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); u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y);
@ -48,17 +53,10 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh, const pxl8_mesh* mesh,
const pxl8_mat4* model, const pxl8_mat4* model,
const pxl8_material* material, const pxl8_gfx_material* material,
const pxl8_atlas* textures const pxl8_atlas* textures
); );
void pxl8_cpu_draw_mesh_wireframe(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
u8 color
);
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu); u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu);
@ -66,11 +64,8 @@ u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);
void pxl8_cpu_render_glows( void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows, const pxl8_glow* glows,
u32 glow_count, u32 glow_count
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
); );
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu); void pxl8_cpu_resolve(pxl8_cpu_backend* cpu);

View file

@ -1,4 +1,5 @@
#include "pxl8_font.h" #include "pxl8_font.h"
#include "pxl8_mem.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -15,7 +16,7 @@ pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32*
*atlas_height = rows_needed * font->default_height; *atlas_height = rows_needed * font->default_height;
i32 atlas_size = (*atlas_width) * (*atlas_height); i32 atlas_size = (*atlas_width) * (*atlas_height);
*atlas_data = (u8*)malloc(atlas_size); *atlas_data = (u8*)pxl8_malloc(atlas_size);
if (!*atlas_data) { if (!*atlas_data) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }

View file

@ -6,7 +6,6 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_backend.h" #include "pxl8_backend.h"
#include "pxl8_blend.h"
#include "pxl8_blit.h" #include "pxl8_blit.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
@ -15,6 +14,7 @@
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mem.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
#include "pxl8_types.h" #include "pxl8_types.h"
@ -25,7 +25,6 @@ typedef struct pxl8_sprite_cache_entry {
} pxl8_sprite_cache_entry; } pxl8_sprite_cache_entry;
struct pxl8_gfx { struct pxl8_gfx {
pxl8_additive_table* additive_table;
pxl8_atlas* atlas; pxl8_atlas* atlas;
pxl8_gfx_backend backend; pxl8_gfx_backend backend;
pxl8_colormap* colormap; pxl8_colormap* colormap;
@ -35,7 +34,6 @@ struct pxl8_gfx {
pxl8_frustum frustum; pxl8_frustum frustum;
const pxl8_hal* hal; const pxl8_hal* hal;
bool initialized; bool initialized;
pxl8_overbright_table* overbright_table;
pxl8_palette* palette; pxl8_palette* palette;
pxl8_palette_cube* palette_cube; pxl8_palette_cube* palette_cube;
pxl8_pixel_mode pixel_mode; pxl8_pixel_mode pixel_mode;
@ -44,6 +42,7 @@ struct pxl8_gfx {
u32 sprite_cache_capacity; u32 sprite_cache_capacity;
u32 sprite_cache_count; u32 sprite_cache_count;
pxl8_viewport viewport; pxl8_viewport viewport;
pxl8_mat4 view_proj;
}; };
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
@ -78,10 +77,14 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0; return gfx ? gfx->framebuffer_width : 0;
} }
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) { pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx) {
return gfx ? gfx->palette : NULL; return gfx ? gfx->palette : NULL;
} }
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx) {
return gfx ? gfx->colormap : NULL;
}
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) { u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->palette) return 0; if (!gfx || !gfx->palette) return 0;
if (color <= 0xFFFFFF) color = (color << 8) | 0xFF; if (color <= 0xFFFFFF) color = (color << 8) | 0xFF;
@ -91,37 +94,24 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
return pxl8_palette_find_closest(gfx->palette, r, g, b); return pxl8_palette_find_closest(gfx->palette, r, g, b);
} }
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) { void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) {
if (!gfx) return; if (!gfx || !gfx->palette || !colors) return;
if (gfx->palette) { pxl8_set_palette(gfx->palette, colors, count);
pxl8_palette_destroy(gfx->palette); 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));
gfx->palette = pal;
if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(gfx->palette);
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
}
} }
} }
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (!gfx || !filepath) return -1; if (!gfx || !gfx->palette || !filepath) return -1;
pxl8_palette* pal = gfx->palette; pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
if (!pal) return -1;
pxl8_result result = pxl8_palette_load_ase(pal, filepath);
if (result != PXL8_OK) return (i32)result; if (result != PXL8_OK) return (i32)result;
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(pal);
if (gfx->colormap) { if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL); pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
} }
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors); pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
} }
} }
return 0; return 0;
@ -133,7 +123,7 @@ pxl8_gfx* pxl8_gfx_create(
pxl8_pixel_mode mode, pxl8_pixel_mode mode,
pxl8_resolution resolution pxl8_resolution resolution
) { ) {
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx)); pxl8_gfx* gfx = (pxl8_gfx*)pxl8_calloc(1, sizeof(pxl8_gfx));
if (!gfx) { if (!gfx) {
pxl8_error("Failed to allocate graphics context"); pxl8_error("Failed to allocate graphics context");
return NULL; return NULL;
@ -149,7 +139,7 @@ pxl8_gfx* pxl8_gfx_create(
if (!gfx->platform_data) { if (!gfx->platform_data) {
pxl8_error("Platform data cannot be NULL"); pxl8_error("Platform data cannot be NULL");
free(gfx); pxl8_free(gfx);
return NULL; return NULL;
} }
@ -168,7 +158,7 @@ pxl8_gfx* pxl8_gfx_create(
gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu); gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu);
if (mode != PXL8_PIXEL_HICOLOR) { if (mode != PXL8_PIXEL_HICOLOR) {
gfx->colormap = calloc(1, sizeof(pxl8_colormap)); gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
if (gfx->colormap) { if (gfx->colormap) {
pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap); pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap);
} }
@ -187,18 +177,16 @@ pxl8_gfx* pxl8_gfx_create(
void pxl8_gfx_destroy(pxl8_gfx* gfx) { void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return; if (!gfx) return;
pxl8_additive_table_destroy(gfx->additive_table);
pxl8_atlas_destroy(gfx->atlas); pxl8_atlas_destroy(gfx->atlas);
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_destroy(gfx->backend.cpu); pxl8_cpu_destroy(gfx->backend.cpu);
} }
free(gfx->colormap); pxl8_free(gfx->colormap);
pxl8_overbright_table_destroy(gfx->overbright_table);
pxl8_palette_cube_destroy(gfx->palette_cube); pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette); pxl8_palette_destroy(gfx->palette);
free(gfx->sprite_cache); pxl8_free(gfx->sprite_cache);
free(gfx); pxl8_free(gfx);
} }
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) { static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
@ -239,7 +227,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (!gfx->sprite_cache) { if (!gfx->sprite_cache) {
gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY; gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY;
gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc( gfx->sprite_cache = (pxl8_sprite_cache_entry*)pxl8_calloc(
gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry) gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry)
); );
if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY; if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY;
@ -284,7 +272,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) { if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
u32 new_capacity = gfx->sprite_cache_capacity * 2; u32 new_capacity = gfx->sprite_cache_capacity * 2;
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc( pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)pxl8_realloc(
gfx->sprite_cache, gfx->sprite_cache,
new_capacity * sizeof(pxl8_sprite_cache_entry) new_capacity * sizeof(pxl8_sprite_cache_entry)
); );
@ -523,6 +511,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (!gfx || !gfx->atlas) return; if (!gfx || !gfx->atlas) return;
u8* framebuffer = NULL; u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0; i32 fb_width = 0;
i32 fb_height = 0; i32 fb_height = 0;
@ -531,6 +520,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return; if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); 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_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break; break;
@ -567,6 +557,11 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) { if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; 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); 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 { } else {
for (i32 py = 0; py < draw_height; py++) { for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) { for (i32 px = 0; px < draw_width; px++) {
@ -580,6 +575,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
if (light_accum) light_accum[dest_idx] = 0;
} }
} }
} }
@ -612,13 +608,17 @@ pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8
return frame; return frame;
} }
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) { void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms) {
if (!gfx || !camera) return; if (!gfx || !camera) return;
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
frame.lights = lights ? pxl8_lights_data(lights) : NULL;
frame.lights_count = lights ? pxl8_lights_count(lights) : 0;
pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view);
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view);
gfx->frustum = pxl8_frustum_from_matrix(vp); gfx->frustum = pxl8_frustum_from_matrix(vp);
gfx->view_proj = vp;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
@ -634,6 +634,38 @@ const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
return &gfx->frustum; return &gfx->frustum;
} }
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return &gfx->view_proj;
}
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform) {
if (!gfx || !in || !out) return 0;
pxl8_mat4 mvp = transform ? pxl8_mat4_multiply(gfx->view_proj, *transform) : gfx->view_proj;
f32 hw = (f32)gfx->framebuffer_width * 0.5f;
f32 hh = (f32)gfx->framebuffer_height * 0.5f;
u32 visible = 0;
for (u32 i = 0; i < count; i++) {
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(mvp, (pxl8_vec4){in[i].x, in[i].y, in[i].z, 1.0f});
if (clip.w <= 0.0f) {
out[i].z = -1.0f;
continue;
}
f32 inv_w = 1.0f / clip.w;
out[i].x = hw + clip.x * inv_w * hw;
out[i].y = hh - clip.y * inv_w * hh;
out[i].z = clip.w;
visible++;
}
return visible;
}
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) { void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) {
if (!gfx) return; if (!gfx) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {
@ -667,7 +699,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
} }
} }
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) { void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
if (!gfx || !mesh || !model || !material) return; if (!gfx || !mesh || !model || !material) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
@ -678,17 +710,6 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
} }
} }
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color) {
if (!gfx || !mesh) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_mesh_wireframe(gfx->backend.cpu, mesh, model, color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
}
void pxl8_3d_end_frame(pxl8_gfx* gfx) { void pxl8_3d_end_frame(pxl8_gfx* gfx) {
if (!gfx) return; if (!gfx) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {
@ -733,31 +754,27 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
} }
} }
static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return; if (!gfx || !gfx->palette) return;
if (!gfx->additive_table) {
gfx->additive_table = pxl8_additive_table_create(gfx->palette);
}
if (!gfx->overbright_table) {
gfx->overbright_table = pxl8_overbright_table_create(gfx->palette);
}
if (!gfx->palette_cube) { if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette); gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
} }
} }
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) { void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return; if (!gfx || !gfx->palette) return;
if (gfx->additive_table) { pxl8_gfx_ensure_blend_tables(gfx);
pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette);
}
if (gfx->overbright_table) {
pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette);
}
if (gfx->palette_cube) { if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette); pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
} }
} }
@ -765,29 +782,33 @@ void pxl8_gfx_colormap_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette || !gfx->colormap) return; if (!gfx || !gfx->palette || !gfx->colormap) return;
u32* colors = pxl8_palette_colors(gfx->palette); u32* colors = pxl8_palette_colors(gfx->palette);
if (colors) { if (colors) {
pxl8_colormap_generate(gfx->colormap, colors, NULL); pxl8_colormap_generate(gfx->colormap, colors);
} }
} }
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b) {
if (!gfx || !gfx->palette) return 0;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || !gfx->palette || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
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) { void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
if (!gfx || !params || count == 0) return; if (!gfx || !params || count == 0) return;
switch (effect) { switch (effect) {
case PXL8_GFX_EFFECT_GLOWS: { case PXL8_GFX_EFFECT_GLOWS: {
pxl8_gfx_ensure_blend_tables(gfx); const pxl8_glow* glows = (const pxl8_glow*)params;
if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return;
const pxl8_glow_source* glows = (const pxl8_glow_source*)params;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_render_glows( pxl8_cpu_render_glows(gfx->backend.cpu, glows, count);
gfx->backend.cpu,
glows,
count,
gfx->additive_table,
gfx->palette_cube,
gfx->overbright_table
);
break; break;
case PXL8_GFX_BACKEND_GPU: case PXL8_GFX_BACKEND_GPU:
break; break;

View file

@ -2,9 +2,12 @@
#include "pxl8_gfx2d.h" #include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h" #include "pxl8_gfx3d.h"
#include "pxl8_glows.h"
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_colormap.h"
#include "pxl8_palette.h" #include "pxl8_palette.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_gfx pxl8_gfx;
@ -12,53 +15,6 @@ typedef enum pxl8_gfx_effect {
PXL8_GFX_EFFECT_GLOWS = 0, PXL8_GFX_EFFECT_GLOWS = 0,
} pxl8_gfx_effect; } pxl8_gfx_effect;
#define PXL8_MAX_GLOWS 256
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow_source {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow_source;
static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) {
return (pxl8_glow_source){
.color = color,
.depth = 0xFFFF,
.height = 0,
.intensity = intensity,
.radius = radius,
.shape = PXL8_GLOW_CIRCLE,
.x = (i16)x,
.y = (i16)y,
};
}
static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) {
g.depth = depth;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) {
g.shape = shape;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) {
g.height = height;
return g;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -76,13 +32,14 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx); pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath); i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal); void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp); void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
@ -97,6 +54,10 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx);
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count); void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx); void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);
void pxl8_gfx_colormap_update(pxl8_gfx* gfx); void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
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);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -1,53 +1,31 @@
#pragma once #pragma once
#include "pxl8_3d_camera.h" #include "pxl8_3d_camera.h"
#include "pxl8_lights.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_gfx pxl8_gfx;
#define PXL8_MAX_LIGHTS 16
typedef struct pxl8_light {
pxl8_vec3 position;
u8 r, g, b;
u8 intensity;
f32 radius;
f32 radius_sq;
f32 inv_radius_sq;
} pxl8_light;
static inline pxl8_light pxl8_light_create(pxl8_vec3 pos, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
f32 radius_sq = radius * radius;
return (pxl8_light){
.position = pos,
.r = r, .g = g, .b = b,
.intensity = intensity,
.radius = radius,
.radius_sq = radius_sq,
.inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f,
};
}
typedef struct pxl8_3d_uniforms { typedef struct pxl8_3d_uniforms {
u8 ambient; u8 ambient;
pxl8_vec3 celestial_dir; pxl8_vec3 celestial_dir;
f32 celestial_intensity; f32 celestial_intensity;
u8 fog_color; u8 fog_color;
f32 fog_density; f32 fog_density;
pxl8_light lights[PXL8_MAX_LIGHTS];
u32 num_lights;
f32 time; f32 time;
} pxl8_3d_uniforms; } pxl8_3d_uniforms;
typedef struct pxl8_3d_frame { typedef struct pxl8_3d_frame {
pxl8_3d_uniforms uniforms;
pxl8_vec3 camera_dir; pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos; pxl8_vec3 camera_pos;
f32 far_clip; f32 far_clip;
const pxl8_light* lights;
u32 lights_count;
f32 near_clip; f32 near_clip;
pxl8_mat4 projection; pxl8_mat4 projection;
pxl8_3d_uniforms uniforms;
pxl8_mat4 view; pxl8_mat4 view;
} pxl8_3d_frame; } pxl8_3d_frame;
@ -55,15 +33,16 @@ typedef struct pxl8_3d_frame {
extern "C" { extern "C" {
#endif #endif
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms); void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color); void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx); void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material); void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
void pxl8_3d_end_frame(pxl8_gfx* gfx); void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx); u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); 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_3d_uniforms* uniforms);

62
src/gfx/pxl8_glows.c Normal file
View file

@ -0,0 +1,62 @@
#include "pxl8_glows.h"
#include "pxl8_gfx.h"
#include "pxl8_mem.h"
#include <stdlib.h>
struct pxl8_glows {
pxl8_glow* data;
u32 capacity;
u32 count;
};
pxl8_glows* pxl8_glows_create(u32 capacity) {
pxl8_glows* glows = pxl8_calloc(1, sizeof(pxl8_glows));
if (!glows) return NULL;
glows->data = pxl8_calloc(capacity, sizeof(pxl8_glow));
if (!glows->data) {
pxl8_free(glows);
return NULL;
}
glows->capacity = capacity;
glows->count = 0;
return glows;
}
void pxl8_glows_destroy(pxl8_glows* glows) {
if (!glows) return;
pxl8_free(glows->data);
pxl8_free(glows);
}
void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape) {
if (!glows || glows->count >= glows->capacity) return;
pxl8_glow* g = &glows->data[glows->count++];
g->x = x;
g->y = y;
g->radius = radius;
g->intensity = intensity;
g->color = color;
g->shape = shape;
g->depth = 0xFFFF;
g->height = 0;
}
void pxl8_glows_clear(pxl8_glows* glows) {
if (!glows) return;
glows->count = 0;
}
u32 pxl8_glows_count(const pxl8_glows* glows) {
return glows ? glows->count : 0;
}
void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx) {
if (!glows || !gfx || glows->count == 0) return;
pxl8_gfx_apply_effect(gfx, PXL8_GFX_EFFECT_GLOWS, glows->data, glows->count);
}

42
src/gfx/pxl8_glows.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_GLOWS_MAX 16384
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow;
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_glows pxl8_glows;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_glows* pxl8_glows_create(u32 capacity);
void pxl8_glows_destroy(pxl8_glows* glows);
void pxl8_glows_add(pxl8_glows* glows, i16 x, i16 y, u8 radius, u16 intensity, u8 color, u8 shape);
void pxl8_glows_clear(pxl8_glows* glows);
u32 pxl8_glows_count(const pxl8_glows* glows);
const pxl8_glow* pxl8_glows_data(const pxl8_glows* glows);
void pxl8_glows_render(pxl8_glows* glows, pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

114
src/gfx/pxl8_lightmap.c Normal file
View file

@ -0,0 +1,114 @@
#include "pxl8_lightmap.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale) {
pxl8_lightmap* lm = pxl8_calloc(1, sizeof(pxl8_lightmap));
if (!lm) return NULL;
lm->width = width;
lm->height = height;
lm->scale = scale;
lm->data = pxl8_calloc(width * height * 3, sizeof(u8));
if (!lm->data) {
pxl8_free(lm);
return NULL;
}
pxl8_lightmap_clear(lm, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL);
return lm;
}
void pxl8_lightmap_destroy(pxl8_lightmap* lm) {
if (!lm) return;
pxl8_free(lm->data);
pxl8_free(lm);
}
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b) {
if (!lm || !lm->data) return;
u32 count = lm->width * lm->height;
for (u32 i = 0; i < count; i++) {
lm->data[i * 3 + 0] = r;
lm->data[i * 3 + 1] = g;
lm->data[i * 3 + 2] = b;
}
}
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) return;
u32 idx = (y * lm->width + x) * 3;
lm->data[idx + 0] = r;
lm->data[idx + 1] = g;
lm->data[idx + 2] = b;
}
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) {
*r = *g = *b = PXL8_LIGHTMAP_NEUTRAL;
return;
}
u32 idx = (y * lm->width + x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
) {
if (!lm || !lm->data || radius <= 0.0f) return;
f32 radius_sq = radius * radius;
f32 inv_radius_sq = 1.0f / radius_sq;
i32 cx = (i32)(lx * (f32)lm->width);
i32 cy = (i32)(ly * (f32)lm->height);
i32 rad_pixels = (i32)(radius * (f32)lm->width) + 1;
i32 x0 = cx - rad_pixels;
i32 y0 = cy - rad_pixels;
i32 x1 = cx + rad_pixels;
i32 y1 = cy + rad_pixels;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (i32)lm->width) x1 = (i32)lm->width - 1;
if (y1 >= (i32)lm->height) y1 = (i32)lm->height - 1;
f32 scale_x = 1.0f / (f32)lm->width;
f32 scale_y = 1.0f / (f32)lm->height;
for (i32 y = y0; y <= y1; y++) {
f32 dy = ((f32)y * scale_y) - ly;
for (i32 x = x0; x <= x1; x++) {
f32 dx = ((f32)x * scale_x) - lx;
f32 dist_sq = dx * dx + dy * dy;
if (dist_sq >= radius_sq) continue;
f32 falloff = 1.0f - dist_sq * inv_radius_sq;
f32 contrib = falloff * falloff * intensity;
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
i32 nr = (i32)lm->data[idx + 0] + (i32)((f32)(r - 128) * contrib);
i32 ng = (i32)lm->data[idx + 1] + (i32)((f32)(g - 128) * contrib);
i32 nb = (i32)lm->data[idx + 2] + (i32)((f32)(b - 128) * contrib);
if (nr < 0) nr = 0; if (nr > 255) nr = 255;
if (ng < 0) ng = 0; if (ng > 255) ng = 255;
if (nb < 0) nb = 0; if (nb > 255) nb = 255;
lm->data[idx + 0] = (u8)nr;
lm->data[idx + 1] = (u8)ng;
lm->data[idx + 2] = (u8)nb;
}
}
}

48
src/gfx/pxl8_lightmap.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHTMAP_MAX 16
#define PXL8_LIGHTMAP_NEUTRAL 128
typedef struct pxl8_lightmap {
u8* data;
u32 width;
u32 height;
u32 scale;
} pxl8_lightmap;
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale);
void pxl8_lightmap_destroy(pxl8_lightmap* lm);
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b);
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b);
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b);
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
);
static inline void pxl8_lightmap_sample(
const pxl8_lightmap* lm,
f32 u, f32 v,
u8* r, u8* g, u8* b
) {
i32 x = (i32)(u * (f32)lm->width) & (i32)(lm->width - 1);
i32 y = (i32)(v * (f32)lm->height) & (i32)(lm->height - 1);
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
#ifdef __cplusplus
}
#endif

64
src/gfx/pxl8_lights.c Normal file
View file

@ -0,0 +1,64 @@
#include "pxl8_lights.h"
#include "pxl8_mem.h"
#include <stdlib.h>
struct pxl8_lights {
pxl8_light* data;
u32 capacity;
u32 count;
};
pxl8_lights* pxl8_lights_create(u32 capacity) {
if (capacity > PXL8_LIGHTS_MAX) capacity = PXL8_LIGHTS_MAX;
pxl8_lights* lights = pxl8_calloc(1, sizeof(pxl8_lights));
if (!lights) return NULL;
lights->data = pxl8_calloc(capacity, sizeof(pxl8_light));
if (!lights->data) {
pxl8_free(lights);
return NULL;
}
lights->capacity = capacity;
lights->count = 0;
return lights;
}
void pxl8_lights_destroy(pxl8_lights* lights) {
if (!lights) return;
pxl8_free(lights->data);
pxl8_free(lights);
}
void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
if (!lights || lights->count >= lights->capacity) return;
f32 radius_sq = radius * radius;
pxl8_light* l = &lights->data[lights->count++];
l->position.x = x;
l->position.y = y;
l->position.z = z;
l->r = r;
l->g = g;
l->b = b;
l->intensity = intensity;
l->radius = radius;
l->radius_sq = radius_sq;
l->inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f;
}
void pxl8_lights_clear(pxl8_lights* lights) {
if (!lights) return;
lights->count = 0;
}
u32 pxl8_lights_count(const pxl8_lights* lights) {
return lights ? lights->count : 0;
}
const pxl8_light* pxl8_lights_data(const pxl8_lights* lights) {
return lights ? lights->data : NULL;
}

33
src/gfx/pxl8_lights.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include "pxl8_math.h"
#include "pxl8_types.h"
#define PXL8_LIGHTS_MAX 256
typedef struct pxl8_light {
pxl8_vec3 position;
f32 inv_radius_sq;
u8 r, g, b;
u8 intensity;
f32 radius;
f32 radius_sq;
} pxl8_light;
typedef struct pxl8_lights pxl8_lights;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_lights* pxl8_lights_create(u32 capacity);
void pxl8_lights_destroy(pxl8_lights* lights);
void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius);
void pxl8_lights_clear(pxl8_lights* lights);
u32 pxl8_lights_count(const pxl8_lights* lights);
const pxl8_light* pxl8_lights_data(const pxl8_lights* lights);
#ifdef __cplusplus
}
#endif

View file

@ -1,3 +1,4 @@
#include "pxl8_mem.h"
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -6,16 +7,16 @@ pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) {
if (vertex_capacity > PXL8_MESH_MAX_VERTICES) vertex_capacity = PXL8_MESH_MAX_VERTICES; if (vertex_capacity > PXL8_MESH_MAX_VERTICES) vertex_capacity = PXL8_MESH_MAX_VERTICES;
if (index_capacity > PXL8_MESH_MAX_INDICES) index_capacity = PXL8_MESH_MAX_INDICES; if (index_capacity > PXL8_MESH_MAX_INDICES) index_capacity = PXL8_MESH_MAX_INDICES;
pxl8_mesh* mesh = calloc(1, sizeof(pxl8_mesh)); pxl8_mesh* mesh = pxl8_calloc(1, sizeof(pxl8_mesh));
if (!mesh) return NULL; if (!mesh) return NULL;
mesh->vertices = calloc(vertex_capacity, sizeof(pxl8_vertex)); mesh->vertices = pxl8_calloc(vertex_capacity, sizeof(pxl8_vertex));
mesh->indices = calloc(index_capacity, sizeof(u16)); mesh->indices = pxl8_calloc(index_capacity, sizeof(u16));
if (!mesh->vertices || !mesh->indices) { if (!mesh->vertices || !mesh->indices) {
free(mesh->vertices); pxl8_free(mesh->vertices);
free(mesh->indices); pxl8_free(mesh->indices);
free(mesh); pxl8_free(mesh);
return NULL; return NULL;
} }
@ -29,9 +30,9 @@ pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) {
void pxl8_mesh_destroy(pxl8_mesh* mesh) { void pxl8_mesh_destroy(pxl8_mesh* mesh) {
if (!mesh) return; if (!mesh) return;
free(mesh->vertices); pxl8_free(mesh->vertices);
free(mesh->indices); pxl8_free(mesh->indices);
free(mesh); pxl8_free(mesh);
} }
void pxl8_mesh_clear(pxl8_mesh* mesh) { void pxl8_mesh_clear(pxl8_mesh* mesh) {

View file

@ -17,8 +17,15 @@ typedef enum pxl8_blend_mode {
PXL8_BLEND_ADDITIVE, PXL8_BLEND_ADDITIVE,
} pxl8_blend_mode; } pxl8_blend_mode;
typedef struct pxl8_material { typedef struct pxl8_gfx_material {
char name[16];
pxl8_vec3 u_axis;
pxl8_vec3 v_axis;
f32 u_offset;
f32 v_offset;
u32 texture_id; u32 texture_id;
u32 lightmap_id;
u8 alpha; u8 alpha;
u8 blend_mode; u8 blend_mode;
bool dither; bool dither;
@ -26,13 +33,15 @@ typedef struct pxl8_material {
bool dynamic_lighting; bool dynamic_lighting;
bool per_pixel; bool per_pixel;
bool vertex_color_passthrough; bool vertex_color_passthrough;
bool wireframe;
f32 emissive_intensity; f32 emissive_intensity;
} pxl8_material; } pxl8_gfx_material;
typedef struct pxl8_vertex { typedef struct pxl8_vertex {
pxl8_vec3 position; pxl8_vec3 position;
pxl8_vec3 normal; pxl8_vec3 normal;
f32 u, v; f32 u, v;
f32 lu, lv;
u8 color; u8 color;
u8 light; u8 light;
u8 _pad[2]; u8 _pad[2];
@ -62,62 +71,6 @@ static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
return mesh->index_count / 3; return mesh->index_count / 3;
} }
static inline pxl8_material pxl8_material_create(u32 texture_id) {
return (pxl8_material){
.texture_id = texture_id,
.alpha = 255,
.blend_mode = PXL8_BLEND_OPAQUE,
.dither = true,
.double_sided = false,
.dynamic_lighting = false,
.per_pixel = false,
.vertex_color_passthrough = false,
.emissive_intensity = 0.0f,
};
}
static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) {
m.alpha = alpha;
if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) {
m.blend_mode = PXL8_BLEND_ALPHA;
}
return m;
}
static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) {
m.blend_mode = mode;
return m;
}
static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) {
m.double_sided = true;
return m;
}
static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) {
m.emissive_intensity = intensity;
return m;
}
static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) {
m.dynamic_lighting = true;
return m;
}
static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) {
m.dither = false;
return m;
}
static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) {
m.vertex_color_passthrough = true;
return m;
}
static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) {
m.per_pixel = true;
return m;
}
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -5,10 +5,18 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_colormap.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_PALETTE_HASH_SIZE 512 #define PXL8_PALETTE_HASH_SIZE 512
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
typedef struct { typedef struct {
u32 color; u32 color;
i16 index; i16 index;
@ -200,7 +208,7 @@ static void update_cycle_colors(pxl8_palette* pal, u8 slot) {
} }
pxl8_palette* pxl8_palette_create(void) { pxl8_palette* pxl8_palette_create(void) {
pxl8_palette* pal = calloc(1, sizeof(pxl8_palette)); pxl8_palette* pal = pxl8_calloc(1, sizeof(pxl8_palette));
if (!pal) return NULL; if (!pal) return NULL;
pal->colors[0] = 0x00000000; pal->colors[0] = 0x00000000;
@ -221,7 +229,7 @@ pxl8_palette* pxl8_palette_create(void) {
} }
void pxl8_palette_destroy(pxl8_palette* pal) { void pxl8_palette_destroy(pxl8_palette* pal) {
free(pal); pxl8_free(pal);
} }
pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path) { pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path) {
@ -332,10 +340,6 @@ void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b,
unpack_rgba(pal->colors[idx], r, g, b, a); unpack_rgba(pal->colors[idx], r, g, b, a);
} }
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) {
if (pal) pal->colors[idx] = color;
}
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) { void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) {
if (pal) pal->colors[idx] = pack_rgb(r, g, b); if (pal) pal->colors[idx] = pack_rgb(r, g, b);
} }
@ -344,6 +348,17 @@ void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) {
if (pal) pal->colors[idx] = pack_rgba(r, g, b, a); if (pal) pal->colors[idx] = pack_rgba(r, g, b, a);
} }
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count) {
if (!pal || !colors) return;
for (u16 i = 0; i < count; i++) {
u32 rgb = colors[i];
u8 r = (rgb >> 16) & 0xFF;
u8 g = (rgb >> 8) & 0xFF;
u8 b = rgb & 0xFF;
pal->colors[i] = pack_rgb(r, g, b);
}
}
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) { void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) {
if (!pal || count == 0) return; if (!pal || count == 0) return;
@ -472,3 +487,91 @@ pxl8_cycle_range pxl8_cycle_range_disabled(void) {
}; };
return range; return range;
} }
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
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;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = pxl8_calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
pxl8_free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}

View file

@ -5,8 +5,11 @@
#define PXL8_PALETTE_SIZE 256 #define PXL8_PALETTE_SIZE 256
#define PXL8_MAX_CYCLES 8 #define PXL8_MAX_CYCLES 8
#define PXL8_MAX_CYCLE_LEN 16 #define PXL8_MAX_CYCLE_LEN 16
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
typedef struct pxl8_palette pxl8_palette; typedef struct pxl8_palette pxl8_palette;
typedef struct pxl8_palette_cube pxl8_palette_cube;
typedef enum pxl8_cycle_mode { typedef enum pxl8_cycle_mode {
PXL8_CYCLE_LOOP, PXL8_CYCLE_LOOP,
@ -49,9 +52,9 @@ u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);
i32 pxl8_palette_index(const pxl8_palette* pal, u32 color); i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b); void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b);
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a); void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a);
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color);
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b); void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a); void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a);
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count);
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to); void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to);
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1); void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1);
@ -65,6 +68,13 @@ void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period); pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period);
pxl8_cycle_range pxl8_cycle_range_disabled(void); pxl8_cycle_range pxl8_cycle_range_disabled(void);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -3,6 +3,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_mem.h"
#include "pxl8_gfx2d.h" #include "pxl8_gfx2d.h"
#include "pxl8_palette.h" #include "pxl8_palette.h"
#include "pxl8_rng.h" #include "pxl8_rng.h"
@ -37,12 +38,12 @@ struct pxl8_particles {
}; };
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) { pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
pxl8_particles* ps = calloc(1, sizeof(pxl8_particles)); pxl8_particles* ps = pxl8_calloc(1, sizeof(pxl8_particles));
if (!ps) return NULL; if (!ps) return NULL;
ps->particles = calloc(max_count, sizeof(pxl8_particle)); ps->particles = pxl8_calloc(max_count, sizeof(pxl8_particle));
if (!ps->particles) { if (!ps->particles) {
free(ps); pxl8_free(ps);
return NULL; return NULL;
} }
@ -61,8 +62,8 @@ pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
void pxl8_particles_destroy(pxl8_particles* ps) { void pxl8_particles_destroy(pxl8_particles* ps) {
if (!ps) return; if (!ps) return;
free(ps->particles); pxl8_free(ps->particles);
free(ps); pxl8_free(ps);
} }
void pxl8_particles_clear(pxl8_particles* ps) { void pxl8_particles_clear(pxl8_particles* ps) {

View file

@ -6,6 +6,7 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_tilesheet.h" #include "pxl8_tilesheet.h"
struct pxl8_tilesheet { struct pxl8_tilesheet {
@ -58,7 +59,7 @@ static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32
if (idx >= layer->chunks_wide * layer->chunks_high) return NULL; if (idx >= layer->chunks_wide * layer->chunks_high) return NULL;
if (!layer->chunks[idx]) { if (!layer->chunks[idx]) {
layer->chunks[idx] = calloc(1, sizeof(pxl8_tile_chunk)); layer->chunks[idx] = pxl8_calloc(1, sizeof(pxl8_tile_chunk));
if (!layer->chunks[idx]) return NULL; if (!layer->chunks[idx]) return NULL;
layer->chunks[idx]->chunk_x = chunk_x; layer->chunks[idx]->chunk_x = chunk_x;
@ -75,7 +76,7 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
return NULL; return NULL;
} }
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap)); pxl8_tilemap* tilemap = pxl8_calloc(1, sizeof(pxl8_tilemap));
if (!tilemap) return NULL; if (!tilemap) return NULL;
tilemap->width = width; tilemap->width = width;
@ -85,7 +86,7 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
tilemap->tilesheet = pxl8_tilesheet_create(tilemap->tile_size); tilemap->tilesheet = pxl8_tilesheet_create(tilemap->tile_size);
if (!tilemap->tilesheet) { if (!tilemap->tilesheet) {
free(tilemap); pxl8_free(tilemap);
return NULL; return NULL;
} }
@ -103,13 +104,13 @@ pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
layer->visible = (i == 0); layer->visible = (i == 0);
layer->opacity = 255; layer->opacity = 255;
layer->chunks = calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*)); layer->chunks = pxl8_calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
if (!layer->chunks) { if (!layer->chunks) {
for (u32 j = 0; j < i; j++) { for (u32 j = 0; j < i; j++) {
free(tilemap->layers[j].chunks); pxl8_free(tilemap->layers[j].chunks);
} }
if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet); if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet);
free(tilemap); pxl8_free(tilemap);
return NULL; return NULL;
} }
} }
@ -125,17 +126,17 @@ void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
if (layer->chunks) { if (layer->chunks) {
for (u32 j = 0; j < layer->chunk_count; j++) { for (u32 j = 0; j < layer->chunk_count; j++) {
if (layer->chunks[j]) { if (layer->chunks[j]) {
free(layer->chunks[j]); pxl8_free(layer->chunks[j]);
} }
} }
free(layer->chunks); pxl8_free(layer->chunks);
} }
} }
if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet); if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet);
if (tilemap->tile_user_data) free(tilemap->tile_user_data); if (tilemap->tile_user_data) pxl8_free(tilemap->tile_user_data);
free(tilemap); pxl8_free(tilemap);
} }
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap) { u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap) {
@ -155,7 +156,7 @@ void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* u
if (tile_id >= tilemap->tile_user_data_capacity) { if (tile_id >= tilemap->tile_user_data_capacity) {
u32 new_capacity = tile_id + 64; u32 new_capacity = tile_id + 64;
void** new_data = realloc(tilemap->tile_user_data, new_capacity * sizeof(void*)); void** new_data = pxl8_realloc(tilemap->tile_user_data, new_capacity * sizeof(void*));
if (!new_data) return; if (!new_data) return;
for (u32 i = tilemap->tile_user_data_capacity; i < new_capacity; i++) { for (u32 i = tilemap->tile_user_data_capacity; i < new_capacity; i++) {
@ -478,7 +479,7 @@ void pxl8_tilemap_compress(pxl8_tilemap* tilemap) {
} }
if (!has_tiles) { if (!has_tiles) {
free(chunk); pxl8_free(chunk);
layer->chunks[j] = NULL; layer->chunks[j] = NULL;
layer->allocated_chunks--; layer->allocated_chunks--;
} else { } else {
@ -535,8 +536,8 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
u32 tilesheet_width = tiles_per_row * tilemap->tile_size; u32 tilesheet_width = tiles_per_row * tilemap->tile_size;
u32 tilesheet_height = tilesheet_rows * tilemap->tile_size; u32 tilesheet_height = tilesheet_rows * tilemap->tile_size;
if (tilemap->tilesheet->data) free(tilemap->tilesheet->data); if (tilemap->tilesheet->data) pxl8_free(tilemap->tilesheet->data);
tilemap->tilesheet->data = calloc(tilesheet_width * tilesheet_height, 1); tilemap->tilesheet->data = pxl8_calloc(tilesheet_width * tilesheet_height, 1);
if (!tilemap->tilesheet->data) { if (!tilemap->tilesheet->data) {
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -548,8 +549,8 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
tilemap->tilesheet->total_tiles = tileset->tile_count; tilemap->tilesheet->total_tiles = tileset->tile_count;
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid); if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid);
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool)); tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool));
for (u32 i = 0; i < tileset->tile_count; i++) { for (u32 i = 0; i < tileset->tile_count; i++) {
u32 sheet_row = i / tiles_per_row; u32 sheet_row = i / tiles_per_row;

View file

@ -7,6 +7,7 @@
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_tilemap.h" #include "pxl8_tilemap.h"
struct pxl8_tilesheet { struct pxl8_tilesheet {
@ -32,7 +33,7 @@ struct pxl8_tilesheet {
}; };
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) { pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) {
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet)); pxl8_tilesheet* tilesheet = pxl8_calloc(1, sizeof(pxl8_tilesheet));
if (!tilesheet) return NULL; if (!tilesheet) return NULL;
tilesheet->tile_size = tile_size; tilesheet->tile_size = tile_size;
@ -45,37 +46,37 @@ void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return; if (!tilesheet) return;
if (tilesheet->data) { if (tilesheet->data) {
free(tilesheet->data); pxl8_free(tilesheet->data);
} }
if (tilesheet->tile_valid) { if (tilesheet->tile_valid) {
free(tilesheet->tile_valid); pxl8_free(tilesheet->tile_valid);
} }
if (tilesheet->animations) { if (tilesheet->animations) {
for (u32 i = 0; i < tilesheet->animation_count; i++) { for (u32 i = 0; i < tilesheet->animation_count; i++) {
if (tilesheet->animations[i].frames) { if (tilesheet->animations[i].frames) {
free(tilesheet->animations[i].frames); pxl8_free(tilesheet->animations[i].frames);
} }
} }
free(tilesheet->animations); pxl8_free(tilesheet->animations);
} }
if (tilesheet->properties) { if (tilesheet->properties) {
free(tilesheet->properties); pxl8_free(tilesheet->properties);
} }
if (tilesheet->autotile_rules) { if (tilesheet->autotile_rules) {
for (u32 i = 0; i <= tilesheet->total_tiles; i++) { for (u32 i = 0; i <= tilesheet->total_tiles; i++) {
if (tilesheet->autotile_rules[i]) { if (tilesheet->autotile_rules[i]) {
free(tilesheet->autotile_rules[i]); pxl8_free(tilesheet->autotile_rules[i]);
} }
} }
free(tilesheet->autotile_rules); pxl8_free(tilesheet->autotile_rules);
free(tilesheet->autotile_rule_counts); pxl8_free(tilesheet->autotile_rule_counts);
} }
free(tilesheet); pxl8_free(tilesheet);
} }
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx) { pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx) {
@ -89,7 +90,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
} }
if (tilesheet->data) { if (tilesheet->data) {
free(tilesheet->data); pxl8_free(tilesheet->data);
} }
u32 width = ase_file.header.width; u32 width = ase_file.header.width;
@ -111,8 +112,8 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
u16 ase_depth = ase_file.header.color_depth; u16 ase_depth = ase_file.header.color_depth;
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR); bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode); usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
tilesheet->data = malloc(data_size); tilesheet->data = pxl8_malloc(data_size);
if (!tilesheet->data) { if (!tilesheet->data) {
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -138,9 +139,9 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
} else if (ase_depth == 8 && gfx_hicolor) { } else if (ase_depth == 8 && gfx_hicolor) {
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed"); pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
u8* new_data = realloc(tilesheet->data, pixel_count); u8* new_data = pxl8_realloc(tilesheet->data, pixel_count);
if (!new_data) { if (!new_data) {
free(tilesheet->data); pxl8_free(tilesheet->data);
tilesheet->data = NULL; tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -149,16 +150,16 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
memcpy(tilesheet->data, src, pixel_count); memcpy(tilesheet->data, src, pixel_count);
} else { } else {
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth); pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
free(tilesheet->data); pxl8_free(tilesheet->data);
tilesheet->data = NULL; tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_INVALID_ARGUMENT; return PXL8_ERROR_INVALID_ARGUMENT;
} }
} }
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool)); tilesheet->tile_valid = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(bool));
if (!tilesheet->tile_valid) { if (!tilesheet->tile_valid) {
free(tilesheet->data); pxl8_free(tilesheet->data);
tilesheet->data = NULL; tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
@ -259,14 +260,14 @@ pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_til
if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT; if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
if (!tilesheet->animations) { if (!tilesheet->animations) {
tilesheet->animations = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation)); tilesheet->animations = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY; if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY;
} }
pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id]; pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id];
if (anim->frames) free(anim->frames); if (anim->frames) pxl8_free(anim->frames);
anim->frames = malloc(frame_count * sizeof(u16)); anim->frames = pxl8_malloc(frame_count * sizeof(u16));
if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY; if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(anim->frames, frames, frame_count * sizeof(u16)); memcpy(anim->frames, frames, frame_count * sizeof(u16));
@ -340,7 +341,7 @@ void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return; if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return;
if (!tilesheet->properties) { if (!tilesheet->properties) {
tilesheet->properties = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties)); tilesheet->properties = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
if (!tilesheet->properties) return; if (!tilesheet->properties) return;
} }
@ -365,15 +366,15 @@ pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base
} }
if (!tilesheet->autotile_rules) { if (!tilesheet->autotile_rules) {
tilesheet->autotile_rules = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*)); tilesheet->autotile_rules = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
tilesheet->autotile_rule_counts = calloc(tilesheet->total_tiles + 1, sizeof(u32)); tilesheet->autotile_rule_counts = pxl8_calloc(tilesheet->total_tiles + 1, sizeof(u32));
if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) { if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
} }
u32 count = tilesheet->autotile_rule_counts[base_tile_id]; u32 count = tilesheet->autotile_rule_counts[base_tile_id];
pxl8_autotile_rule* new_rules = realloc(tilesheet->autotile_rules[base_tile_id], pxl8_autotile_rule* new_rules = pxl8_realloc(tilesheet->autotile_rules[base_tile_id],
(count + 1) * sizeof(pxl8_autotile_rule)); (count + 1) * sizeof(pxl8_autotile_rule));
if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY; if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY;

View file

@ -6,6 +6,7 @@
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mem.h"
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) { pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) {
if (duration <= 0.0f) { if (duration <= 0.0f) {
@ -13,7 +14,7 @@ pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration)
return NULL; return NULL;
} }
pxl8_transition* transition = (pxl8_transition*)calloc(1, sizeof(pxl8_transition)); pxl8_transition* transition = (pxl8_transition*)pxl8_calloc(1, sizeof(pxl8_transition));
if (!transition) { if (!transition) {
pxl8_error("Failed to allocate transition"); pxl8_error("Failed to allocate transition");
return NULL; return NULL;
@ -33,7 +34,7 @@ pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration)
void pxl8_transition_destroy(pxl8_transition* transition) { void pxl8_transition_destroy(pxl8_transition* transition) {
if (!transition) return; if (!transition) return;
free(transition); pxl8_free(transition);
} }
f32 pxl8_transition_get_progress(const pxl8_transition* transition) { f32 pxl8_transition_get_progress(const pxl8_transition* transition) {

View file

@ -4,9 +4,10 @@
#include <string.h> #include <string.h>
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_mem.h"
pxl8_gui_state* pxl8_gui_state_create(void) { pxl8_gui_state* pxl8_gui_state_create(void) {
pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state)); pxl8_gui_state* state = (pxl8_gui_state*)pxl8_malloc(sizeof(pxl8_gui_state));
if (!state) return NULL; if (!state) return NULL;
memset(state, 0, sizeof(pxl8_gui_state)); memset(state, 0, sizeof(pxl8_gui_state));
@ -15,21 +16,23 @@ pxl8_gui_state* pxl8_gui_state_create(void) {
void pxl8_gui_state_destroy(pxl8_gui_state* state) { void pxl8_gui_state_destroy(pxl8_gui_state* state) {
if (!state) return; if (!state) return;
free(state); pxl8_free(state);
} }
void pxl8_gui_begin_frame(pxl8_gui_state* state) { void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
state->hot_id = 0; state->hot_id = 0;
if (gfx) pxl8_gfx_push_target(gfx);
} }
void pxl8_gui_end_frame(pxl8_gui_state* state) { void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
if (!state->cursor_down) { if (!state->cursor_down) {
state->active_id = 0; state->active_id = 0;
} }
state->cursor_clicked = false; state->cursor_clicked = false;
if (gfx) pxl8_gfx_pop_target(gfx);
} }
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) { void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
@ -80,16 +83,16 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 offset_y = 0; i32 offset_y = 0;
if (is_active) { if (is_active) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 3; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
offset_x = 1; offset_x = 1;
offset_y = 1; offset_y = 1;
} else if (is_hot || cursor_over) { } else if (is_hot || cursor_over) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 8; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
} else { } else {
bg_color = 3; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
border_color = 4; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
} }
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color); pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -98,7 +101,7 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 text_len = (i32)strlen(label); i32 text_len = (i32)strlen(label);
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x; i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
i32 text_y = y + (h / 2) - 5 + offset_y; i32 text_y = y + (h / 2) - 5 + offset_y;
pxl8_2d_text(gfx, label, text_x, text_y, 6); pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gfx_ui_color(gfx, PXL8_UI_FG1));
return clicked; return clicked;
} }
@ -106,14 +109,19 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return; if (!gfx || !title) return;
pxl8_2d_rect_fill(gfx, x, y, w, 28, 1); u8 title_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2); u8 body_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
pxl8_2d_rect(gfx, x, y, w, h, 4); u8 border = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4); u8 title_fg = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, 28, title_bg);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, body_bg);
pxl8_2d_rect(gfx, x, y, w, h, border);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, border);
i32 title_x = x + 10; i32 title_x = x + 10;
i32 title_y = y + (28 / 2) - 5; i32 title_y = y + (28 / 2) - 5;
pxl8_2d_text(gfx, title, title_x, title_y, 8); pxl8_2d_text(gfx, title, title_x, title_y, title_fg);
} }
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) { void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) {

View file

@ -22,8 +22,8 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state);
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y); void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
bool pxl8_gui_is_hovering(const pxl8_gui_state* state); bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
void pxl8_gui_begin_frame(pxl8_gui_state* state); void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_end_frame(pxl8_gui_state* state); void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_cursor_down(pxl8_gui_state* state); void pxl8_gui_cursor_down(pxl8_gui_state* state);
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y); void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);

View file

@ -0,0 +1,41 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_UI_PALETTE_SIZE 16
#define PXL8_UI_BG0 0
#define PXL8_UI_BG1 1
#define PXL8_UI_BG2 2
#define PXL8_UI_BG3 3
#define PXL8_UI_FG0 4
#define PXL8_UI_FG1 5
#define PXL8_UI_FG2 6
#define PXL8_UI_FG3 7
#define PXL8_UI_RED 8
#define PXL8_UI_GREEN 9
#define PXL8_UI_YELLOW 10
#define PXL8_UI_BLUE 11
#define PXL8_UI_PURPLE 12
#define PXL8_UI_AQUA 13
#define PXL8_UI_ORANGE 14
#define PXL8_UI_GRAY 15
static const u32 pxl8_ui_palette[PXL8_UI_PALETTE_SIZE] = {
0xFF282828,
0xFF3c3836,
0xFF504945,
0xFF665c54,
0xFFc7f1fb,
0xFFb2dbeb,
0xFFa1c4d5,
0xFF93aebd,
0xFF3449fb,
0xFF26bbb8,
0xFF2fbdfa,
0xFF98a583,
0xFF9b86d3,
0xFF7cc08e,
0xFF1980fe,
0xFF928374,
};

View file

@ -1,4 +1,4 @@
#include "pxl8_sdl3.h" #include "pxl8_hal.h"
#define SDL_MAIN_USE_CALLBACKS #define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
@ -8,13 +8,15 @@
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
extern const pxl8_hal pxl8_hal_sdl3;
typedef struct pxl8_sdl3_context { typedef struct pxl8_sdl3_context {
SDL_Texture* framebuffer; SDL_Texture* framebuffer;
SDL_Renderer* renderer; SDL_Renderer* renderer;
SDL_Window* window; SDL_Window* window;
u32* rgba_buffer; u32* rgba_buffer;
size_t rgba_buffer_size; usize rgba_buffer_size;
} pxl8_sdl3_context; } pxl8_sdl3_context;
static void* sdl3_create(i32 render_w, i32 render_h, static void* sdl3_create(i32 render_w, i32 render_h,
@ -101,7 +103,7 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w,
if (!platform_data || !pixels) return; if (!platform_data || !pixels) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
size_t pixel_count = w * h; usize pixel_count = w * h;
if (bpp == 4) { if (bpp == 4) {
SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4); SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4);

8
src/hal/pxl8_mem.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "pxl8_types.h"
void* pxl8_malloc(usize size);
void* pxl8_calloc(usize count, usize size);
void* pxl8_realloc(void* ptr, usize size);
void pxl8_free(void* ptr);

19
src/hal/pxl8_mem_sdl3.c Normal file
View file

@ -0,0 +1,19 @@
#include "pxl8_mem.h"
#include <SDL3/SDL.h>
void* pxl8_malloc(usize size) {
return SDL_malloc(size);
}
void* pxl8_calloc(usize count, usize size) {
return SDL_calloc(count, size);
}
void* pxl8_realloc(void* ptr, usize size) {
return SDL_realloc(ptr, size);
}
void pxl8_free(void* ptr) {
SDL_free(ptr);
}

View file

@ -1,5 +0,0 @@
#pragma once
#include "pxl8_hal.h"
extern const pxl8_hal pxl8_hal_sdl3;

View file

@ -1,13 +1,15 @@
local anim = require("pxl8.anim") local anim = require("pxl8.anim")
local bytes = require("pxl8.bytes") local bytes = require("pxl8.bytes")
local core = require("pxl8.core") local core = require("pxl8.core")
local effects = require("pxl8.effects")
local gfx2d = require("pxl8.gfx2d") local gfx2d = require("pxl8.gfx2d")
local gfx3d = require("pxl8.gfx3d") local gfx3d = require("pxl8.gfx3d")
local gui = require("pxl8.gui") local gui = require("pxl8.gui")
local input = require("pxl8.input") local input = require("pxl8.input")
local math3d = require("pxl8.math") local math = require("pxl8.math")
local net = require("pxl8.net") local net = require("pxl8.net")
local particles = require("pxl8.particles") local particles = require("pxl8.particles")
local procgen = require("pxl8.procgen")
local sfx = require("pxl8.sfx") local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap") local tilemap = require("pxl8.tilemap")
local transition = require("pxl8.transition") local transition = require("pxl8.transition")
@ -27,16 +29,20 @@ pxl8.debug = core.debug
pxl8.trace = core.trace pxl8.trace = core.trace
pxl8.quit = core.quit pxl8.quit = core.quit
pxl8.rng_seed = core.rng_seed pxl8.hash32 = math.hash32
pxl8.rng_next = core.rng_next
pxl8.rng_f32 = core.rng_f32 pxl8.rng_f32 = core.rng_f32
pxl8.rng_next = core.rng_next
pxl8.rng_range = core.rng_range pxl8.rng_range = core.rng_range
pxl8.rng_seed = core.rng_seed
pxl8.find_color = core.find_color pxl8.find_color = core.find_color
pxl8.palette_color = core.palette_color pxl8.palette_color = core.palette_color
pxl8.palette_index = core.palette_index pxl8.palette_index = core.palette_index
pxl8.ramp_index = core.ramp_index pxl8.ramp_index = core.ramp_index
pxl8.set_colormap = core.set_colormap
pxl8.set_palette = core.set_palette
pxl8.set_palette_rgb = core.set_palette_rgb pxl8.set_palette_rgb = core.set_palette_rgb
pxl8.update_palette_deps = core.update_palette_deps
pxl8.clear = gfx2d.clear pxl8.clear = gfx2d.clear
pxl8.pixel = gfx2d.pixel pxl8.pixel = gfx2d.pixel
@ -78,18 +84,28 @@ pxl8.Anim = anim.Anim
pxl8.create_anim = anim.Anim.new pxl8.create_anim = anim.Anim.new
pxl8.create_anim_from_ase = anim.Anim.from_ase pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.bounds = math3d.bounds pxl8.bounds = math.bounds
pxl8.Camera3D = gfx3d.Camera3D pxl8.Camera3D = gfx3d.Camera3D
pxl8.create_camera_3d = gfx3d.Camera3D.new pxl8.Mesh = gfx3d.Mesh
pxl8.begin_frame_3d = gfx3d.begin_frame pxl8.begin_frame_3d = gfx3d.begin_frame
pxl8.clear_3d = gfx3d.clear pxl8.clear_3d = gfx3d.clear
pxl8.clear_depth = gfx3d.clear_depth pxl8.clear_depth = gfx3d.clear_depth
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.create_vec3_array = gfx3d.create_vec3_array
pxl8.draw_line_3d = gfx3d.draw_line pxl8.draw_line_3d = gfx3d.draw_line
pxl8.draw_mesh = gfx3d.draw_mesh pxl8.draw_mesh = gfx3d.draw_mesh
pxl8.end_frame_3d = gfx3d.end_frame pxl8.end_frame_3d = gfx3d.end_frame
pxl8.Mesh = gfx3d.Mesh pxl8.project_points = gfx3d.project_points
pxl8.create_mesh = gfx3d.Mesh.new
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
pxl8.Compressor = sfx.Compressor pxl8.Compressor = sfx.Compressor
pxl8.create_compressor = sfx.Compressor.new pxl8.create_compressor = sfx.Compressor.new
@ -103,16 +119,18 @@ pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label pxl8.gui_label = gui.label
pxl8.gui_window = gui.window pxl8.gui_window = gui.window
pxl8.mat4_identity = math3d.mat4_identity pxl8.mat4_identity = math.mat4_identity
pxl8.mat4_lookat = math3d.mat4_lookat pxl8.mat4_lookat = math.mat4_lookat
pxl8.mat4_multiply = math3d.mat4_multiply pxl8.mat4_multiply = math.mat4_multiply
pxl8.mat4_ortho = math3d.mat4_ortho pxl8.mat4_multiply_vec3 = math.mat4_multiply_vec3
pxl8.mat4_perspective = math3d.mat4_perspective pxl8.mat4_multiply_vec4 = math.mat4_multiply_vec4
pxl8.mat4_rotate_x = math3d.mat4_rotate_x pxl8.mat4_orthographic = math.mat4_orthographic
pxl8.mat4_rotate_y = math3d.mat4_rotate_y pxl8.mat4_perspective = math.mat4_perspective
pxl8.mat4_rotate_z = math3d.mat4_rotate_z pxl8.mat4_rotate_x = math.mat4_rotate_x
pxl8.mat4_scale = math3d.mat4_scale pxl8.mat4_rotate_y = math.mat4_rotate_y
pxl8.mat4_translate = math3d.mat4_translate pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math.mat4_translate
pxl8.Net = net.Net pxl8.Net = net.Net
pxl8.create_net = net.Net.new pxl8.create_net = net.Net.new
@ -141,9 +159,10 @@ pxl8.pack_u64_le = bytes.pack_u64_le
pxl8.Particles = particles.Particles pxl8.Particles = particles.Particles
pxl8.create_particles = particles.Particles.new pxl8.create_particles = particles.Particles.new
pxl8.Graph = procgen.Graph
pxl8.create_graph = procgen.create_graph
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
pxl8.procgen_tex = world.procgen_tex
pxl8.SfxContext = sfx.SfxContext pxl8.SfxContext = sfx.SfxContext
pxl8.SfxNode = sfx.SfxNode pxl8.SfxNode = sfx.SfxNode

View file

@ -20,27 +20,40 @@ function core.get_fps()
end end
function core.palette_color(index) function core.palette_color(index)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end if pal == nil then return 0 end
return C.pxl8_palette_color(pal, index) return C.pxl8_palette_color(pal, index)
end end
function core.palette_index(color) function core.palette_index(color)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return -1 end if pal == nil then return -1 end
return C.pxl8_palette_index(pal, color) return C.pxl8_palette_index(pal, color)
end end
function core.set_palette_rgb(index, r, g, b) function core.set_palette_rgb(index, r, g, b)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return end if pal == nil then return end
C.pxl8_palette_set_rgb(pal, index, r, g, b) C.pxl8_palette_set_rgb(pal, index, r, g, b)
end
function core.set_palette(colors, count)
C.pxl8_gfx_set_palette_colors(core.gfx, colors, count)
end
function core.set_colormap(data, size)
local cm = C.pxl8_gfx_colormap(core.gfx)
if cm == nil then return end
C.pxl8_set_colormap(cm, data, size)
end
function core.update_palette_deps()
C.pxl8_gfx_blend_tables_update(core.gfx) C.pxl8_gfx_blend_tables_update(core.gfx)
C.pxl8_gfx_colormap_update(core.gfx) C.pxl8_gfx_colormap_update(core.gfx)
end end
function core.ramp_index(position) function core.ramp_index(position)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end if pal == nil then return 0 end
return C.pxl8_palette_ramp_index(pal, position) return C.pxl8_palette_ramp_index(pal, position)
end end

View file

@ -8,25 +8,72 @@ effects.GLOW_CIRCLE = 0
effects.GLOW_DIAMOND = 1 effects.GLOW_DIAMOND = 1
effects.GLOW_SHAFT = 2 effects.GLOW_SHAFT = 2
function effects.glows(glows) local Glows = {}
if not glows or #glows == 0 then return end Glows.__index = Glows
local count = #glows function Glows.new(capacity)
local glow_array = ffi.new("pxl8_glow_source[?]", count) local ptr = C.pxl8_glows_create(capacity or 1000)
if ptr == nil then
for i, g in ipairs(glows) do return nil
local idx = i - 1 end
glow_array[idx].x = g.x or 0 return setmetatable({ _ptr = ptr }, Glows)
glow_array[idx].y = g.y or 0
glow_array[idx].radius = g.radius or 8
glow_array[idx].intensity = g.intensity or 255
glow_array[idx].color = g.color or 15
glow_array[idx].depth = g.depth or 0xFFFF
glow_array[idx].height = g.height or 0
glow_array[idx].shape = g.shape or 0
end end
C.pxl8_gfx_apply_effect(core.gfx, C.PXL8_GFX_EFFECT_GLOWS, glow_array, count) 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 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
function Lights.new(capacity)
local ptr = C.pxl8_lights_create(capacity or 256)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Lights)
end
function Lights:add(x, y, z, r, g, b, intensity, radius)
C.pxl8_lights_add(self._ptr, x, y, z, r or 255, g or 255, b or 255, intensity or 255, radius or 10)
end
function Lights:clear()
C.pxl8_lights_clear(self._ptr)
end
function Lights:count()
return C.pxl8_lights_count(self._ptr)
end
function Lights:destroy()
if self._ptr then
C.pxl8_lights_destroy(self._ptr)
self._ptr = nil
end
end
effects.Lights = Lights
return effects return effects

View file

@ -75,6 +75,15 @@ function Camera3D:update(dt)
C.pxl8_3d_camera_update(self._ptr, dt) C.pxl8_3d_camera_update(self._ptr, dt)
end end
function Camera3D:world_to_screen(x, y, z, width, height)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
local result = C.pxl8_3d_camera_world_to_screen(self._ptr, pos, width, height)
if result.visible then
return {x = result.x, y = result.y, depth = result.depth}
end
return nil
end
gfx3d.Camera3D = Camera3D gfx3d.Camera3D = Camera3D
local Mesh = {} local Mesh = {}
@ -123,14 +132,15 @@ gfx3d.Mesh = Mesh
function gfx3d.draw_mesh(mesh, opts) function gfx3d.draw_mesh(mesh, opts)
opts = opts or {} opts = opts or {}
local model = ffi.new("pxl8_mat4") local model = ffi.new("pxl8_mat4")
model.m[0] = 1 local s = opts.scale or 1
model.m[5] = 1 model.m[0] = s
model.m[10] = 1 model.m[5] = s
model.m[10] = s
model.m[15] = 1 model.m[15] = 1
if opts.x then model.m[12] = opts.x end if opts.x then model.m[12] = opts.x end
if opts.y then model.m[13] = opts.y end if opts.y then model.m[13] = opts.y end
if opts.z then model.m[14] = opts.z end if opts.z then model.m[14] = opts.z end
local material = ffi.new("pxl8_material", { local material = ffi.new("pxl8_gfx_material", {
texture_id = opts.texture or 0, texture_id = opts.texture or 0,
alpha = opts.alpha or 255, alpha = opts.alpha or 255,
blend_mode = opts.blend_mode or 0, blend_mode = opts.blend_mode or 0,
@ -139,12 +149,13 @@ function gfx3d.draw_mesh(mesh, opts)
dynamic_lighting = opts.lighting or false, dynamic_lighting = opts.lighting or false,
per_pixel = opts.per_pixel or false, per_pixel = opts.per_pixel or false,
vertex_color_passthrough = opts.passthrough or false, vertex_color_passthrough = opts.passthrough or false,
wireframe = opts.wireframe or false,
emissive_intensity = opts.emissive or 0.0, emissive_intensity = opts.emissive or 0.0,
}) })
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
end end
function gfx3d.begin_frame(camera, uniforms) function gfx3d.begin_frame(camera, lights, uniforms)
uniforms = uniforms or {} uniforms = uniforms or {}
local u = ffi.new("pxl8_3d_uniforms") local u = ffi.new("pxl8_3d_uniforms")
@ -164,27 +175,8 @@ function gfx3d.begin_frame(camera, uniforms)
end end
u.celestial_intensity = uniforms.celestial_intensity or 0.0 u.celestial_intensity = uniforms.celestial_intensity or 0.0
u.num_lights = 0 local lights_ptr = lights and lights._ptr or nil
if uniforms.lights then C.pxl8_3d_begin_frame(core.gfx, camera._ptr, lights_ptr, u)
for i, light in ipairs(uniforms.lights) do
if i > 16 then break end
local idx = i - 1
u.lights[idx].position.x = light.x or 0
u.lights[idx].position.y = light.y or 0
u.lights[idx].position.z = light.z or 0
u.lights[idx].r = light.r or 255
u.lights[idx].g = light.g or 255
u.lights[idx].b = light.b or 255
u.lights[idx].intensity = light.intensity or 255
u.lights[idx].radius = light.radius or 100
local radius_sq = u.lights[idx].radius * u.lights[idx].radius
u.lights[idx].radius_sq = radius_sq
u.lights[idx].inv_radius_sq = radius_sq > 0 and (1.0 / radius_sq) or 0
u.num_lights = i
end
end
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u)
end end
function gfx3d.clear(color) function gfx3d.clear(color)
@ -205,4 +197,12 @@ function gfx3d.end_frame()
C.pxl8_3d_end_frame(core.gfx) C.pxl8_3d_end_frame(core.gfx)
end end
function gfx3d.project_points(input, output, count, transform)
C.pxl8_3d_project_points(core.gfx, input, output, count, transform)
end
function gfx3d.create_vec3_array(count)
return ffi.new("pxl8_vec3[?]", count)
end
return gfx3d return gfx3d

View file

@ -16,7 +16,7 @@ function Gui.new()
end end
function Gui:begin_frame() function Gui:begin_frame()
C.pxl8_gui_begin_frame(self._ptr) C.pxl8_gui_begin_frame(self._ptr, core.gfx)
end end
function Gui:button(id, x, y, w, h, label) function Gui:button(id, x, y, w, h, label)
@ -43,7 +43,7 @@ function Gui:destroy()
end end
function Gui:end_frame() function Gui:end_frame()
C.pxl8_gui_end_frame(self._ptr) C.pxl8_gui_end_frame(self._ptr, core.gfx)
end end
function Gui:get_cursor_pos() function Gui:get_cursor_pos()

View file

@ -1,53 +1,65 @@
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C local C = ffi.C
local math3d = {} local math = {}
function math3d.mat4_identity() function math.hash32(x)
return C.pxl8_hash32(x)
end
function math.mat4_identity()
return C.pxl8_mat4_identity() return C.pxl8_mat4_identity()
end end
function math3d.mat4_mul(a, b) function math.mat4_multiply(a, b)
return C.pxl8_mat4_mul(a, b) return C.pxl8_mat4_multiply(a, b)
end end
function math3d.mat4_translate(x, y, z) function math.mat4_multiply_vec3(m, v)
return C.pxl8_mat4_multiply_vec3(m, v)
end
function math.mat4_multiply_vec4(m, v)
return C.pxl8_mat4_multiply_vec4(m, v)
end
function math.mat4_translate(x, y, z)
return C.pxl8_mat4_translate(x, y, z) return C.pxl8_mat4_translate(x, y, z)
end end
function math3d.mat4_rotate_x(angle) function math.mat4_rotate_x(angle)
return C.pxl8_mat4_rotate_x(angle) return C.pxl8_mat4_rotate_x(angle)
end end
function math3d.mat4_rotate_y(angle) function math.mat4_rotate_y(angle)
return C.pxl8_mat4_rotate_y(angle) return C.pxl8_mat4_rotate_y(angle)
end end
function math3d.mat4_rotate_z(angle) function math.mat4_rotate_z(angle)
return C.pxl8_mat4_rotate_z(angle) return C.pxl8_mat4_rotate_z(angle)
end end
function math3d.mat4_scale(x, y, z) function math.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z) return C.pxl8_mat4_scale(x, y, z)
end end
function math3d.mat4_ortho(left, right, bottom, top, near, far) function math.mat4_orthographic(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far) return C.pxl8_mat4_orthographic(left, right, bottom, top, near, far)
end end
function math3d.mat4_perspective(fov, aspect, near, far) function math.mat4_perspective(fov, aspect, near, far)
return C.pxl8_mat4_perspective(fov, aspect, near, far) return C.pxl8_mat4_perspective(fov, aspect, near, far)
end end
function math3d.mat4_lookat(eye, center, up) function math.mat4_lookat(eye, center, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]}) local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]}) local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]}) local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec) return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
end end
function math3d.bounds(x, y, w, h) function math.bounds(x, y, w, h)
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h}) return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
end end
return math3d return math

View file

@ -12,7 +12,7 @@ function Particles.new(max_count)
if ps == nil then if ps == nil then
return nil return nil
end end
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal ~= nil then if pal ~= nil then
C.pxl8_particles_set_palette(ps, pal) C.pxl8_particles_set_palette(ps, pal)
end end

99
src/lua/pxl8/procgen.lua Normal file
View file

@ -0,0 +1,99 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local procgen = {}
procgen.OP_CONST = C.PXL8_OP_CONST
procgen.OP_INPUT_AGE = C.PXL8_OP_INPUT_AGE
procgen.OP_INPUT_SEED = C.PXL8_OP_INPUT_SEED
procgen.OP_INPUT_TIME = C.PXL8_OP_INPUT_TIME
procgen.OP_INPUT_X = C.PXL8_OP_INPUT_X
procgen.OP_INPUT_Y = C.PXL8_OP_INPUT_Y
procgen.OP_ABS = C.PXL8_OP_ABS
procgen.OP_ADD = C.PXL8_OP_ADD
procgen.OP_CEIL = C.PXL8_OP_CEIL
procgen.OP_CLAMP = C.PXL8_OP_CLAMP
procgen.OP_COS = C.PXL8_OP_COS
procgen.OP_DIV = C.PXL8_OP_DIV
procgen.OP_FLOOR = C.PXL8_OP_FLOOR
procgen.OP_FRACT = C.PXL8_OP_FRACT
procgen.OP_GRADIENT_LINEAR = C.PXL8_OP_GRADIENT_LINEAR
procgen.OP_GRADIENT_RADIAL = C.PXL8_OP_GRADIENT_RADIAL
procgen.OP_LERP = C.PXL8_OP_LERP
procgen.OP_MAX = C.PXL8_OP_MAX
procgen.OP_MIN = C.PXL8_OP_MIN
procgen.OP_MOD = C.PXL8_OP_MOD
procgen.OP_MUL = C.PXL8_OP_MUL
procgen.OP_NEGATE = C.PXL8_OP_NEGATE
procgen.OP_NOISE_FBM = C.PXL8_OP_NOISE_FBM
procgen.OP_NOISE_PERLIN = C.PXL8_OP_NOISE_PERLIN
procgen.OP_NOISE_RIDGED = C.PXL8_OP_NOISE_RIDGED
procgen.OP_NOISE_TURBULENCE = C.PXL8_OP_NOISE_TURBULENCE
procgen.OP_NOISE_VALUE = C.PXL8_OP_NOISE_VALUE
procgen.OP_POW = C.PXL8_OP_POW
procgen.OP_QUANTIZE = C.PXL8_OP_QUANTIZE
procgen.OP_SELECT = C.PXL8_OP_SELECT
procgen.OP_SIN = C.PXL8_OP_SIN
procgen.OP_SMOOTHSTEP = C.PXL8_OP_SMOOTHSTEP
procgen.OP_SQRT = C.PXL8_OP_SQRT
procgen.OP_SUB = C.PXL8_OP_SUB
procgen.OP_VORONOI_CELL = C.PXL8_OP_VORONOI_CELL
procgen.OP_VORONOI_EDGE = C.PXL8_OP_VORONOI_EDGE
procgen.OP_VORONOI_ID = C.PXL8_OP_VORONOI_ID
local Graph = {}
Graph.__index = Graph
function Graph.new(capacity)
capacity = capacity or 64
local ptr = C.pxl8_graph_create(capacity)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Graph)
end
function Graph:destroy()
if self._ptr then
C.pxl8_graph_destroy(self._ptr)
self._ptr = nil
end
end
function Graph:clear()
C.pxl8_graph_clear(self._ptr)
end
function Graph:add_node(op, in0, in1, in2, in3, param)
return C.pxl8_graph_add_node(self._ptr, op, in0 or 0, in1 or 0, in2 or 0, in3 or 0, param or 0)
end
function Graph:set_output(reg)
C.pxl8_graph_set_output(self._ptr, reg)
end
function Graph:set_seed(seed)
C.pxl8_graph_set_seed(self._ptr, seed)
end
function Graph:eval_texture(width, height)
width = width or 64
height = height or 64
local buffer = ffi.new("u8[?]", width * height)
C.pxl8_graph_eval_texture(self._ptr, buffer, width, height)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
procgen.Graph = Graph
function procgen.create_graph(capacity)
return Graph.new(capacity)
end
return procgen

View file

@ -85,36 +85,14 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
return result.x, result.y, result.z return result.x, result.y, result.z
end end
function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled)
end
function World:unload() function World:unload()
C.pxl8_world_unload(self._ptr) C.pxl8_world_unload(self._ptr)
end end
world.World = World world.World = World
function world.procgen_tex(params)
local width = params.width or 64
local height = params.height or 64
local buffer = ffi.new("u8[?]", width * height)
local tex_params = ffi.new("pxl8_procgen_tex_params")
local name = params.name or ""
ffi.copy(tex_params.name, name, math.min(#name, 15))
tex_params.seed = params.seed or 0
tex_params.width = width
tex_params.height = height
tex_params.scale = params.scale or 1.0
tex_params.roughness = params.roughness or 0.0
tex_params.base_color = params.base_color or 0
tex_params.variation = params.variation or 0
C.pxl8_procgen_tex(buffer, tex_params)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
return world return world

View file

@ -1,5 +1,14 @@
#include "pxl8_math.h" #include "pxl8_math.h"
u32 pxl8_hash32(u32 x) {
x ^= x >> 16;
x *= 0x85EBCA6Bu;
x ^= x >> 13;
x *= 0xC2B2AE35u;
x ^= x >> 16;
return x;
}
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) { pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2){ return (pxl8_vec2){
.x = a.x + b.x, .x = a.x + b.x,
@ -101,7 +110,7 @@ pxl8_mat4 pxl8_mat4_identity(void) {
return mat; return mat;
} }
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) { pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_mat4 mat = {0}; pxl8_mat4 mat = {0};
for (i32 col = 0; col < 4; col++) { for (i32 col = 0; col < 4; col++) {
@ -117,7 +126,7 @@ pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) {
return mat; return mat;
} }
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) { pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) {
return (pxl8_vec4){ return (pxl8_vec4){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w, .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w, .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
@ -126,7 +135,7 @@ pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) {
}; };
} }
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v) { pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v) {
return (pxl8_vec3){ return (pxl8_vec3){
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z, .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z,
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z, .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z,
@ -193,7 +202,7 @@ pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) {
return mat; return mat;
} }
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) { pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
pxl8_mat4 mat = {0}; pxl8_mat4 mat = {0};
mat.m[0] = 2.0f / (right - left); mat.m[0] = 2.0f / (right - left);
@ -246,15 +255,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
pxl8_frustum frustum; pxl8_frustum frustum;
const f32* m = vp.m; const f32* m = vp.m;
frustum.planes[0].normal.x = m[3] - m[0]; frustum.planes[0].normal.x = m[3] + m[0];
frustum.planes[0].normal.y = m[7] - m[4]; frustum.planes[0].normal.y = m[7] + m[4];
frustum.planes[0].normal.z = m[11] - m[8]; frustum.planes[0].normal.z = m[11] + m[8];
frustum.planes[0].distance = m[15] - m[12]; frustum.planes[0].distance = m[15] + m[12];
frustum.planes[1].normal.x = m[3] + m[0]; frustum.planes[1].normal.x = m[3] - m[0];
frustum.planes[1].normal.y = m[7] + m[4]; frustum.planes[1].normal.y = m[7] - m[4];
frustum.planes[1].normal.z = m[11] + m[8]; frustum.planes[1].normal.z = m[11] - m[8];
frustum.planes[1].distance = m[15] + m[12]; frustum.planes[1].distance = m[15] - m[12];
frustum.planes[2].normal.x = m[3] + m[1]; frustum.planes[2].normal.x = m[3] + m[1];
frustum.planes[2].normal.y = m[7] + m[5]; frustum.planes[2].normal.y = m[7] + m[5];
@ -266,15 +275,15 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
frustum.planes[3].normal.z = m[11] - m[9]; frustum.planes[3].normal.z = m[11] - m[9];
frustum.planes[3].distance = m[15] - m[13]; frustum.planes[3].distance = m[15] - m[13];
frustum.planes[4].normal.x = m[3] - m[2]; frustum.planes[4].normal.x = m[3] + m[2];
frustum.planes[4].normal.y = m[7] - m[6]; frustum.planes[4].normal.y = m[7] + m[6];
frustum.planes[4].normal.z = m[11] - m[10]; frustum.planes[4].normal.z = m[11] + m[10];
frustum.planes[4].distance = m[15] - m[14]; frustum.planes[4].distance = m[15] + m[14];
frustum.planes[5].normal.x = m[3] + m[2]; frustum.planes[5].normal.x = m[3] - m[2];
frustum.planes[5].normal.y = m[7] + m[6]; frustum.planes[5].normal.y = m[7] - m[6];
frustum.planes[5].normal.z = m[11] + m[10]; frustum.planes[5].normal.z = m[11] - m[10];
frustum.planes[5].distance = m[15] + m[14]; frustum.planes[5].distance = m[15] - m[14];
for (i32 i = 0; i < 6; i++) { for (i32 i = 0; i < 6; i++) {
f32 len = pxl8_vec3_length(frustum.planes[i].normal); f32 len = pxl8_vec3_length(frustum.planes[i].normal);
@ -289,19 +298,21 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
} }
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) { bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) {
const f32 FRUSTUM_EPSILON = -75.0f;
for (i32 i = 0; i < 6; i++) { for (i32 i = 0; i < 6; i++) {
pxl8_vec3 normal = frustum->planes[i].normal; pxl8_vec3 normal = frustum->planes[i].normal;
f32 d = frustum->planes[i].distance; f32 d = frustum->planes[i].distance;
pxl8_vec3 p_vertex = { pxl8_vec3 p_vertex = {
(normal.x >= 0.0f) ? max.x : min.x, (normal.x > 0.0f) ? max.x : min.x,
(normal.y >= 0.0f) ? max.y : min.y, (normal.y > 0.0f) ? max.y : min.y,
(normal.z >= 0.0f) ? max.z : min.z (normal.z > 0.0f) ? max.z : min.z
}; };
f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d; f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d;
if (p_dist < 0.0f) { if (p_dist < FRUSTUM_EPSILON) {
return false; return false;
} }
} }

View file

@ -4,16 +4,49 @@
#include "pxl8_types.h" #include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#include <xmmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#include <arm_neon.h>
#endif
#define PXL8_PI 3.14159265358979323846f #define PXL8_PI 3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f) #define PXL8_TAU (PXL8_PI * 2.0f)
static inline f32 pxl8_fast_inv_sqrt(f32 x) { static inline f32 pxl8_fast_inv_sqrt(f32 x) {
#if 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; f32 half = 0.5f * x;
i32 i = *(i32*)&x; i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1); i = 0x5f3759df - (i >> 1);
x = *(f32*)&i; x = *(f32*)&i;
x = x * (1.5f - half * x * x); x = x * (1.5f - half * x * x);
return x; return x;
#endif
}
static inline f32 pxl8_fast_rcp(f32 x) {
#if 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
} }
typedef struct pxl8_vec2 { typedef struct pxl8_vec2 {
@ -33,18 +66,27 @@ typedef struct pxl8_mat4 {
} pxl8_mat4; } pxl8_mat4;
typedef struct pxl8_plane { typedef struct pxl8_plane {
pxl8_vec3 normal;
f32 distance; f32 distance;
pxl8_vec3 normal;
} pxl8_plane; } pxl8_plane;
typedef struct pxl8_frustum { typedef struct pxl8_frustum {
pxl8_plane planes[6]; pxl8_plane planes[6];
} pxl8_frustum; } pxl8_frustum;
typedef struct pxl8_projected_point {
f32 depth;
i32 x;
i32 y;
bool visible;
} pxl8_projected_point;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_hash32(u32 x);
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b); pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b); f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_length(pxl8_vec2 v); f32 pxl8_vec2_length(pxl8_vec2 v);
@ -63,10 +105,10 @@ pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);
pxl8_mat4 pxl8_mat4_identity(void); pxl8_mat4 pxl8_mat4_identity(void);
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up); pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b); pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v); pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v); pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far); pxl8_mat4 pxl8_mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far); pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle); pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle); pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);

View file

@ -1,291 +0,0 @@
#pragma once
#include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#define PXL8_SIMD_SSE2 1
#include <emmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#define PXL8_SIMD_NEON 1
#include <arm_neon.h>
#else
#define PXL8_SIMD_SCALAR 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(PXL8_SIMD_SSE2)
typedef struct { __m128 v; } pxl8_f32x4;
typedef struct { __m128i v; } pxl8_i32x4;
typedef struct { __m128i v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ _mm_set1_ps(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
return _mm_movemask_ps(a.v);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
_mm_storeu_ps(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ _mm_set1_epi32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
_mm_storeu_si128((__m128i*)out, a.v);
}
static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) {
return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) };
}
static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) {
__m128i not_mask = _mm_andnot_si128(mask.v, a.v);
__m128i and_mask = _mm_and_si128(mask.v, b.v);
return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) };
}
static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) {
return _mm_movemask_epi8(a.v);
}
#elif defined(PXL8_SIMD_NEON)
typedef struct { float32x4_t v; } pxl8_f32x4;
typedef struct { int32x4_t v; } pxl8_i32x4;
typedef struct { uint16x8_t v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ vdupq_n_f32(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
f32 arr[4] = {a, b, c, d};
return (pxl8_f32x4){ vld1q_f32(arr) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vaddq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vsubq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmulq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vdivq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vminq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
uint32x4_t cmp = vcltq_f32(a.v, b.v);
return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
uint32x4_t input = vreinterpretq_u32_f32(a.v);
static const i32 shifts[4] = {0, 1, 2, 3};
uint32x4_t shifted = vshrq_n_u32(input, 31);
return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) |
(vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ vcvtq_s32_f32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
vst1q_f32(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ vdupq_n_s32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vandq_s32(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vorrq_s32(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
vst1q_s32(out, a.v);
}
#else
typedef struct { f32 v[4]; } pxl8_f32x4;
typedef struct { i32 v[4]; } pxl8_i32x4;
typedef struct { u16 v[8]; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){{ x, x, x, x }};
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){{ a, b, c, d }};
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]<b.v[0]?a.v[0]:b.v[0], a.v[1]<b.v[1]?a.v[1]:b.v[1],
a.v[2]<b.v[2]?a.v[2]:b.v[2], a.v[3]<b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]>b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1],
a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
pxl8_f32x4 r;
u32* rv = (u32*)r.v;
rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0;
rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0;
rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0;
rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0;
return r;
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
u32* av = (u32*)a.v;
return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 |
((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3;
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }};
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){{ x, x, x, x }};
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]<<n, a.v[1]<<n, a.v[2]<<n, a.v[3]<<n }};
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]>>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }};
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }};
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }};
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
#endif
#ifdef __cplusplus
}
#endif

View file

@ -1,4 +1,5 @@
#include "pxl8_net.h" #include "pxl8_net.h"
#include "pxl8_mem.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -94,7 +95,7 @@ bool pxl8_net_connected(const pxl8_net* net) {
} }
pxl8_net* pxl8_net_create(const pxl8_net_config* config) { pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
pxl8_net* net = calloc(1, sizeof(pxl8_net)); pxl8_net* net = pxl8_calloc(1, sizeof(pxl8_net));
if (!net) return NULL; if (!net) return NULL;
net->mode = config->mode; net->mode = config->mode;
@ -116,7 +117,7 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
void pxl8_net_destroy(pxl8_net* net) { void pxl8_net_destroy(pxl8_net* net) {
if (!net) return; if (!net) return;
pxl8_net_disconnect(net); pxl8_net_disconnect(net);
free(net); pxl8_free(net);
} }
void pxl8_net_disconnect(pxl8_net* net) { void pxl8_net_disconnect(pxl8_net* net) {
@ -203,11 +204,11 @@ u64 pxl8_net_player_id(const pxl8_net* net) {
bool pxl8_net_poll(pxl8_net* net) { bool pxl8_net_poll(pxl8_net* net) {
if (!net || !net->connected) return false; if (!net || !net->connected) return false;
size_t len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf)); usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
if (len < sizeof(pxl8_msg_header)) return false; if (len < sizeof(pxl8_msg_header)) return false;
pxl8_msg_header hdr; pxl8_msg_header hdr;
size_t offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr); usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
if (hdr.type != PXL8_MSG_SNAPSHOT) return false; if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap; pxl8_snapshot_header snap;
@ -243,17 +244,17 @@ void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick) {
net->predicted_tick = tick; net->predicted_tick = tick;
} }
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len) { usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len) {
if (!net || !net->connected) return 0; if (!net || !net->connected) return 0;
struct sockaddr_in from; struct sockaddr_in from;
socklen_t from_len = sizeof(from); socklen_t from_len = sizeof(from);
ssize_t received = recvfrom(net->sock, (char*)buf, len, 0, ssize_t received = recvfrom(net->sock, (char*)buf, len, 0,
(struct sockaddr*)&from, &from_len); (struct sockaddr*)&from, &from_len);
return (received > 0) ? (size_t)received : 0; return (received > 0) ? (usize)received : 0;
} }
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len) { pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len) {
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
ssize_t sent = sendto(net->sock, (const char*)data, len, 0, ssize_t sent = sendto(net->sock, (const char*)data, len, 0,
@ -273,7 +274,7 @@ pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) {
.sequence = 0 .sequence = 0
}; };
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf)); usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset); offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset); return pxl8_net_send(net, buf, offset);
@ -290,7 +291,7 @@ pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) {
.sequence = 0 .sequence = 0
}; };
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf)); usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset); offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset); return pxl8_net_send(net, buf, offset);

View file

@ -41,8 +41,8 @@ u64 pxl8_net_player_id(const pxl8_net* net);
bool pxl8_net_poll(pxl8_net* net); bool pxl8_net_poll(pxl8_net* net);
u8* pxl8_net_predicted_state(pxl8_net* net); u8* pxl8_net_predicted_state(pxl8_net* net);
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick); void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len); usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len);
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len); pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len);
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd); pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input); pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net); const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);

View file

@ -1,7 +1,7 @@
#include "pxl8_protocol.h" #include "pxl8_protocol.h"
#include "pxl8_bytes.h" #include "pxl8_bytes.h"
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len) { usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_msg_header)) return 0; if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->sequence); pxl8_write_u32_be(&s, msg->sequence);
@ -11,7 +11,7 @@ size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg) { usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg) {
if (len < sizeof(pxl8_msg_header)) return 0; if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->sequence = pxl8_read_u32_be(&s); msg->sequence = pxl8_read_u32_be(&s);
@ -21,7 +21,7 @@ size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_head
return s.offset; return s.offset;
} }
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len) { usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_input_msg)) return 0; if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->buttons); pxl8_write_u32_be(&s, msg->buttons);
@ -35,7 +35,7 @@ size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg) { usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg) {
if (len < sizeof(pxl8_input_msg)) return 0; if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->buttons = pxl8_read_u32_be(&s); msg->buttons = pxl8_read_u32_be(&s);
@ -49,7 +49,7 @@ size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg
return s.offset; return s.offset;
} }
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len) { usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_command_msg)) return 0; if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, msg->cmd_type); pxl8_write_u16_be(&s, msg->cmd_type);
@ -59,7 +59,7 @@ size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, siz
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg) { usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg) {
if (len < sizeof(pxl8_command_msg)) return 0; if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->cmd_type = pxl8_read_u16_be(&s); msg->cmd_type = pxl8_read_u16_be(&s);
@ -69,7 +69,7 @@ size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command
return s.offset; return s.offset;
} }
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len) { usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len) {
if (len < sizeof(pxl8_entity_state)) return 0; if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u64_be(&s, state->entity_id); pxl8_write_u64_be(&s, state->entity_id);
@ -77,7 +77,7 @@ size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8*
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state) { usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state) {
if (len < sizeof(pxl8_entity_state)) return 0; if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
state->entity_id = pxl8_read_u64_be(&s); state->entity_id = pxl8_read_u64_be(&s);
@ -85,7 +85,7 @@ size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_en
return s.offset; return s.offset;
} }
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len) { usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_event_msg)) return 0; if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u8(&s, msg->event_type); pxl8_write_u8(&s, msg->event_type);
@ -93,7 +93,7 @@ size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg) { usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg) {
if (len < sizeof(pxl8_event_msg)) return 0; if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->event_type = pxl8_read_u8(&s); msg->event_type = pxl8_read_u8(&s);
@ -101,7 +101,7 @@ size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg
return s.offset; return s.offset;
} }
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len) { usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len) {
if (len < sizeof(pxl8_snapshot_header)) return 0; if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, hdr->entity_count); pxl8_write_u16_be(&s, hdr->entity_count);
@ -112,7 +112,7 @@ size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr,
return s.offset; return s.offset;
} }
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr) { usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr) {
if (len < sizeof(pxl8_snapshot_header)) return 0; if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len); pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->entity_count = pxl8_read_u16_be(&s); hdr->entity_count = pxl8_read_u16_be(&s);

View file

@ -70,23 +70,23 @@ typedef struct pxl8_snapshot_header {
f32 time; f32 time;
} pxl8_snapshot_header; } pxl8_snapshot_header;
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len); usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg); usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg);
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len); usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len);
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg); usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg);
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len); usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len);
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg); usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg);
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len); usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len);
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state); usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state);
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len); usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len);
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg); usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg);
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len); usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len);
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr); usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr);
#ifdef __cplusplus #ifdef __cplusplus
} }

332
src/procgen/pxl8_graph.c Normal file
View file

@ -0,0 +1,332 @@
#include "pxl8_graph.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_math.h"
#include "pxl8_mem.h"
static inline u32 hash2d(i32 x, i32 y, u32 seed) {
return pxl8_hash32((u32)x ^ ((u32)y * 2654435769u) ^ seed);
}
static inline f32 hash2d_f(i32 x, i32 y, u32 seed) {
return (f32)hash2d(x, y, seed) / (f32)0xFFFFFFFF;
}
static f32 gradient2d(i32 ix, i32 iy, f32 fx, f32 fy, u32 seed) {
u32 h = hash2d(ix, iy, seed);
f32 angle = (f32)h / (f32)0xFFFFFFFF * 6.28318530718f;
f32 gx = cosf(angle);
f32 gy = sinf(angle);
return gx * fx + gy * fy;
}
static inline f32 smoothstep(f32 t) {
return t * t * (3.0f - 2.0f * t);
}
static inline f32 lerp(f32 a, f32 b, f32 t) {
return a + t * (b - a);
}
static f32 noise_value(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = hash2d_f(ix, iy, seed);
f32 n10 = hash2d_f(ix + 1, iy, seed);
f32 n01 = hash2d_f(ix, iy + 1, seed);
f32 n11 = hash2d_f(ix + 1, iy + 1, seed);
return lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
}
static f32 noise_perlin(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = gradient2d(ix, iy, fx, fy, seed);
f32 n10 = gradient2d(ix + 1, iy, fx - 1.0f, fy, seed);
f32 n01 = gradient2d(ix, iy + 1, fx, fy - 1.0f, seed);
f32 n11 = gradient2d(ix + 1, iy + 1, fx - 1.0f, fy - 1.0f, seed);
f32 result = lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
return result * 0.5f + 0.5f;
}
static f32 noise_fbm(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
value += amplitude * noise_perlin(x, y, frequency, seed + (u32)i * 1337);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_ridged(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
n = 1.0f - fabsf(n * 2.0f - 1.0f);
value += amplitude * n;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_turbulence(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
value += amplitude * fabsf(n * 2.0f - 1.0f);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static void voronoi(f32 x, f32 y, f32 scale, u32 seed, f32* cell_dist, f32* edge_dist, i32* cell_id) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 cx = (i32)floorf(sx);
i32 cy = (i32)floorf(sy);
f32 fx = sx - (f32)cx;
f32 fy = sy - (f32)cy;
f32 min_dist = 1e30f;
f32 second_dist = 1e30f;
i32 closest_id = 0;
for (i32 dy = -1; dy <= 1; dy++) {
for (i32 dx = -1; dx <= 1; dx++) {
i32 nx = cx + dx;
i32 ny = cy + dy;
u32 h = hash2d(nx, ny, seed);
f32 px = (f32)dx + (f32)(h & 0xFF) / 255.0f - 0.5f - fx;
f32 py = (f32)dy + (f32)((h >> 8) & 0xFF) / 255.0f - 0.5f - fy;
f32 dist = px * px + py * py;
if (dist < min_dist) {
second_dist = min_dist;
min_dist = dist;
closest_id = (i32)h;
} else if (dist < second_dist) {
second_dist = dist;
}
}
}
*cell_dist = sqrtf(min_dist);
*edge_dist = sqrtf(second_dist) - sqrtf(min_dist);
*cell_id = closest_id;
}
static f32 gradient_linear(f32 x, f32 y, f32 angle) {
f32 dx = cosf(angle);
f32 dy = sinf(angle);
f32 result = x * dx + y * dy;
return fmaxf(0.0f, fminf(1.0f, result));
}
static f32 gradient_radial(f32 x, f32 y, f32 cx, f32 cy) {
f32 dx = x - cx;
f32 dy = y - cy;
return fmaxf(0.0f, fminf(1.0f, sqrtf(dx * dx + dy * dy)));
}
pxl8_graph* pxl8_graph_create(u32 capacity) {
pxl8_graph* graph = pxl8_calloc(1, sizeof(pxl8_graph));
if (!graph) return NULL;
graph->nodes = pxl8_calloc(capacity, sizeof(pxl8_node));
if (!graph->nodes) {
pxl8_free(graph);
return NULL;
}
graph->capacity = capacity;
graph->count = 0;
graph->seed = 0;
graph->output_reg = 0;
return graph;
}
void pxl8_graph_destroy(pxl8_graph* graph) {
if (!graph) return;
pxl8_free(graph->nodes);
pxl8_free(graph);
}
void pxl8_graph_clear(pxl8_graph* graph) {
if (!graph) return;
graph->count = 0;
graph->output_reg = 0;
}
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param) {
if (!graph || graph->count >= graph->capacity) return 0;
u8 out = (u8)(graph->count + 4);
pxl8_node* node = &graph->nodes[graph->count++];
node->op = (u8)op;
node->out = out;
node->in[0] = in0;
node->in[1] = in1;
node->in[2] = in2;
node->in[3] = in3;
node->param = param;
return out;
}
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg) {
if (graph) graph->output_reg = reg;
}
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed) {
if (graph) graph->seed = seed;
}
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx) {
if (!graph || !ctx) return 0.0f;
for (u32 i = 0; i < graph->count; i++) {
const pxl8_node* n = &graph->nodes[i];
f32 a = ctx->regs[n->in[0]];
f32 b = ctx->regs[n->in[1]];
f32 c = ctx->regs[n->in[2]];
f32 d = ctx->regs[n->in[3]];
f32 result = 0.0f;
switch (n->op) {
case PXL8_OP_CONST: result = n->param; break;
case PXL8_OP_INPUT_AGE: result = ctx->regs[3]; break;
case PXL8_OP_INPUT_SEED: result = (f32)ctx->seed; break;
case PXL8_OP_INPUT_TIME: result = ctx->regs[2]; break;
case PXL8_OP_INPUT_X: result = ctx->regs[0]; break;
case PXL8_OP_INPUT_Y: result = ctx->regs[1]; break;
case PXL8_OP_ABS: result = fabsf(a); break;
case PXL8_OP_CEIL: result = ceilf(a); break;
case PXL8_OP_COS: result = cosf(a); break;
case PXL8_OP_FLOOR: result = floorf(a); break;
case PXL8_OP_FRACT: result = a - floorf(a); break;
case PXL8_OP_NEGATE: result = -a; break;
case PXL8_OP_SIN: result = sinf(a); break;
case PXL8_OP_SQRT: result = sqrtf(a); break;
case PXL8_OP_ADD: result = a + b; break;
case PXL8_OP_DIV: result = b != 0.0f ? a / b : 0.0f; break;
case PXL8_OP_MAX: result = fmaxf(a, b); break;
case PXL8_OP_MIN: result = fminf(a, b); break;
case PXL8_OP_MOD: result = b != 0.0f ? fmodf(a, b) : 0.0f; break;
case PXL8_OP_MUL: result = a * b; break;
case PXL8_OP_POW: result = powf(a, b); break;
case PXL8_OP_SUB: result = a - b; break;
case PXL8_OP_CLAMP: result = fmaxf(b, fminf(c, a)); break;
case PXL8_OP_LERP: result = a + c * (b - a); break;
case PXL8_OP_SELECT: result = c > 0.0f ? a : b; break;
case PXL8_OP_SMOOTHSTEP: {
f32 t = fmaxf(0.0f, fminf(1.0f, (c - a) / (b - a)));
result = t * t * (3.0f - 2.0f * t);
break;
}
case PXL8_OP_NOISE_FBM: result = noise_fbm(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_PERLIN: result = noise_perlin(a, b, c, ctx->seed); break;
case PXL8_OP_NOISE_RIDGED: result = noise_ridged(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_TURBULENCE:result = noise_turbulence(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_VALUE: result = noise_value(a, b, c, ctx->seed); break;
case PXL8_OP_VORONOI_CELL: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = cell;
break;
}
case PXL8_OP_VORONOI_EDGE: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = edge;
break;
}
case PXL8_OP_VORONOI_ID: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = (f32)(id & 0xFF) / 255.0f;
break;
}
case PXL8_OP_GRADIENT_LINEAR: result = gradient_linear(a, b, c); break;
case PXL8_OP_GRADIENT_RADIAL: result = gradient_radial(a, b, c, d); break;
case PXL8_OP_QUANTIZE: {
u8 base = (u8)n->param;
f32 range = b;
f32 clamped = fmaxf(0.0f, fminf(1.0f, a));
result = (f32)(base + (u8)fminf(clamped * range, range - 1.0f));
break;
}
default: break;
}
ctx->regs[n->out] = result;
}
return ctx->regs[graph->output_reg];
}
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height) {
if (!graph || !buffer) return;
pxl8_graph_context ctx = {0};
ctx.seed = graph->seed;
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
ctx.regs[0] = (f32)x / (f32)width;
ctx.regs[1] = (f32)y / (f32)height;
ctx.regs[2] = 0.0f;
ctx.regs[3] = 0.0f;
f32 result = pxl8_graph_eval(graph, &ctx);
buffer[y * width + x] = (u8)fmaxf(0.0f, fminf(255.0f, result));
}
}
}

90
src/procgen/pxl8_graph.h Normal file
View file

@ -0,0 +1,90 @@
#pragma once
#include "pxl8_types.h"
typedef enum pxl8_graph_op {
PXL8_OP_CONST, // param -> out
PXL8_OP_INPUT_AGE, // particle normalized age -> out
PXL8_OP_INPUT_SEED, // seed -> out
PXL8_OP_INPUT_TIME, // time -> out
PXL8_OP_INPUT_X, // x coord -> out
PXL8_OP_INPUT_Y, // y coord -> out
PXL8_OP_ABS, // |a| -> out
PXL8_OP_CEIL, // ceil(a) -> out
PXL8_OP_COS, // cos(a) -> out
PXL8_OP_FLOOR, // floor(a) -> out
PXL8_OP_FRACT, // fract(a) -> out
PXL8_OP_NEGATE, // -a -> out
PXL8_OP_SIN, // sin(a) -> out
PXL8_OP_SQRT, // sqrt(a) -> out
PXL8_OP_ADD, // a + b -> out
PXL8_OP_DIV, // a / b -> out
PXL8_OP_MAX, // max(a, b) -> out
PXL8_OP_MIN, // min(a, b) -> out
PXL8_OP_MOD, // fmod(a, b) -> out
PXL8_OP_MUL, // a * b -> out
PXL8_OP_POW, // pow(a, b) -> out
PXL8_OP_SUB, // a - b -> out
PXL8_OP_CLAMP, // clamp(a, min, max) -> out
PXL8_OP_LERP, // lerp(a, b, t) -> out
PXL8_OP_SELECT, // t > 0 ? a : b -> out
PXL8_OP_SMOOTHSTEP, // smoothstep(edge0, edge1, x) -> out
PXL8_OP_NOISE_FBM, // fbm(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_PERLIN, // perlin noise(x, y, scale) -> out
PXL8_OP_NOISE_RIDGED, // ridged(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_TURBULENCE,// turbulence(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_VALUE, // value noise(x, y, scale) -> out
PXL8_OP_VORONOI_CELL, // voronoi cell distance(x, y, scale) -> out
PXL8_OP_VORONOI_EDGE, // voronoi edge distance(x, y, scale) -> out
PXL8_OP_VORONOI_ID, // voronoi cell id(x, y, scale) -> out
PXL8_OP_GRADIENT_LINEAR, // linear gradient(x, y, angle) -> out
PXL8_OP_GRADIENT_RADIAL, // radial gradient(x, y, cx, cy) -> out
PXL8_OP_QUANTIZE, // quantize to palette: base + floor(a * range) -> out
PXL8_OP_COUNT
} pxl8_graph_op;
typedef struct pxl8_node {
u8 in[4];
u8 op;
u8 out;
f32 param;
} pxl8_node;
typedef struct pxl8_graph {
u32 capacity;
u32 count;
pxl8_node* nodes;
u8 output_reg;
u32 seed;
} pxl8_graph;
typedef struct pxl8_graph_context {
f32 regs[256];
u32 seed;
} pxl8_graph_context;
#ifdef __cplusplus
extern "C" {
#endif
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx);
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);
void pxl8_graph_clear(pxl8_graph* graph);
pxl8_graph* pxl8_graph_create(u32 capacity);
void pxl8_graph_destroy(pxl8_graph* graph);
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);
#ifdef __cplusplus
}
#endif

View file

@ -1,14 +1,14 @@
#include "pxl8_repl.h" #include "pxl8_repl.h"
#include "pxl8_mem.h"
#include <poll.h> #include <poll.h>
#include <pthread.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <SDL3/SDL.h>
#include <linenoise.h> #include <linenoise.h>
#define PXL8_MAX_REPL_COMMAND_SIZE 4096 #define PXL8_MAX_REPL_COMMAND_SIZE 4096
@ -29,7 +29,7 @@ struct pxl8_repl {
atomic_uint log_read_idx; atomic_uint log_read_idx;
atomic_bool should_quit; atomic_bool should_quit;
pthread_t thread; SDL_Thread* thread;
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE]; char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
pxl8_repl_command command; pxl8_repl_command command;
}; };
@ -56,15 +56,15 @@ static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) {
"pxl8.error", "pxl8.debug", "pxl8.trace" "pxl8.error", "pxl8.debug", "pxl8.trace"
}; };
size_t buf_len = strlen(buf); usize buf_len = strlen(buf);
for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) { for (usize i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
if (strncmp(buf, fennel_keywords[i], buf_len) == 0) { if (strncmp(buf, fennel_keywords[i], buf_len) == 0) {
linenoiseAddCompletion(lc, fennel_keywords[i]); linenoiseAddCompletion(lc, fennel_keywords[i]);
} }
} }
for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) { for (usize i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) { if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
linenoiseAddCompletion(lc, pxl8_functions[i]); linenoiseAddCompletion(lc, pxl8_functions[i]);
} }
@ -105,7 +105,7 @@ static void pxl8_repl_flush_logs(pxl8_repl* repl) {
fflush(stdout); fflush(stdout);
} }
static void* pxl8_repl_thread(void* arg) { static int pxl8_repl_thread(void* arg) {
pxl8_repl* repl = (pxl8_repl*)arg; pxl8_repl* repl = (pxl8_repl*)arg;
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n"); printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n");
@ -204,8 +204,7 @@ static void* pxl8_repl_thread(void* arg) {
lw = atomic_load(&repl->log_write_idx); lw = atomic_load(&repl->log_write_idx);
} }
fflush(stdout); fflush(stdout);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; SDL_Delay(1);
nanosleep(&ts, NULL);
} }
atomic_store(&repl->cmd_complete, false); atomic_store(&repl->cmd_complete, false);
} }
@ -214,11 +213,11 @@ static void* pxl8_repl_thread(void* arg) {
pxl8_repl_flush_logs(repl); pxl8_repl_flush_logs(repl);
return NULL; return 0;
} }
pxl8_repl* pxl8_repl_create(void) { pxl8_repl* pxl8_repl_create(void) {
pxl8_repl* repl = (pxl8_repl*)calloc(1, sizeof(pxl8_repl)); pxl8_repl* repl = (pxl8_repl*)pxl8_calloc(1, sizeof(pxl8_repl));
if (!repl) return NULL; if (!repl) return NULL;
repl->accumulator[0] = '\0'; repl->accumulator[0] = '\0';
@ -237,8 +236,9 @@ pxl8_repl* pxl8_repl_create(void) {
g_repl = repl; g_repl = repl;
if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) { repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
free(repl); if (!repl->thread) {
pxl8_free(repl);
g_repl = NULL; g_repl = NULL;
return NULL; return NULL;
} }
@ -251,19 +251,18 @@ void pxl8_repl_destroy(pxl8_repl* repl) {
atomic_store(&repl->should_quit, true); atomic_store(&repl->should_quit, true);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000}; SDL_Delay(2);
nanosleep(&ts, NULL);
printf("\r\033[K"); printf("\r\033[K");
fflush(stdout); fflush(stdout);
pthread_join(repl->thread, NULL); SDL_WaitThread(repl->thread, NULL);
pxl8_repl_flush_logs(repl); pxl8_repl_flush_logs(repl);
g_repl = NULL; g_repl = NULL;
system("stty sane 2>/dev/null"); system("stty sane 2>/dev/null");
free(repl); pxl8_free(repl);
} }
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) { pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) {

View file

@ -17,6 +17,7 @@
#include "pxl8_gui.h" #include "pxl8_gui.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_script_ffi.h" #include "pxl8_script_ffi.h"
struct pxl8_script { struct pxl8_script {
@ -37,7 +38,7 @@ struct pxl8_script {
static int pxl8_cart_loader(lua_State* L) { static int pxl8_cart_loader(lua_State* L) {
const char* found_path = lua_tostring(L, lua_upvalueindex(1)); const char* found_path = lua_tostring(L, lua_upvalueindex(1));
const char* code = lua_tostring(L, lua_upvalueindex(2)); const char* code = lua_tostring(L, lua_upvalueindex(2));
size_t code_len = lua_objlen(L, lua_upvalueindex(2)); usize code_len = lua_objlen(L, lua_upvalueindex(2));
bool is_fennel = lua_toboolean(L, lua_upvalueindex(3)); bool is_fennel = lua_toboolean(L, lua_upvalueindex(3));
if (is_fennel) { if (is_fennel) {
@ -75,9 +76,9 @@ static int pxl8_cart_searcher(lua_State* L) {
} }
char path[512]; char path[512];
size_t len = strlen(modname); usize len = strlen(modname);
size_t j = 0; usize j = 0;
for (size_t i = 0; i < len && j < sizeof(path) - 5; i++) { for (usize i = 0; i < len && j < sizeof(path) - 5; i++) {
if (modname[i] == '.') { if (modname[i] == '.') {
path[j++] = '/'; path[j++] = '/';
} else { } else {
@ -114,9 +115,9 @@ static int pxl8_cart_searcher(lua_State* L) {
return 1; return 1;
} }
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) { static void pxl8_script_repl_promote_locals(const char* input, char* output, usize output_size) {
size_t i = 0; usize i = 0;
size_t j = 0; usize j = 0;
bool in_string = false; bool in_string = false;
bool in_comment = false; bool in_comment = false;
@ -182,18 +183,18 @@ static void pxl8_script_set_error(pxl8_script* script, const char* error) {
src += 9; src += 9;
const char* mod_start = src; const char* mod_start = src;
while (*src && !(*src == '"' && *(src+1) == ']')) src++; while (*src && !(*src == '"' && *(src+1) == ']')) src++;
size_t mod_len = src - mod_start; usize mod_len = src - mod_start;
if (mod_len > 4 && strncmp(mod_start, "pxl8", 4) == 0) { if (mod_len > 4 && strncmp(mod_start, "pxl8", 4) == 0) {
const char* prefix = "src/lua/"; const char* prefix = "src/lua/";
while (*prefix && dst < end) *dst++ = *prefix++; while (*prefix && dst < end) *dst++ = *prefix++;
for (size_t i = 0; i < mod_len && dst < end; i++) { for (usize i = 0; i < mod_len && dst < end; i++) {
*dst++ = (mod_start[i] == '.') ? '/' : mod_start[i]; *dst++ = (mod_start[i] == '.') ? '/' : mod_start[i];
} }
const char* suffix = ".lua"; const char* suffix = ".lua";
while (*suffix && dst < end) *dst++ = *suffix++; while (*suffix && dst < end) *dst++ = *suffix++;
} else { } else {
for (size_t i = 0; i < mod_len && dst < end; i++) { for (usize i = 0; i < mod_len && dst < end; i++) {
*dst++ = mod_start[i]; *dst++ = mod_start[i];
} }
} }
@ -244,13 +245,13 @@ static void pxl8_install_embed_searcher(lua_State* L) {
} }
pxl8_script* pxl8_script_create(bool repl_mode) { pxl8_script* pxl8_script_create(bool repl_mode) {
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script)); pxl8_script* script = (pxl8_script*)pxl8_calloc(1, sizeof(pxl8_script));
if (!script) return NULL; if (!script) return NULL;
script->repl_mode = repl_mode; script->repl_mode = repl_mode;
script->L = luaL_newstate(); script->L = luaL_newstate();
if (!script->L) { if (!script->L) {
free(script); pxl8_free(script);
return NULL; return NULL;
} }
@ -349,7 +350,7 @@ void pxl8_script_destroy(pxl8_script* script) {
} }
lua_close(script->L); lua_close(script->L);
} }
free(script); pxl8_free(script);
} }
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) { void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) {
@ -395,7 +396,7 @@ void pxl8_script_set_sys(pxl8_script* script, void* sys) {
} }
} }
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) { static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, usize basename_size) {
char filename_copy[PATH_MAX]; char filename_copy[PATH_MAX];
pxl8_strncpy(filename_copy, filename, sizeof(filename_copy)); pxl8_strncpy(filename_copy, filename, sizeof(filename_copy));
@ -470,7 +471,7 @@ static time_t get_latest_script_mod_time(const char* dir_path) {
latest = subdir_time; latest = subdir_time;
} }
} else { } else {
size_t len = strlen(entry->d_name); usize len = strlen(entry->d_name);
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) || bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0); (len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
@ -618,8 +619,8 @@ static pxl8_result pxl8_script_eval_internal(pxl8_script* script, const char* co
const char* error = lua_tostring(script->L, -1); const char* error = lua_tostring(script->L, -1);
if (error) { if (error) {
char cleaned_error[2048]; char cleaned_error[2048];
size_t j = 0; usize j = 0;
for (size_t i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) { for (usize i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) {
if (error[i] == '\t') { if (error[i] == '\t') {
cleaned_error[j++] = ' '; cleaned_error[j++] = ' ';
} else { } else {
@ -877,7 +878,7 @@ typedef struct {
static void ser_buffer_init(ser_buffer* buf) { static void ser_buffer_init(ser_buffer* buf) {
buf->capacity = 1024; buf->capacity = 1024;
buf->data = malloc(buf->capacity); buf->data = pxl8_malloc(buf->capacity);
buf->size = 0; buf->size = 0;
} }
@ -886,7 +887,7 @@ static void ser_buffer_grow(ser_buffer* buf, u32 needed) {
while (buf->size + needed > buf->capacity) { while (buf->size + needed > buf->capacity) {
buf->capacity *= 2; buf->capacity *= 2;
} }
buf->data = realloc(buf->data, buf->capacity); buf->data = pxl8_realloc(buf->data, buf->capacity);
} }
} }
@ -930,7 +931,7 @@ static void ser_write_value(ser_buffer* buf, lua_State* L, int idx, int depth) {
ser_write_f64(buf, lua_tonumber(L, idx)); ser_write_f64(buf, lua_tonumber(L, idx));
break; break;
case LUA_TSTRING: { case LUA_TSTRING: {
size_t len; usize len;
const char* str = lua_tolstring(L, idx, &len); const char* str = lua_tolstring(L, idx, &len);
ser_write_u8(buf, SER_STRING); ser_write_u8(buf, SER_STRING);
ser_write_u32(buf, (u32)len); ser_write_u32(buf, (u32)len);
@ -1080,7 +1081,7 @@ void pxl8_script_deserialize_globals(pxl8_script* script, const u8* data, u32 si
} }
void pxl8_script_free_serialized(u8* data) { void pxl8_script_free_serialized(u8* data) {
free(data); pxl8_free(data);
} }
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) { pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {

View file

@ -11,6 +11,8 @@ static const char* pxl8_ffi_cdefs =
"typedef int64_t i64;\n" "typedef int64_t i64;\n"
"typedef float f32;\n" "typedef float f32;\n"
"typedef double f64;\n" "typedef double f64;\n"
"typedef size_t usize;\n"
"typedef ptrdiff_t isize;\n"
"typedef struct pxl8 pxl8;\n" "typedef struct pxl8 pxl8;\n"
"typedef struct pxl8_gfx pxl8_gfx;\n" "typedef struct pxl8_gfx pxl8_gfx;\n"
"typedef struct { int x, y, w, h; } pxl8_bounds;\n" "typedef struct { int x, y, w, h; } pxl8_bounds;\n"
@ -27,11 +29,16 @@ static const char* pxl8_ffi_cdefs =
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" "i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"typedef struct pxl8_palette pxl8_palette;\n" "typedef struct pxl8_palette pxl8_palette;\n"
"pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);\n" "pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);\n"
"u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n" "u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n"
"void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n" "void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n"
"void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);\n"
"i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n" "i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n"
"u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n" "u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n"
"typedef struct pxl8_colormap pxl8_colormap;\n"
"pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);\n"
"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" "i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\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_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
@ -192,21 +199,27 @@ static const char* pxl8_ffi_cdefs =
"\n" "\n"
"typedef struct pxl8_light {\n" "typedef struct pxl8_light {\n"
" pxl8_vec3 position;\n" " pxl8_vec3 position;\n"
" f32 inv_radius_sq;\n"
" u8 r, g, b;\n" " u8 r, g, b;\n"
" u8 intensity;\n" " u8 intensity;\n"
" f32 radius;\n" " f32 radius;\n"
" f32 radius_sq;\n" " f32 radius_sq;\n"
" f32 inv_radius_sq;\n"
"} pxl8_light;\n" "} pxl8_light;\n"
"\n" "\n"
"typedef struct pxl8_lights pxl8_lights;\n"
"pxl8_lights* pxl8_lights_create(u32 capacity);\n"
"void pxl8_lights_destroy(pxl8_lights* lights);\n"
"void pxl8_lights_add(pxl8_lights* lights, f32 x, f32 y, f32 z, u8 r, u8 g, u8 b, u8 intensity, f32 radius);\n"
"void pxl8_lights_clear(pxl8_lights* lights);\n"
"u32 pxl8_lights_count(const pxl8_lights* lights);\n"
"const pxl8_light* pxl8_lights_data(const pxl8_lights* lights);\n"
"\n"
"typedef struct pxl8_3d_uniforms {\n" "typedef struct pxl8_3d_uniforms {\n"
" u8 ambient;\n" " u8 ambient;\n"
" pxl8_vec3 celestial_dir;\n" " pxl8_vec3 celestial_dir;\n"
" f32 celestial_intensity;\n" " f32 celestial_intensity;\n"
" u8 fog_color;\n" " u8 fog_color;\n"
" f32 fog_density;\n" " f32 fog_density;\n"
" pxl8_light lights[16];\n"
" u32 num_lights;\n"
" f32 time;\n" " f32 time;\n"
"} pxl8_3d_uniforms;\n" "} pxl8_3d_uniforms;\n"
"\n" "\n"
@ -224,12 +237,14 @@ static const char* pxl8_ffi_cdefs =
"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n" "pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n"
"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n" "pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n"
"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n" "void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n"
"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" "\n"
"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n" "typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n"
"\n" "\n"
"typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n" "typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n"
"\n" "\n"
"typedef struct pxl8_glow_source {\n" "typedef struct pxl8_glow {\n"
" u8 color;\n" " u8 color;\n"
" u16 depth;\n" " u16 depth;\n"
" u8 height;\n" " u8 height;\n"
@ -238,22 +253,37 @@ static const char* pxl8_ffi_cdefs =
" pxl8_glow_shape shape;\n" " pxl8_glow_shape shape;\n"
" i16 x;\n" " i16 x;\n"
" i16 y;\n" " i16 y;\n"
"} pxl8_glow_source;\n" "} pxl8_glow;\n"
"\n" "\n"
"void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);\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" "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" "void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\n"
"\n" "\n"
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);\n" "void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n" "void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n" "void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n" "void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n" "void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
"\n" "\n"
"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n" "typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n"
"\n" "\n"
"typedef struct pxl8_material {\n" "typedef struct pxl8_gfx_material {\n"
" char name[16];\n"
" pxl8_vec3 u_axis;\n"
" pxl8_vec3 v_axis;\n"
" f32 u_offset;\n"
" f32 v_offset;\n"
" u32 texture_id;\n" " u32 texture_id;\n"
" u32 lightmap_id;\n"
" u8 alpha;\n" " u8 alpha;\n"
" u8 blend_mode;\n" " u8 blend_mode;\n"
" bool dither;\n" " bool dither;\n"
@ -261,13 +291,15 @@ static const char* pxl8_ffi_cdefs =
" bool dynamic_lighting;\n" " bool dynamic_lighting;\n"
" bool per_pixel;\n" " bool per_pixel;\n"
" bool vertex_color_passthrough;\n" " bool vertex_color_passthrough;\n"
" bool wireframe;\n"
" f32 emissive_intensity;\n" " f32 emissive_intensity;\n"
"} pxl8_material;\n" "} pxl8_gfx_material;\n"
"\n" "\n"
"typedef struct pxl8_vertex {\n" "typedef struct pxl8_vertex {\n"
" pxl8_vec3 position;\n" " pxl8_vec3 position;\n"
" pxl8_vec3 normal;\n" " pxl8_vec3 normal;\n"
" f32 u, v;\n" " f32 u, v;\n"
" f32 lu, lv;\n"
" u8 color;\n" " u8 color;\n"
" u8 light;\n" " u8 light;\n"
" u8 _pad[2];\n" " u8 _pad[2];\n"
@ -287,12 +319,16 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n" "void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n" "u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n" "void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);\n" "void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"\n"
"u32 pxl8_hash32(u32 x);\n"
"\n" "\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n" "pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n" "pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
"pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);\n" "pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);\n"
"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n" "pxl8_vec3 pxl8_mat4_multiply_vec3(pxl8_mat4 m, pxl8_vec3 v);\n"
"pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v);\n"
"pxl8_mat4 pxl8_mat4_orthographic(float left, float right, float bottom, float top, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n" "pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n"
"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n" "pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n"
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n" "pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
@ -316,18 +352,76 @@ static const char* pxl8_ffi_cdefs =
" int num_rooms;\n" " int num_rooms;\n"
"} pxl8_procgen_params;\n" "} pxl8_procgen_params;\n"
"\n" "\n"
"typedef struct pxl8_procgen_tex_params {\n" "typedef enum pxl8_graph_op {\n"
" char name[16];\n" " PXL8_OP_CONST = 0,\n"
" unsigned int seed;\n" " PXL8_OP_INPUT_AGE,\n"
" int width;\n" " PXL8_OP_INPUT_SEED,\n"
" int height;\n" " PXL8_OP_INPUT_TIME,\n"
" float scale;\n" " PXL8_OP_INPUT_X,\n"
" float roughness;\n" " PXL8_OP_INPUT_Y,\n"
" unsigned char base_color;\n"
" unsigned char variation;\n"
"} pxl8_procgen_tex_params;\n"
"\n" "\n"
"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n" " PXL8_OP_ABS,\n"
" PXL8_OP_CEIL,\n"
" PXL8_OP_COS,\n"
" PXL8_OP_FLOOR,\n"
" PXL8_OP_FRACT,\n"
" PXL8_OP_NEGATE,\n"
" PXL8_OP_SIN,\n"
" PXL8_OP_SQRT,\n"
"\n"
" PXL8_OP_ADD,\n"
" PXL8_OP_DIV,\n"
" PXL8_OP_MAX,\n"
" PXL8_OP_MIN,\n"
" PXL8_OP_MOD,\n"
" PXL8_OP_MUL,\n"
" PXL8_OP_POW,\n"
" PXL8_OP_SUB,\n"
"\n"
" PXL8_OP_CLAMP,\n"
" PXL8_OP_LERP,\n"
" PXL8_OP_SELECT,\n"
" PXL8_OP_SMOOTHSTEP,\n"
"\n"
" PXL8_OP_NOISE_FBM,\n"
" PXL8_OP_NOISE_PERLIN,\n"
" PXL8_OP_NOISE_RIDGED,\n"
" PXL8_OP_NOISE_TURBULENCE,\n"
" PXL8_OP_NOISE_VALUE,\n"
"\n"
" PXL8_OP_VORONOI_CELL,\n"
" PXL8_OP_VORONOI_EDGE,\n"
" PXL8_OP_VORONOI_ID,\n"
"\n"
" PXL8_OP_GRADIENT_LINEAR,\n"
" PXL8_OP_GRADIENT_RADIAL,\n"
"\n"
" PXL8_OP_QUANTIZE,\n"
" PXL8_OP_COUNT\n"
"} pxl8_graph_op;\n"
"\n"
"typedef struct pxl8_node {\n"
" u8 in[4];\n"
" u8 op;\n"
" u8 out;\n"
" f32 param;\n"
"} pxl8_node;\n"
"\n"
"typedef struct pxl8_graph {\n"
" u32 capacity;\n"
" u32 count;\n"
" pxl8_node* nodes;\n"
" u8 output_reg;\n"
" u32 seed;\n"
"} pxl8_graph;\n"
"\n"
"pxl8_graph* pxl8_graph_create(u32 capacity);\n"
"void pxl8_graph_destroy(pxl8_graph* graph);\n"
"void pxl8_graph_clear(pxl8_graph* graph);\n"
"u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);\n"
"void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);\n"
"void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);\n"
"void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
"\n" "\n"
"typedef struct pxl8_bsp pxl8_bsp;\n" "typedef struct pxl8_bsp pxl8_bsp;\n"
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n" "typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
@ -350,12 +444,13 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_world_is_loaded(const pxl8_world* world);\n" "bool pxl8_world_is_loaded(const pxl8_world* world);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" "pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
"\n" "\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" "typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
"pxl8_gui_state* pxl8_gui_state_create(void);\n" "pxl8_gui_state* pxl8_gui_state_create(void);\n"
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n" "void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n" "void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_end_frame(pxl8_gui_state* state);\n" "void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n" "void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n"
"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n" "void pxl8_gui_cursor_down(pxl8_gui_state* state);\n"
"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n" "void pxl8_gui_cursor_up(pxl8_gui_state* state);\n"
@ -486,40 +581,40 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_bit_test(u32 val, u8 bit);\n" "bool pxl8_bit_test(u32 val, u8 bit);\n"
"void pxl8_bit_toggle(u32* val, u8 bit);\n" "void pxl8_bit_toggle(u32* val, u8 bit);\n"
"\n" "\n"
"void pxl8_pack_u8(u8* buf, size_t offset, u8 val);\n" "void pxl8_pack_u8(u8* buf, usize offset, u8 val);\n"
"void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);\n" "void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);\n"
"void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);\n" "void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);\n"
"void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);\n" "void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);\n"
"void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);\n" "void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);\n"
"void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);\n" "void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);\n"
"void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);\n" "void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);\n"
"void pxl8_pack_i8(u8* buf, size_t offset, i8 val);\n" "void pxl8_pack_i8(u8* buf, usize offset, i8 val);\n"
"void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);\n" "void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);\n"
"void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);\n" "void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);\n"
"void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);\n" "void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);\n"
"void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);\n" "void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);\n"
"void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);\n" "void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);\n"
"void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);\n" "void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);\n"
"void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);\n" "void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);\n"
"void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);\n" "void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);\n"
"void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);\n" "void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);\n"
"void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);\n" "void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);\n"
"\n" "\n"
"u8 pxl8_unpack_u8(const u8* buf, size_t offset);\n" "u8 pxl8_unpack_u8(const u8* buf, usize offset);\n"
"u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);\n" "u16 pxl8_unpack_u16_be(const u8* buf, usize offset);\n"
"u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);\n" "u16 pxl8_unpack_u16_le(const u8* buf, usize offset);\n"
"u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);\n" "u32 pxl8_unpack_u32_be(const u8* buf, usize offset);\n"
"u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);\n" "u32 pxl8_unpack_u32_le(const u8* buf, usize offset);\n"
"u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);\n" "u64 pxl8_unpack_u64_be(const u8* buf, usize offset);\n"
"u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);\n" "u64 pxl8_unpack_u64_le(const u8* buf, usize offset);\n"
"i8 pxl8_unpack_i8(const u8* buf, size_t offset);\n" "i8 pxl8_unpack_i8(const u8* buf, usize offset);\n"
"i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);\n" "i16 pxl8_unpack_i16_be(const u8* buf, usize offset);\n"
"i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);\n" "i16 pxl8_unpack_i16_le(const u8* buf, usize offset);\n"
"i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);\n" "i32 pxl8_unpack_i32_be(const u8* buf, usize offset);\n"
"i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);\n" "i32 pxl8_unpack_i32_le(const u8* buf, usize offset);\n"
"i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);\n" "i64 pxl8_unpack_i64_be(const u8* buf, usize offset);\n"
"i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);\n" "i64 pxl8_unpack_i64_le(const u8* buf, usize offset);\n"
"f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);\n" "f32 pxl8_unpack_f32_be(const u8* buf, usize offset);\n"
"f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);\n" "f32 pxl8_unpack_f32_le(const u8* buf, usize offset);\n"
"f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);\n" "f64 pxl8_unpack_f64_be(const u8* buf, usize offset);\n"
"f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);\n"; "f64 pxl8_unpack_f64_le(const u8* buf, usize offset);\n";

View file

@ -6,9 +6,15 @@
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mem.h"
#define VOICE_SCALE 0.15f #define VOICE_SCALE 0.15f
static inline f32 sanitize_audio(f32 x) {
union { f32 f; u32 u; } conv = { .f = x };
return ((conv.u & 0x7F800000) == 0x7F800000) ? 0.0f : x;
}
typedef enum envelope_state { typedef enum envelope_state {
ENV_ATTACK = 0, ENV_ATTACK = 0,
ENV_DECAY, ENV_DECAY,
@ -461,14 +467,14 @@ static void delay_process_stereo(void* state, f32* left, f32* right) {
static void delay_destroy_state(void* state) { static void delay_destroy_state(void* state) {
delay_state* d = (delay_state*)state; delay_state* d = (delay_state*)state;
if (d) { if (d) {
free(d->buffer_l); pxl8_free(d->buffer_l);
free(d->buffer_r); pxl8_free(d->buffer_r);
free(d); pxl8_free(d);
} }
} }
static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) { static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) {
c->buffer = (f32*)calloc(size, sizeof(f32)); c->buffer = (f32*)pxl8_calloc(size, sizeof(f32));
c->size = size; c->size = size;
c->feedback = feedback; c->feedback = feedback;
c->damping = damping; c->damping = damping;
@ -477,7 +483,7 @@ static void comb_init(comb_filter* c, u32 size, f32 feedback, f32 damping) {
} }
static void comb_destroy(comb_filter* c) { static void comb_destroy(comb_filter* c) {
free(c->buffer); pxl8_free(c->buffer);
} }
static f32 comb_process(comb_filter* c, f32 input) { static f32 comb_process(comb_filter* c, f32 input) {
@ -492,14 +498,14 @@ static f32 comb_process(comb_filter* c, f32 input) {
} }
static void allpass_init(allpass_filter* a, u32 size) { static void allpass_init(allpass_filter* a, u32 size) {
a->buffer = (f32*)calloc(size, sizeof(f32)); a->buffer = (f32*)pxl8_calloc(size, sizeof(f32));
a->size = size; a->size = size;
a->feedback = 0.5f; a->feedback = 0.5f;
a->index = 0; a->index = 0;
} }
static void allpass_destroy(allpass_filter* a) { static void allpass_destroy(allpass_filter* a) {
free(a->buffer); pxl8_free(a->buffer);
} }
static f32 allpass_process(allpass_filter* a, f32 input) { static f32 allpass_process(allpass_filter* a, f32 input) {
@ -555,7 +561,7 @@ static void reverb_destroy_state(void* state) {
if (r) { if (r) {
for (int i = 0; i < 4; i++) comb_destroy(&r->combs[i]); for (int i = 0; i < 4; i++) comb_destroy(&r->combs[i]);
for (int i = 0; i < 2; i++) allpass_destroy(&r->allpasses[i]); for (int i = 0; i < 2; i++) allpass_destroy(&r->allpasses[i]);
free(r); pxl8_free(r);
} }
} }
@ -601,7 +607,7 @@ static void compressor_process_stereo(void* state, f32* left, f32* right) {
} }
static void compressor_destroy_state(void* state) { static void compressor_destroy_state(void* state) {
free(state); pxl8_free(state);
} }
static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* out_right) { static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* out_right) {
@ -649,12 +655,12 @@ static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* ou
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) { pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
if (!hal || !hal->audio_create) return NULL; if (!hal || !hal->audio_create) return NULL;
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)calloc(1, sizeof(pxl8_sfx_mixer)); pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)pxl8_calloc(1, sizeof(pxl8_sfx_mixer));
if (!mixer) return NULL; if (!mixer) return NULL;
mixer->output_buffer = (f32*)calloc(PXL8_SFX_BUFFER_SIZE * 2, sizeof(f32)); mixer->output_buffer = (f32*)pxl8_calloc(PXL8_SFX_BUFFER_SIZE * 2, sizeof(f32));
if (!mixer->output_buffer) { if (!mixer->output_buffer) {
free(mixer); pxl8_free(mixer);
return NULL; return NULL;
} }
@ -663,8 +669,8 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2); mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
if (!mixer->audio_handle) { if (!mixer->audio_handle) {
free(mixer->output_buffer); pxl8_free(mixer->output_buffer);
free(mixer); pxl8_free(mixer);
return NULL; return NULL;
} }
@ -681,8 +687,8 @@ void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
mixer->hal->audio_destroy(mixer->audio_handle); mixer->hal->audio_destroy(mixer->audio_handle);
} }
free(mixer->output_buffer); pxl8_free(mixer->output_buffer);
free(mixer); pxl8_free(mixer);
} }
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) { void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
@ -716,8 +722,8 @@ void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
left = fmaxf(-1.0f, fminf(1.0f, left)); left = fmaxf(-1.0f, fminf(1.0f, left));
right = fmaxf(-1.0f, fminf(1.0f, right)); right = fmaxf(-1.0f, fminf(1.0f, right));
if (!isfinite(left)) left = 0.0f; left = sanitize_audio(left);
if (!isfinite(right)) right = 0.0f; right = sanitize_audio(right);
mixer->output_buffer[i * 2] = left; mixer->output_buffer[i * 2] = left;
mixer->output_buffer[i * 2 + 1] = right; mixer->output_buffer[i * 2 + 1] = right;
@ -785,7 +791,7 @@ f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer) {
} }
pxl8_sfx_context* pxl8_sfx_context_create(void) { pxl8_sfx_context* pxl8_sfx_context_create(void) {
pxl8_sfx_context* ctx = (pxl8_sfx_context*)calloc(1, sizeof(pxl8_sfx_context)); pxl8_sfx_context* ctx = (pxl8_sfx_context*)pxl8_calloc(1, sizeof(pxl8_sfx_context));
if (!ctx) return NULL; if (!ctx) return NULL;
ctx->volume = 1.0f; ctx->volume = 1.0f;
@ -804,7 +810,7 @@ void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx) {
node = next; node = next;
} }
free(ctx); pxl8_free(ctx);
} }
void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume) { void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume) {
@ -871,26 +877,26 @@ void pxl8_sfx_node_destroy(pxl8_sfx_node* node) {
if (node->destroy && node->state) { if (node->destroy && node->state) {
node->destroy(node->state); node->destroy(node->state);
} }
free(node); pxl8_free(node);
} }
pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg) { pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node)); pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL; if (!node) return NULL;
delay_state* d = (delay_state*)calloc(1, sizeof(delay_state)); delay_state* d = (delay_state*)pxl8_calloc(1, sizeof(delay_state));
if (!d) { if (!d) {
free(node); pxl8_free(node);
return NULL; return NULL;
} }
d->buffer_l = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32)); d->buffer_l = (f32*)pxl8_calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
d->buffer_r = (f32*)calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32)); d->buffer_r = (f32*)pxl8_calloc(PXL8_SFX_MAX_DELAY_SAMPLES, sizeof(f32));
if (!d->buffer_l || !d->buffer_r) { if (!d->buffer_l || !d->buffer_r) {
free(d->buffer_l); pxl8_free(d->buffer_l);
free(d->buffer_r); pxl8_free(d->buffer_r);
free(d); pxl8_free(d);
free(node); pxl8_free(node);
return NULL; return NULL;
} }
@ -933,12 +939,12 @@ void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix) {
} }
pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg) { pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node)); pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL; if (!node) return NULL;
reverb_state* r = (reverb_state*)calloc(1, sizeof(reverb_state)); reverb_state* r = (reverb_state*)pxl8_calloc(1, sizeof(reverb_state));
if (!r) { if (!r) {
free(node); pxl8_free(node);
return NULL; return NULL;
} }
@ -991,12 +997,12 @@ void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix) {
} }
pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg) { pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg) {
pxl8_sfx_node* node = (pxl8_sfx_node*)calloc(1, sizeof(pxl8_sfx_node)); pxl8_sfx_node* node = (pxl8_sfx_node*)pxl8_calloc(1, sizeof(pxl8_sfx_node));
if (!node) return NULL; if (!node) return NULL;
compressor_state* c = (compressor_state*)calloc(1, sizeof(compressor_state)); compressor_state* c = (compressor_state*)pxl8_calloc(1, sizeof(compressor_state));
if (!c) { if (!c) {
free(node); pxl8_free(node);
return NULL; return NULL;
} }

View file

@ -8,6 +8,7 @@
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#define BSP_VERSION 29 #define BSP_VERSION 29
@ -40,15 +41,23 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT]; pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header; } pxl8_bsp_header;
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) { static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
pxl8_vec3 v; f32 x = pxl8_read_f32(stream);
v.x = pxl8_read_f32(stream); f32 y = pxl8_read_f32(stream);
v.y = pxl8_read_f32(stream); f32 z = pxl8_read_f32(stream);
v.z = pxl8_read_f32(stream); return (pxl8_vec3){x, z, y};
return v;
} }
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) { static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, usize file_size) {
if (chunk->size == 0) return true; if (chunk->size == 0) return true;
if (chunk->offset >= file_size) return false; if (chunk->offset >= file_size) return false;
if (chunk->offset + chunk->size > file_size) return false; if (chunk->offset + chunk->size > file_size) return false;
@ -75,32 +84,13 @@ static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_id
return *out_vert_idx < bsp->num_vertices; return *out_vert_idx < bsp->num_vertices;
} }
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
i32 edge_idx = bsp->surfedges[surfedge_idx];
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
}
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
}
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT; if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
memset(bsp, 0, sizeof(*bsp)); memset(bsp, 0, sizeof(*bsp));
u8* file_data = NULL; u8* file_data = NULL;
size_t file_size = 0; usize file_size = 0;
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size); pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
if (result != PXL8_OK) { if (result != PXL8_OK) {
pxl8_error("Failed to load BSP file: %s", path); pxl8_error("Failed to load BSP file: %s", path);
@ -109,7 +99,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (file_size < sizeof(pxl8_bsp_header)) { if (file_size < sizeof(pxl8_bsp_header)) {
pxl8_error("BSP file too small: %s", path); pxl8_error("BSP file too small: %s", path);
free(file_data); pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
@ -120,7 +110,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (header.version != BSP_VERSION) { if (header.version != BSP_VERSION) {
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION); pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
free(file_data); pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
@ -133,7 +123,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
bsp->num_vertices = chunk->size / 12; bsp->num_vertices = chunk->size / 12;
if (bsp->num_vertices > 0) { if (bsp->num_vertices > 0) {
bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex)); bsp->vertices = pxl8_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
if (!bsp->vertices) goto error_cleanup; if (!bsp->vertices) goto error_cleanup;
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_vertices; i++) { for (u32 i = 0; i < bsp->num_vertices; i++) {
@ -145,7 +135,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_edges = chunk->size / 4; bsp->num_edges = chunk->size / 4;
if (bsp->num_edges > 0) { if (bsp->num_edges > 0) {
bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge)); bsp->edges = pxl8_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_edges; i++) { for (u32 i = 0; i < bsp->num_edges; i++) {
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream); bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
@ -157,7 +147,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_surfedges = chunk->size / 4; bsp->num_surfedges = chunk->size / 4;
if (bsp->num_surfedges > 0) { if (bsp->num_surfedges > 0) {
bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32)); bsp->surfedges = pxl8_calloc(bsp->num_surfedges, sizeof(i32));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_surfedges; i++) { for (u32 i = 0; i < bsp->num_surfedges; i++) {
bsp->surfedges[i] = pxl8_read_i32(&stream); bsp->surfedges[i] = pxl8_read_i32(&stream);
@ -168,7 +158,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_planes = chunk->size / 20; bsp->num_planes = chunk->size / 20;
if (bsp->num_planes > 0) { if (bsp->num_planes > 0) {
bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane)); bsp->planes = pxl8_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_planes; i++) { for (u32 i = 0; i < bsp->num_planes; i++) {
bsp->planes[i].normal = read_vec3(&stream); bsp->planes[i].normal = read_vec3(&stream);
@ -179,16 +169,20 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
chunk = &header.chunks[CHUNK_TEXINFO]; chunk = &header.chunks[CHUNK_TEXINFO];
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
bsp->num_texinfo = chunk->size / 40; bsp->num_materials = chunk->size / 40;
if (bsp->num_texinfo > 0) { if (bsp->num_materials > 0) {
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); bsp->materials = pxl8_calloc(bsp->num_materials, sizeof(pxl8_gfx_material));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_texinfo; i++) { for (u32 i = 0; i < bsp->num_materials; i++) {
bsp->texinfo[i].u_axis = read_vec3(&stream); bsp->materials[i].u_axis = read_vec3(&stream);
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream); bsp->materials[i].u_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].v_axis = read_vec3(&stream); bsp->materials[i].v_axis = read_vec3(&stream);
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream); bsp->materials[i].v_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].miptex = pxl8_read_u32(&stream); bsp->materials[i].texture_id = pxl8_read_u32(&stream);
bsp->materials[i].alpha = 255;
bsp->materials[i].dither = true;
bsp->materials[i].dynamic_lighting = true;
bsp->materials[i].double_sided = true;
} }
} }
@ -196,14 +190,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_faces = chunk->size / 20; bsp->num_faces = chunk->size / 20;
if (bsp->num_faces > 0) { if (bsp->num_faces > 0) {
bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face)); bsp->faces = pxl8_calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_faces; i++) { for (u32 i = 0; i < bsp->num_faces; i++) {
bsp->faces[i].plane_id = pxl8_read_u16(&stream); bsp->faces[i].plane_id = pxl8_read_u16(&stream);
bsp->faces[i].side = pxl8_read_u16(&stream); bsp->faces[i].side = pxl8_read_u16(&stream);
bsp->faces[i].first_edge = pxl8_read_u32(&stream); bsp->faces[i].first_edge = pxl8_read_u32(&stream);
bsp->faces[i].num_edges = pxl8_read_u16(&stream); bsp->faces[i].num_edges = pxl8_read_u16(&stream);
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream); bsp->faces[i].material_id = pxl8_read_u16(&stream);
bsp->faces[i].styles[0] = pxl8_read_u8(&stream); bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
bsp->faces[i].styles[1] = pxl8_read_u8(&stream); bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
bsp->faces[i].styles[2] = pxl8_read_u8(&stream); bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
@ -219,14 +213,24 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
bsp->num_nodes = chunk->size / 24; bsp->num_nodes = chunk->size / 24;
if (bsp->num_nodes > 0) { if (bsp->num_nodes > 0) {
bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node)); bsp->nodes = pxl8_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_nodes; i++) { for (u32 i = 0; i < bsp->num_nodes; i++) {
bsp->nodes[i].plane_id = pxl8_read_u32(&stream); bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
bsp->nodes[i].children[0] = pxl8_read_i16(&stream); bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
bsp->nodes[i].children[1] = pxl8_read_i16(&stream); bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream); i16 nx = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream); i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->nodes[i].mins[0] = nx;
bsp->nodes[i].mins[1] = nz;
bsp->nodes[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->nodes[i].maxs[0] = mx;
bsp->nodes[i].maxs[1] = mz;
bsp->nodes[i].maxs[2] = my;
bsp->nodes[i].first_face = pxl8_read_u16(&stream); bsp->nodes[i].first_face = pxl8_read_u16(&stream);
bsp->nodes[i].num_faces = pxl8_read_u16(&stream); bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
} }
@ -236,13 +240,23 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
bsp->num_leafs = chunk->size / 28; bsp->num_leafs = chunk->size / 28;
if (bsp->num_leafs > 0) { if (bsp->num_leafs > 0) {
bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf)); bsp->leafs = pxl8_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_leafs; i++) { for (u32 i = 0; i < bsp->num_leafs; i++) {
bsp->leafs[i].contents = pxl8_read_i32(&stream); bsp->leafs[i].contents = pxl8_read_i32(&stream);
bsp->leafs[i].visofs = pxl8_read_i32(&stream); bsp->leafs[i].visofs = pxl8_read_i32(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream); i16 nx = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream); i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->leafs[i].mins[0] = nx;
bsp->leafs[i].mins[1] = nz;
bsp->leafs[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->leafs[i].maxs[0] = mx;
bsp->leafs[i].maxs[1] = mz;
bsp->leafs[i].maxs[2] = my;
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream); bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream); bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream); for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
@ -253,7 +267,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
bsp->num_marksurfaces = chunk->size / 2; bsp->num_marksurfaces = chunk->size / 2;
if (bsp->num_marksurfaces > 0) { if (bsp->num_marksurfaces > 0) {
bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16)); bsp->marksurfaces = pxl8_calloc(bsp->num_marksurfaces, sizeof(u16));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_marksurfaces; i++) { for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
bsp->marksurfaces[i] = pxl8_read_u16(&stream); bsp->marksurfaces[i] = pxl8_read_u16(&stream);
@ -264,11 +278,21 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
bsp->num_models = chunk->size / 64; bsp->num_models = chunk->size / 64;
if (bsp->num_models > 0) { if (bsp->num_models > 0) {
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model)); bsp->models = pxl8_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
pxl8_stream_seek(&stream, chunk->offset); pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) { for (u32 i = 0; i < bsp->num_models; i++) {
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream); f32 minx = pxl8_read_f32(&stream);
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream); f32 miny = pxl8_read_f32(&stream);
f32 minz = pxl8_read_f32(&stream);
bsp->models[i].mins[0] = minx;
bsp->models[i].mins[1] = minz;
bsp->models[i].mins[2] = miny;
f32 maxx = pxl8_read_f32(&stream);
f32 maxy = pxl8_read_f32(&stream);
f32 maxz = pxl8_read_f32(&stream);
bsp->models[i].maxs[0] = maxx;
bsp->models[i].maxs[1] = maxz;
bsp->models[i].maxs[2] = maxy;
bsp->models[i].origin = read_vec3(&stream); bsp->models[i].origin = read_vec3(&stream);
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream); for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
bsp->models[i].visleafs = pxl8_read_i32(&stream); bsp->models[i].visleafs = pxl8_read_i32(&stream);
@ -281,7 +305,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->visdata_size = chunk->size; bsp->visdata_size = chunk->size;
if (bsp->visdata_size > 0) { if (bsp->visdata_size > 0) {
bsp->visdata = malloc(bsp->visdata_size); bsp->visdata = pxl8_malloc(bsp->visdata_size);
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size); memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
} }
@ -289,11 +313,11 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->lightdata_size = chunk->size; bsp->lightdata_size = chunk->size;
if (bsp->lightdata_size > 0) { if (bsp->lightdata_size > 0) {
bsp->lightdata = malloc(bsp->lightdata_size); bsp->lightdata = pxl8_malloc(bsp->lightdata_size);
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size); memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
} }
free(file_data); pxl8_free(file_data);
for (u32 i = 0; i < bsp->num_faces; i++) { for (u32 i = 0; i < bsp->num_faces; i++) {
pxl8_bsp_face* face = &bsp->faces[i]; pxl8_bsp_face* face = &bsp->faces[i];
@ -327,7 +351,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
error_cleanup: error_cleanup:
pxl8_error("BSP chunk validation failed: %s", path); pxl8_error("BSP chunk validation failed: %s", path);
free(file_data); pxl8_free(file_data);
pxl8_bsp_destroy(bsp); pxl8_bsp_destroy(bsp);
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
@ -335,18 +359,21 @@ error_cleanup:
void pxl8_bsp_destroy(pxl8_bsp* bsp) { void pxl8_bsp_destroy(pxl8_bsp* bsp) {
if (!bsp) return; if (!bsp) return;
free(bsp->edges); pxl8_free(bsp->cell_portals);
free(bsp->faces); pxl8_free(bsp->edges);
free(bsp->leafs); pxl8_free(bsp->faces);
free(bsp->lightdata); pxl8_free(bsp->leafs);
free(bsp->marksurfaces); pxl8_free(bsp->lightdata);
free(bsp->models); pxl8_free(bsp->marksurfaces);
free(bsp->nodes); pxl8_free(bsp->materials);
free(bsp->planes); pxl8_free(bsp->models);
free(bsp->surfedges); pxl8_free(bsp->nodes);
free(bsp->texinfo); pxl8_free(bsp->planes);
free(bsp->vertices); pxl8_free(bsp->render_face_flags);
free(bsp->visdata); pxl8_free(bsp->surfedges);
pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices);
pxl8_free(bsp->visdata);
memset(bsp, 0, sizeof(*bsp)); memset(bsp, 0, sizeof(*bsp));
} }
@ -375,85 +402,88 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
i32 visofs = bsp->leafs[leaf_from].visofs; i32 visofs = bsp->leafs[leaf_from].visofs;
if (visofs < 0) return true; if (visofs < 0) return true;
u32 target_byte = leaf_to >> 3; u32 row_size = (bsp->num_leafs + 7) >> 3;
u32 target_bit = leaf_to & 7; u32 byte_idx = (u32)leaf_to >> 3;
u32 pvs_size = (bsp->num_leafs + 7) / 8; u32 bit_idx = (u32)leaf_to & 7;
u32 pos = (u32)visofs; u8* vis = bsp->visdata + visofs;
u32 current_byte = 0; u8* vis_end = bsp->visdata + bsp->visdata_size;
u32 out = 0;
while (current_byte < pvs_size && pos < bsp->visdata_size) { while (out < row_size && vis < vis_end) {
u8 b = bsp->visdata[pos++]; if (*vis) {
if (out == byte_idx) {
if (b != 0) { return (*vis & (1 << bit_idx)) != 0;
if (current_byte == target_byte) {
return (b & (1 << target_bit)) != 0;
} }
current_byte++; out++;
vis++;
} else { } else {
if (pos >= bsp->visdata_size) return false; vis++;
u32 count = bsp->visdata[pos++]; if (vis >= vis_end) break;
if (target_byte < current_byte + count) { u32 count = *vis++;
if (out + count > byte_idx && byte_idx >= out) {
return false; return false;
} }
current_byte += count; out += count;
} }
} }
return false; return out > byte_idx ? false : true;
} }
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) { pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
pxl8_bsp_pvs pvs = {0}; pxl8_bsp_pvs pvs = {0};
u32 pvs_size = (bsp->num_leafs + 7) / 8; u32 row = (bsp->num_leafs + 7) >> 3;
pvs.data = calloc(pvs_size, 1); pvs.data = pxl8_malloc(row);
pvs.size = pvs_size; pvs.size = row;
if (!pvs.data) return pvs; if (!pvs.data) return pvs;
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) { if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
memset(pvs.data, 0xFF, pvs_size); memset(pvs.data, 0xFF, row);
return pvs; return pvs;
} }
i32 visofs = bsp->leafs[leaf].visofs; i32 visofs = bsp->leafs[leaf].visofs;
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) { if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
memset(pvs.data, 0xFF, pvs_size); memset(pvs.data, 0xFF, row);
return pvs; return pvs;
} }
u32 pos = (u32)visofs; u8* in = bsp->visdata + visofs;
u32 out = 0; u8* out = pvs.data;
u8* out_end = pvs.data + row;
while (out < pvs_size && pos < bsp->visdata_size) { do {
u8 b = bsp->visdata[pos++]; if (*in) {
*out++ = *in++;
if (b != 0) {
pvs.data[out++] = b;
} else { } else {
if (pos >= bsp->visdata_size) break; in++;
u32 count = bsp->visdata[pos++]; i32 c = *in++;
out += count; while (c > 0 && out < out_end) {
*out++ = 0;
c--;
} }
} }
} while (out < out_end);
return pvs; return pvs;
} }
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) { void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
if (pvs) { if (pvs) {
free(pvs->data); pxl8_free(pvs->data);
pvs->data = NULL; pvs->data = NULL;
pvs->size = 0; pvs->size = 0;
} }
} }
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) { bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
if (!pvs || !pvs->data || leaf < 0) return false; if (!pvs || !pvs->data || leaf < 0) return true;
u32 byte_idx = leaf >> 3; u32 byte_idx = (u32)leaf >> 3;
u32 bit_idx = leaf & 7; u32 bit_idx = (u32)leaf & 7;
if (byte_idx >= pvs->size) return false; if (byte_idx >= pvs->size) return true;
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0; return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
} }
@ -546,6 +576,89 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max); return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
} }
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
return pxl8_frustum_test_aabb(frustum, mins, maxs);
}
static inline bool screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh( static void collect_face_to_mesh(
const pxl8_bsp* bsp, const pxl8_bsp* bsp,
u32 face_id, u32 face_id,
@ -564,17 +677,18 @@ static void collect_face_to_mesh(
} }
} }
const pxl8_bsp_texinfo* texinfo = NULL; const pxl8_gfx_material* material = NULL;
f32 tex_scale = 64.0f; f32 tex_scale = 64.0f;
if (face->texinfo_id < bsp->num_texinfo) { if (face->material_id < bsp->num_materials) {
texinfo = &bsp->texinfo[face->texinfo_id]; material = &bsp->materials[face->material_id];
} }
u16 base_idx = (u16)mesh->vertex_count; u16 base_idx = (u16)mesh->vertex_count;
u32 num_verts = 0; u32 num_verts = 0;
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
i32 surfedge_idx = face->first_edge + i; u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
i32 surfedge_idx = face->first_edge + edge_i;
u32 vert_idx; u32 vert_idx;
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) { if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
@ -584,9 +698,9 @@ static void collect_face_to_mesh(
pxl8_vec3 pos = bsp->vertices[vert_idx].position; pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 u = 0.0f, v = 0.0f; f32 u = 0.0f, v = 0.0f;
if (texinfo) { if (material) {
u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale; u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale; v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale;
} }
u8 light = 255; u8 light = 255;
@ -613,8 +727,8 @@ static void collect_face_to_mesh(
} }
} }
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) { void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces) return; if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192); pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return; if (!mesh) return;
@ -623,109 +737,115 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t
if (mesh->index_count > 0) { if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity(); pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_create(texture_id); pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
} }
pxl8_mesh_destroy(mesh); pxl8_mesh_destroy(mesh);
} }
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) { void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
static int call_count = 0; if (!gfx || !bsp || bsp->num_faces == 0) return;
if (!gfx || !bsp || bsp->num_faces == 0) { if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
if (call_count++ < 5) { if (!bsp->materials || bsp->num_materials == 0) return;
pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u",
(void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0);
}
return;
}
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) { const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (call_count++ < 5) { if (!frustum || !vp) return;
pxl8_debug("bsp_render_textured: frustum is NULL!");
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
if (!bsp_mut->render_face_flags) {
bsp_mut->render_face_flags = pxl8_calloc(bsp->num_faces, 1);
if (!bsp_mut->render_face_flags) return;
} }
memset(bsp_mut->render_face_flags, 0, bsp->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = pxl8_calloc(visited_bytes, 1);
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return; return;
} }
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
static u8* rendered_faces = NULL; visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
static u32 rendered_faces_capacity = 0; cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
if (rendered_faces_capacity < bsp->num_faces) { f32 wall_height = 128.0f;
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
if (!new_buffer) return; while (head < tail) {
rendered_faces = new_buffer; portal_queue_entry entry = queue[head++];
rendered_faces_capacity = bsp->num_faces; u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
} }
memset(rendered_faces, 0, bsp->num_faces);
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
u32 current_texture = 0xFFFFFFFF;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (rendered_faces[face_id]) continue;
rendered_faces[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) {
continue; continue;
} }
const pxl8_bsp_face* face = &bsp->faces[face_id]; visited[byte] |= (1 << bit);
u32 texture_id = 0; cell_windows[target] = new_window;
if (face->texinfo_id < bsp->num_texinfo) { queue[tail++] = (portal_queue_entry){target, new_window};
texture_id = bsp->texinfo[face->texinfo_id].miptex;
}
if (texture_id != current_texture && mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh_clear(mesh);
}
current_texture = texture_id;
collect_face_to_mesh(bsp, face_id, mesh);
} }
} }
if (mesh->index_count > 0) { pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
pxl8_mat4 identity = pxl8_mat4_identity(); if (!mesh) {
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture))); pxl8_free(visited);
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); pxl8_free(cell_windows);
pxl8_free(queue);
return;
} }
pxl8_mesh_destroy(mesh); u32 current_material = 0xFFFFFFFF;
} u32 total_faces = 0;
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) {
if (!gfx || !bsp) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u8 line_color = (u8)(color & 0xFF);
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; u32 byte = leaf_id >> 3;
u32 bit = leaf_id & 7;
if (!(visited[byte] & (1 << bit))) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
if (!leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) { for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i; u32 surf_idx = leaf->first_marksurface + i;
@ -734,23 +854,45 @@ void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 cam
u32 face_id = bsp->marksurfaces[surf_idx]; u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue; if (face_id >= bsp->num_faces) continue;
if (bsp_mut->render_face_flags[face_id]) continue;
bsp_mut->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue; if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id]; const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= bsp->num_materials) continue;
total_faces++;
for (u32 e = 0; e < face->num_edges; e++) { if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
i32 surfedge_idx = face->first_edge + e; pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
pxl8_mesh_clear(mesh);
}
if (surfedge_idx >= (i32)bsp->num_surfedges) continue; current_material = material_id;
collect_face_to_mesh(bsp, face_id, mesh);
u32 v0_idx, v1_idx;
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
pxl8_3d_draw_line(gfx, p0, p1, line_color);
} }
} }
if (mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
}
static u32 debug_count = 0;
if (debug_count++ < 5) {
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
if (mesh->vertex_count > 0) {
pxl8_vertex* v = &mesh->vertices[0];
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
v->position.x, v->position.y, v->position.z, v->u, v->v);
} }
} }
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}

View file

@ -17,7 +17,7 @@ typedef struct pxl8_bsp_face {
u16 side; u16 side;
u8 styles[4]; u8 styles[4];
u16 texinfo_id; u16 material_id;
pxl8_vec3 aabb_min; pxl8_vec3 aabb_min;
pxl8_vec3 aabb_max; pxl8_vec3 aabb_max;
@ -63,16 +63,6 @@ typedef struct pxl8_bsp_plane {
i32 type; i32 type;
} pxl8_bsp_plane; } pxl8_bsp_plane;
typedef struct pxl8_bsp_texinfo {
u32 miptex;
char name[16];
f32 u_offset;
pxl8_vec3 u_axis;
f32 v_offset;
pxl8_vec3 v_axis;
} pxl8_bsp_texinfo;
typedef struct pxl8_bsp_vertex { typedef struct pxl8_bsp_vertex {
pxl8_vec3 position; pxl8_vec3 position;
@ -91,47 +81,52 @@ typedef struct pxl8_bsp_lightmap_sample {
u8 r; u8 r;
} pxl8_bsp_lightmap_sample; } pxl8_bsp_lightmap_sample;
typedef struct pxl8_bsp_material_batch {
u16* face_indices;
u32 face_count;
u8 material_id;
pxl8_mesh* mesh;
} pxl8_bsp_material_batch;
typedef struct pxl8_bsp_pvs { typedef struct pxl8_bsp_pvs {
u8* data; u8* data;
u32 size; u32 size;
} pxl8_bsp_pvs; } pxl8_bsp_pvs;
typedef struct pxl8_bsp_portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} pxl8_bsp_portal;
typedef struct pxl8_bsp_cell_portals {
pxl8_bsp_portal portals[4];
u8 num_portals;
} pxl8_bsp_cell_portals;
typedef struct pxl8_bsp { typedef struct pxl8_bsp {
pxl8_bsp_cell_portals* cell_portals;
pxl8_bsp_edge* edges; pxl8_bsp_edge* edges;
pxl8_bsp_face* faces; pxl8_bsp_face* faces;
pxl8_bsp_leaf* leafs; pxl8_bsp_leaf* leafs;
u8* lightdata; u8* lightdata;
pxl8_bsp_lightmap* lightmaps; pxl8_bsp_lightmap* lightmaps;
u16* marksurfaces; u16* marksurfaces;
pxl8_bsp_material_batch* material_batches; pxl8_gfx_material* materials;
pxl8_bsp_model* models; pxl8_bsp_model* models;
pxl8_bsp_node* nodes; pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes; pxl8_bsp_plane* planes;
u8* render_face_flags;
i32* surfedges; i32* surfedges;
pxl8_bsp_texinfo* texinfo;
u32* vertex_lights; u32* vertex_lights;
pxl8_bsp_vertex* vertices; pxl8_bsp_vertex* vertices;
u8* visdata; u8* visdata;
u32 lightdata_size; u32 lightdata_size;
u32 num_cell_portals;
u32 num_edges; u32 num_edges;
u32 num_faces; u32 num_faces;
u32 num_leafs; u32 num_leafs;
u32 num_lightmaps; u32 num_lightmaps;
u32 num_marksurfaces; u32 num_marksurfaces;
u32 num_material_batches; u32 num_materials;
u32 num_models; u32 num_models;
u32 num_nodes; u32 num_nodes;
u32 num_planes; u32 num_planes;
u32 num_surfedges; u32 num_surfedges;
u32 num_texinfo;
u32 num_vertex_lights; u32 num_vertex_lights;
u32 num_vertices; u32 num_vertices;
u32 visdata_size; u32 visdata_size;
@ -155,9 +150,8 @@ pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset); pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v); pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id); void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos); void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -1,21 +1,40 @@
#include "pxl8_gen.h" #include "pxl8_gen.h"
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_rng.h" #include "pxl8_rng.h"
#define CELL_SIZE 64.0f
#define PVS_MAX_DEPTH 64
#define WALL_HEIGHT 128.0f
typedef struct room_grid { typedef struct room_grid {
u8* cells; u8* cells;
i32 width; i32 width;
i32 height; i32 height;
} room_grid; } room_grid;
typedef struct bsp_build_context {
pxl8_bsp* bsp;
const room_grid* grid;
u32 node_count;
u32 plane_offset;
} bsp_build_context;
typedef struct light_source {
pxl8_vec3 position;
f32 intensity;
f32 radius;
} light_source;
static bool room_grid_init(room_grid* grid, i32 width, i32 height) { static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
grid->width = width; grid->width = width;
grid->height = height; grid->height = height;
grid->cells = calloc(width * height, sizeof(u8)); grid->cells = pxl8_calloc(width * height, sizeof(u8));
return grid->cells != NULL; return grid->cells != NULL;
} }
@ -57,6 +76,333 @@ static void room_grid_fill(room_grid* grid, u8 value) {
} }
} }
static f32 compute_vertex_light(
pxl8_vec3 pos,
pxl8_vec3 normal,
const light_source* lights,
u32 num_lights,
f32 ambient
) {
f32 total = ambient;
for (u32 i = 0; i < num_lights; i++) {
pxl8_vec3 to_light = {
lights[i].position.x - pos.x,
lights[i].position.y - pos.y,
lights[i].position.z - pos.z
};
f32 dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
f32 dist = sqrtf(dist_sq);
if (dist > lights[i].radius) continue;
if (dist < 1.0f) dist = 1.0f;
f32 inv_dist = 1.0f / dist;
pxl8_vec3 light_dir = {
to_light.x * inv_dist,
to_light.y * inv_dist,
to_light.z * inv_dist
};
f32 ndotl = normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z;
if (ndotl < 0) ndotl = 0;
f32 attenuation = 1.0f - (dist / lights[i].radius);
if (attenuation < 0) attenuation = 0;
attenuation *= attenuation;
total += lights[i].intensity * ndotl * attenuation;
}
if (total > 1.0f) total = 1.0f;
return total;
}
static void compute_bsp_vertex_lighting(
pxl8_bsp* bsp,
const light_source* lights,
u32 num_lights,
f32 ambient
) {
if (!bsp || bsp->num_vertices == 0) return;
bsp->vertex_lights = pxl8_calloc(bsp->num_vertices, sizeof(u32));
if (!bsp->vertex_lights) return;
bsp->num_vertex_lights = bsp->num_vertices;
for (u32 f = 0; f < bsp->num_faces; f++) {
const pxl8_bsp_face* face = &bsp->faces[f];
pxl8_vec3 normal = {0, 1, 0};
if (face->plane_id < bsp->num_planes) {
normal = bsp->planes[face->plane_id].normal;
}
for (u32 e = 0; e < face->num_edges; e++) {
i32 surfedge_idx = face->first_edge + e;
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
i32 edge_idx = bsp->surfedges[surfedge_idx];
u32 vert_idx;
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) continue;
vert_idx = bsp->edges[edge_idx].vertex[0];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) continue;
vert_idx = bsp->edges[edge_idx].vertex[1];
}
if (vert_idx >= bsp->num_vertices) continue;
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 light = compute_vertex_light(pos, normal, lights, num_lights, ambient);
u8 light_byte = (u8)(light * 255.0f);
bsp->vertex_lights[vert_idx] = ((u32)light_byte << 24) | 0x00FFFFFF;
}
}
pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights);
}
static pxl8_bsp_cell_portals* build_pxl8_bsp_cell_portals(const room_grid* grid, f32 cell_size) {
i32 total_cells = grid->width * grid->height;
pxl8_bsp_cell_portals* portals = pxl8_calloc(total_cells, sizeof(pxl8_bsp_cell_portals));
if (!portals) return NULL;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) != 0) continue;
i32 c = y * grid->width + x;
f32 cx = x * cell_size;
f32 cz = y * cell_size;
if (x > 0 && room_grid_get(grid, x - 1, y) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx;
p->z0 = cz;
p->x1 = cx;
p->z1 = cz + cell_size;
p->target_leaf = y * grid->width + (x - 1);
}
if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size;
p->z0 = cz + cell_size;
p->x1 = cx + cell_size;
p->z1 = cz;
p->target_leaf = y * grid->width + (x + 1);
}
if (y > 0 && room_grid_get(grid, x, y - 1) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx + cell_size;
p->z0 = cz;
p->x1 = cx;
p->z1 = cz;
p->target_leaf = (y - 1) * grid->width + x;
}
if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) {
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
p->x0 = cx;
p->z0 = cz + cell_size;
p->x1 = cx + cell_size;
p->z1 = cz + cell_size;
p->target_leaf = (y + 1) * grid->width + x;
}
}
}
return portals;
}
typedef struct flood_entry {
u32 leaf;
u32 depth;
} flood_entry;
static void portal_flood_bfs(
u32 start_leaf,
const pxl8_bsp_cell_portals* portals,
const pxl8_bsp_leaf* leafs,
u8* pvs,
u32 num_leafs,
f32 cell_size,
i32 grid_width
) {
(void)cell_size;
(void)grid_width;
u32 pvs_bytes = (num_leafs + 7) / 8;
u8* visited = pxl8_calloc(pvs_bytes, 1);
flood_entry* queue = pxl8_malloc(num_leafs * sizeof(flood_entry));
if (!visited || !queue) {
pxl8_free(visited);
pxl8_free(queue);
return;
}
u32 head = 0, tail = 0;
pvs[start_leaf >> 3] |= (1 << (start_leaf & 7));
visited[start_leaf >> 3] |= (1 << (start_leaf & 7));
queue[tail++] = (flood_entry){start_leaf, 0};
while (head < tail) {
flood_entry e = queue[head++];
if (e.depth >= PVS_MAX_DEPTH) continue;
if (leafs[e.leaf].contents == -1) continue;
const pxl8_bsp_cell_portals* cp = &portals[e.leaf];
for (u8 i = 0; i < cp->num_portals; i++) {
u32 target = cp->portals[i].target_leaf;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) continue;
visited[byte] |= (1 << bit);
if (leafs[target].contents == -1) continue;
pvs[byte] |= (1 << bit);
queue[tail++] = (flood_entry){target, e.depth + 1};
}
}
pxl8_free(visited);
pxl8_free(queue);
}
static u8* compute_leaf_pvs(u32 start_leaf, const pxl8_bsp_cell_portals* portals,
u32 num_leafs, const pxl8_bsp_leaf* leafs,
const room_grid* grid, f32 cell_size) {
u32 pvs_bytes = (num_leafs + 7) / 8;
u8* pvs = pxl8_calloc(pvs_bytes, 1);
if (!pvs) return NULL;
portal_flood_bfs(start_leaf, portals, leafs, pvs, num_leafs, cell_size, grid->width);
return pvs;
}
static u32 rle_compress_pvs(const u8* pvs, u32 pvs_bytes, u8* out) {
u32 out_pos = 0;
u32 i = 0;
while (i < pvs_bytes) {
if (pvs[i] != 0) {
out[out_pos++] = pvs[i++];
} else {
u32 count = 0;
while (i < pvs_bytes && pvs[i] == 0 && count < 255) {
count++;
i++;
}
out[out_pos++] = 0;
out[out_pos++] = (u8)count;
}
}
return out_pos;
}
static pxl8_result build_pvs_data(pxl8_bsp* bsp, const pxl8_bsp_cell_portals* portals,
const room_grid* grid, f32 cell_size) {
u32 num_leafs = bsp->num_leafs;
u32 pvs_bytes = (num_leafs + 7) / 8;
u32 max_visdata = num_leafs * pvs_bytes * 2;
u8* visdata = pxl8_malloc(max_visdata);
if (!visdata) return PXL8_ERROR_OUT_OF_MEMORY;
u32 visdata_pos = 0;
u8* compressed = pxl8_malloc(pvs_bytes * 2);
if (!compressed) {
pxl8_free(visdata);
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 debug_count = 0;
for (u32 leaf = 0; leaf < num_leafs; leaf++) {
if (bsp->leafs[leaf].contents == -1) {
bsp->leafs[leaf].visofs = -1;
continue;
}
u8* pvs = compute_leaf_pvs(leaf, portals, num_leafs, bsp->leafs, grid, cell_size);
if (!pvs) {
pxl8_free(compressed);
pxl8_free(visdata);
return PXL8_ERROR_OUT_OF_MEMORY;
}
if (debug_count < 3) {
u32 visible = 0;
for (u32 b = 0; b < pvs_bytes; b++) {
for (u32 i = 0; i < 8; i++) {
if (pvs[b] & (1 << i)) visible++;
}
}
pxl8_debug("Leaf %u PVS: %u cells visible (portals: %u)", leaf, visible, portals[leaf].num_portals);
debug_count++;
}
u32 compressed_size = rle_compress_pvs(pvs, pvs_bytes, compressed);
bsp->leafs[leaf].visofs = visdata_pos;
memcpy(visdata + visdata_pos, compressed, compressed_size);
visdata_pos += compressed_size;
pxl8_free(pvs);
}
pxl8_free(compressed);
bsp->visdata = pxl8_realloc(visdata, visdata_pos > 0 ? visdata_pos : 1);
bsp->visdata_size = visdata_pos;
pxl8_debug("Built PVS: %u leafs, %u bytes visdata", num_leafs, visdata_pos);
return PXL8_OK;
}
static i32 build_bsp_node(bsp_build_context* ctx, i32 x0, i32 y0, i32 x1, i32 y1, i32 depth) {
if (x1 - x0 == 1 && y1 - y0 == 1) {
i32 leaf_idx = y0 * ctx->grid->width + x0;
return -(leaf_idx + 1);
}
i32 node_idx = ctx->node_count++;
pxl8_bsp_node* node = &ctx->bsp->nodes[node_idx];
i32 plane_idx = ctx->plane_offset++;
pxl8_bsp_plane* plane = &ctx->bsp->planes[plane_idx];
node->plane_id = plane_idx;
if (depth % 2 == 0) {
i32 mid_x = (x0 + x1) / 2;
f32 split_pos = mid_x * CELL_SIZE;
plane->normal = (pxl8_vec3){1, 0, 0};
plane->dist = split_pos;
node->children[0] = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1);
node->children[1] = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1);
} else {
i32 mid_y = (y0 + y1) / 2;
f32 split_pos = mid_y * CELL_SIZE;
plane->normal = (pxl8_vec3){0, 0, 1};
plane->dist = split_pos;
node->children[0] = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1);
node->children[1] = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1);
}
return node_idx;
}
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) { static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
i32 vertex_count = 0; i32 vertex_count = 0;
i32 face_count = 0; i32 face_count = 0;
@ -74,37 +420,47 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
} }
face_count += floor_ceiling_count * 2; face_count += floor_ceiling_count;
vertex_count = face_count * 4; vertex_count = face_count * 4;
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices", pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices",
grid->width, grid->height, face_count, vertex_count); grid->width, grid->height, face_count, vertex_count);
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex)); i32 total_cells = grid->width * grid->height;
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face)); u32 max_nodes = 2 * total_cells;
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane)); u32 total_planes = face_count + max_nodes;
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = calloc(vertex_count, sizeof(i32));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) { bsp->vertices = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_vertex));
bsp->faces = pxl8_calloc(face_count, sizeof(pxl8_bsp_face));
bsp->planes = pxl8_calloc(total_planes, sizeof(pxl8_bsp_plane));
bsp->edges = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = pxl8_calloc(vertex_count, sizeof(i32));
bsp->nodes = pxl8_calloc(max_nodes, sizeof(pxl8_bsp_node));
u32* face_cell = pxl8_calloc(face_count, sizeof(u32));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges ||
!bsp->surfedges || !bsp->nodes || !face_cell) {
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->texinfo = NULL; bsp->materials = NULL;
bsp->num_texinfo = 0; bsp->num_materials = 0;
i32 vert_idx = 0; i32 vert_idx = 0;
i32 face_idx = 0; i32 face_idx = 0;
i32 edge_idx = 0; i32 edge_idx = 0;
const f32 cell_size = 64.0f; const f32 cell_size = CELL_SIZE;
const f32 wall_height = 128.0f; const f32 wall_height = WALL_HEIGHT;
for (i32 y = 0; y < grid->height; y++) { for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) { for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) { if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size; f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size; f32 fy = (f32)y * cell_size;
i32 cell_idx = y * grid->width + x;
if (room_grid_get(grid, x - 1, y) == 1) { if (room_grid_get(grid, x - 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
@ -112,13 +468,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size}; bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size}; bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0}; bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
bsp->planes[face_idx].dist = -fx; bsp->planes[face_idx].dist = fx;
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -127,6 +483,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -139,13 +496,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size}; bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy}; bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0}; bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
bsp->planes[face_idx].dist = fx + cell_size; bsp->planes[face_idx].dist = -(fx + cell_size);
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -154,6 +511,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -166,13 +524,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy}; bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy}; bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1}; bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
bsp->planes[face_idx].dist = -fy; bsp->planes[face_idx].dist = fy;
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -181,6 +539,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -193,13 +552,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size}; bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size}; bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1}; bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
bsp->planes[face_idx].dist = fy + cell_size; bsp->planes[face_idx].dist = -(fy + cell_size);
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -208,6 +567,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -222,6 +582,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
if (room_grid_get(grid, x, y) == 0) { if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size; f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size; f32 fy = (f32)y * cell_size;
i32 cell_idx = y * grid->width + x;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size}; bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
@ -234,32 +595,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx; bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0; bsp->faces[face_idx].material_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
bsp->planes[face_idx].dist = -wall_height;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) { for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
@ -268,6 +604,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -278,28 +615,147 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->num_vertices = vertex_count; bsp->num_vertices = vertex_count;
bsp->num_faces = face_count; bsp->num_faces = face_count;
bsp->num_planes = face_count;
bsp->num_edges = vertex_count; bsp->num_edges = vertex_count;
bsp->num_surfedges = vertex_count; bsp->num_surfedges = vertex_count;
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf)); bsp->leafs = pxl8_calloc(total_cells, sizeof(pxl8_bsp_leaf));
bsp->marksurfaces = calloc(face_count, sizeof(u16)); bsp->marksurfaces = pxl8_calloc(face_count, sizeof(u16));
if (!bsp->leafs || !bsp->marksurfaces) { if (!bsp->leafs || !bsp->marksurfaces) {
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->num_leafs = 1; bsp->num_leafs = total_cells;
bsp->num_marksurfaces = face_count; bsp->num_marksurfaces = face_count;
bsp->leafs[0].first_marksurface = 0; u32* faces_per_cell = pxl8_calloc(total_cells, sizeof(u32));
bsp->leafs[0].num_marksurfaces = face_count; u32* cell_offset = pxl8_calloc(total_cells, sizeof(u32));
bsp->leafs[0].contents = -2; u32* cell_cursor = pxl8_calloc(total_cells, sizeof(u32));
if (!faces_per_cell || !cell_offset || !cell_cursor) {
pxl8_free(faces_per_cell);
pxl8_free(cell_offset);
pxl8_free(cell_cursor);
pxl8_free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY;
}
for (i32 i = 0; i < face_count; i++) { for (i32 i = 0; i < face_count; i++) {
bsp->marksurfaces[i] = i; faces_per_cell[face_cell[i]]++;
} }
u32 offset = 0;
for (i32 c = 0; c < total_cells; c++) {
cell_offset[c] = offset;
offset += faces_per_cell[c];
}
for (i32 i = 0; i < face_count; i++) {
u32 c = face_cell[i];
bsp->marksurfaces[cell_offset[c] + cell_cursor[c]++] = i;
}
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
i32 c = y * grid->width + x;
pxl8_bsp_leaf* leaf = &bsp->leafs[c];
f32 fx = (f32)x * cell_size;
f32 fz = (f32)y * cell_size;
leaf->mins[0] = (i16)fx;
leaf->mins[1] = 0;
leaf->mins[2] = (i16)fz;
leaf->maxs[0] = (i16)(fx + cell_size);
leaf->maxs[1] = (i16)wall_height;
leaf->maxs[2] = (i16)(fz + cell_size);
if (room_grid_get(grid, x, y) == 0) {
leaf->contents = -2;
leaf->first_marksurface = cell_offset[c];
leaf->num_marksurfaces = faces_per_cell[c];
} else {
leaf->contents = -1;
leaf->first_marksurface = 0;
leaf->num_marksurfaces = 0;
}
leaf->visofs = -1;
}
}
pxl8_free(faces_per_cell);
pxl8_free(cell_offset);
pxl8_free(cell_cursor);
pxl8_free(face_cell);
bsp_build_context ctx = {
.bsp = bsp,
.grid = grid,
.node_count = 0,
.plane_offset = face_count,
};
build_bsp_node(&ctx, 0, 0, grid->width, grid->height, 0);
bsp->num_nodes = ctx.node_count;
bsp->num_planes = ctx.plane_offset;
pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes",
bsp->num_nodes, bsp->num_leafs, bsp->num_planes);
pxl8_bsp_cell_portals* portals = build_pxl8_bsp_cell_portals(grid, cell_size);
if (!portals) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 walkable_cells = 0;
u32 total_portals = 0;
i32 first_walkable = -1;
for (i32 c = 0; c < total_cells; c++) {
if (bsp->leafs[c].contents == -2) {
walkable_cells++;
if (first_walkable < 0) first_walkable = c;
}
total_portals += portals[c].num_portals;
}
pxl8_debug("Portal stats: %u walkable cells, %u total portals (avg %.1f per cell)",
walkable_cells, total_portals, (f32)total_portals / walkable_cells);
if (first_walkable >= 0) {
u32 pvs_bytes = (total_cells + 7) / 8;
u8* visited = pxl8_calloc(pvs_bytes, 1);
u8* queue = pxl8_malloc(total_cells * sizeof(u32));
u32 head = 0, tail = 0;
((u32*)queue)[tail++] = first_walkable;
visited[first_walkable >> 3] |= (1 << (first_walkable & 7));
u32 reachable = 0;
while (head < tail) {
u32 c = ((u32*)queue)[head++];
reachable++;
for (u8 i = 0; i < portals[c].num_portals; i++) {
u32 n = portals[c].portals[i].target_leaf;
u32 nb = n >> 3, ni = n & 7;
if (!(visited[nb] & (1 << ni))) {
visited[nb] |= (1 << ni);
((u32*)queue)[tail++] = n;
}
}
}
pxl8_debug("Connectivity: %u/%u walkable cells reachable from leaf %d",
reachable, walkable_cells, first_walkable);
pxl8_free(visited);
pxl8_free(queue);
}
pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size);
if (pvs_result != PXL8_OK) {
pxl8_free(portals);
return pvs_result;
}
bsp->cell_portals = portals;
bsp->num_cell_portals = total_cells;
return PXL8_OK; return PXL8_OK;
} }
@ -348,6 +804,9 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
i32 room_count = 0; i32 room_count = 0;
i32 max_attempts = params->num_rooms * 10; i32 max_attempts = params->num_rooms * 10;
const f32 cell_size = CELL_SIZE;
const f32 light_height = 80.0f;
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) { for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1)); i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1)); i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
@ -394,7 +853,27 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
params->width, params->height, room_count); params->width, params->height, room_count);
pxl8_result result = grid_to_bsp(bsp, &grid); pxl8_result result = grid_to_bsp(bsp, &grid);
free(grid.cells); pxl8_free(grid.cells);
if (result != PXL8_OK) {
return result;
}
light_source lights[256];
u32 num_lights = 0;
for (i32 i = 0; i < room_count && num_lights < 256; i++) {
f32 cx = (rooms[i].x + rooms[i].w / 2.0f) * cell_size;
f32 cy = (rooms[i].y + rooms[i].h / 2.0f) * cell_size;
lights[num_lights++] = (light_source){
.position = {cx, light_height, cy},
.intensity = 0.8f,
.radius = 300.0f,
};
}
compute_bsp_vertex_lighting(bsp, lights, num_lights, 0.1f);
return result; return result;
} }
@ -417,88 +896,3 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
return PXL8_ERROR_INVALID_ARGUMENT; return PXL8_ERROR_INVALID_ARGUMENT;
} }
} }
static u32 hash2d(i32 x, i32 y) {
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
h ^= h >> 13;
h ^= h << 17;
h ^= h >> 5;
return h;
}
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
if (!buffer || !params) return;
for (i32 y = 0; y < params->height; y++) {
for (i32 x = 0; x < params->width; x++) {
f32 u = (f32)x / (f32)params->width;
f32 v = (f32)y / (f32)params->height;
u8 color = params->base_color;
// Tile-based pattern (floor style)
if (params->seed == 11111) {
i32 tile_x = (i32)floorf(u * 8.0f);
i32 tile_y = (i32)floorf(v * 8.0f);
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
color = params->base_color + quantized;
// Checkerboard dither
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
color = (color < 255) ? color + 1 : color;
}
}
// Large tile pattern (ceiling style)
else if (params->seed == 22222) {
i32 coarse_x = (i32)floorf(u * 2.0f);
i32 coarse_y = (i32)floorf(v * 2.0f);
u32 coarse_h = hash2d(coarse_x, coarse_y);
i32 subdivision = (coarse_h >> 8) & 0x3;
i32 tile_x, tile_y;
switch (subdivision) {
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
}
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
if (pattern < 0.25f) color = params->base_color;
else if (pattern < 0.50f) color = params->base_color + 1;
else if (pattern < 0.75f) color = params->base_color + 2;
else color = params->base_color + 3;
}
// Brick pattern (wall style)
else {
f32 brick_y = floorf(v * 4.0f);
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
i32 brick_x = (i32)floorf(u * 4.0f + offset);
brick_y = (i32)brick_y;
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
u32 h = hash2d(brick_x, (i32)brick_y);
f32 noise = (f32)(h & 0xFF) / 255.0f;
// Mortar lines
if (brick_u > 0.47f || brick_v > 0.47f) {
color = params->base_color - 2;
} else {
i32 shade = (i32)(noise * 3.0f);
color = params->base_color + shade;
}
}
buffer[y * params->width + x] = color;
}
}
}

View file

@ -21,24 +21,11 @@ typedef struct pxl8_procgen_params {
i32 num_rooms; i32 num_rooms;
} pxl8_procgen_params; } pxl8_procgen_params;
typedef struct pxl8_procgen_tex_params {
char name[16];
u32 seed;
i32 width;
i32 height;
f32 scale;
f32 roughness;
u8 base_color;
u8 variation;
u8 max_color;
} pxl8_procgen_tex_params;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params); pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -7,23 +7,21 @@
#include "pxl8_gen.h" #include "pxl8_gen.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mem.h"
struct pxl8_world { struct pxl8_world {
pxl8_bsp bsp; pxl8_bsp bsp;
bool loaded; bool loaded;
bool wireframe;
u32 wireframe_color;
}; };
pxl8_world* pxl8_world_create(void) { pxl8_world* pxl8_world_create(void) {
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world)); pxl8_world* world = (pxl8_world*)pxl8_calloc(1, sizeof(pxl8_world));
if (!world) { if (!world) {
pxl8_error("Failed to allocate world"); pxl8_error("Failed to allocate world");
return NULL; return NULL;
} }
world->loaded = false; world->loaded = false;
world->wireframe_color = 15;
return world; return world;
} }
@ -35,7 +33,7 @@ void pxl8_world_destroy(pxl8_world* world) {
pxl8_bsp_destroy(&world->bsp); pxl8_bsp_destroy(&world->bsp);
} }
free(world); pxl8_free(world);
} }
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) { pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
@ -98,12 +96,12 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
pxl8_bsp* bsp = &world->bsp; pxl8_bsp* bsp = &world->bsp;
u32 max_texinfo = count * 6; u32 max_materials = count * 6;
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo)); bsp->materials = pxl8_calloc(max_materials, sizeof(pxl8_gfx_material));
if (!bsp->texinfo) { if (!bsp->materials) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->num_texinfo = 0; bsp->num_materials = 0;
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) { for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
pxl8_bsp_face* face = &bsp->faces[face_idx]; pxl8_bsp_face* face = &bsp->faces[face_idx];
@ -136,45 +134,50 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
} }
u32 texinfo_idx = bsp->num_texinfo; u32 material_idx = bsp->num_materials;
bool found_existing = false; bool found_existing = false;
for (u32 i = 0; i < bsp->num_texinfo; i++) { for (u32 i = 0; i < bsp->num_materials; i++) {
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 && if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
bsp->texinfo[i].miptex == matched->texture_id && bsp->materials[i].texture_id == matched->texture_id &&
bsp->texinfo[i].u_axis.x == u_axis.x && bsp->materials[i].u_axis.x == u_axis.x &&
bsp->texinfo[i].u_axis.y == u_axis.y && bsp->materials[i].u_axis.y == u_axis.y &&
bsp->texinfo[i].u_axis.z == u_axis.z && bsp->materials[i].u_axis.z == u_axis.z &&
bsp->texinfo[i].v_axis.x == v_axis.x && bsp->materials[i].v_axis.x == v_axis.x &&
bsp->texinfo[i].v_axis.y == v_axis.y && bsp->materials[i].v_axis.y == v_axis.y &&
bsp->texinfo[i].v_axis.z == v_axis.z) { bsp->materials[i].v_axis.z == v_axis.z) {
texinfo_idx = i; material_idx = i;
found_existing = true; found_existing = true;
break; break;
} }
} }
if (!found_existing) { if (!found_existing) {
if (bsp->num_texinfo >= max_texinfo) { if (bsp->num_materials >= max_materials) {
pxl8_error("Too many unique texinfo entries"); pxl8_error("Too many unique material entries");
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name)); pxl8_gfx_material* mat = &bsp->materials[material_idx];
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0'; memcpy(mat->name, matched->name, sizeof(mat->name));
bsp->texinfo[texinfo_idx].miptex = matched->texture_id; mat->name[sizeof(mat->name) - 1] = '\0';
bsp->texinfo[texinfo_idx].u_offset = 0.0f; mat->texture_id = matched->texture_id;
bsp->texinfo[texinfo_idx].v_offset = 0.0f; mat->u_offset = 0.0f;
bsp->texinfo[texinfo_idx].u_axis = u_axis; mat->v_offset = 0.0f;
bsp->texinfo[texinfo_idx].v_axis = v_axis; mat->u_axis = u_axis;
mat->v_axis = v_axis;
mat->alpha = 255;
mat->dither = true;
mat->double_sided = true;
mat->dynamic_lighting = true;
bsp->num_texinfo++; bsp->num_materials++;
} }
face->texinfo_id = texinfo_idx; face->material_id = material_idx;
} }
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries", pxl8_info("Applied %u textures to %u faces, created %u materials",
count, bsp->num_faces, bsp->num_texinfo); count, bsp->num_faces, bsp->num_materials);
return PXL8_OK; return PXL8_OK;
} }
@ -398,9 +401,13 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
return; return;
} }
if (world->wireframe) { pxl8_bsp_render(gfx, &world->bsp, camera_pos);
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); }
} else {
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos); void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
if (!world || !world->loaded) return;
for (u32 i = 0; i < world->bsp.num_materials; i++) {
world->bsp.materials[i].wireframe = enabled;
} }
} }

View file

@ -32,6 +32,7 @@ bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radi
bool pxl8_world_is_loaded(const pxl8_world* world); bool pxl8_world_is_loaded(const pxl8_world* world);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
#ifdef __cplusplus #ifdef __cplusplus
} }