Chaotix's 32X Graphics Drawing System

Discussion in 'Discussion & Q&A' started by Devon, Nov 8, 2023.

  1. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,376
    Location:
    your mom
    I've dug a bit into Chaotix, and I have managed to document how a good chunk of its graphics rendering system on the 32X side works. Of course, some of this is already documented, but I think it'd still be nice to have it explained here.

    Sprite Data

    The main index table for the sprite sets is located at $A0000 in the ROM. Each sprite set starts with another index table for each sprite in the set. Blank entries have the pointer set to -1.

    The sprite format is quite interesting, as it is designed to avoid storing unnecessary blank pixels. First, it starts off with this header that defines the sprite boundaries:
    Code:
    .w Left boundary
    .w Right boundary
    .w Top boundary << 8
    .w Bottom boundary << 8
    The best way I can describe these values is with this example: let's say you wanted to have a 32x32px sprite with its pivot set in the middle. You'd set the left and top boundaries to -16, and the right and bottom boundaries to 16. Basically, a sprite's pivot point is always at (0, 0), and the pixels are centered around that.

    After this header comes the actual pixel data. Chaotix stores pixels as individual rows. Each row starts off with its own header that defines its positioning in the sprite:
    Code:
    .b Left position
    .b Right position
    .b Y position
    .b 0
    The same rules for the positioning being centered around the pivot point at (0, 0) in the sprite header apply here as well. After that is an array of palette indices for each pixel in the row.

    The end of the sprite data is found when the left and right values are the same.

    So, that's the main sprite format. However, I'm not done talking about it, because sprites can actually be set to be compressed. The compression basically involves removing unneeded bits from the sprite data by subtracting a minimum value for each data value and determining the max number of bits that needs to be supported. Decompressing a compressed sprite will result in the regular sprite format.

    A compressed sprite starts off with this header:
    Code:
    .w Uncompressed size
    .b $42
    .b Left boundary
    .b Bits per X position value
    .b Top boundary
    .b Bits per Y position value
    .b Base pixel value
    .b Bits per pixel value
    
    That "$42" is basically the magic number that defines a sprite as compressed. The left and top boundaries get defined here, alongside how many bits that an X position value, Y position value, and pixel value take up in the compressed data, and the base pixel value that gets added to each opaque pixel value. The left and top boundaries also act as the base values that get added to each X and Y position value.

    The compressed data bitstream start off with the right and bottom boundary values, and then the pixel rows. Decompressing is really just a matter of reading a number of bits, depending on the type of value, adding its associated base value, and then storing it in the decompression buffer.

    When drawing sprites, they can be drawn anywhere on the screen, and can be scaled in either the X and Y axis. Unfortunately, this is no support for rotating sprites. This is why Chaotix still has separate sprites for angled character sprites, for example.

    Sprite Lists

    Chaotix stores many different lists that define what sprite sets to load. The format is simple: each entry contains 2 bytes: the sprite set ID, and its palette index offset. When it loads a sprite set, it'll read/decompress each of its frames into a buffer, and offset each pixel value, so you can basically choose which part of the palette that the sprite should use.

    The end of a sprite list is found when the sprite set ID is set to -1.

    When going into a stage, Chaotix will load 2 sprite lists: a global list and a stage specific list. The global sprite list is located at $A0200 in the ROM. The index table for stage specific sprite lists is located at $A0210 in the ROM, with each entry being a 16-bit relative offset.

    Player Sprites

    The player sprites are special. The way Chaotix handles this involves allocating the first 16 sprite sets as player sprites, with the first 8 being the main sprites and the other 8 being the limbs. The sprite data MUST be uncompressed, because with how many frames a character has, it really can't be pre-loaded. So, the game just loads in the currently displayed frame for a character as the graphics are rendered.

    Polygons

    Alongside sprites, Chaotix can render basic polygons. You can define different kinds of shapes to be drawn and have their vertices placed anywhere on the screen. You can also draw 3D polygons that can be placed and rotated in the context of a 3D space. For that, Chaotix uses a buffer that contains a table of polygons that can be picked from and drawn.

    Boundaries

    You can define a set of boundaries in which graphics will be visible in. For 3D polygons and scaled sprites, you can also define 3D boundaries in which they'll be visible in as well. By default, graphics will be visible on the entire screen, and 3D polygons and scaled sprite can be drawn at range 1-$7FFF. For 3D polygons, this applies to its Z position, and for scaled sprites,this applies to its scale values.

    Palettes and Priority

    On the 32X, graphics can either be drawn behind the Genesis graphics, or in front of them. The way Chaotix handles being able to determine a sprite's priority is to basically contain 2 copies of the palette in the 32X's CRAM. The first 128 colors contain the regular palette data, and the last 128 colors contain those same colors, but with their priority flag flipped. Chaotix will then choose which half of CRAM to use for drawing a sprite.

    Palettes are actually managed by the 68000 sides for regular stages. When performing graphics updates, the FM bit is set to 0 so that palette updates can be made, and then it's set back to 1 so that the graphics rendering can begin.

    Commands

    To send a command to the 32X, you set the command ID in communication register 0 (word). While a command is being processed, it will remain at that ID, and once it's done, it will be cleared. Here's a list of the commands:
    • 01 - Reset renderer
    • 02 - Render graphics (clears frame buffer)
    • 03 - Initialize stage graphics
    • 04 - Initialize character palette index offsets for character selection menu
    • 05 - Nothing
    • 06 - Nothing
    • 07 - Load a sprite set
    • 08 - Handle special stage
    • 09 - Render graphics (doesn't clear frame buffer)
    I've not looked into the special stages much at all, so I won't be covering that command. The focus will be for rendering during regular stages and other screens, so I will cover the other commands.

    Sending Data

    Chaotix uses DREQ for data being sent from by the 68000 side. The way this is handled is that the Master SH-2's command interrupt is set to accept a DREQ request and copy data into a double buffered area. When the 68000 wants to send data, it simply sets up the DREQ parameters, sends a Master SH-2 command interrupt signal, and then sends the data over. If I recall, DREQ data must be sent in units of 4 words, due to a hardware thing.

    Initialization
    Generally, this is just a matter of running commands 01, then 03, and then running command 02 twice to make sure that things are all flushed properly. Running command 01 will reset the renderer and running command 03 will prepare for rendering stage graphics.

    Command 01 requires no additional parameters or data to be sent, but command 03 does. It expects the following data to be sent via DREQ:
    Code:
    .b Attraction ID
    .b Level ID
    .b Time zone ID
    .b Player 1 character ID
    .b Player2 character ID
    The first 3 are used as indices for the stage specific sprite list index table, and the last 2 are used to define the character palette index offsets to $12 and $24 respectively.

    Running this command will also reset the visible space, and set the near and far range to 4-$4000. You can find the parameters set at $6000FE4 in the SH-2 program ($787E4 in the ROM). The 3D polygons used for the title card also get defined. You can find that data at $6000FF0 in the SH-2 program ($787F0 in the ROM), where 12 4-sided polygons are defined. After that, the palette index offsets are set, and the sprites are loaded.

    Rendering

    Now let's get into actually getting stuff drawn. The way this is done is that you send in a list of draw commands via DREQ, and then call either command 02 or 09. It should be noted that Chaotix opts to not wait for the 32X to fully process the command due to how slow the process can take. So instead, it just runs the command whenever the 32X is able to every frame.

    Chaotix builds the draw command list via a buffer that is built in RAM first, and then sent over via DREQ. Here's a list of the commands that I was able to figure out:
    • 00 - End of list
    • 01 - Draw sprite
    • 02 - Draw trailing dot at velocity
    • 03 - Draw trailing dot at angle
    • 04 - Draw trailing dot at angle using global acceleration
    • 05 - Set global trailing dot acceleration
    • 06 - Set dissolve and trailing dot velocity
    • 07 - Define 2D boundaries
    • 08 - Define 3D boundaries
    • 09 - Draw line
    • 0A - Draw polygon
    • 0B - Define 3D polygon shape
    • 0C - Draw 3D polygon shape
    • 0D - Draw top of sprite dissolving
    • 0E - Draw sprite dissolving from the bottom
    • 0F - Draw a stack of bars
    • 10 - Draw the distortion on the Sega logo
    • 11 - Draw the HUD (not fully sure what it's doing so far)
    • 12 - Draw bonus stage item
    • 13 - Load new stage specific sprite list
    • 14 - ? (Something to do with the HUD)
    • 15 - Set player palette index offsets
    • 16 - Draw final boss arena
    • 17 - Draw zoomed sprite
    • 18 - Set zoom value
    • 19 - Draw stage end/game over circle transition
    • 1A - Draw power-up icons
    Let's go over each of them now and see how they are structured.

    Draw Command 00 - End of list

    Self explanatory. It defines the end of the list.

    Draw Command 01 - Draw sprite
    Code:
    .b $01
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    .w X scale
    .w Y scale
    Pretty self explanatory for the most part. For the default 100% scaling, set a scale value to $100. Lower values will scale up, higher values will scale down.

    The draw flags are in this format in binary:
    Code:
    0000 mmyx
    • x - X flip flag
    • y - Y flip flag
    • m - Mode
      • 00 - Normal
      • 01 - Priority
      • 02 - "Transparency" via vertical dithering (only if both X and Y scale are set to $100)
    Draw Command 02 - Draw trailing dot at velocity
    Code:
    .b $02
    .b $00
    .b Palette index
    .b Trail flags
    .w Draw area length (only when flag is set)
    .w X
    .w Y
    .w X velocity
    .w Y velocity
    .w X acceleration
    .w Y acceleration
    This will draw a moving dot that leaves a trail.

    The trail flags are as follows:
    Code:
    00ae 000t
    • a - Set draw area length relative to position (overrides the "e" flag)
    • e - Set draw area up to end of screen
    • t - Thickness
      • 0 - 1px
      • 1 - 2px
    If there's a draw area that's defined, then when the dot moves past the end of that area, it will reset itself back at its starting position. Note that there seems to be a bug with this functionality where it it will wrap to the top of the screen after going past the bottom, and then reset itself when it goes back to its initial Y position.

    Draw Command 03 - Draw trailing dot at angle

    Code:
    .b $03
    .b $00
    .b Palette index
    .b Trail flags
    .w Draw area length (only when flag is set)
    .w X
    .w Y
    .w Angle
    .w Velocity
    .w Acceleration
    Draw Command 04 - Draw trailing dot at angle using global acceleration
    Code:
    .b $04
    .b $00
    .b Palette index
    .b Trail flags
    .w Draw area length (only when flag is set)
    .w X
    .w Y
    .w Angle
    .w Velocity
    Draw Command 05 - Set global trailing dot acceleration
    Code:
    .b $05
    .b $00
    .l Global X acceleration
    .l Global Y acceleration
    Draw Command 06 - Set dissolve and trailing dot velocity
    Code:
    .b $06
    .b $00
    .w X velocity
    .w Y velocity
    Chaotix uses this to move the dissolved particles along with the camera.

    Draw Command 07 - Define 2D boundaries

    Code:
    .b $07
    .b $00
    .w Left
    .w Right
    .w Top
    .w Bottom
    Draw Command 08 - Define 3D boundaries
    Code:
    .b $08
    .b $00
    .w Near
    .w Far
    Draw Command 09 - Draw line
    Code:
    .b $09
    .b Palette index
    .w Point 1 X
    .w Point 1 Y
    .w Point 2 X
    .w Point 2 Y
    Draw Command 0A - Draw polygon
    Code:
    .b $0A
    .b Palette index
    .b Draw mode
    .b Number of vertices
    [For each vertex]
        .w X
        .w Y
    The draw modes are as follows:
    • 0 - Corners
    • 1 - Wireframe
    • 2 - Solid
    Draw Command 0B - Define 3D polygon shape
    Code:
    .b $0B
    .b $00
    .b Shape ID
    .b Number of vertices
    [For each vertex]
        .w X
        .w Y
    Draw Command 0C - Draw 3D polygon shape
    Code:
    .b $0C
    .b $00
    .b Palette index
    .b Draw mode
    .b Shape ID
    .b Pitch rotation
    .b Yaw rotation
    .b Roll rotation
    .w X
    .w Y
    .w Z
    Note that there is no Z sorting involved, so you gotta do that yourself.

    Draw Command 0D - Draw top of sprite dissolving
    Code:
    .b $0D
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    Draw Command 0E - Draw sprite dissolving from the bottom
    Code:
    .b $0E
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    Draw Command 0F - Draw a stack of bars
    Code:
    .b $0F
    .b Bar count
    [For each bar]
        .w Height
        .b Even pixel palette index
        .b Odd pixel palette index
    Draw Command 10 - Draw the distortion on the Sega logo
    Code:
    .b $10
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    .w X scale
    .w Left distortion
    .w Right distortion
    .w X distortion intensity
    .w Y scale
    .w Top distortion
    .w Bottom distortion
    .w Y distortion intensity
    Draw Command 11 - Draw the HUD (not fully sure what it's doing so far)
    Code:
    .b $11
    .b $00
    .w ?
    .w ?
    .w ?
    .w ?
    or
    Code:
    .b $11
    .b $01
    .l Score
    .w Ring count
    Draw Command 12 - Draw bonus stage item
    Code:
    .b $12
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    .w Z
    .w Length
    .b Palette index (top side)
    .b Palette index (bottom side)
    .b Palette index (left side)
    .b Palette index (right side)
    Draw Command 13 - Load new stage specific sprite list
    Code:
    .b $13
    [For each entry]
        .b Sprite set ID
        .b Palette index offset
    .b $FF
    This overwrites the currently loaded stage specific sprites.

    Draw Command 14 - ? (Something to do with the HUD)
    Code:
    .b $14
    .b ?
    .w ?
    .w ?
    .w ?
    .w ?
    Draw Command 15 - Set player palette index offsets
    Code:
    .b $15
    .b $00
    .b Player 1 character ID
    .b Player 1 palette index offset
    .b Player 2 character ID
    .b Player 2 palette index offset
    Draw Command 16 - Draw final boss arena
    Code:
    .b $16
    .b Angle
    .w X
    .w Y
    .w Z
    Draw Command 17 - Draw zoomed sprite
    Code:
    .b $17
    .b Sprite set ID
    .b Sprite frame ID
    .b Draw flags
    .w X
    .w Y
    .w X scale
    .w Y scale
    Draw Command 18 - Set zoom value
    Code:
    .b $18
    .b $00
    .w Zoom value
    Draw Command 19 - Draw stage end/game over circle transition
    Code:
    .b $19
    .b Scale
    You can find the parameters for the sprites that gets drawn for this at $600347C in the SH-2 program ($7AC7C in the ROM), which is as follows:
    Code:
    ; Sprite 1
    dc.w $0050      ; X
    dc.w $0070      ; Y
    dc.w %00        ; Flip flags (no flip)
    dc.b $51        ; Sprite set ID
    dc.b $00        ; Sprite frame ID
    
    ; Sprite 2
    dc.w $00F0      ; X
    dc.w $0070      ; Y
    dc.w %11        ; Flip flags (X and Y flip)
    dc.b $51        ; Sprite set ID
    dc.b $00        ; Sprite frame ID
    
    ; Sprite 3
    dc.w $00A0      ; X
    dc.w $0070      ; Y
    dc.w %00        ; Flip flags (no flip)
    dc.b $51        ; Sprite set ID
    dc.b $01        ; Sprite frame ID
    The code defines the number of sprites in this list at $6003436 in the SH-2 program ($7AC36 in the ROM).

    Draw Command 1A - Draw power-up icons
    Code:
    .b $1A
    .b Player 1 power-up flags
    .b Player 2 power-up flags
    .b Shared power-up flags
    The Other 32X Commands

    Not a whole lot to say. Command 04 simply sets every character's palette offset for use with character selection menus. Command 07 takes in a parameter via communication register 2 (word), where the upper byte is the sprite list ID and the lower byte is the palette index offset. I have nothing to say regarding the special stage command, because that's a completely different beast, and I've not looked into it yet.

    Conclusion

    So yeah, this is the gist of how graphics are rendered in Chaotix. I hope this might be of some use for hacking and documentation stuff. I know it's not exactly complete, but it's still pretty extensive.
     
    Last edited: Nov 11, 2023
    Pacca, Kurk, TheInvisibleSun and 5 others like this.
  2. Devon

    Devon I'm a loser, baby, so why don't you kill me? Member

    Joined:
    Aug 26, 2013
    Messages:
    1,376
    Location:
    your mom
    I have updated the post. I found out how the missing draw commands (the ones that used to be marked with "?") worked. They are used for drawing trailing dots.

    Here's a sample of what I'm talking about.

    [​IMG]

    And this is the vertical wrap bug that I mentioned.

    [​IMG]
     
    Last edited: Nov 9, 2023
  3. PsychoSk8r

    PsychoSk8r HighKnights Member

    Joined:
    Aug 9, 2007
    Messages:
    271
    Location:
    Birmingham, UK
    I can’t imagine the amount of research and experimentation it took to learn all this clearly boundary pushing information. Nice job, and thanks for sharing! It’s work like this that keeps the communities scope of capability expanding.
     
    NayTheGamer, Kurk and Devon like this.