;;-*-Lisp-*-
(in-package goal)

;; name: texture.gc
;; name in dgo: texture
;; dgos: ENGINE, GAME

#|@file
Jak 2 texture system:
At a high level, the responsibility of the texture system is to make sure that each renderer
can use the textures it needs: the texture should be in VRAM at the right point in time, and
the renderer must know where the texture is in VRAM.

An oversimplified explanation is that each "bucket" in the rendering system needs a single
"texture page" (tpage) loaded into VRAM. Each level has a tpage for each category, where the
categories are:

tfrag (background tie/tfrag)
pris  (foreground)
shrub (background shrub)
alpha
water
warp
pris2
sprite
map
sky

From the point of view of the rendering code, this oversimplification is basically true.
Renderers use textures by sending "adgif shaders" to the GS. These tell the GS how to use
the texture (mipmapping, size, format) and the location of the texture in VRAM (tbp).
When levels load, they must "log in" their adgif shaders with the texture system, and the texture
system will modify these in place so that things work.

However, behind the scenes, there are many tricks.

Texture data falls into two categories:
- boot
- common

"boot"   textures are ones that are loaded on boot, transferred to VRAM, and live there forever.
"common" textures are ones that are loaded as they are needed. There's an area in VRAM
         called the "common segment" that holds these.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
basic texture page methods
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

each "texture page" is a group of textures that are (from the point of view of renderers)
loaded all at once to vram, and contain all the textures needed for one category in one level.

however, the texture system does some tricks:
texture pages are divided into three "segments". The smaller number segments contain high
resolution mipmaps of textures (needed for close up), and the high number segments contain lower ones.
by looking at the distance to the closest object, we can figure out if the near textures
are needed or not, and skip segments if needed.

additionally, some texture pages have a chunk system that allows more specific control.
|#

;; DECOMP BEGINS

(defmethod print ((this texture-page))
  "Print a texture page with name and size."
  (format #t "#<texture-page ~S :length ~D :dest #x~X :size ~DK @ #x~X>"
    (-> this name)
    (-> this length)
    (shr (-> this segment 0 dest) 6)
    (shr (+ (-> this size) 255) 8)
    this
    )
  this
  )

(defmethod length ((this texture-page))
  "Get the number of textures in a texture-page."
  (-> this length)
  )

(defmethod asize-of ((this texture-page))
  "Get the size, in bytes, of a texture-page.
   Note that this does not include the texture objects, or the texture data."
  (the-as int (+ (-> this type size) (* (-> this length) 4)))
  )

(defmethod mem-usage ((this texture-page) (arg0 memory-usage-block) (arg1 int))
  "Get the amount of memory used by a texture page, including texture and texture data."
  (set! (-> arg0 length) (max 83 (-> arg0 length)))
  (set! (-> arg0 data 82 name) "texture")
  (+! (-> arg0 data 82 count) (-> this length))
  ;; og:preserve-this note: in jak 1 this + was a - for reasons I do not understand.
  (let ((v1-7 (+ (asize-of this) (* (-> this dram-size) 4))))
    ;; also add the size of the texture objects.
    (dotimes (a0-6 (-> this length))
      (if (-> this data a0-6)
          (+! v1-7 112)
          )
      )
    (+! (-> arg0 data 82 used) v1-7)
    (+! (-> arg0 data 82 total) (logand -16 (+ v1-7 15)))
    )
  this
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture upload dma
;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun texture-bpp ((tex-fmt gs-psm))
  "Get the number of bits per pixel for a texture format."
  (case tex-fmt
    (((gs-psm mt8))
     8
     )
    (((gs-psm mt4))
     4
     )
    (((gs-psm ct16) (gs-psm ct16s) (gs-psm mz16) (gs-psm mz16s))
     16
     )
    (else
      32
      )
    )
  )

(defun texture-qwc ((width int) (height int) (tex-fmt gs-psm))
  "Get the number of quadwords (16-bytes), rounded up, for a given texture size/format."
  (let ((v1-0 (texture-bpp tex-fmt)))
    (/ (+ (* (* width height) v1-0) 127) 128)
    )
  )

