This commit is contained in:
water111
2026-08-19 22:24:34 -07:00
parent ac70997e9e
commit 02fef1fe63
3 changed files with 45 additions and 166 deletions
@@ -54,11 +54,7 @@
(defun-debug print-terrain-stats ()
"Print active-level memory use and every renderer-statistics bucket, update the display strings,
and clear the counters for the next reporting interval."
;; make our own profile thing.
(if *debug-segment*
(add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0)
'draw
(new 'static 'rgba :r #x40 :b #x40 :a #x80)))
(add-profile-frame! #x40 0 #x40 #x80 'draw)
(dotimes (i (-> *level* length))
(let ((lev (-> *level* level i)))
(when (= (-> lev status) 'active)
@@ -68,7 +64,6 @@
(-> lev index)
(-> lev name)
(* 0.0009765625 (the float (-> lev mem-usage)))))))
;; print all stats
(format *stdcon* "~0k~%grps frags tris dverts strip insts~%")
(print-tr-stat (-> *terrain-stats* pris) "pris" *temp-string*)
(print-tr-stat (-> *merc-global-stats* merc) "merc" *temp-string*)
@@ -107,11 +102,11 @@
(clear-tr-stat (-> *terrain-stats* ocean-mid))
(clear-tr-stat (-> *terrain-stats* ocean-near))
(clear-tr-stat (-> *terrain-stats* total))
(if *debug-segment*
(add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) 'draw (new 'static 'rgba :b #xff :a #x80)))
(add-profile-frame! 0 0 #xff #x80 'draw)
0
(none))
(defun update-subdivide-settings! ((settings subdivide-settings) (math-cam math-camera) (idx int))
"Select a close/far LOD profile, construct five nonuniform world-distance thresholds, convert
them to camera-scaled distances, and update the tfragment thresholds."
@@ -125,14 +120,14 @@
(set! (-> settings meters 3) (+ (-> settings meters 4) (/ band-unit 2)))
(set! (-> settings meters 2) (+ (-> settings meters 3) band-unit))
(set! (-> settings meters 1) (+ (-> settings meters 2) (* 2.0 band-unit))))
;; VU code compares against homogeneous camera-space distance, so scale the world thresholds
;; once here instead of converting each fragment during subdivision. The EE classifier wants the
;; unscaled world values instead, because it works from a bounding sphere in world units; it gets
;; them negated so it can add rather than subtract. meters[3] is computed for the record and has no
;; consumer on either side.
;; On the VU, tfrag compares the FOG value, which is proportional to distance
;; from the camera. This scale factor inverts it. inv-hmge-scale.w is the fog range and
;; d converts from homogenous coordinates to actual distance in meters
(let ((camera-scale (/ (-> math-cam inv-hmge-scale w) (-> math-cam d))))
(dotimes (i 5)
(set! (-> settings dist i) (* camera-scale (-> settings meters i)))))
;; On the EE, the DMA generation code looks at world space positions and expects negated distances.
(set! (-> *tfrag-work* frag-dists x) (- (-> settings meters 0)))
(set! (-> *tfrag-work* frag-dists y) (- (-> settings meters 1)))
(set! (-> *tfrag-work* frag-dists z) (- (-> settings meters 2)))
@@ -143,28 +138,24 @@
(define *subdivide-settings* (new 'global 'subdivide-settings (meters 30) (meters 70)))
(defun set-tfrag-dists! ((dists tfrag-dists))
"Fill the affine collapse coefficients for the two tfragment subdivision levels. For distance d in
[near, far], y is (far - d) / (far - near), x is half of (d - near) / (far - near), and w stores
the far boundary, which is the distance at which that level has finished collapsing."
;; Each k0*d + k1 pair evaluates the descending y weight and half-scaled ascending x weight
;; without a divide in the VU program. Entry 0 is level 0 and covers dist[0] down to dist[1];
;; entry 1 is level 1 and covers dist[1] down to dist[2].
;;
;; The VU applies these as pos = pos * y + (parent-a + parent-b) * x, so at y = 0, x = 0.5 a
;; subdivided vertex is sitting exactly on the midpoint of the parent edge it splits and the
;; coarser mesh can take over without moving anything. Everything in here is per frame and per
;; level, never per fragment: that is what makes two terrain patches agree on where a vertex they
;; both own has got to, which is why terrain does not crack along a detail boundary.
;;
;; The w lanes are the same numbers again, kept unscaled so the VU can compare a transformed w
;; against them directly for its discrete "both parents have finished collapsing" test. Remember
;; these are camera-scaled through inv-hmge-scale.w, which is the fog slope and is negative, so
;; dist[] is negative and grows more negative with distance.
"Fill the affine collapse coefficients for the two tfragment subdivision levels."
;; The VU computes weights.xy = k0.xy * pos.w + k1.xy
;; where
;; weights.x goes from 0 -> 0.5
;; weights.y goes from 1 -> 0
;; and
;; pos = p * weights.y + (parent-a + parent-b) * weights.x
;; so
;; pos goes from "p" to the midpoint of the parents as the level of detail drops.
(let ((far-0 (-> *subdivide-settings* dist 0))
(boundary-1 (-> *subdivide-settings* dist 1))
(near-2 (-> *subdivide-settings* dist 2)))
;; also store the boundaries so VU can check if both parents are beyond the threshold and
;; collapse.
(set! (-> dists k0s 0 w) far-0)
(set! (-> dists k0s 1 w) boundary-1)
;; find k0/k1 coefficients
(let ((inverse-span-0 (/ 1.0 (- far-0 boundary-1)))
(inverse-span-1 (/ 1.0 (- boundary-1 near-2))))
(set! (-> dists k0s 0 y) (- inverse-span-0))
@@ -21,9 +21,7 @@
(giftag gs-gif-tag :inline)
(color qword :inline)))
;; VU1 memory for the distorter. The tables are uploaded starting at entry, so every address in the
;; program is that base plus a quadword index into the type -- change the type and the program
;; follows. The count word and the input area are not part of any type and stay plain constants.
;; VU1 memory for the distorter.
;;
;; 352 - 479 entry: 128 quadwords, alternating screen direction and framebuffer ST offset
;; 480 - 488 ientry: the first direction of each turn count from 3 through 11
@@ -102,24 +100,8 @@
(new 'static 'gs-prim :prim (gs-prim-type tri-strip) :tme #x1)
:nreg #xf))
;; set up registers
(set! (-> tables giftag regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id st)
:regs1 (gif-reg-id rgbaq)
:regs2 (gif-reg-id xyzf2)
:regs3 (gif-reg-id st)
:regs4 (gif-reg-id rgbaq)
:regs5 (gif-reg-id xyzf2)
:regs6 (gif-reg-id st)
:regs7 (gif-reg-id rgbaq)
:regs8 (gif-reg-id xyzf2)
:regs9 (gif-reg-id st)
:regs10 (gif-reg-id rgbaq)
:regs11 (gif-reg-id xyzf2)
:regs12 (gif-reg-id st)
:regs13 (gif-reg-id rgbaq)
:regs14 (gif-reg-id xyzf2)))
(set! (-> tables giftag regs) (gs-reg-list st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2))
;; set color
(set! (-> tables color vector4w x) 128)
(set! (-> tables color vector4w y) 128)
+21 -115
View File
@@ -93,8 +93,7 @@
(none))
(defun add-to-sprite-aux-list ((system sparticle-system) (cpuinfo sparticle-cpuinfo) (sprite-data sprite-vec-data-3d))
"Record a particle's four-byte 2D warp-sprite reference in the auxiliary list when space remains,
then clear the submitted 3D sprite's alpha. This is a sparticle callback."
"Sparticle callback to add a sprite to the aux-list."
(let ((aux-list *sprite-aux-list*))
(when (< (-> aux-list entry) (-> aux-list num-entries))
(set! (-> aux-list data (-> aux-list entry)) (-> cpuinfo sprite))
@@ -135,14 +134,11 @@
(fog-max float :overlay-at (-> fog-clamp y))
(max-scale float :overlay-at (-> fog-clamp z))))
;; Sprite VU1 memory map. Nothing here is a bare number twice: the input buffer is described by the
;; per-chunk packet layout and the constants by sprite-frame-data, whose field order is its upload
;; order, so both ends of the DMA name the same thing.
;;
;; Sprite VU1 memory map.
;; 0 chunk header: the sprite count for this batch
;; 1 - 144 48 sprites x 3 vector quadwords
;; 145 - 384 48 sprites x 5 adgif quadwords
;; 400 - 799 the same layout again -- VIF's own double buffer, selected with BASE and OFFSET
;; 400 - 799 Same as 0-399, double buffered input
;; 800 - 849 GIF output buffer 0
;; 850 - 899 GIF output buffer 1
;; 900 - 903 the active transform
@@ -170,15 +166,12 @@
(defconstant SPRITE-VU-USER-HVDF (+ SPRITE-VU-HVDF 1))
;; The frame constants. sprite-frame-data is uploaded as one 41-quadword block and its field order is
;; its upload order, so every constant the microprogram loads can name a field instead of a number.
(defconstant SPRITE-VU-FRAME-DATA 980)
(defmacro sprite-frame-const (&rest path)
`(+ SPRITE-VU-FRAME-DATA (/ (offset-of sprite-frame-data ,@path) 16)))
;; VU1 entry points. Entry 0 loads the constants that outlive a chunk; the other three are one
;; MSCAL per chunk.
;; VU1 entry points.
(defconstant SPRITE-VU-ENTRY-INIT 0)
(defconstant SPRITE-VU-ENTRY-2D-WORLD 3)
@@ -198,14 +191,7 @@
;; the A+D mode
;; each adgif has 5x registers, so we set up a tag to do 5x a+d's
(set! (-> data adgif-giftag tag) (new 'static 'gif-tag64 :nloop #x1 :nreg #x5))
(set! (-> data adgif-giftag regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id a+d)
:regs1 (gif-reg-id a+d)
:regs2 (gif-reg-id a+d)
:regs3 (gif-reg-id a+d)
:regs4 (gif-reg-id a+d)))
(set! (-> data adgif-giftag regs) (gs-reg-list a+d a+d a+d a+d a+d))
;; World-space 2D sprites use one shared color followed by four ST/XYZF2 pairs.
(set! (-> data sprite-2d-giftag tag)
(new 'static
@@ -216,18 +202,8 @@
:prim
(new 'static 'gs-prim :prim (gs-prim-type tri-fan) :tme #x1 :fge #x1 :abe #x1)
:nreg #x9))
(set! (-> data sprite-2d-giftag regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id rgbaq)
:regs1 (gif-reg-id st)
:regs2 (gif-reg-id xyzf2)
:regs3 (gif-reg-id st)
:regs4 (gif-reg-id xyzf2)
:regs5 (gif-reg-id st)
:regs6 (gif-reg-id xyzf2)
:regs7 (gif-reg-id st)
:regs8 (gif-reg-id xyzf2)))
(set! (-> data sprite-2d-giftag regs) (gs-reg-list rgbaq st xyzf2 st xyzf2 st xyzf2 st xyzf2))
;; Screen-space 2D sprites use the same vertex layout without fog.
(set! (-> data sprite-2d-giftag-2 tag)
(new 'static
@@ -238,18 +214,8 @@
:prim
(new 'static 'gs-prim :prim (gs-prim-type tri-fan) :tme #x1 :abe #x1)
:nreg #x9))
(set! (-> data sprite-2d-giftag-2 regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id rgbaq)
:regs1 (gif-reg-id st)
:regs2 (gif-reg-id xyzf2)
:regs3 (gif-reg-id st)
:regs4 (gif-reg-id xyzf2)
:regs5 (gif-reg-id st)
:regs6 (gif-reg-id xyzf2)
:regs7 (gif-reg-id st)
:regs8 (gif-reg-id xyzf2)))
(set! (-> data sprite-2d-giftag-2 regs) (gs-reg-list rgbaq st xyzf2 st xyzf2 st xyzf2 st xyzf2))
;; 3D sprites carry ST, color, and XYZF2 for each of their four vertices.
(set! (-> data sprite-3d-giftag tag)
(new 'static
@@ -261,21 +227,8 @@
(new 'static 'gs-prim :prim (gs-prim-type tri-fan) :tme #x1 :fge #x1 :abe #x1)
:nreg #xc))
;; note that we have rgbaq's per vertex in 3d
(set! (-> data sprite-3d-giftag regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id st)
:regs1 (gif-reg-id rgbaq)
:regs2 (gif-reg-id xyzf2)
:regs3 (gif-reg-id st)
:regs4 (gif-reg-id rgbaq)
:regs5 (gif-reg-id xyzf2)
:regs6 (gif-reg-id st)
:regs7 (gif-reg-id rgbaq)
:regs8 (gif-reg-id xyzf2)
:regs9 (gif-reg-id st)
:regs10 (gif-reg-id rgbaq)
:regs11 (gif-reg-id xyzf2)))
(set! (-> data sprite-3d-giftag regs) (gs-reg-list st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2))
;; A clipped 3D quad becomes a variable-length triangle fan, so each vertex supplies one
;; ST/RGBAQ/XYZF2 triplet.
(set! (-> data clipped-giftag tag)
@@ -287,8 +240,8 @@
:prim
(new 'static 'gs-prim :prim (gs-prim-type tri-fan) :tme #x1 :fge #x1 :abe #x1)
:nreg #x3))
(set! (-> data clipped-giftag regs)
(new 'static 'gif-tag-regs :regs0 (gif-reg-id st) :regs1 (gif-reg-id rgbaq) :regs2 (gif-reg-id xyzf2)))
(set! (-> data clipped-giftag regs) (gs-reg-list st rgbaq xyzf2))
;; Triangle giftag for the warped-sprite path. sprite-vu1-block does not load this frame
;; constant, so that path is not active here.
(set! (-> data warp-giftag tag)
@@ -300,18 +253,8 @@
:prim
(new 'static 'gs-prim :prim (gs-prim-type tri) :tme #x1 :abe #x1)
:nreg #xc))
(set! (-> data warp-giftag regs)
(new 'static
'gif-tag-regs
:regs0 (gif-reg-id st)
:regs1 (gif-reg-id rgbaq)
:regs2 (gif-reg-id xyzf2)
:regs3 (gif-reg-id st)
:regs4 (gif-reg-id rgbaq)
:regs5 (gif-reg-id xyzf2)
:regs6 (gif-reg-id st)
:regs7 (gif-reg-id rgbaq)
:regs8 (gif-reg-id xyzf2)))
(set! (-> data warp-giftag regs) (gs-reg-list st rgbaq xyzf2 st rgbaq xyzf2 st rgbaq xyzf2))
;; set up an adgif.
(set! (-> data screen-shader prims 1) (gs-reg64 tex0-1))
(set! (-> data screen-shader tex0) (new 'static 'gs-tex0 :tbw #x8 :tw #xa :th #x8 :tbp0 (* tbp-offset 32)))
@@ -324,7 +267,7 @@
(new 'static 'gs-clamp :wms (gs-tex-wrap-mode region-clamp) :wmt (gs-tex-wrap-mode region-clamp) :maxu 639 :maxv 239))
(set! (-> data screen-shader prims 9) (gs-reg64 alpha-1))
(set! (-> data screen-shader alpha) (new 'static 'gs-alpha :b #x1 :d #x1))
;; sin/cosine table
;; sin/cosine polynomial coefficients for rotation
(set! (-> data sincos-01 z) 0.999998)
(set! (-> data sincos-23 z) -0.16666014)
(set! (-> data sincos-45 z) 0.008326521)
@@ -345,12 +288,11 @@
(set! (-> data basis-y y) (- (-> *math-camera* perspective vector 1 y)))
(set! (-> data min-scale) (sqrtf (* (/ 1.0 (-> data basis-x x)) (/ 1.0 (-> data basis-y y)))))
(set! (-> data inv-area) (/ 1.0 (* (-> data min-scale) (-> data min-scale))))
;; Four blocks of four corners. A 2D sprite's flag field is a quadword index straight into this
;; array, so it selects its own block: 0 gives a quad centred on the sprite position, 4 gives one
;; anchored at its left edge, which is what a bar or a run of text wants. Block 8 is the texture
;; coordinate corners, the same for every sprite, and block 12 is a quad lying in the XZ plane --
;; the 3D path's starting shape, before the quaternion rotates it, and the reason a fake shadow
;; ends up flat on the ground.
;; cdata stores 4 sets of 4 corners. The mode can be picked by flags / 4.
;; 0 - centered (-0.5, -0.5) to (0.5, 0.5)
;; 4 - (0, -0.5) to (1, 0.5)
;; 8 - (0, 0) to (1, 1), used for texture coords
;; 12 - XZ plane, for 3D
(set-vector! (-> data cdata 0) -0.5 -0.5 0.0 0.0)
(set-vector! (-> data cdata 1) 0.5 -0.5 0.0 0.0)
(set-vector! (-> data cdata 2) 0.5 0.5 0.0 0.0)
@@ -548,27 +490,6 @@
0
(none))
#|
(defun sprite-add-frame-data ((dma-buff dma-buffer) (tbp-offset uint))
"Upload the frame data."
(let ((s5-0 41)) ;; qwc of frame data.
(dma-buffer-add-cnt-vif2 dma-buff s5-0
(new 'static 'vif-tag :cmd (vif-cmd stcycl)
:imm (new 'static 'vif-stcycl-imm :cl 4 :wl 4))
(new 'static 'vif-tag :cmd (vif-cmd unpack-v4-32) :num s5-0
:imm (new 'static 'vif-unpack-imm :addr 980))
)
;; setup frame data directly in the dma buffer.
(sprite-setup-frame-data
(the-as sprite-frame-data (-> dma-buff base))
(the-as int tbp-offset)
)
(&+! (-> dma-buff base) (* s5-0 16))
)
(none)
)
|#
(defun sprite-add-frame-data ((dma-buff dma-buffer) (tbp-offset uint))
"Build and upload the 41 quadwords of sprite constants shared by every chunk in this frame."
(let ((frame-qwc 41))
@@ -608,21 +529,6 @@
(&+ (-> sprites vec-data) (* 48 start-sprite-idx))
(new 'static 'vif-tag :cmd (vif-cmd nop))
(new 'static 'vif-tag :cmd (vif-cmd unpack-v4-32) :num qwc-pkt2 :imm (new 'static 'vif-unpack-imm :flg 1 :addr SPRITE-VU-VEC-DATA))))
#|
;(when (= mscal-addr 3)
(dotimes (i num-sprites)
(let ((spidx (+ i start-sprite-idx)))
;(when (or (= spidx (-> sprites num-sprites 0)) (= spidx (+ 1 (-> sprites num-sprites 0))))
(let ((data (the sprite-vec-data-2d (&+ (-> sprites vec-data) (* 48 (+ i start-sprite-idx))))))
(let ((vec (-> data x-y-z-sx)))
(format 0 "sp: ~d ~f ~f ~f~%" (+ i start-sprite-idx) (-> vec x) (-> vec y) (-> vec z))
)
)
; )
)
)
; )
|#
;; third packet is adgif data (5 qw/sprite)
(let ((qwc-pkt3 (* 5 num-sprites)))
(dma-buffer-add-ref-vif2 dma-buff