Files
jak-project/goal_src/jak1/engine/util/capture.gc
T
2026-07-26 14:43:53 -04:00

109 lines
5.0 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "kernel/gkernel.gc")
(require "engine/gfx/hw/gs.gc")
;; Functions for taking a screenshot of VRAM.
;; These are debug-only and leak memory.
;;
;; The GS framebuffers live in local VRAM rather than EE memory. A screenshot
;; therefore uses a local-to-host GS transfer to read both field-sized
;; framebuffers, then interleaves their scanlines into a full-height raw image.
;; DECOMP BEGINS
;; this file is debug only
(declare-file (debug))
;; VIF/GIF packet for transferring a rectangle from GS VRAM to EE memory.
(deftype gs-store-image-packet (structure)
((vifcode vif-tag 4)
(giftag gif-tag)
(bitbltbuf gs-bitbltbuf)
(bitbltbuf-addr gs-reg64)
(trxpos gs-trxpos)
(trxpos-addr gs-reg64)
(trxreg gs-trxreg)
(trxreg-addr gs-reg64)
(finish int64)
(finish-addr gs-reg64)
(trxdir gs-trxdir)
(trxdir-addr gs-reg64)))
(defun gs-set-default-store-image ((packet gs-store-image-packet)
(source-base int)
(source-buffer-width int)
(source-format gs-psm)
(source-x int)
(source-y int)
(read-width int)
(read-height int))
"Build the seven-quadword VIF/GIF packet that reads a GS VRAM rectangle into EE memory."
(set! (-> packet vifcode 0) (new 'static 'vif-tag :cmd (vif-cmd nop)))
;; Stop background PATH3 traffic from racing the readback.
(set! (-> packet vifcode 1) (new 'static 'vif-tag :imm #x8000 :cmd (vif-cmd mskpath3)))
;; Wait for preceding GS work, then send the five A+D register writes.
(set! (-> packet vifcode 2) (new 'static 'vif-tag :cmd (vif-cmd flusha) :msk #x1))
(set! (-> packet vifcode 3) (new 'static 'vif-tag :imm #x6 :cmd (vif-cmd direct) :msk #x1))
(set! (-> packet giftag)
(the-as gif-tag
(make-u128 (new 'static 'gif-tag-regs :regs0 (gif-reg-id a+d)) (new 'static 'gif-tag64 :nloop #x5 :eop #x1 :nreg #x1))))
(set! (-> packet bitbltbuf)
(new 'static 'gs-bitbltbuf :sbp source-base :sbw source-buffer-width :spsm (the-as int source-format)))
(set! (-> packet bitbltbuf-addr) (gs-reg64 bitbltbuf))
(set! (-> packet trxpos) (new 'static 'gs-trxpos :ssax source-x :ssay source-y))
(set! (-> packet trxpos-addr) (gs-reg64 trxpos))
(set! (-> packet trxreg) (new 'static 'gs-trxreg :rrw read-width :rrh read-height))
(set! (-> packet trxreg-addr) (gs-reg64 trxreg))
(set! (-> packet finish) 0)
(set! (-> packet finish-addr) (gs-reg64 finish))
;; XDIR 1 is local-to-host; this final register starts the transfer.
(set! (-> packet trxdir) (new 'static 'gs-trxdir :xdir #x1))
(set! (-> packet trxdir-addr) (gs-reg64 trxdir))
(.sync.l)
7)
(defun store-image ((field-order int))
"Capture both interlaced framebuffers and write their scanlines to image.raw in the requested field order."
(let ((width 512)
(height (-> *video-parms* screen-sy))
(output (new 'debug 'file-stream "image.raw" 'write)))
;; The debug heap arrays remain allocated after this routine returns.
(let ((field0-buffer (the-as (array uint128) (new 'debug 'boxed-array uint128 (/ (* width height) 4)))))
(let ((field1-buffer (the-as (array uint128) (new 'debug 'boxed-array uint128 (/ (* width height) 4)))))
(let ((packet (new 'static 'gs-store-image-packet)))
;; BITBLTBUF uses a finer base-address unit than FRAME: #x2800 and
;; #x3000 are the two display buffers at FRAME bases 320 and 384.
(gs-set-default-store-image packet #x2800 (/ width 64) (gs-psm ct32) 0 0 width height)
(flush-cache 0)
(gs-store-image packet (-> field0-buffer data))
(sync-path 0 0)
(gs-set-default-store-image packet #x3000 (/ width 64) (gs-psm ct32) 0 0 width height)
(flush-cache 0)
(gs-store-image packet (-> field1-buffer data)))
(sync-path 0 0)
;; Each buffer contributes alternating scanlines. field-order identifies
;; which one supplies the first row of the reconstructed image.
(let ((field0-pixels (-> field0-buffer data))
(field1-pixels (-> field1-buffer data)))
(cond
((zero? field-order)
(let ((i 0))
(while (< i height)
(file-stream-write output (&+ field0-pixels (* i (* width 4))) (the-as uint (* width 4)))
(file-stream-write output (&+ field1-pixels (* i (* width 4))) (the-as uint (* width 4)))
(+! i 1))))
(else
(let ((i 0))
(while (< i height)
(file-stream-write output (&+ field1-pixels (* i (* width 4))) (the-as uint (* width 4)))
(file-stream-write output (&+ field0-pixels (* i (* width 4))) (the-as uint (* width 4)))
(+! i 1))))))
(format #t "oddeven = ~d~%" field-order)
;; delete does not reclaim debug-heap allocations.
(delete field1-buffer))
(delete field0-buffer))
(file-stream-close output))
0)