(defun physical-address ((ptr pointer))
  "Convert a pointer (possibly to the uncached mapping) to a 'physical address' that "
  (logand #xfffffff ptr)
  )

(defun dma-buffer-add-ref-texture ((dma-buf dma-buffer) (tex-data-ptr pointer) (width int) (height int) (tex-fmt gs-psm))
  "Add a texture upload to a dma buffer that refers to texture data.
   This does not copy the texture data, but instead inserts a refernce.
   This also assumes that the GS is currently set up for the right type of image upload."
  (let ((padr (physical-address tex-data-ptr))
        (qwc-remaining (texture-qwc width height tex-fmt))
        )
    ;; break up transfer into 0x7fff quadword chunks.
    (while (> qwc-remaining 0)
      (let ((qwc-transfer (min #x7fff qwc-remaining)))
        (let ((eop (if (= qwc-remaining qwc-transfer)
                       1
                       0
                       )
                   )
              )
          (let* ((a2-2 dma-buf)
                 (a3-1 (the-as object (-> a2-2 base)))
                 )
            (set! (-> (the-as dma-packet a3-1) dma) (new 'static 'dma-tag :qwc #x1 :id (dma-tag-id cnt)))
            (set! (-> (the-as dma-packet a3-1) vif0) (new 'static 'vif-tag))
            (set! (-> (the-as dma-packet a3-1) vif1) (new 'static 'vif-tag :imm #x1 :cmd (vif-cmd direct) :msk #x1))
            (set! (-> a2-2 base) (&+ (the-as pointer a3-1) 16))
            )
          (let* ((a2-3 dma-buf)
                 (a3-3 (the-as object (-> a2-3 base)))
                 )
            (set! (-> (the-as gs-gif-tag a3-3) tag)
                  (new 'static 'gif-tag64 :flg (gif-flag image) :eop eop :nloop qwc-transfer)
                  )
            (set! (-> (the-as gs-gif-tag a3-3) regs) (new 'static 'gif-tag-regs))
            (set! (-> a2-3 base) (&+ (the-as pointer a3-3) 16))
            )
          )
        (let* ((a1-9 dma-buf)
               (a2-4 (the-as object (-> a1-9 base)))
               )
          (set! (-> (the-as dma-packet a2-4) dma)
                (new 'static 'dma-tag :id (dma-tag-id ref) :addr (the-as int padr) :qwc qwc-transfer)
                )
          (set! (-> (the-as dma-packet a2-4) vif0) (new 'static 'vif-tag))
          (set! (-> (the-as dma-packet a2-4) vif1)
                (new 'static 'vif-tag :cmd (vif-cmd direct) :msk #x1 :imm qwc-transfer)
                )
          (set! (-> a1-9 base) (&+ (the-as pointer a2-4) 16))
          )
        (&+! padr (* qwc-transfer 16))
        (set! qwc-remaining (- qwc-remaining qwc-transfer))
        )
      )
    )
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; basic texture methods
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; each texture is just some metadata about a texture, including its location in vram
;; and format settings.

(defmethod print ((this texture))
  (format #t "#<texture ~20S psm: ~6S  ~4D x ~4D  num-mips: ~D :size ~4DK "
    (-> this name)
    (psm->string (-> this psm)) ;; format
    (-> this w)
    (-> this h)
    (-> this num-mips)
    (shr (-> this size) 8)
    )
  ;; print the location and size of each mip level
  (dotimes (s5-1 (the-as int (-> this num-mips)))
    (format #t " #x~X/~X" (-> this dest s5-1) (-> this width s5-1))
    )
  ;; print clut (color look up table) location in vram
  (if (< (texture-bpp (-> this psm)) 16)
      (format #t " :clut #x~X/1" (-> this clutdest))
      )
  (format #t " @ #x~X>" this)
  this
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture memory layout
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; The GS uses a crazy memory layout. Read the manual for more details.
;; these functions are wrong and unused, though the ct32-24-block-table is right
;; and used in the eye code.

(defun gs-find-block ((bx int) (by int) (fmt gs-psm))
  (cond
    ((= fmt (gs-psm ct32))
     (-> ct32-24-block-table (+ bx (* by 8)))
     )
    ((= fmt (gs-psm ct24))
     (-> ct32-24-block-table (+ bx (* by 8)))
     )
    ((= fmt (gs-psm ct16))
     (-> ct16-block-table (+ bx (* by 4)))
     )
    ((= fmt (gs-psm ct16s))
     (-> ct16s-block-table (+ bx (* by 4)))
     )
    ((= fmt (gs-psm mz32))
     (-> mz32-24-block-table (+ bx (* by 8)))
     )
    ((= fmt (gs-psm mz24))
     (-> mz32-24-block-table (+ bx (* by 8)))
     )
    ((= fmt (gs-psm mz16))
     (-> mz16-block-table (+ bx (* by 4)))
     )
    ((= fmt (gs-psm mz16s))
     (-> mz16s-block-table (+ bx (* by 4)))
     )
    ((= fmt (gs-psm mt8))
     (-> mt8-block-table (+ bx (* by 8)))
     )
    ((= fmt (gs-psm mt4))
     (-> mt4-block-table (+ bx (* by 4)))
     )
    (else
      0
      )
    )
  )

(defun gs-page-width ((arg0 gs-psm))
  (case arg0
    (((gs-psm ct32) (gs-psm ct24) (gs-psm ct16) (gs-psm ct16s))
     64
     )
    (((gs-psm mt8) (gs-psm mt4))
     128
     )
    (else
      (format #t "Warning: Unknown block width for psm ~D~%" arg0)
      1
      )
    )
  )

(defun gs-page-height ((arg0 gs-psm))
  (case arg0
    (((gs-psm ct32) (gs-psm ct24))
     32
     )
    (((gs-psm ct16) (gs-psm ct16s))
     64
     )
    (((gs-psm mt8))
     64
     )
    (((gs-psm mt4))
     128
     )
    (else
      (format #t "Warning: Unknown block width for psm ~D~%" arg0)
      1
      )
    )
  )

(defun gs-block-width ((arg0 gs-psm))
  (case arg0
    (((gs-psm ct32) (gs-psm ct24))
     8
     )
    (((gs-psm ct16) (gs-psm ct16s) (gs-psm mt8))
     16
     )
    (((gs-psm mt4))
     32
     )
    (else
      (format #t "Warning: Unknown block width for psm ~D~%" arg0)
      1
      )
    )
  )

(defun gs-block-height ((arg0 gs-psm))
  (case arg0
    (((gs-psm ct32) (gs-psm ct24) (gs-psm ct16) (gs-psm ct16s))
     8
     )
    (((gs-psm mt8) (gs-psm mt4))
     16
     )
    (else
      (format #t "Warning: Unknown block width for psm ~D~%" arg0)
      1
      )
    )
  )

(defun gs-largest-block ((arg0 int) (arg1 int) (arg2 gs-psm))
  (let* ((s5-0 (gs-block-width arg2))
         (v1-0 (gs-block-height arg2))
         (a0-6 (* (/ (+ s5-0 -1 arg0) s5-0) s5-0))
         (a1-4 (* (/ (+ v1-0 -1 arg1) v1-0) v1-0))
         (s5-1 (/ a0-6 s5-0))
         (s3-1 (/ a1-4 v1-0))
         (s4-1 0)
         )
    (dotimes (s2-0 s5-1)
      (dotimes (s1-0 s3-1)
        (set! s4-1 (max s4-1 (gs-find-block s2-0 s1-0 arg2)))
        )
      )
    s4-1
    )
  )

(defun gs-blocks-used ((arg0 int) (arg1 int) (arg2 gs-psm))
  (let* ((s4-0 (gs-page-width arg2))
         (v1-0 (gs-page-height arg2))
         (a0-6 (* (/ (+ s4-0 -1 arg0) s4-0) s4-0))
         (a1-4 (* (/ (+ v1-0 -1 arg1) v1-0) v1-0))
         (s3-0 (/ a0-6 s4-0))
         (s1-0 (/ a1-4 v1-0))
         (a0-9 (- arg0 (* (+ s3-0 -1) s4-0)))
         (a1-7 (- arg1 (* (+ s1-0 -1) v1-0)))
         )
    (if (or (< a0-9 s4-0) (< a1-7 v1-0))
        (+ (gs-largest-block a0-9 a1-7 arg2) 1 (* (+ (* s3-0 s1-0) -1) 32))
        (* (* s1-0 s3-0) 32)
        )
    )
  )

;;;;;;;;;;;;;;;;;
;; texture pool
;;;;;;;;;;;;;;;;;

;; the "texture-pool" is the global manager of textures.
;; among other things, it tracks which page is uploaded in each vram "page"
;;  (confusingly a texture-page is many vram "pages" big)
;; The vram is divided into 128 vram pages, and the texture system will skip uploads
;; if a vram page already has the desired texture.

;; there are "default" textures, these only live in vram
;; "common" textures are just normal level textures that get uploaded as needed
;; "common-page" textures are common textures that the engine can easily request for
;; special cases, like displaying a splash screen.

(defmethod new texture-pool ((allocation symbol) (type-to-make type))
  "Allocate and initialize a texture-pool."
  (initialize! (object-new allocation type-to-make (the-as int (-> type-to-make size))))
  )


;; internally, the texture-pool has a bump allocator for vram. This assigns vram to various boot textures/segments
;; at boot time and isn't used at runtime. VRAM is often resued for multiple textures in a single frame,
;; and the common segment does more complicated management at runtime.

(defmethod allocate-vram-words! ((this texture-pool) (num-words int))
  "Allocate the given number of vram words."
  (let ((v0-0 (-> this cur)))
    (+! (-> this cur) num-words)
    v0-0
    )
  )

;; "common page"
;;  The "common page" system is a weird way to have special hard-coded textures for the engine
;;  for weird things like splash screens. These go in the common segment.

;; In jak 2, there are only 2 common page textures.

;; The "common" thing about these textures is that they are used by the engine directly, and
;; not any specific level/art-group.
;; So it's reasonable for them to hardcode tpage-ids here.

(defmethod get-common-page-slot-by-id ((this texture-pool) (tpage-id int))
  "Check to see if the given tpage should go in the common page list.
   If so, return the slot. Otherwise, return -1."
  (case tpage-id
    ((33)
     1
     )
    ((34)
     2
     )
    (else
      -1
      )
    )
  )

(defmethod initialize! ((this texture-pool))
  "Initialize or reset the state of the texture-pool."
  ;; reset allocator
  (set! (-> this cur) 0)
  (set! (-> this top) (-> this cur))
  ;; set the allocation function to default-allocate.
  (set! (-> this allocate-func) texture-page-default-allocate)
  ;; allocate some textures for effects, etc
  (allocate-defaults this)
  ;; allocate the font palette.
  (format #t "font-palette start #x~x~%" (/ (-> this cur) 64))
  (set! (-> this font-palette) (allocate-vram-words! this 64))
  (format #t "font-palette end #x~x~%" (/ (-> this cur) 64))

  ;; reset common-page texture-pages
  (dotimes (v1-8 32)
    (set! (-> this common-page v1-8) (the-as texture-page 0))
    )

  ;; request no common-pages
  (set! (-> this common-page-mask) 0)

  ;; enable uploads for all textures
  (set! (-> this texture-enable-user-menu)
        (texture-enable-mask tfrag pris shrub alpha water warp sprite map sky)
        )
  (set! (-> this texture-enable-user) (texture-enable-mask tfrag pris shrub alpha water warp sprite map sky))
  ;; mark all vram slots as unoccupied.
  (dotimes (v1-13 128)
    (set! (-> this ids v1-13) (the-as uint 0))
    )
  this
  )

(defmethod get-leftover-block-count ((this texture-page) (num-segments int) (upload-offset int))
  "Unused and somewhat useless function to figure out how many blocks we overflow into the next page.
   This could be used with gs-largest-block to figure out how much of a page we use if we don't fit
   exactly."
  (let ((offset upload-offset))
    (dotimes (i num-segments)
      (+! offset (-> this segment i size))
      )
    (logand (/ offset 64) 63)
    )
  )

(defmethod print-usage ((this texture-pool))
  "Print out how much of a texture-pool is used.
   This is not quite right because it does not count the frame or z buffer as used space."
  (format #t "--------------------~%")
  (format #t "texture pool ~DK - ~DK (~DK used, ~DK free)~%"
    (/ (-> this top) 256)
    (/ (-> this cur) 256)
    (/ (- (-> this cur) (-> this top)) 256)
    (/ (- #xfa000 (-> this cur)) 256)
    )
  (format #t "--------------------~%")
  this
  )

(defmethod allocate-segment ((this texture-pool) (seg texture-pool-segment) (num-words int))
  "Assign vram to the given segment."
  (set! (-> seg size) (the-as uint num-words))
  (set! (-> seg dest) (the-as uint (allocate-vram-words! this num-words)))
  seg
  )

(defmethod allocate-defaults ((this texture-pool))
  "Assign vram for the texture system."

  ;; this "common" segment is about 1 MB and will hold basically all textures.
  ;; unlike jak 1, there's no near textures. instead, there's just a lot more uploads.
  ;; this seems like it would put a huge pressure on vram usage because these uploads
  ;; aren't free.
  (format #t "texture start #x~x~%" (/ (-> this cur) 64))
  (allocate-segment this (-> this segment-common) #x3e000)
  (format #t "texture end #x~x~%" (/ (-> this cur) 64))

  ;; these "dynamic" textures are written to by renderers, and they have a fixed address.
  ;; notice that some of them overlap each other.
  (set! (-> *ocean-envmap-texture-base* vram-word) (the-as uint (allocate-vram-words! this #x9400)))
  (set! (-> *ocean-envmap-texture-base* vram-block) (shr (-> *ocean-envmap-texture-base* vram-word) 6))
  (set! (-> *ocean-envmap-texture-base* vram-page) (shr (-> *ocean-envmap-texture-base* vram-word) 11))
  (set! (-> *ocean-texture-base* vram-word) (+ (-> *ocean-envmap-texture-base* vram-word) 4096))
  (set! (-> *ocean-texture-base* vram-block) (shr (-> *ocean-texture-base* vram-word) 6))
  (set! (-> *ocean-texture-base* vram-page) (shr (-> *ocean-texture-base* vram-word) 11))
  (set! (-> *grey-scale-base* vram-word) (+ (-> *ocean-envmap-texture-base* vram-word) 4096))
  (set! (-> *grey-scale-base* vram-block) (shr (-> *grey-scale-base* vram-word) 6))
  (set! (-> *grey-scale-base* vram-page) (shr (-> *grey-scale-base* vram-word) 11))
  (set! (-> *eyes-texture-base* vram-word) (+ (-> *ocean-envmap-texture-base* vram-word) 4096))
  (set! (-> *eyes-texture-base* vram-block) (shr (-> *eyes-texture-base* vram-word) 6))
  (set! (-> *eyes-texture-base* vram-page) (shr (-> *eyes-texture-base* vram-word) 11))
  (set! (-> *map-texture-base* vram-word) (+ (-> *ocean-envmap-texture-base* vram-word) 4096))
  (set! (-> *map-texture-base* vram-block) (shr (-> *map-texture-base* vram-word) 6))
  (set! (-> *map-texture-base* vram-page) (shr (-> *map-texture-base* vram-word) 11))
  (set! (-> *skull-gem-texture-base* vram-word) (+ #x9000 (-> *ocean-envmap-texture-base* vram-word)))
  (set! (-> *skull-gem-texture-base* vram-block) (shr (-> *skull-gem-texture-base* vram-word) 6))
  (set! (-> *skull-gem-texture-base* vram-page) (shr (-> *skull-gem-texture-base* vram-word) 11))
  (format #t "dynamic end #x~x~%" (/ (-> this cur) 64))
  0
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture page allocation
;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; these functions assign texture-pages to vram.
;; unlike the functions above, these
;; - happen at runtime, as levels are loaded/unloaded.
;; - may assign multiple textures to the same vram address
;; it's ok for two textures to be mapped to the same vram address as
;; long as they are in different pages.

(defmethod remove-data-from-heap ((this texture-page) (heap kheap))
  "Remove a texture-page's data from main memory.
   This is intended to be run on textures that are permanently in VRAM.
   This only works if the texture-page was the last thing added to the heap,
   and does NO checking to make sure this is the case.
   The texture-page and texture records are kept (just metadata), the actual image data is discarded."
  (set! (-> heap current) (-> this segment 0 block-data))
  this
  )

(defun texture-page-default-allocate ((pool texture-pool) (tpage texture-page) (heap kheap) (tpage-id int))
  "Allocate a texture to be permanently stored in VRAM, and remove it from main memory.
  This is only safe to call if the most recently loaded thing is this texture.
  This will perform texture uploads, so this should not be called during drawing."

  ;; for each segment, permanently allocate VRAM
  (dotimes (seg 3)
    (let ((vram-loc (allocate-vram-words! pool (the-as int (-> tpage segment seg size)))))
      ;; and adjust the texture so it points to the allocated vram
      (relocate-dests! tpage vram-loc seg)
      )
    )

  ;; load to VRAM
  (upload-now! tpage (tex-upload-mode seg0-1-2))

  ;; and remove image data from main memory.
  (remove-data-from-heap tpage heap)

  ;; for accurate accounting of memory
  (set! (-> tpage dram-size) (the-as uint 0))

  ;; clear masks for each texture
  (dotimes (tex-idx (-> tpage length))
    (let ((tex (-> tpage data tex-idx)))
      (when tex
        (dotimes (mask-idx 3)
          (dotimes (mask-word 3)
            ;; og:preserve-this
            (set! (-> tex masks data mask-idx mask data mask-word) 0)
            ;(set! (-> (the-as texture (+ (+ (* mask-idx 16) (* mask-word 4)) (the-as int tex))) masks data 0 mask x) 0)
            )
          )
        )
      )
    )
  tpage
  )

(defun texture-page-common-allocate ((pool texture-pool) (tpage texture-page) (heap kheap) (tpage-id int))
  "Set up a texture that will be uploaded to VRAM as needed by the texture system.
   These will go in the 'common' segment.
   No upload is actually done."

  (let ((vram-loc (-> pool segment-common dest)))
    (dotimes (seg 3)
      ;; set up the VRAM address of the segment
      (relocate-dests! tpage (the-as int vram-loc) seg)
      ;; don't put the segments on top of each other, they need to be spaced out
      ;; so they can be all loaded at once.
      (+! vram-loc (-> tpage segment seg size))
      )
    )

  ;; this texture stays in main memory
  ;; and is uploaded again and again as needed
  ;; so the dram-size is the full size.
  (set! (-> tpage dram-size) (-> tpage size))
  tpage
  )

(defun texture-page-font-allocate ((pool texture-pool) (tpage texture-page) (heap kheap) (tpage-id int))
  "Special allocation for the font textures.
   These are temporarily loaded to the common segment, then later moved."

  ;; set up their dest to go in common
  (texture-page-common-allocate pool tpage heap tpage-id)

  ;; upload them to common segment now
  (upload-now! tpage (tex-upload-mode seg0-1-2))

  ;; kick out of main memory now that they are in VRAM
  (remove-data-from-heap tpage heap)
  (set! (-> tpage dram-size) (the-as uint 0))

  ;; clear masks. is this actually needed for font?
  (dotimes (tex-idx (-> tpage length))
    (let ((tex (-> tpage data tex-idx)))
      (when tex
        (dotimes (mask-idx 3)
          (dotimes (mask-word 3)
            ;; og:preserve-this
            (set! (-> tex masks data mask-idx mask data mask-word) 0)
            ;(set! (-> (the-as texture (+ (+ (* mask-idx 16) (* mask-word 4)) (the-as int tex))) masks data 0 mask x) 0)
            )
          )
        )
      )
    )
  tpage
  )

;;;;;;;;;;;;;;;;;;;;;;;;;
;; level combo set up
;;;;;;;;;;;;;;;;;;;;;;;;;

;; several renderers don't have separate buckets per level:
;; warp, hud, and sprite

;; so for these renderers, we need to make "combo" layouts where VRAM
;; is filled with pages from all levels.

;; For sprite and hud, it's the responsibility of the renderer to update adgifs to
;; point to new locations and levels are added/removed.

;; For warp, it's the responsibility of the texture system to update adgifs.

(defmethod lay-out-sprite-tex ((this texture-pool))
  "Lay out sprite textures from all levels so all can fit into
   VRAM at the same time."

  ;; this assumes that the common-segment starts at 0.
  (let ((vram-loc 0))
    ;; og:preserve-this constant
    ;; loop over all active levels
    (countdown (level-idx LEVEL_TOTAL)
      (let ((lev (-> *level* level level-idx)))
        (when (or (= (-> lev status) 'active)
                  (= (-> lev status) 'alive)
                  (= (-> lev status) 'loaded)
                  (= (-> lev status) 'reserved)
                  )
          ;; get the sprite tpage
          (let ((tpage (-> lev texture-page 7)))
            (when tpage
              ;; add each segment
              (dotimes (seg 3)
                (relocate-dests! tpage vram-loc seg)
                (+! vram-loc (-> tpage segment seg size))
                )
              ;; don't forget to align textures.
              ;; they do some tricks with segments to let them pack tightly,
              ;; but these can't be done here where the neighbor levels are unknown.
              (set! vram-loc (shl (sar (+ vram-loc 4095) 12) 12))
              )
            )
          )
        )

      ;; check for out of memory.
      (if (< #x3e000 vram-loc)
          (format 0 "ERROR: Ran out of texture memory for SPRITE ~dk of 992k" (/ vram-loc 64))
          )
      )
    )
  0
  (none)
  )

(defmethod lay-out-hud-tex ((this texture-pool))
  "Lay out hud/map textures from all levels so all can fit into
   VRAM at the same time."
  (let ((level-idx 0))
    ;; og:preserve-this constant
    (countdown (vram-loc LEVEL_TOTAL)
      (let ((lev (-> *level* level vram-loc)))
        (when (or (= (-> lev status) 'active)
                  (= (-> lev status) 'alive)
                  (= (-> lev status) 'loaded)
                  (= (-> lev status) 'reserved)
                  )
          (let ((tpage (-> lev texture-page 8)))
            (when tpage
              (dotimes (seg 3)
                (relocate-dests! tpage level-idx seg)
                (+! level-idx (-> tpage segment seg size))
                )
              (set! level-idx (shl (sar (+ level-idx 4095) 12) 12))
              )
            )
          )
        )
      )
    (if (< #x3e000 level-idx)
        (format 0 "ERROR: Ran out of texture memory for HUD ~dk of 992k" (/ level-idx 64))
        )
    )
  0
  (none)
  )

;; for warp, we do the same as above, but we also update all references to textures
;; to point to the new ones.

;; When levels load, they log in their adgifs (references to textures), which does two things:
;; - at login time, the adgif is adjusted to point to the texture.
;; - if the texture moves, the texture system can iterate through all adgifs referencing the texture
;;   and update them. The texture system stores a linked list of adgifs per texture in the texture-page-dir.

(defmethod lay-out-warp-tex ((this texture-pool))
  "Lay out warp textures from all levels so all can fit into
   VRAM at the same time.
   Also update all adgifs."

  ;; again, assume that common-segment starts at 0
  (let ((vram-loc 0))
    ;; og:preserve-this constant
    ;; iterate over active levels
    (countdown (level-idx LEVEL_TOTAL)
      (let ((lev (-> *level* level level-idx)))
        (when (or (= (-> lev status) 'active)
                  (= (-> lev status) 'alive)
                  (= (-> lev status) 'loaded)
                  (= (-> lev status) 'reserved)
                  )
          ;; get the warp tpage for this level
          (let ((tpage (-> lev texture-page 5)))
            (when tpage
              ;; the "base" address is the vram address of the start of this tpage
              ;; we're moving it from old to new. Initially, dest is 0, so this works
              ;; even on the first time
              (let ((old-dest-base (-> tpage segment 0 dest))
                    (new-dest-base vram-loc)
                    )
                ;; loop over segments in tpage, assign them to unique vram
                (dotimes (s1-0 3)
                  (relocate-dests! tpage vram-loc s1-0)
                  (+! vram-loc (-> tpage segment s1-0 size))
                  )
                ;; align after tpage (see note above)
                (set! vram-loc (shl (sar (+ vram-loc 4095) 12) 12))

                ;; figure out how much tbp should be adjusted by. instead of figuring out tbp from scratch,
                ;; we can just adjust the current value (on the first run, this will assume each tpage is loaded at 0
                ;; because this is how they are logged in by the level system)
                (let ((tbp-adjust (shr (- new-dest-base (the-as int old-dest-base)) 6)))
                  ;; only if we actually move
                  (when (nonzero? tbp-adjust)
                    ;; for each texture
                    (dotimes (texture-idx (-> tpage length))
                      ;; grab the first adgif-shader by looking in *texture-page-dir* for this tpage and texture-idx.
                      ;; the pointers don't store the lower 4 bits and assume 16-byte alignment for adgif-shader objects.
                      (let ((adgif-iter
                              (the-as
                                adgif-shader
                                (* (-> (the-as shader-ptr (-> *texture-page-dir* entries (-> tpage id) link next texture-idx)) shader) 16)
                                )
                              )
                            )
                        ;; iterate through list, bumping tbp and cbp (clut vram addr)
                        (while (nonzero? (the-as uint adgif-iter))
                          (+! (-> adgif-iter tex0 tbp0) tbp-adjust)
                          (+! (-> adgif-iter tex0 cbp) tbp-adjust)
                          (set! adgif-iter (the-as adgif-shader (* (-> adgif-iter next shader) 16)))
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    (if (< #xa000 vram-loc)
        (format 0 "ERROR: Ran out of texture memory for WARP ~dk of 160k" (/ vram-loc 64))
        )
    )
  0
  (none)
  )

(defmethod clear-ids ((this texture-pool))
  "Forget everything we have in VRAM and invalidate all caching
   of common-segment textures."

  ;; this ids array tracks what the current texture-page loaded in each vram "chunk".
  (dotimes (v1-0 128)
    (set! (-> this ids v1-0) (the-as uint 0))
    )
  0
  (none)
  )

(defmethod update-sprites ((this texture-pool))
  "Redo the layout of sprite textures. This should be done when
   a new level is added that needs new sprite textures, or a level
   is unloaded and its sprite textures are no longer available."

  ;; assign texture to vram addresses
  (lay-out-sprite-tex this)

  ;; forget all previous ids, don't want to reuse the old sprite textures
  ;; as it may have the wrong layout.
  (clear-ids this)

  ;; flag that we no longer need to update this.
  (set! (-> this update-sprites-flag) #f)
  (none)
  )

(defmethod update-warp-and-hud ((this texture-pool))
  "Redo the layout of warp/hud/map textures. This should be done when
   a new level is added that needs new sprite textures, or a level
   is unloaded and its sprite textures are no longer available."
  (lay-out-hud-tex this)
  (lay-out-warp-tex this)
  (clear-ids this)
  (set! (-> this update-flag) #f)
  (none)
  )

(defmethod mark-hud-warp-sprite-dirty ((this texture-pool))
  "Mark that we should update sprite/warp/hud/map before the next use.
   This should happen when any level is loaded/unloaded."
  (set! (-> this update-sprites-flag) #t)
  (set! (-> this update-flag) #t)
  (none)
  )


(defun texture-page-common-boot-allocate ((pool texture-pool) (tpage texture-page) (heap kheap) (tpage-id int))
  "Set up texture that is loaded at boot."

  ;; first, see if it's a common-page texture.
  (let ((common-page-slot-id (get-common-page-slot-by-id pool tpage-id)))
    (cond
      ((>= common-page-slot-id 0)
       ;; if so, put it in the common-page.
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> pool common-page common-page-slot-id) tpage)
       )
      ;; these next cases check for "default-level" textures.
      ;; these are common-segment textures that are loaded at boot, and
      ;; stay with the "default-level" always.
      ((= tpage-id 917)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 0) tpage)
       )
      ((= tpage-id 918)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 1) tpage)
       )
      ((= tpage-id 1106)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 4) tpage)
       )
      ((= tpage-id 1141)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 9) tpage)
       )
      ((= tpage-id 12)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 7) tpage)
       )
      ((= tpage-id 1658)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 8) tpage)
       )
      ((= tpage-id 2841)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 5) tpage)
       )
      ((= tpage-id 2932)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 2) tpage)
       )
      ((= tpage-id 3219)
       (texture-page-common-allocate pool tpage heap tpage-id)
       (set! (-> *level* default-level texture-page 3) tpage)
       )
      ((= tpage-id 3076)
       (texture-page-font-allocate pool tpage heap tpage-id)
       )
      (else
        ;; this texture goes in VRAM permanently.
        (set! (-> *texture-pool* allocate-func) texture-page-default-allocate)
        (texture-page-default-allocate pool tpage heap tpage-id)
        )
      )
    )
  (set! (-> tpage dram-size) (-> tpage size))
  tpage
  )


;;;;;;;;;;;;;;;;;;
;; tpage upload
;;;;;;;;;;;;;;;;;;

;; these functions load tpages using the dma-buffer-add-ref-texture
;; and set up the GS for uploads.
;; the texture-pages are designed in a very special way that allows large uploads
;; to be chained together, so there is a bunch of logic to group together consecutive
;; uploads.

(defun upload-vram-data ((buf dma-buffer) (dest int) (data pointer) (height int) (width int))
  "Generate DMA data to upload texture. This simply sets up a texture upload to happen in the future.
  The texture data itself is referenced, not copied into the dma-buffer."
  (#when PC_PORT
    ;; disabled.
    (return #f)
    )
  (while (> height 0)
    (let ((height-this-time (min 2048 height)))
      (dma-buffer-add-gs-set buf
                             (bitbltbuf (new 'static 'gs-bitbltbuf :dbw (/ width 64) :dbp dest))
                             (trxpos (new 'static 'gs-trxpos))
                             (trxreg (new 'static 'gs-trxreg :rrw width :rrh height-this-time))
                             (trxdir (new 'static 'gs-trxdir))
                             )
      (dma-buffer-add-ref-texture buf data width height-this-time (gs-psm ct32))
      )
    (+! dest 4096)
    (&+! data #x100000)
    (+! height -2048)
    )
  (none)
  )

(defun upload-vram-pages ((pool texture-pool)
                          (dest-seg texture-pool-segment)
                          (tpage texture-page)
                          (mode tex-upload-mode)
                          (bucket bucket-id)
                          )
  "Build DMA for uploading the given texture-page, only uploading as needed."
  (local-vars
    (data-ptr pointer)
    (vram-ptr uint)
    (tpage-num-chunks int)
    (chunks-pending int)
    (first-chunk int)
    (tpage-id uint)
    )
  (if (not tpage)
      (return 0)
      )
  ;; count total uploaded, for statistics
  (let ((num-chunks 0))
    (let* ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
           (s4-0 (-> dma-buf base))
           )

      ;; the data in main memory to upload
      (set! data-ptr (-> tpage segment 0 block-data))

      ;; where to send the data in VRAM
      (set! vram-ptr (shr (-> tpage segment 0 dest) 12))

      ;; the total number of chunks we want to upload (assume just seg 0)
      (set! tpage-num-chunks (the-as int (-> tpage segment 0 size)))

      ;; the number of chunks in a row to upload
      (set! chunks-pending 0)

      ;; the index of the first chunk to upload in the combo above
      (set! first-chunk 0)

      (set! tpage-id (-> tpage id))

      ;; adjust based on the mode requested
      (case mode
            (((tex-upload-mode none))
             ;; why even bother...
             (return 0)
             )
            (((tex-upload-mode seg0))
             )
            (((tex-upload-mode seg0-1))
             ;; add seg 1
             (set! tpage-num-chunks (the-as int (+ tpage-num-chunks (-> tpage segment 1 size))))
             )
            (((tex-upload-mode seg0-1-2))
             ;; all segs
             (set! tpage-num-chunks (the-as int (-> tpage size)))
             )
            (((tex-upload-mode seg2))
             ;; just seg2
             (set! data-ptr (-> tpage segment 2 block-data))
             (set! vram-ptr (shr (-> tpage segment 2 dest) 12))
             (set! tpage-num-chunks (the-as int (-> tpage segment 2 size)))
             )
            )
      ;; align, and truncate if we would go past the segment max
      (set! tpage-num-chunks (shr (min (the-as int (-> dest-seg size)) (the-as int (+ tpage-num-chunks 4095))) 12))

      ;; this loop builds runs of consecutive chunks.
      (dotimes (s1-0 tpage-num-chunks)
        ;; where to put this chunk in vram
        (let ((v1-28 (+ vram-ptr s1-0)))
          (cond
            ((zero? chunks-pending) ;; nothing in the run:

             ;; doesn't match what's in vram. need to upload, so start a run.
             (when (!= (-> pool ids v1-28) tpage-id)
               (set! first-chunk s1-0)
               (set! (-> pool ids v1-28) tpage-id)
               (set! chunks-pending (+ chunks-pending 1))
               )
             )
            ;; otherwise, we have chunks pending.
            ;; and, in this case, we don't need to upload. so this breaks the combo
            ;; and we should upload whatever is pending.
            ((= (-> pool ids v1-28) tpage-id)
             (upload-vram-data
               dma-buf
               (the-as int (* (+ vram-ptr first-chunk) 64))
               (&+ data-ptr (shl first-chunk 14))
               (* chunks-pending 32)
               128
               )
             (+! num-chunks chunks-pending) ;; track total for stats
             (set! chunks-pending 0)
             0
             )
            (else
              ;; continue the run.
              (set! (-> pool ids v1-28) tpage-id)
              (set! chunks-pending (+ chunks-pending 1))
              )
            )
          )
        )

      ;; done looping, upload any remaining run.
      (when (nonzero? chunks-pending)
        (upload-vram-data
          dma-buf
          (the-as int (* (+ vram-ptr first-chunk) 64))
          (&+ data-ptr (shl first-chunk 14))
          (* chunks-pending 32)
          128
          )
        (+! num-chunks chunks-pending)
        )

      ;; og:preserve-this
      ;; add a texflush.
      (dma-buffer-add-gs-set dma-buf (texflush 1))

      (#when PC_PORT
        (dma-buffer-add-cnt-vif2 dma-buf 1 (new 'static 'vif-tag :cmd (vif-cmd pc-port)) (the-as vif-tag 3))
        (dma-buffer-add-uint64 dma-buf tpage)
        (dma-buffer-add-uint64 dma-buf mode)
        )

      (let ((a3-3 (-> dma-buf base)))
        (let ((v1-54 (the-as dma-packet (-> dma-buf base))))
          (set! (-> v1-54 dma) (new 'static 'dma-tag :id (dma-tag-id next)))
          (set! (-> v1-54 vif0) (new 'static 'vif-tag))
          (set! (-> v1-54 vif1) (new 'static 'vif-tag))
          (set! (-> dma-buf base) (the-as pointer (&+ v1-54 16)))
          )
        (dma-bucket-insert-tag
          (-> *display* frames (-> *display* on-screen) bucket-group)
          bucket
          s4-0
          (the-as (pointer dma-tag) a3-3)
          )
        )
      )
    (shl num-chunks 14)
    )
  )

(defun update-vram-pages ((pool texture-pool) (dest-seg texture-pool-segment) (tpage texture-page) (mode tex-upload-mode))
  "Copy-pasta version of the above function that just sets ids, but does no uploads."
  (-> tpage segment 0 block-data)
  (let ((vram-ptr (shr (-> tpage segment 0 dest) 12))
        (tpage-num-chunks (-> tpage segment 0 size))
        (chunks-pending 0)
        )
    0
    (let ((tpage-id (-> tpage id)))
      (cond
        ((= mode (tex-upload-mode none))
         (return 0)
         )
        ((= mode (tex-upload-mode seg0))
         )
        ((= mode (tex-upload-mode seg0-1))
         (+! tpage-num-chunks (-> tpage segment 1 size))
         )
        ((= mode (tex-upload-mode seg0-1-2))
         (set! tpage-num-chunks (-> tpage size))
         )
        ((= mode (tex-upload-mode seg2))
         (-> tpage segment 2 block-data)
         (set! vram-ptr (shr (-> tpage segment 2 dest) 12))
         (set! tpage-num-chunks (-> tpage segment 2 size))
         )
        )
      (let ((adjusted-num-chunks (shr (min (the-as int (-> dest-seg size)) (the-as int (+ tpage-num-chunks 4095))) 12)))
        (dotimes (chunk-idx adjusted-num-chunks)
          (let ((chunk-ptr (+ vram-ptr chunk-idx)))
            (cond
              ((zero? chunks-pending)
               (when (!= (-> pool ids chunk-ptr) tpage-id)
                 (set! (-> pool ids chunk-ptr) tpage-id)
                 (+! chunks-pending 1)
                 )
               )
              ((= (-> pool ids chunk-ptr) tpage-id)
               (set! chunks-pending 0)
               )
              (else
                (set! (-> pool ids chunk-ptr) tpage-id)
                (+! chunks-pending 1)
                )
              )
            )
          )
        )
      )
    )
  0
  )

;; og:preserve-this pc port function
(defun upload-vram-pages-pris-pc ((pool texture-pool)
                                  (dest-seg texture-pool-segment)
                                  (tpage texture-page)
                                  (bucket bucket-id)
                                  (mask (pointer int32))
                                  )
  "Build DMA for uploading the given texture-page in pc format.
  We don't use the mask system of the original game properly."
  (if (not tpage)
      (return 0)
      )
  (let* ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
         (s4-0 (-> dma-buf base))
         (any-uploads #f)
         (vram-ptr (shr (-> tpage segment 0 dest) 12))
         (tpage-num-chunks (the-as int (-> tpage size)))
         (tpage-id (-> tpage id))
         )
    ;; align and truncate
    (set! tpage-num-chunks (shr (min (the-as int (-> dest-seg size)) (the-as int (+ tpage-num-chunks 4095))) 12))

    ;; loop over chunks, seeing if any need to be uploaded.
    (dotimes (chunk-idx tpage-num-chunks)
      (let ((mask-work (-> mask (/ chunk-idx 32))))
        (when (logtest? mask-work (ash 1 (logand chunk-idx 31)))
          (set! any-uploads #t)
          )
        )
      )

    ;; not used at all. we don't set merc masks on PC, so this should happen most of the time.
    ; (when (not any-uploads)
    ;   (return 0)
    ;   )

    ;; but non-merc users of this function (like map/hud) will get here

    ;; upload everything in the tpage.
    ;; uploads are "free" on PC, so no harm, but we have to tell the game we're doing this
    ;; otherwise it might think that an old texture was left behind and skip a later upload
    (dotimes (chunk-idx tpage-num-chunks)
      (let ((chunk-dest (+ vram-ptr chunk-idx)))
        (set! (-> pool ids chunk-dest) tpage-id)
        )
      )

    ;; tell pc port we upload everything.
    (dma-buffer-add-cnt-vif2 dma-buf 1 (new 'static 'vif-tag :cmd (vif-cmd pc-port)) (the-as vif-tag 3))
    (dma-buffer-add-uint64 dma-buf tpage)
    (dma-buffer-add-uint64 dma-buf -1)

    (let ((a3-5 (-> dma-buf base)))
      (let ((v1-43 (the-as dma-packet (-> dma-buf base))))
        (set! (-> v1-43 dma) (new 'static 'dma-tag :id (dma-tag-id next)))
        (set! (-> v1-43 vif0) (new 'static 'vif-tag))
        (set! (-> v1-43 vif1) (new 'static 'vif-tag))
        (set! (-> dma-buf base) (the-as pointer (&+ v1-43 16)))
        )
      (dma-bucket-insert-tag
        (-> *display* frames (-> *display* on-screen) bucket-group)
        bucket
        s4-0
        (the-as (pointer dma-tag) a3-5)
        )
      )
    )
  0
  )


(defun upload-vram-pages-pris ((pool texture-pool)
                               (dest-seg texture-pool-segment)
                               (tpage texture-page)
                               (bucket bucket-id)
                               (mask (pointer int32))
                               )
  "Does nothing on PC.
  Build DMA for uploading the given texture-page, only uploading as needed.
   Unlike the normal upload-vram-pages, this just takes an array of mask bits,
   and only uploads seg 0 (there is no upload mode).
   See upload-vram-pages for some more details."
  (local-vars
    (data-ptr pointer)
    (vram-ptr uint)
    (tpage-num-chunks int)
    (chunks-pending int)
    (first-chunk int)
    (tpage-id uint)
    (should-upload symbol)
    )
  (if (not tpage)
      (return 0)
      )
  (let ((total-chunks-uploaded 0))
    (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf))
                                 bucket
                                 )
      (set! data-ptr (-> tpage segment 0 block-data))
      (set! vram-ptr (shr (-> tpage segment 0 dest) 12))
      (set! tpage-num-chunks (the-as int (-> tpage size)))
      (set! chunks-pending 0)
      (set! first-chunk 0)
      (set! tpage-id (-> tpage id))

      ;; align and truncate
      (set! tpage-num-chunks (shr (min (the-as int (-> dest-seg size)) (the-as int (+ tpage-num-chunks 4095))) 12))

      ;; loop over chunks, building runs of consecutive uploads.
      (dotimes (chunk-idx tpage-num-chunks)
        (let ((chunk-dest (+ vram-ptr chunk-idx)))
          (let ((mask-work (-> mask (/ chunk-idx 32))))
            (set! should-upload (logtest? mask-work (ash 1 (logand chunk-idx 31))))
            )
          (cond
            ((zero? chunks-pending)
             (when (and (!= (-> pool ids chunk-dest) tpage-id) should-upload)
               (set! first-chunk chunk-idx)
               (set! (-> pool ids chunk-dest) tpage-id)
               (set! chunks-pending (+ chunks-pending 1))
               )
             )
            ((or (= (-> pool ids chunk-dest) tpage-id) (not should-upload))
             (upload-vram-data
               dma-buf
               (the-as int (* (+ vram-ptr first-chunk) 64))
               (&+ data-ptr (shl first-chunk 14))
               (* chunks-pending 32)
               128
               )
             (+! total-chunks-uploaded chunks-pending)
             (set! chunks-pending 0)
             0
             )
            (else
              (set! (-> pool ids chunk-dest) tpage-id)
              (set! chunks-pending (+ chunks-pending 1))
              )
            )
          )
        )
      (when (nonzero? chunks-pending)
        (upload-vram-data
          dma-buf
          (the-as int (* (+ vram-ptr first-chunk) 64))
          (&+ data-ptr (shl first-chunk 14))
          (* chunks-pending 32)
          128
          )
        (+! total-chunks-uploaded chunks-pending)
        )
      (dma-buffer-add-gs-set dma-buf (texflush 1))
      )
    (shl total-chunks-uploaded 14)
    )
  )

;;;;;;;;;;;;;;;;;
;; level texture
;;;;;;;;;;;;;;;;;

(defun texture-page-level-allocate ((pool texture-pool) (tpage texture-page) (heap kheap) (tpage-id int))
  "Allocate function for level textures."

  ;; a bit of a hack: we assume that textures come immediately after the last code of a level,
  ;; so if we see that code-memory-end isn't set, set it here.
  (if (zero? (-> *level* loading-level code-memory-end))
      (set! (-> *level* loading-level code-memory-end) (the-as pointer tpage))
      )

  ;; see if we got a common-page texture
  (let ((common-page-slot-id (get-common-page-slot-by-id pool tpage-id)))
    (cond
      ((>= common-page-slot-id 0)
       ;; we did! allocate as normal
       (texture-page-common-allocate pool tpage heap tpage-id)
       ;; and add to common-page
       (set! (-> pool common-page common-page-slot-id) tpage)
       )
      (else
        ;; otherwise just add like normal
        ;; note: unlike jak 1, there's no "near page" that levels use.
        ;; it's all in the common segment.
        (texture-page-common-allocate pool tpage heap tpage-id)
        )
      )
    )
  tpage
  )

(defun texture-page-size-check ((pool texture-pool) (lev level) (silent symbol))
  "Check sizes for level textures, return a mask with a bit set for each failing tpage."

  ;; note that with jak 2's "combo" textures for warp/sprite/hud, we can't
  ;; detect out of VRAM until we actually we have all levels and try to layout the combo.

  (let ((gp-0 0))
    (let ((v1-0 (-> lev texture-page 0)))
      (when v1-0
        (if (< (the-as uint #x3e000) (-> v1-0 size))
            (set! gp-0 (logior gp-0 1))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S TFRAG  tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-0 name)
              (shr (-> v1-0 size) 8)
              992
              )
            )
        )
      )
    (let ((v1-2 (-> lev texture-page 1)))
      (when v1-2
        (if (< (the-as uint #x3e000) (-> v1-2 size))
            (set! gp-0 (logior gp-0 2))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S PRIS  tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-2 name)
              (shr (-> v1-2 size) 8)
              992
              )
            )
        )
      )
    (let ((v1-4 (-> lev texture-page 6)))
      (when v1-4
        (if (< (the-as uint #x3e000) (-> v1-4 size))
            (set! gp-0 (logior gp-0 64))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S PRIS2 tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-4 name)
              (shr (-> v1-4 size) 8)
              992
              )
            )
        )
      )
    (let ((v1-6 (-> lev texture-page 2)))
      (when v1-6
        (if (< (the-as uint #x3e000) (-> v1-6 size))
            (set! gp-0 (logior gp-0 4))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S SHRUB tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-6 name)
              (shr (-> v1-6 size) 8)
              992
              )
            )
        )
      )
    (let ((v1-8 (-> lev texture-page 3)))
      (when v1-8
        (if (< (the-as uint #x3e000) (-> v1-8 size))
            (set! gp-0 (logior gp-0 8))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S ALPHA tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-8 name)
              (shr (-> v1-8 size) 8)
              992
              )
            )
        )
      )
    (let ((v1-10 (-> lev texture-page 4)))
      (when v1-10
        (if (< (the-as uint #x3e000) (-> v1-10 size))
            (set! gp-0 (logior gp-0 16))
            )
        (if (not silent)
            (format #t "~Tlevel ~10S WATER tpage ~A uses ~DK of common ~DK~%"
              (-> lev name)
              (-> v1-10 name)
              (shr (-> v1-10 size) 8)
              992
              )
            )
        )
      )

    ;; just print sizes.
    (let ((v1-12 (-> lev texture-page 7)))
      (when v1-12
        (if (not silent)
            (format #t "~Tlevel ~10S SPRITE tpage ~A uses ~DKK~%" (-> lev name) (-> v1-12 name) (shr (-> v1-12 size) 8))
            )
        )
      )
    (let ((v1-14 (-> lev texture-page 8)))
      (when v1-14
        (if (not silent)
            (format #t "~Tlevel ~10S HUD tpage ~A uses ~DKK~%" (-> lev name) (-> v1-14 name) (shr (-> v1-14 size) 8))
            )
        )
      )
    (let ((v1-16 (-> lev texture-page 5)))
      (when v1-16
        (if (not silent)
            (format #t "~Tlevel ~10S WARP tpage ~A uses ~DKK~%" (-> lev name) (-> v1-16 name) (shr (-> v1-16 size) 8))
            )
        )
      )
    gp-0
    )
  )

(defmethod login-level-textures ((pool texture-pool) (lev level) (num-tpage-ids int) (tpage-ids (pointer texture-id)))
  "After all tpages are loaded, call this function to set up level textures.
   It'll call texture-page-login on each tpage, and set up texture-page field of level."

  ;; the properly set up texture pages will be stored here. for now, set to #f
  (dotimes (v1-0 18)
    (set! (-> lev texture-page v1-0) #f)
    )

  ;; iterate through the tpage indices given to us by the bsp-header. There's one per tpage category.
  ;; we might have loaded texture in the wrong order, this will untangle that.
  ;; it'll also make sure that the login succeeds.
  ;; note that unlike jak 1, this won't call texture-page-login for texture-pages that we didn't get.
  (dotimes (lev-tex-idx num-tpage-ids)
    ;; get the tpage-id in the bsp
    (let ((tpage-id (-> tpage-ids lev-tex-idx)))
      (when (and (nonzero? tpage-id) (< lev-tex-idx 18))
        ;; now loop through the tpages that were loaded as part of this level, and find one that matches:
        (dotimes (loaded-tpage-idx (-> lev loaded-texture-page-count))
          ;; check for match
          (when (= (-> lev loaded-texture-page loaded-tpage-idx id) (-> tpage-id page))
            ;; confirm that this tpage matches the one in the directory.
            (let ((logged-in-tpage-id (texture-page-login tpage-id texture-page-common-allocate loading-level)))
              ;; if we got a successful login, and there tpage matches the bsp one
              (if (and logged-in-tpage-id (= (-> logged-in-tpage-id page) (-> lev loaded-texture-page loaded-tpage-idx)))
                  ;; so store it as successful!
                  (set! (-> lev texture-page lev-tex-idx) (-> logged-in-tpage-id page))
                  )
              )
            (goto cfg-20)
            )
          )
        )
      )
    (label cfg-20)
    )

  ;; check for oversize. if so print errors.
  (let ((a2-3 (texture-page-size-check pool lev #t)))
    (when (nonzero? a2-3)
      (format #t "--------------------  tpage overflow error #x~X~%" a2-3)
      (texture-page-size-check pool lev #f)
      (format #t "--------------------~%")
      )
    )
  0
  (none)
  )

(defmethod add-level-tpage-dma ((pool texture-pool) (lev level) (cat tpage-category) (bucket bucket-id))
  "Add dma to upload a tpage of a level."
  (with-pp
    ;; get the tpage from the level
    (let ((tpage (-> lev texture-page cat)))

      ;; closest object no longer used at all, it's all masks!
      (-> lev closest-object-array cat)

      (when (and tpage (nonzero? tpage))
        (case cat ;; different logic for different categories
          (((tpage-category tfrag))
           ;; I guess we have two categories here, grab both and or their masks.
           (let ((v1-7 (-> lev texture-mask 0))
                 (a0-2 (-> lev texture-mask 11))
                 )
             (dotimes (a1-1 3)
               (logior! (-> v1-7 mask data a1-1) (-> a0-2 mask data a1-1))
               )
             )
           ;; upload with masks! the terribly named -pris function just means it takes a mask
           ;; instead of the old seg0,1,2 system.
           (set! (-> lev upload-size 0) (upload-vram-pages-pris-pc
                                          pool
                                          (-> pool segment-common)
                                          tpage
                                          bucket
                                          (the-as (pointer int32) (-> lev texture-mask))
                                          )
                 )
           )
          (((tpage-category shrub))
           ;; shrub: works just like tfrag
           (let ((v1-11 (-> lev texture-mask 2))
                 (a0-6 (-> lev texture-mask 13))
                 )
             (dotimes (a1-3 3)
               (logior! (-> v1-11 mask data a1-3) (-> a0-6 mask data a1-3))
               )
             )
           (set! (-> lev upload-size 2) (upload-vram-pages-pris
                                          pool
                                          (-> pool segment-common)
                                          tpage
                                          bucket
                                          (the-as (pointer int32) (-> lev texture-mask 2))
                                          )
                 )
           )
          (((tpage-category alpha))
           ;; alpha has some special cases
           (cond
             ;; og:preserve-this constant
             ((= (-> lev index) LEVEL_MAX) ;; default level
              ;; if the auto-save-icon-flag is set, clear flag and upload next time.
              (if (not (-> *bigmap* auto-save-icon-flag))
                  (set! (-> lev upload-size 8)
                        (upload-vram-pages pool (-> pool segment-common) tpage (tex-upload-mode seg0-1-2) bucket)
                        )
                  )
              (set! (-> *bigmap* auto-save-icon-flag) #f)
              )
             (else
               ;; otherwise, use the usual logic.
               (let ((t0-13 (-> lev texture-mask 3)))
                 (let ((v1-22 (-> lev texture-mask 14)))
                   (dotimes (a0-12 3)
                     (logior! (-> t0-13 mask data a0-12) (-> v1-22 mask data a0-12))
                     )
                   )
                 (set! (-> lev upload-size 3)
                       (upload-vram-pages-pris pool (-> pool segment-common) tpage bucket (the-as (pointer int32) t0-13))
                       )
                 )
               )
             )
           )
          (((tpage-category pris))
           ;; pris work like normal, but only 1 tpage.
           (set! (-> lev upload-size 1) (upload-vram-pages-pris-pc
                                          pool
                                          (-> pool segment-common)
                                          tpage
                                          bucket
                                          (the-as (pointer int32) (-> lev texture-mask 12))
                                          )
                 )
           )
          (((tpage-category water))
           ;; water is like normal, 1 tpage.
           (let ((t0-15 (-> lev texture-mask 4)))
             (let ((v1-27 (-> lev texture-mask 15)))
               (dotimes (a0-19 3)
                 (logior! (-> t0-15 mask data a0-19) (-> v1-27 mask data a0-19))
                 )
               )
             (set! (-> lev upload-size 4)
                   (upload-vram-pages-pris-pc pool (-> pool segment-common) tpage bucket (the-as (pointer int32) t0-15))
                   )
             )
           )
          (((tpage-category warp))
           ;; warp has no masks, just uploads all
           (set! (-> lev upload-size 5)
                 (upload-vram-pages pool (-> pool segment-common) tpage (tex-upload-mode seg0-1-2) bucket)
                 )
           )
          (((tpage-category pris2))
           ;; pris2 is normal, 1 tpage.
           (set! (-> lev upload-size 6) (upload-vram-pages-pris-pc
                                          pool
                                          (-> pool segment-common)
                                          tpage
                                          bucket
                                          (the-as (pointer int32) (-> lev texture-mask 17))
                                          )
                 )
           )
          (((tpage-category sprite))
           ;; sprite skips uploads if the level isn't drawing, but otherwise uploads the whole thing.
           (when (or (= (-> lev display?) 'display) (= (-> lev display?) 'actor) (= (-> lev index) LEVEL_MAX))
                 (set! (-> lev upload-size 7)
                       (upload-vram-pages pool (-> pool segment-common) tpage (tex-upload-mode seg0-1-2) bucket)
                       )
               )
           )
          (((tpage-category map))
           ;; map doesn't use masks for the default level.
           (cond
             ;; og:preserve-this constant
             ((= (-> lev index) LEVEL_MAX)
              (set! (-> lev upload-size 8)
                    (upload-vram-pages pool (-> pool segment-common) tpage (tex-upload-mode seg0-1-2) bucket)
                    )
              )
             (else
               (let ((t0-20 (-> lev texture-mask cat)))
                 (set! (-> lev upload-size 8)
                       (upload-vram-pages-pris-pc pool (-> pool segment-common) tpage bucket (the-as (pointer int32) t0-20))
                       )
                 )
               )
             )
           )
          (((tpage-category sky))
           ;; sky uploads the whole thing.
           (set! (-> lev upload-size 9)
                 (upload-vram-pages pool (-> pool segment-common) tpage (tex-upload-mode seg0-1-2) bucket)
                 )
           )
          )

        ;; next, update anims, if needed
        (let ((s2-0 (-> lev texture-anim-array cat)))
          (cond
            ((= cat (tpage-category warp))
             ;; og:preserve-this constant
             ;; warps put all their update-texture-anim's with the default level:
             (when (= (-> lev index) LEVEL_MAX)
               (dotimes (s2-1 LEVEL_TOTAL)
                 (let ((v1-54 (-> *level* level s2-1)))
                   (when (or (= (-> v1-54 status) 'active) (= (-> v1-54 status) 'reserved))
                     (let ((a1-26 (-> v1-54 texture-anim-array 5)))
                       (if a1-26
                           (update-texture-anim bucket a1-26)
                           )
                       )
                     )
                   )
                 )
               )
             )
            ((= cat (tpage-category sky))
             ;; hack for a certain level here...
             (cond
               ((and (level-get-target-inside *level*) (= (-> (level-get-target-inside *level*) info taskname) 'nest))
                (let ((f30-0 (seconds-per-frame)))
                  (set! (-> pp clock seconds-per-frame) (* 10.0 (seconds-per-frame)))
                  (if s2-0
                      (update-texture-anim bucket s2-0)
                      )
                  (set! (-> pp clock seconds-per-frame) f30-0)
                  )
                )
               (else
                 (if s2-0
                     (update-texture-anim bucket s2-0)
                     )
                 )
               )
             )
            (else
              ;; otherwise, just update if there's any anims.
              (if s2-0
                  (update-texture-anim bucket s2-0)
                  )
              )
            )
          )
        )
      )

    ;; unclear what can set these yet, but levels can request that certain vram is dirty
    (let ((v1-77 (-> lev texture-dirty-masks cat)))
      (dotimes (a0-58 128)
        (let ((a2-2 (-> v1-77 mask data (/ a0-58 32))))
          (when (logtest? a2-2 (ash 1 (logand a0-58 31)))
            (set! (-> pool ids a0-58) (the-as uint 0))
            0
            )
          )
        )
      (set! (-> v1-77 mask quad) (the-as uint128 0))
      )
    0
    0
    (none)
    )
  )

(defun set-skull-gem-masks ()
  "Set the default-level texture-mask to include masks from three skull gem textures."
  (local-vars (v0-3 uint128) (v1-2 uint128) (v1-3 uint128))
  (let ((gp-0 (-> *level* default-level texture-mask)))
    (let* ((s5-0 (get-texture skull-gem-alpha-00 level-default-tfrag))
           (s4-0 (get-texture skull-gem-alpha-01 level-default-tfrag))
           (a0-4 (get-texture skull-gem-alpha-02 level-default-tfrag))
           (v1-1 (-> gp-0 0 mask quad))
           (a1-0 (-> s5-0 masks data 0 mask quad))
           (a2-0 (-> s4-0 masks data 0 mask quad))
           (a0-5 (-> a0-4 masks data 0 mask quad))
           )
      (.por v1-2 v1-1 a1-0)
      (.por v1-3 v1-2 a2-0)
      (.por v0-3 v1-3 a0-5)
      )
    (set! (-> gp-0 0 mask quad) v0-3)
    )
  (none)
  )

(defun upload-textures ((arg0 texture-pool))
  "Build DMA for all texture uploads.
   This should be called after all drawing is done and all masks/distances are set."

  ;; set up skull-gem stuff, not yet understood
  (cond
    ((not (-> *blit-displays-work* screen-copied))
     (set-skull-gem-masks)
     (set! (-> *level* default-level texture-anim-array 0) *skull-gem-texture-anim-array*)
     )
    (else
      (set! (-> *level* default-level texture-anim-array 0) #f)
      )
    )

  ;; og:preserve-this constant
  ;; clear upload-size
  (dotimes (lev-idx LEVEL_TOTAL)
    (let ((lev (-> *level* level lev-idx)))
      (when (or (= (-> lev status) 'active) (= (-> lev status) 'reserved))
        (dotimes (a1-6 18)
          (set! (-> lev upload-size a1-6) 0)
          )
        )
      )
    )

  ;; loop over tpages, using texture-page-translate to get all of them.
  (dotimes (num-tpage (-> *texture-page-translate* length))
    (let* ((tpage-info (-> *texture-page-translate* num-tpage))
           (src-level (-> *level* draw-level (-> tpage-info level-index)))
           )
      (when (= num-tpage 63) ;; ??
        (nop!)
        (nop!)
        0
        )
      (when (and src-level (logtest? (-> *texture-pool* texture-enable-user) (-> tpage-info texture-user)))
        (cond
          ;; og:preserve-this constant
          ((= (-> tpage-info level-index) LEVEL_MAX)
           ;; always upload default-level textures
           (add-level-tpage-dma
             arg0
             src-level
             (the-as tpage-category (-> tpage-info level-texture-page))
             (-> tpage-info bucket)
             )
           )
          (else
            ;; otherwise, only upload level textures if not in menu-mode.
            (if (not (-> *blit-displays-work* menu-mode))
                (add-level-tpage-dma
                  arg0
                  src-level
                  (the-as tpage-category (-> tpage-info level-texture-page))
                  (-> tpage-info bucket)
                  )
                )
            )
          )
        )
      )
    )

  ;; og:preserve-this constant
  ;; reset masks and closest array for the next frame!
  (dotimes (v1-16 LEVEL_TOTAL)
    (let ((a0-30 (-> *level* level v1-16)))
      (when (or (= (-> a0-30 status) 'active) (= (-> a0-30 status) 'reserved))
        (dotimes (a1-15 18)
          (set! (-> a0-30 closest-object-array a1-15) 4095996000.0)
          (set! (-> a0-30 texture-mask a1-15 mask quad) (the-as uint128 0))
          )
        )
      )
    )
  0
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture DMA outside of main DMA list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; there are a few places at boot where we need to do uploads/copies
;; without using the main DMA list.

;; to hold the dma chain - doesn't hold textures, just DMA :ref tags and similar.
(kmemopen global "texture-dma-buffers")
(define *txt-dma-list* (new 'global 'dma-buffer 4096))
(kmemclose)

(defmethod upload-now! ((this texture-page) (arg0 tex-upload-mode))
  "Send the given texture-page to VRAM right now. This function doesn't return until
   it has happened, and only should be used during boot."

   ;; og:preserve-this
   (#when PC_PORT
     ;; load it to the PC Port's texture pool.
     (__pc-texture-upload-now this arg0)
     )

  (let ((gp-0 *txt-dma-list*))
    (let ((v1-0 gp-0))
      (set! (-> v1-0 base) (-> v1-0 data))
      (set! (-> v1-0 end) (&-> v1-0 data-buffer (-> v1-0 allocated-length)))
      )
    (add-to-dma-buffer this gp-0 arg0)
    (dma-buffer-add-gs-set gp-0 (texflush 1))
    (let* ((v1-6 gp-0)
           (a0-7 (the-as object (-> v1-6 base)))
           )
      (set! (-> (the-as dma-packet a0-7) dma) (new 'static 'dma-tag :id (dma-tag-id end)))
      (set! (-> (the-as (pointer int64) a0-7) 1) 0)
      (set! (-> v1-6 base) (&+ (the-as pointer a0-7) 16))
      )
    ;; the actual send
    (#unless PC_PORT
             (dma-buffer-send-chain (the-as dma-bank-source #x1000a000) gp-0)
             )
    )
  ;; wait for dma to finish.
  (dma-sync (the-as pointer #x1000a000) 0 0)
  (none)
  )

(defmethod add-to-dma-buffer ((this texture-page) (arg0 dma-buffer) (arg1 tex-upload-mode))
  "Add upload data for a texture-page. This is a simple version for non common-segment textures."
  (local-vars (sv-16 int))
  (let ((v1-0 arg1))
    (set! sv-16 (cond
                  ((= v1-0 (tex-upload-mode none))
                   0
                   )
                  ((= v1-0 (tex-upload-mode seg0-1))
                   (the-as int (+ (-> this segment 0 size) (-> this segment 1 size)))
                   )
                  ((= v1-0 (tex-upload-mode seg0-1-2))
                   (the-as int (-> this size))
                   )
                  (else
                    (the-as int (-> this segment (the-as int arg1) size))
                    )
                  )
          )
    )
  (let* ((v1-7 (max 0 (the-as int arg1)))
         (a3-4 (* (/ (+ (/ sv-16 64) 63) 64) 32))
         (t1-0 (shr (-> this segment v1-7 dest) 6))
         (a2-10 (-> this segment v1-7 block-data))
         )
    (upload-vram-data arg0 (the-as int t1-0) a2-10 a3-4 128)
    )
  sv-16
  )

(defun texture-relocate ((dma-buff dma-buffer) (tex texture) (dest-loc int) (dest-fmt gs-psm) (clut-dst int))
   "Move a texture in VRAM. Unrelated to the texture-relocate-later, which moves textures in RAM.
   Will try to move the whole thing, including the clut, assuming you provide a destination for it.
   Note that this uses the format/width stuff properly, so it will be slower, but won't scramble your texture."

   ;; og:preserve-this
   (#when PC_PORT
     ;; as far as I know this is only used for fonts which have 1 mip level.
     (__pc-texture-relocate (/ dest-loc 64) (-> tex dest 0) dest-fmt)
     (return dma-buff)
     )
   (dotimes (v1-0 (the-as int (-> tex num-mips)))
    (let ((t1-1 (ash (-> tex w) (- v1-0)))
          (t2-3 (ash (-> tex h) (- v1-0)))
          )
      (dma-buffer-add-gs-set dma-buff
                             (bitbltbuf (new 'static 'gs-bitbltbuf
                                          :sbp (-> tex dest v1-0)
                                          :sbw (-> tex width v1-0)
                                          :spsm (the-as int (-> tex psm))
                                          :dbp (/ dest-loc 64)
                                          :dbw (-> tex width v1-0)
                                          :dpsm (the-as int dest-fmt)
                                          )
                                        )
                             (trxpos (new 'static 'gs-trxpos))
                             (trxreg (new 'static 'gs-trxreg :rrw t1-1 :rrh t2-3))
                             (trxdir (new 'static 'gs-trxdir :xdir #x2))
                             )
      )
    (set! (-> tex dest v1-0) (the-as uint (/ dest-loc 64)))
    )
  (cond
    ((< clut-dst 0)
     )
    ((= (-> tex psm) (gs-psm mt4))
     (dma-buffer-add-gs-set dma-buff
                            (bitbltbuf (new 'static 'gs-bitbltbuf
                                         :sbw #x1
                                         :dbw #x1
                                         :dpsm (-> tex clutpsm)
                                         :dbp (/ clut-dst 64)
                                         :spsm (-> tex clutpsm)
                                         :sbp (-> tex clutdest)
                                         )
                                       )
                            (trxpos (new 'static 'gs-trxpos))
                            (trxreg (new 'static 'gs-trxreg :rrw #x8 :rrh #x2))
                            (trxdir (new 'static 'gs-trxdir :xdir #x2))
                            )
     (set! (-> tex clutdest) (the-as uint (/ clut-dst 64)))
     )
    ((= (-> tex psm) (gs-psm mt8))
     (dma-buffer-add-gs-set dma-buff
                            (bitbltbuf (new 'static 'gs-bitbltbuf
                                         :sbw #x2
                                         :dbw #x2
                                         :dpsm (-> tex clutpsm)
                                         :dbp (/ clut-dst 64)
                                         :spsm (-> tex clutpsm)
                                         :sbp (-> tex clutdest)
                                         )
                                       )
                            (trxpos (new 'static 'gs-trxpos))
                            (trxreg (new 'static 'gs-trxreg :rrw #x10 :rrh #x10))
                            (trxdir (new 'static 'gs-trxdir :xdir #x2))
                            )
     (set! (-> tex clutdest) (the-as uint (/ clut-dst 64)))
     )
    )
  (set! (-> tex psm) dest-fmt)
  dma-buff
  )

(defmethod setup-font-texture ((this texture-pool))
  "Do relocations of font textures."
  (local-vars (sv-16 int) (sv-20 int))
  (let ((s3-0 (-> this font-palette)))
    (set! sv-16 (-> this cur))
    (set! sv-20 (/ s3-0 64))
    (let ((s5-0
            (texture-page-login (new 'static 'texture-id :index #x1 :page #xc04) texture-page-default-allocate global)
            )
          )
      (if (and s5-0 (-> s5-0 page))
          (set! sv-16 (the-as int (-> s5-0 page segment 0 dest)))
          )
      (let ((s4-0 *txt-dma-list*))
        (let ((v1-6 s4-0))
          (set! (-> v1-6 base) (-> v1-6 data))
          (set! (-> v1-6 end) (&-> v1-6 data-buffer (-> v1-6 allocated-length)))
          )
        (let ((s2-0 (lookup-texture-by-id (new 'static 'texture-id :index #x1 :page #xc04)))
              (s1-0 #xc2000)
              (s0-0 36)
              )
          (set! (-> s2-0 h) 320)
          (texture-relocate s4-0 s2-0 s1-0 (the-as gs-psm s0-0) s3-0)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* small-font-0-tmpl)) s2-0 s1-0 s0-0 sv-20)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* small-font-2-tmpl)) s2-0 s1-0 s0-0 sv-20)
          )
        (let ((s3-1 (lookup-texture-by-id (new 'static 'texture-id :page #xc04)))
              (s2-1 #xc2000)
              (s1-1 44)
              )
          (set! (-> s3-1 h) 320)
          (texture-relocate s4-0 s3-1 s2-1 (the-as gs-psm s1-1) -1)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* small-font-1-tmpl)) s3-1 s2-1 s1-1 sv-20)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* small-font-3-tmpl)) s3-1 s2-1 s1-1 sv-20)
          )
        (let ((s3-2 (lookup-texture-by-id (new 'static 'texture-id :index #x4 :page #xc04)))
              (s2-2 #x90000)
              (s1-2 36)
              )
          (set! (-> s3-2 h) 800)
          (texture-relocate s4-0 s3-2 s2-2 (the-as gs-psm s1-2) -1)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* large-font-0-tmpl)) s3-2 s2-2 s1-2 sv-20)
          )
        (let ((s3-3 (lookup-texture-by-id (new 'static 'texture-id :index #x2 :page #xc04)))
              (s2-3 #x90000)
              (s1-3 44)
              )
          (set! (-> s3-3 h) 800)
          (texture-relocate s4-0 s3-3 s2-3 (the-as gs-psm s1-3) -1)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* large-font-1-tmpl)) s3-3 s2-3 s1-3 sv-20)
          )
        (let ((s3-4 (lookup-texture-by-id (new 'static 'texture-id :index #x5 :page #xc04)))
              (s2-4 #x5e000)
              (s1-4 36)
              )
          (set! (-> s3-4 h) 800)
          (texture-relocate s4-0 s3-4 s2-4 (the-as gs-psm s1-4) -1)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* large-font-2-tmpl)) s3-4 s2-4 s1-4 sv-20)
          )
        (let ((s3-5 (lookup-texture-by-id (new 'static 'texture-id :index #x3 :page #xc04)))
              (s2-5 #x5e000)
              (s1-5 44)
              )
          (set! (-> s3-5 h) 800)
          (texture-relocate s4-0 s3-5 s2-5 (the-as gs-psm s1-5) -1)
          (font-set-tex0 (the-as (pointer gs-tex0) (-> *font-work* large-font-3-tmpl)) s3-5 s2-5 s1-5 sv-20)
          )
        (dma-buffer-add-gs-set s4-0 (texflush 1))
        (let* ((v1-31 s4-0)
               (a0-32 (the-as object (-> v1-31 base)))
               )
          (set! (-> (the-as dma-packet a0-32) dma) (new 'static 'dma-tag :id (dma-tag-id end)))
          (set! (-> (the-as (pointer int64) a0-32) 1) 0)
          (set! (-> v1-31 base) (&+ (the-as pointer a0-32) 16))
          )
        (#unless PC_PORT
          (dma-buffer-send-chain (the-as dma-bank-source #x10009000) s4-0)
          )
        )
      (dma-sync (the-as pointer #x10009000) 0 0)
      (if (and s5-0 (-> s5-0 page) (= (-> this cur) (+ sv-16 (-> s5-0 page size))))
          (set! (-> this cur) sv-16)
          (format 0 "ERROR: could not resize texture pool to remove gamefont.~%")
          )
      )
    )
  0
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; basic methods for texture-page-dir
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; the texture-page-dir contains an entry per texture page, for every texture page that we will ever see.
;; at runtime, the level system will allocate link arrays per page. Each page has an array of "link"s, with
;; one link per texture. The link is the head of a linked list through all the static adgif-shaders of the
;; level.

(defmethod asize-of ((this texture-page-dir))
  "Get the size in memory of a texture-page-dir, just the main array, not allocated link stuff."
  (the-as int (+ (-> texture-page-dir size) (* 12 (+ (-> this length) -1))))
  )

(defmethod length ((this texture-page-dir))
  "Get the number of elements."
  (-> this length)
  )

(defmethod relocate ((this texture-page-dir) (arg0 kheap) (arg1 (pointer uint8)))
  "Set up a texture-page-dir when it is loaded."
  ;; there's just one of these, just set the global one to this.
  (set! *texture-page-dir* this)
  (none)
  )

(defmethod relocate-dests! ((this texture-page) (new-dest int) (segs int))
  "Update a texture-page so the texture-page and all its textures point to a new vram
   address."
  (let ((new-tbp (shr new-dest 6))
        (old-tbp (shr (-> this segment segs dest) 6))
        )
    (when (!= new-tbp old-tbp)
      (dotimes (tex-idx (-> this length))
        (when (-> this data tex-idx)
          (let* ((tex (-> this data tex-idx))
                 (num-mips (-> tex num-mips))
                 )
            (if (zero? segs)
                (set! (-> tex clutdest) (+ (- (-> tex clutdest) old-tbp) new-tbp))
                )
            (dotimes (mip-idx (the-as int num-mips))
              (let ((t4-0 mip-idx)
                    (t5-0 num-mips)
                    )
                (if (= segs (if (>= (the-as uint 2) t5-0)
                                (+ (- -1 t4-0) t5-0)
                                (max 0 (- 2 t4-0))
                                )
                       )
                    (set! (-> tex dest mip-idx) (+ (- (-> tex dest mip-idx) old-tbp) new-tbp))
                    )
                )
              )
            )
          )
        )
      (set! (-> this segment segs dest) (the-as uint new-dest))
      )
    )
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; texture-page management
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmethod relocate ((this texture-page) (loading-heap kheap) (name (pointer uint8)))
  "Set up a texture-page when it is loaded. This function is called by the linker when
   loading is completed."

  (cond
    ;; version check!
    ((or (not this) (not (file-info-correct-version? (-> this info) (file-kind tpage) 0)))
     (the-as texture-page #f)
     )
    (else
      (let ((loading-level (-> *level* loading-level)))
        (when loading-level
          ;; if we have a currently loading-level, put this in the loaded-texture-page list
          (set! (-> loading-level loaded-texture-page (-> loading-level loaded-texture-page-count)) this)
          (+! (-> loading-level loaded-texture-page-count) 1)

          ;; move from small-edge to small-center mode if we have 2 or more tpages, I guess?
          (if (and (>= (-> loading-level loaded-texture-page-count) 2) (zero? (-> loading-level load-buffer-mode)))
              (set! (-> loading-level load-buffer-mode) (load-buffer-mode small-center))
              )
          )
        )

      ;; set up dests (no idea why the tpage doesn't come with this set properly)
      (set! (-> this segment 1 dest) (-> this segment 0 size))
      (set! (-> this segment 2 dest) (+ (-> this segment 0 size) (-> this segment 1 size)))

      ;; og:preserve-this added texture remap
      (dotimes (texture-idx (-> this length))
        (let ((tex (-> this data texture-idx)))
          (when (and tex (nonzero? tex))
            (let ((offset (__pc-get-tex-remap (the int (-> this id)) texture-idx)))
              (when (nonzero? offset)
                )
              (+! (-> tex dest 0) offset)
              )
            )
          )
        )

      (let* ((tpage-id (-> this id))
             (dir-entry (-> *texture-page-dir* entries tpage-id))
             )

        ;; clear the memcpy later flag, the allocation function will set it if needed
        ;; (I think this never happens in jak 2?)
        (set! (-> *texture-relocate-later* memcpy) #f)

        ;; do allocation!
        ((-> *texture-pool* allocate-func) *texture-pool* this loading-heap (the-as int tpage-id))
        (cond
          ((not (-> *texture-relocate-later* memcpy))
           ;; not postponing texture memcpy, so it's safe to allocate links now.
           ;; (otherwise its unsafe because we'll want to kick out the texture)
           (set! (-> dir-entry page) this)
           (if (not (-> dir-entry link))
               (set! (-> dir-entry link)
                     (the-as texture-link (malloc 'loading-level (* (max (-> dir-entry length) (-> this length)) 4)))
                     )
               )
           )
          (else
            ;; postponing memcpy and link allocation, fill out our info.
            (let ((v1-19 *texture-relocate-later*))
              (set! (-> v1-19 entry) dir-entry)
              (set! (-> v1-19 page) this)
              )
            )
          )
        )
      this
      )
    )
  )

(defun relocate-later ()
  "The other half to texture-relocate-later: this function does the pending memcpy."
  (let ((gp-0 *texture-relocate-later*))
    (let ((s5-0 (-> gp-0 entry))
          (s4-0 (-> gp-0 page))
          )
      (ultimate-memcpy (the-as pointer (-> gp-0 dest)) (the-as pointer (-> gp-0 source)) (-> gp-0 move))
      (set! (-> s5-0 page) s4-0)
      (if (not (-> s5-0 link))
          (set! (-> s5-0 link)
                (the-as texture-link (malloc 'loading-level (* (max (-> s5-0 length) (-> s4-0 length)) 4)))
                )
          )
      )
    (set! (-> gp-0 memcpy) #f)
    )
  #f
  )

(defun texture-page-login ((tex-id texture-id) (alloc-func (function texture-pool texture-page kheap int texture-page)) (heap kheap))
  "Get a texture-page-dir-entry for the given texture.
   If the texture page is not loaded, it will attempt to load it with loado, which
   would only succeed on development machines."

  ;; make sure it's a valid tpage id
  (when (and (nonzero? (-> tex-id page)) (< (-> tex-id page) (the-as uint (-> *texture-page-dir* length))))
    ;; check the existing entry
    (let ((dir-entry (-> *texture-page-dir* entries (-> tex-id page))))
      (when (not (-> dir-entry page)) ;; empty entry, need to load!

        ;; don't forget the old allocation function...
        (let ((old-alloc-func (-> *texture-pool* allocate-func)))
          (set! (-> *texture-pool* allocate-func) alloc-func)

          ;; load and call relocate.
          (let* ((name (make-file-name (file-kind tpage) (the-as string (* (-> tex-id page) 8)) 0 #f))
                 (loaded-tpage (the-as texture-page (loado name heap)))
                 )
            (if loaded-tpage
                (relocate loaded-tpage heap (charp-basename (-> name data)))
                )
            )
          (set! (-> *texture-pool* allocate-func) old-alloc-func)
          )
        )
      dir-entry
      )
    )
  )

(defun lookup-texture-by-id ((arg0 texture-id))
  "Get a texture for the given texture-id.
   This will do a load that only succeeds on development hardware if it doesn't exist."
  (let ((a0-2 (texture-page-login arg0 texture-page-default-allocate loading-level))
        (v1-0 (the-as texture-page #f))
        )
    (if (and a0-2 (begin (set! v1-0 (-> a0-2 page)) v1-0) (< (-> arg0 index) (the-as uint (-> v1-0 length))))
        (-> v1-0 data (-> arg0 index))
        )
    )
  )

(defun lookup-texture-by-id-fast ((arg0 texture-id))
  "Get a texture for the given id. return #f if it doesn't exist."
  (let ((a1-2 (if (and (nonzero? (-> arg0 page)) (< (-> arg0 page) (the-as uint (-> *texture-page-dir* length))))
                  (-> *texture-page-dir* entries (-> arg0 page))
                  )
              )
        (v1-6 (the-as texture-page #f))
        )
    (if (and a1-2 (begin (set! v1-6 (-> a1-2 page)) v1-6) (< (-> arg0 index) (the-as uint (-> v1-6 length))))
        (-> v1-6 data (-> arg0 index))
        )
    )
  )

(defun lookup-texture-by-name ((arg0 string) (arg1 string) (arg2 (pointer texture-page)))
  "Get texture by name. Slow. Return #f if it doesn't exist."
  (local-vars (sv-16 texture-page-dir))
  (set! sv-16 *texture-page-dir*)
  (dotimes (s3-0 (-> sv-16 length))
    (let ((s2-0 (-> sv-16 entries s3-0 page)))
      (when (and s2-0 (or (not arg1) (string= (-> s2-0 name) arg1)))
        (dotimes (s1-0 (-> s2-0 length))
          (let ((s0-0 (-> s2-0 data s1-0)))
            (when (and s0-0 (string= (-> s0-0 name) arg0))
              (if arg2
                  (set! (-> arg2 0) s2-0)
                  )
              (return s0-0)
              )
            )
          )
        )
      )
    )
  (the-as texture #f)
  )

(defun lookup-texture-id-by-name ((arg0 string) (arg1 string))
  "Get texture id by name. Slow. Return 0 if it doesn't exist."
  (local-vars (sv-16 texture-page-dir))
  (set! sv-16 *texture-page-dir*)
  (dotimes (gp-0 (-> sv-16 length))
    (let ((s3-0 (-> sv-16 entries gp-0 page)))
      (when (and s3-0 (or (not arg1) (string= (-> s3-0 name) arg1)))
        (dotimes (s2-0 (-> s3-0 length))
          (let ((v1-7 (-> s3-0 data s2-0)))
            (if (and v1-7 (string= (-> v1-7 name) arg0))
                (return (new 'static 'texture-id :page gp-0 :index s2-0))
                )
            )
          )
        )
      )
    )
  (the-as texture-id 0)
  )

(defun lookup-level-texture-by-name ((arg0 string) (arg1 level) (arg2 (pointer texture-page)))
  "Like lookup-texture-by-name, but will check texture-pages listed in this level first.
   Unclear why these wouldn't be part of the texture-page-dir."
  (dotimes (s3-0 18)
    (let ((s2-0 (-> arg1 texture-page s3-0)))
      (when (and s2-0 (nonzero? s2-0))
        (dotimes (s1-0 (-> s2-0 length))
          (let ((s0-0 (-> s2-0 data s1-0)))
            (when (and s0-0 (string= (-> s0-0 name) arg0))
              (if arg2
                  (set! (-> arg2 0) s2-0)
                  )
              (return s0-0)
              )
            )
          )
        )
      )
    )
  (lookup-texture-by-name arg0 (the-as string #f) arg2)
  )

(defmethod unload-page ((this texture-pool) (arg0 texture-page))
  "Unload the given texture-page from the texture-page-dir."
  (local-vars (a0-2 int))
  (let ((v1-0 *texture-page-dir*))
    (dotimes (a0-1 (-> v1-0 length))
      (when (= arg0 (-> v1-0 entries a0-1 page))
        (set! a0-2 a0-1)
        (goto cfg-7)
        )
      )
    (set! a0-2 -1)
    (label cfg-7)
    (when (>= a0-2 0)
      (set! (-> v1-0 entries a0-2 page) #f)
      (set! (-> v1-0 entries a0-2 link) #f)
      )
    )
  0
  (none)
  )

;;;;;;;;;;;;;;;;;;;;;;;;;
;; adgif shader/link
;;;;;;;;;;;;;;;;;;;;;;;;;

;; debug menu shader editor
(define *shader-list* '())
(define *edit-shader* (new 'static 'texture-id))

(defun link-texture-by-id ((arg0 texture-id) (arg1 adgif-shader))
  "Link the adgif-shader to the texture specified."
  (when (not (or (zero? (-> arg0 page)) (>= (-> arg0 page) (the-as uint (-> *texture-page-dir* length)))))
    (let ((s4-0 (-> *texture-page-dir* entries (-> arg0 page))))
      (if (not (-> s4-0 link))
          (set! (-> s4-0 link) (the-as texture-link (malloc 'loading-level (* (-> s4-0 length) 4))))
          )
      (when (< (-> arg0 index) (the-as uint (-> s4-0 length)))
        (set! (-> arg1 next shader) (-> s4-0 link next (-> arg0 index) shader))
        (set! (-> s4-0 link next (-> arg0 index) shader) (shr (the-as int arg1) 4))
        )
      s4-0
      )
    )
  )

(defmethod unlink-shaders-in-heap ((this texture-page-dir) (heap kheap))
  "Unlink all adgif shaders that are in heap.
   This iterates through _everything_ so it is somewhat slow."
  (local-vars (dist-past-end uint))
  (let ((mem-start (-> heap base))
        (mem-end (-> heap top-base))
        )
    (dotimes (entry-idx (-> this length))
      (let* ((entry (-> this entries entry-idx))
             (tex-page (-> entry page))
             )
        (when tex-page
          (let ((link-arr (-> entry link next))
                (tex-count (min (-> tex-page length) (-> entry length)))
                )
            0
            (when link-arr
              (dotimes (tex-idx tex-count)
                (let ((link-slot (&-> link-arr 0))
                      (shader (the-as adgif-shader (* (-> link-arr 0 shader) 16)))
                      )
                  (while (nonzero? (the-as uint shader))
                    (b!
                      (< (the-as int (- (the-as uint shader) (the-as uint mem-start))) 0)
                      cfg-8
                      :delay (set! dist-past-end (- (the-as uint shader) (the-as uint mem-end)))
                      )
                    (b! (>= (the-as int dist-past-end) 0) cfg-8 :delay (nop!))
                    (let ((t4-2 (-> shader next)))
                      (b! #t cfg-9 :delay (set! (-> link-slot 0) t4-2))
                      )
                    (label cfg-8)
                    (set! link-slot (&-> shader next))
                    (label cfg-9)
                    (set! shader (the-as adgif-shader (* (-> shader next shader) 16)))
                    )
                  )
                (set! link-arr (&-> link-arr 1))
                )
              )
            )
          )
        )
      )
    )
  0
  )

(defun adgif-shader<-texture! ((arg0 adgif-shader) (arg1 texture))
  "Set up an adgif-shader from a texture."
  (set! (-> arg0 tex1 mxl) (+ (-> arg1 num-mips) -1))
  (set! (-> arg0 tex1 l) (-> arg1 mip-shift))
  (set! (-> arg0 tex1 mmag) (logand (-> arg1 tex1-control) 1))
  (set! (-> arg0 tex1 mmin) (shr (-> arg1 tex1-control) 1))
  (set! (-> arg0 tex0) (new 'static 'gs-tex0
                         :tcc #x1
                         :cld #x1
                         :cpsm (-> arg1 clutpsm)
                         :cbp (-> arg1 clutdest)
                         :tfx (-> arg0 tex0 tfx)
                         :th (log2 (-> arg1 h))
                         :tw (log2 (-> arg1 w))
                         :tbw (-> arg1 width 0)
                         :tbp0 (-> arg1 dest 0)
                         :psm (the-as int (-> arg1 psm))
                         )
        )
  (set! (-> arg0 miptbp1) (new 'static 'gs-miptbp
                            :tbp1 (-> arg1 dest 1)
                            :tbw1 (-> arg1 width 1)
                            :tbp2 (-> arg1 dest 2)
                            :tbw2 (-> arg1 width 2)
                            :tbp3 (-> arg1 dest 3)
                            :tbw3 (-> arg1 width 3)
                            )
        )
  (when (< (the-as uint 4) (-> arg1 num-mips))
    (set! (-> arg0 alpha-as-miptb2) (new 'static 'gs-miptbp
                                      :tbp1 (-> arg1 dest 4)
                                      :tbw1 (-> arg1 width 4)
                                      :tbp2 (-> arg1 dest 5)
                                      :tbw2 (-> arg1 width 5)
                                      :tbp3 (-> arg1 dest 6)
                                      :tbw3 (-> arg1 width 6)
                                      )
          )
    (set! (-> (&-> arg0 reg-4-u32) 0) (gs-reg32 miptbp2-1))
    )
  arg0
  )

(defun adgif-shader-update! ((arg0 adgif-shader) (arg1 texture))
  "Update an adgif shader in response to a texture uv-dist change."
  (let ((s5-0 (the int (/ 256.0 (-> arg1 uv-dist)))))
    (case (-> arg0 tex1 l)
      ((1)
       (set! (-> arg0 tex1 k) (+ (logand (ash s5-0 (- 5 (log2 s5-0))) 31) -350 (* (log2 s5-0) 32)))
       )
      (else
        (set! (-> arg0 tex1 k) (+ (logand (ash s5-0 (- 4 (log2 s5-0))) 15) -175 (* (log2 s5-0) 16)))
        )
      )
    )
  (none)
  )

;; combines the previous two functions.
(def-mips2c adgif-shader<-texture-with-update! (function adgif-shader texture adgif-shader))

(defun hack-texture ((arg0 texture))
  "Some sort of uv hack for hi-res mode"
  (set! (-> arg0 uv-dist) 1000000.0)
  (set! (-> arg0 masks data 0 dist) (+ 40960000.0 (-> arg0 masks data 0 dist)))
  (set! (-> arg0 masks data 1 dist) (+ 40960000.0 (-> arg0 masks data 1 dist)))
  )

(define-extern level-remap-texture (function texture-id texture-id))


(defun adgif-shader-login ((arg0 adgif-shader))
  "Initialize an adgif-shader
   confirm the texture exists, and add to texture system.
   Use the level system to remap the texture-id in the adgif-shader.
   If the texture doesn't exist, will load it with loado."

  ;; only if we haven't done it before
  (when (logtest? (-> arg0 link-test) (link-test-flags needs-log-in))
    (logclear! (-> arg0 link-test) (link-test-flags needs-log-in bit-9))
    (set! (-> arg0 texture-id) (level-remap-texture (-> arg0 texture-id)))
    (when (= (-> arg0 texture-id page) 2797)
      (nop!)
      (nop!)
      0
      )
    ;; add us to list of adgifs for this texture
    (link-texture-by-id (-> arg0 texture-id) arg0)
    ;; find the texture
    (let ((s5-0 (lookup-texture-by-id (-> arg0 texture-id))))
      (cond
        (s5-0
          ;; hack! set some stuff differently if we're screenshotting
          (if (and *debug-segment* (-> *screen-shot-work* highres-enable))
              (hack-texture s5-0)
              )
          ;; set up this adgif shader from the texture.
          (adgif-shader<-texture-with-update! arg0 s5-0)
          )
        (else
          (format 0 "login<1> could not find texture ~X in obj ~A shader ~X~%"
            (-> arg0 texture-id)
            (-> *kernel-context* login-object)
            arg0
            )
          )
        )
      s5-0
      )
    )
  )

(defun adgif-shader-login-no-remap ((arg0 adgif-shader))
  "Initialize an adgif-shader like adgif-shader-login, but skips
   the level-remap-texture system."
  (when (logtest? (-> arg0 link-test) (link-test-flags needs-log-in))
    (logclear! (-> arg0 link-test) (link-test-flags needs-log-in bit-9))
    (link-texture-by-id (-> arg0 texture-id) arg0)
    (let ((s5-0 (lookup-texture-by-id (-> arg0 texture-id))))
      (cond
        (s5-0
          (if (and *debug-segment* (-> *screen-shot-work* highres-enable))
              (hack-texture s5-0)
              )
          (adgif-shader<-texture-with-update! arg0 s5-0)
          )
        (else
          (format 0 "login<2> could not find texture ~X in obj ~A shader ~X~%"
            (-> arg0 texture-id)
            (-> *kernel-context* login-object)
            arg0
            )
          )
        )
      s5-0
      )
    )
  )

(defun adgif-shader-login-fast ((arg0 adgif-shader))
  "Initialize an adgif-shader and register with texture system, but doesn't handle missing textures.
   Will remap with level info."
  (when (logtest? (-> arg0 link-test) (link-test-flags needs-log-in))
    (logclear! (-> arg0 link-test) (link-test-flags needs-log-in bit-9))
    (set! (-> arg0 texture-id) (level-remap-texture (-> arg0 texture-id)))
    (let ((v1-4 (-> arg0 texture-id)))
      (when (and (nonzero? (-> v1-4 page)) (< (-> v1-4 page) (the-as uint (-> *texture-page-dir* length))))
        (let ((a0-9 (-> *texture-page-dir* entries (-> v1-4 page))))
          (when (and (< (-> v1-4 index) (the-as uint (-> a0-9 length))) (-> a0-9 link))
            (set! (-> arg0 next shader) (-> a0-9 link next (-> v1-4 index) shader))
            (set! (-> a0-9 link next (-> v1-4 index) shader) (shr (the-as int arg0) 4))
            )
          (when (and (-> a0-9 page) (< (-> v1-4 index) (the-as uint (-> a0-9 page length))))
            (let ((s5-0 (-> a0-9 page data (-> v1-4 index))))
              (when s5-0
                (if (and *debug-segment* (-> *screen-shot-work* highres-enable))
                    (hack-texture s5-0)
                    )
                (adgif-shader<-texture-with-update! arg0 s5-0)
                )
              s5-0
              )
            )
          )
        )
      )
    )
  )

(defun adgif-shader-login-no-remap-fast ((arg0 adgif-shader))
  "Initialize an adgif-shader and register with texture system, but doesn't handle missing textures.
   Does not remap with level info."
  (when (logtest? (-> arg0 link-test) (link-test-flags needs-log-in))
    (logclear! (-> arg0 link-test) (link-test-flags needs-log-in bit-9))
    (let ((v1-4 (-> arg0 texture-id)))
      (when (and (nonzero? (-> v1-4 page)) (< (-> v1-4 page) (the-as uint (-> *texture-page-dir* length))))
        (let ((a0-8 (-> *texture-page-dir* entries (-> v1-4 page))))
          (when (and (< (-> v1-4 index) (the-as uint (-> a0-8 length))) (-> a0-8 link))
            (set! (-> arg0 next shader) (-> a0-8 link next (-> v1-4 index) shader))
            (set! (-> a0-8 link next (-> v1-4 index) shader) (shr (the-as int arg0) 4))
            )
          (when (and (-> a0-8 page) (< (-> v1-4 index) (the-as uint (-> a0-8 page length))))
            (let ((s5-0 (-> a0-8 page data (-> v1-4 index))))
              (when s5-0
                (if (and *debug-segment* (-> *screen-shot-work* highres-enable))
                    (hack-texture s5-0)
                    )
                (adgif-shader<-texture-with-update! arg0 s5-0)
                )
              s5-0
              )
            )
          )
        )
      )
    )
  )

;; modified for PC port: never bother with the debug-mode texture login.
;; it will never work on PC, and it causes problems with custom levels, due to the way that it allocates the link
;; array for texture-pages that don't exist, which are never then set to false, causing them to be referenced
;; after the level is unloaded. This probably works fine if you are playing one level at a time, or if the backup
;; texture page loading actually works.
(when (or (not *debug-segment*) PC_PORT)
  ;; when not debugging, use the "fast" logins. There's no way we can load a texture over the network anyway.
  (set! adgif-shader-login adgif-shader-login-fast)
  (set! adgif-shader-login-no-remap adgif-shader-login-no-remap-fast)
  )

(defun adgif-shader<-texture-simple! ((arg0 adgif-shader) (arg1 texture))
  (set! (-> arg0 tex1) (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))
  (set! (-> arg0 tex0 tfx) 0)
  (if arg1
      (adgif-shader<-texture! arg0 arg1)
      )
  (set! (-> arg0 clamp) (new 'static 'gs-clamp :wms (gs-tex-wrap-mode clamp) :wmt (gs-tex-wrap-mode clamp)))
  (set! (-> arg0 alpha) (new 'static 'gs-alpha :b #x1 :d #x1))
  (set! (-> arg0 prims 1) (gs-reg64 tex0-1))
  (set! (-> arg0 prims 3) (gs-reg64 tex1-1))
  (set! (-> arg0 prims 5) (gs-reg64 miptbp1-1))
  (set! (-> arg0 clamp-reg) (gs-reg64 clamp-1))
  (set! (-> arg0 prims 9) (gs-reg64 alpha-1))
  arg0
  )

(defun set-dirty-mask! ((arg0 level) (arg1 int) (arg2 int) (arg3 int))
  "Set dirty bits for a levels textures. This tells the texture system to reupload the texture.
   even if the old version is still in vram.
   Sets bits arg2 -> arg3 for page arg1.
   "
  (let ((s4-0 (sar (+ arg2 #x3fff) 14))
        (s5-0 (sar (+ arg3 #x3fff) 14))
        (gp-0 (-> arg0 texture-dirty-masks arg1))
        (v1-5 (new 'stack 'texture-mask))
        )
    (cond
      ((< 64 s4-0)
       (set! (-> v1-5 mask dword 0) (the-as uint -1))
       (set! (-> v1-5 mask dword 1) (the-as uint (+ (ash 1 (+ s4-0 -64)) -1)))
       )
      (else
        (set! (-> v1-5 mask dword 0) (the-as uint (+ (ash 1 s4-0) -1)))
        )
      )
    (when (nonzero? s5-0)
      (set! (-> v1-5 mask dword 1)
            (logior (ash (-> v1-5 mask dword 1) s5-0) (ash (-> v1-5 mask dword 0) (+ s5-0 -64)))
            )
      (set! (-> v1-5 mask dword 0) (ash (-> v1-5 mask dword 0) s5-0))
      )
    (set! (-> gp-0 mask dword 0) (the-as uint (logior (-> gp-0 mask dword 0) (-> v1-5 mask dword 0))))
    (set! (-> gp-0 mask dword 1) (the-as uint (logior (-> gp-0 mask dword 1) (-> v1-5 mask dword 1))))
    )
  0
  (none)
  )

(defun-debug texture-page-dir-inspect ((arg0 texture-page-dir) (arg1 symbol))
  (format #t "[~8x] ~A~%" arg0 (-> arg0 type))
  (let ((v1-0 *texture-pool*))
    (format
      #t
      "~Ttexture pool (~DK used, ~DK free)~%"
      (/ (- (-> v1-0 cur) (-> v1-0 top)) 256)
      (/ (- (shl (-> *video-params* display-fbp) 11) (-> v1-0 cur)) 256)
      )
    )
  (dotimes (s4-0 (-> *level* length))
    (let ((a1-3 (-> *level* level s4-0)))
      (if (= (-> a1-3 status) 'active)
          (texture-page-size-check *texture-pool* a1-3 #f)
          )
      )
    )
  (format #t "~Tlength: ~D~%" (-> arg0 length))
  (format #t "~Tdata[~D]: @ #x~X~%" (-> arg0 length) (-> arg0 entries))
  (dotimes (s4-1 (-> arg0 length))
    (let ((s3-0 (-> arg0 entries s4-1 page))
          (s2-0 (-> arg0 entries s4-1 link))
          )
      (cond
        (s3-0
          (format
            #t
            "~T  [~3D]   loaded ~S ~A~%"
            s4-1
            (if s2-0
                "  linked"
                "unlinked"
                )
            s3-0
            )
          )
        (else
          (if (= arg1 'full)
              (format
                #t
                "~T  [~3D] unloaded ~S #<texture-page :length ~D>~%"
                s4-1
                (if s2-0
                    "  linked"
                    "unlinked"
                    )
                (-> arg0 entries s4-1 length)
                )
              )
          )
        )
      (when (and (or s3-0 s2-0) arg1)
        (dotimes (s1-0 (-> arg0 entries s4-1 length))
          (cond
            ((not s2-0)
             (format #t "~T    [~3D]   unlinked" s1-0)
             )
            ((zero? (-> s2-0 next s1-0 shader))
             (format #t "~T    [~3D]   UNUSED  " s1-0)
             )
            (else
              (let ((t9-9 format)
                    (a0-12 #t)
                    (a1-10 "~T    [~3D] ~3D links ")
                    (a2-11 s1-0)
                    (a3-9 0)
                    )
                (let ((v1-40 (the-as object (* (-> s2-0 next s1-0 shader) 16))))
                  (while (nonzero? (the-as uint v1-40))
                    (nop!)
                    (+! a3-9 1)
                    (set! v1-40 (* (-> (the-as adgif-shader v1-40) next shader) 16))
                    )
                  )
                (t9-9 a0-12 a1-10 a2-11 a3-9)
                )
              )
            )
          (cond
            ((not s3-0)
             (format #t " unloaded~%")
             )
            ((not (-> s3-0 data s1-0))
             (format #t "    empty~%")
             )
            (else
              (format #t " ~A~%" (-> s3-0 data s1-0))
              )
            )
          )
        )
      )
    )
  (none)
  )


(defmethod inspect ((this texture-page-dir))
  (texture-page-dir-inspect this #f)
  this
  )

(define *texture-pool* (new 'global 'texture-pool))
