Skip to content

Display Process

Ian Brown edited this page May 22, 2024 · 5 revisions

The frame display is driven by a list of DMA commands (we'll refer to this as a display list or a dlist for short). These commands are stored in one of 2 static buffers - at any stage one of these buffers is transmitting data to the VIF/GIF whilst the other is being populated for the next frame.

Main display loop

When running a scene such as the pre-game menus, the following loop is executed: The first 2 frames are blanked.

start frame start dlist DMA switch the current dlist DMA buffer read input call the scene function (populates the next dlist) deal with CD streaming (async IO) wait for dlist DMA completion increment frame number wait for VBlank end frame

When running the game itself a similar but more complex loop is executed.

Start Frame

startFrame() sends a GIF packet via PATH 3 and waits for it to complete sending. The packet does the following:

  • Sets frame buffer 1 to 0xA0000 with width 1280, pixel format PSMCT16 and implied height 512.
  • Sets zBuffer to 0x1E0000, PSMZ16
  • Sets the scissor (0, 0) -> (1279, 511)
  • Sets the XY offset to (1408, 1792) or 1792 if the previous frame was odd.
    • This centers the frame buffer in the primitive coordinate space.
  • Enables dither
  • Disables alpha blend control
  • Sets color clamping
  • Sets the RGB color
  • Sets Alpha test off
  • Clears the screen by using a set of 64 pixel
  • wide sprites
  • Sets the FOG color
  • Sets the scissorY - possibly applies a letterbox effect

Sends the packet via PATH 3 / GIF

sceGsSyncPath(0, 0)

Start DList DMA

Calls the dmaHandler with the a special START_CHANNEL input which causes it to start transerring the first item of the dlist to the GIF/VIF

Call the scene function

The scene function is a function pointer passed as a parameter. If it returns true then the scene is over, else it continues.

The scene function populates the display list for the next frame. It creates DMA packets in curDMABufTail and calls queueDMA to add the packets to the display list.

There are 2 dlist banks - one is being sent to the VIF/GIF and the other is being populated for the next frame. The activeDlistBank variable tracks the one being written to whilst dlistBankBeingUploaded tracks the one being uploaded. Note there is no need to have 2 variables.

wait for dlist transmission and processing

The DMA handler signals a sema once all dlists have been sent. We wait on this sema and also wait on sceGsSyncPath(0,0). I suspect the latter is unnecessary.

wait for Vblank

We wait for the vblank interrupt to set a sema and change a variable.

display setting

The display mode is set to interlaced frame mode (not sure why this needs to be done every time) If more than 2 frame have been processed, enable the read circuits, else disable them.

end frame

Maybe badly named because what this really does is display the frame buffer that has been generated by the dlist.

It changes the alpha and colours on a pre-built DMA program and sends it to the GIF. This support fade-in / color modulation effects.

The DMA program does the following:

  • Disables dither
  • sets Frame buffer point to 0, Width of 640 and PSM of PSMCT32
  • sets scissor to (0,0) -> (639, 255)
  • sets XYOFFSET_1 (0x6c00, 0x7800) = (1728.0, 1920.0)
  • TEX1_1 = no mipmap
  • TEST_1 = only update FB, ignore z buffer
  • set RGBAQ
  • set PRIM flat shaded sprite, textured, alpha blended, no fog, UV mapped, context 1
  • set ALPHA_1 Cv=(Cs - Cd)*FIX>>7 + Cd where FIX is 0x80
  • TEXFLUSH
  • TEX0_1
    • pointer = 0x28000
    • width = 1280
    • PSM = PSMCT16
    • w, h both 1024
    • Texture function: RGB, HIGHLIGHT
    • CLUT: addr =0, PSMCT32, CSM1

We then loop, copying 32 x 64 pixel sprites from the texture (the framebuffer we wrote to originally) to the frame buffer. The texture is scaled down by a factor of 2.

We then do the same loop but setting TEX0_1 pointer to 0x2D000.

What this is doing is pointing half way along the first scan-line of the same texture and it was triggered the half screen bug in the early emulator days. What we're doing is the left half of the screen and then the right half ... but why? Well, it's because the maximum texture width than can be defined in TEX0_1 is 1024 and our source frame buffer is 1280 pixels wide. Treating it as 2 textures solves this issue and makes the texture cache more complex for emulator authors.

Once the display has been update, dither is enabled with a dither matrix and we're done.