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

156 lines
5.3 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "engine/ps2/timer-h.gc")
(require "kernel/gcommon.gc")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Timer (EE timers)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun timer-reset ((timer timer-bank))
"Clear an EE hardware timer counter."
(#when PC_PORT
;; The host timer cannot be reset, so retain its current value as the new origin.
(if (= timer TIMER1_BANK) (set! *timer-reset-value* (get-bus-clock/256)) (format 0 "Unknown timer #x~X in timer-reset~%"))
(return (the uint 0)))
(#unless PC_PORT
(sync.l))
(set! (-> timer count) 0)
(#unless PC_PORT
(sync.l)))
(defun timer-count ((timer timer-bank))
"Read an EE hardware timer counter."
(#when PC_PORT
(when (= timer TIMER1_BANK)
(return (- (get-bus-clock/256) *timer-reset-value*)))
(format 0 "Unknown timer #x~X requested.~%" timer))
(#unless PC_PORT
(sync.l))
(let ((count (-> timer count))) (#unless PC_PORT (sync.l)) count))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Interrupt Control
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; cop0 status register "interrupt enable" flag
;; if cop0 status is needed anywhere else, move this elsewhere
(defconstant COP0_STATUS_IE (the-as uint #x1))
(#unless PC_PORT
(defun disable-irq ()
"Clear the global interrupt-enable bit."
(rlet ((status :type uint)) (m status Status) (logand! status (lognot COP0_STATUS_IE)) (m Status status) (sync.p))))
(#when PC_PORT
(defun disable-irq ()
"Clear the global interrupt-enable bit."
;; These assembly helpers are no-ops because host interrupts are not controlled by EE status.
(rlet ((status :type uint))
(.mfc0 status Status)
(logand! status (lognot COP0_STATUS_IE))
(.mtc0 Status status)
(.sync.p))))
(#unless PC_PORT
(defun enable-irq ()
"Set the global interrupt-enable bit."
(rlet ((status :type uint)) (m status Status) (logior! status COP0_STATUS_IE) (m Status status) (sync.p))))
(#when PC_PORT
(defun enable-irq ()
"Set the global interrupt-enable bit."
;; These assembly helpers are no-ops because host interrupts are not controlled by EE status.
(rlet ((status :type uint)) (.mfc0 status Status) (logior! status COP0_STATUS_IE) (.mtc0 Status status) (.sync.p))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Stopwatch (CPU clock cycle counting)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun stopwatch-init ((watch stopwatch))
"Initialize an idle stopwatch with no accumulated time."
(set! (-> watch begin-level) 0)
(set! (-> watch prev-time-elapsed) 0))
(defun stopwatch-reset ((watch stopwatch))
"Clear accumulated time and restart the current interval when running."
(set! (-> watch prev-time-elapsed) 0)
(when (> (-> watch begin-level) 0)
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(set! (-> watch start-time) count))))
(defun stopwatch-start ((watch stopwatch))
"Start an idle stopwatch from zero."
(when (zero? (-> watch begin-level))
(set! (-> watch begin-level) 1)
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(set! (-> watch start-time) count))))
(defun stopwatch-stop ((watch stopwatch))
"Stop a running stopwatch and accumulate its current interval."
(when (> (-> watch begin-level) 0)
(set! (-> watch begin-level) 0)
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(+! (-> watch prev-time-elapsed) (- count (-> watch start-time)))))
(none))
(defun stopwatch-begin ((watch stopwatch))
"Enter a nested timed region, starting the stopwatch at the outermost level."
(when (zero? (-> watch begin-level))
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(set! (-> watch start-time) count)))
(+! (-> watch begin-level) 1))
(defun stopwatch-end ((watch stopwatch))
"Leave a nested timed region, accumulating time when the outermost level ends.
Calls must be balanced: ending below level zero prevents later intervals from stopping."
(+! (-> watch begin-level) -1)
(when (zero? (-> watch begin-level))
(set! (-> watch begin-level) 0)
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(+! (-> watch prev-time-elapsed) (- count (-> watch start-time)))))
(none))
(defun stopwatch-elapsed-ticks ((watch stopwatch))
"Return accumulated CPU cycles, including the current running interval."
(let ((elapsed (-> watch prev-time-elapsed)))
(when (> (-> watch begin-level) 0)
(let ((count 0))
(#unless PC_PORT
(m count Count))
(#when PC_PORT
(set! count (the int (get-cpu-clock))))
(+! elapsed (- count (-> watch start-time)))))
elapsed))
(defglobalconstant EE_SECONDS_PER_TICK (/ 1.0 300000000)) ;; 300MHz is a "decent enough" estimate
(defmacro cpu-ticks-to-seconds (ticks)
"Convert 300 MHz EE cycle-counter ticks to seconds."
`(* ,EE_SECONDS_PER_TICK ,ticks))
(defun stopwatch-elapsed-seconds ((watch stopwatch))
"Return the stopwatch's elapsed CPU time in seconds."
(cpu-ticks-to-seconds (stopwatch-elapsed-ticks watch)))