don't click here

SCD Graphics documentation?

Discussion in 'Technical Discussion' started by Pyrochrome, Oct 18, 2021.

  1. Pyrochrome

    Pyrochrome

    Member
    6
    0
    1
    We all know the Sega Cd can perform raster effects like rotation and scaling, but is there any documentation on how exactly this is achieved on a hardware level? The only info I can find is some barebones research on an unofficial FAQ. Anybody have concrete info I could use, or a sample of code that demonstrates these capabilities? I'd love to give Sega Cd homebrew a try and this by far is the biggest roadblock.
     
  2. Chimes

    Chimes

    The One SSG-EG Maniac Member
    651
    492
    63
  3. Clownacy

    Clownacy

    Tech Member
    1,061
    608
    93
    • Informative Informative x 1
    • List
  4. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,453
    93
    your mom
    Another bump, but I can explain how it works. Most of what happens is dealt with on the Sub CPU side, as it's a CD hardware thing, which the Genesis doesn't really have direct access to (Genesis can only really see Word RAM on the CD side).

    So, yes, indeed, the Sega CD is capable of performing scaling and rotation effects on a hardware level. If it's set to "2M" mode (where the entirety of the CD's 2 Megabit Word RAM is given to either the Genesis or CD side instead of being split evenly between them like in "1M/1M" mode), then the graphics chip is set to do that. In "1M/1M" mode, it does something entirely different, where it basically can convert a bitmap into tile data, or split up pixels into individual bytes, but that's not what I'm gonna be talking about here. The "hardware/scaling" process goes like this...

    First, you start off with a "stamp bank" and a "stamp map". This is what makes up the source image that you want to apply the transformations to. Stamps can either be 16x16 or 32x32, and are formatted just like Genesis sprite tiles. The stamp map is just a tilemap made up of those stamps. The map can either be 256x256px in size, or 4096x4096px.

    To transform this source image, you need to provide a "trace table". What is contained in that is a list of "starts" and "deltas" for every line in the final rendered image (called the "image buffer"). Basically, first, you describe the size of the final render, and then for each line of pixels in the render, it'll grab a set of starting coordinates, grab a pixel from there in the source image and plot it in the render's line, and it'll continually add those deltas to those starting coordinates and repeatedly grab more pixels for the line it's on.

    The process is summed up here:
    [​IMG]

    So, let's get into the gritty details on how to go about this. Let's start with the registers. They are all 16 bit wide.

    0xFF8058 controls the size of stamps and the stamp map, and also whether to wrap the map if it goes out of bounds when rendering or to just plot blank pixels. Bit 0 controls the wrapping, bit 1 controls the size of a stamp (0 = 16x16px, 1 = 32x32px), and bit 2 controls the size of the stamp map (0 = 256x256px, 1 = 4096x4096px).

    [​IMG]

    We'll talk about the GRON bit in a bit.

    0xFF805A controls the location of the stamp map in Word RAM. The value you put in is the offset relative to the start of Word RAM divided by 4. What the raw location needs to be a multiple of depends on the sizes you have set:
    • 16x16px stamps, 256x256px stamp map: Multiples of 0x200
    • 32x32px stamps, 256x256px stamp map: Multiples of 0x80
    • 16x16px stamps, 4096x4096px stamp map: Multiples of 0x20000
    • 32x32px stamps, 4096x4096px stamp map: Multiples of 0x8000
    [​IMG]

    0xFF805C controls the height of the image buffer in tiles, minus 1. The maximum height is 32 tiles.

    [​IMG]

    0xFF805E controls the location of the image buffer in Word RAM. The same relative offset and division by 4 rules apply. The raw location must be a multiple of 0x20.

    [​IMG]

    0xFF8060 controls the image buffer offset. Basically, you can offset the output render up to 7 pixels on each axis. Bits 0-2 control the horizontal offset and bits 3-5 control the vertical offset.

    [​IMG]

    0xFF8062 controls the image buffer's width in pixels. The maximum width is 511 pixels.

    0xFF8064 controls the image buffer's height in pixels. Note that the rendering process actually decrements this as it renders each line, so don't forget to reset it when beginning a new render. The maximum height is 255 pixels.

    [​IMG]

    The final register, 0xFF8066, controls the location of the trace table. Same relative offset and division by 4 rules as the other 2 address registers. The raw location must be a multiple of 8. Setting this begins the rendering process.

    [​IMG]

    Also, a brief shout out to the memory mode register (0xFF8002), which has a couple of bits for controlling a little about how the output image is rendered in terms of how it deals with previously rendered data leftover in memory. Bits 3-4 control that:
    • %00 - Writes over the leftover data, regardless of blank pixels.
    • %01 - Skips any blank pixel already written, effectively rendering "under" the previous data.
    • %10 - Prohibited
    • %11 - Any non-blank pixel writes over the leftover data, effectively rendering "over" the previous data.
    [​IMG]

    So, now let's talk about formats.

    Like I mentioned before, stamps are basically formatted exactly how the Genesis formats 8x8 tiles for sprites (arranged vertically, then horizontally), so we can mark that off.

    A stamp map is relatively simple: it's just a set of stamp IDs and flags, arranged horizontally, then vertically. A stamp ID and its flags are formatted as so:
    • Bits 0-10 are the ID. A stamp ID is calculated by taking a stamp's location relative to the start of Word RAM and dividing it by 0x80. With 32x32px stamps, bits 0 and 1 are actually ignored, since those go into locations that are partially within stamps, so for 32x32px stamps, IDs are multiples of 4.
    • Bits 13-14 control the rotation of the stamp. %00 = 0 degrees, %01 = 90 degrees, %10 = 180 degrees, and %11 = 270 degrees.
    • Bit 15 is the horizontal flip flag. Note: this is applied first before rotation is.
    [​IMG]

    [​IMG]

    And now, the trace table. Like, I've mentioned before, it's a list of "starts" and "deltas" for each line to be rendered in the image buffer. A start coordinate is formatted in unsigned 13.3 fixed point, while a delta value is formatted in signed 5.11 fixed point.

    [​IMG]

    So, to basically set it up, you set up your stamps, stamp map, and trace table in Word RAM, and to render, you set up the registers, and let it do it's thing. Your rendered image will be in the image buffer. How can you tell when a render is done? There are 2 methods:
    • Set up interrupt level 1. It gets triggered upon the end of a render.
    • Check the GRON bit (bit 15) in the stamp data size register (0xFF8058). It's clear when there's no rendering going on, and it's set when there is.
    Once that's all done, you can then transfer the image buffer data to VRAM on the Genesis side. It is possible to DMA directly from Word RAM, but due to an issue where it's delayed by a cycle, you'll have to add 2 to the DMA source address, and I believe you also have to manually write the first word or longword of data manually. You can also just copy it from Word RAM over to Genesis RAM and then DMA from there.

    The tiles generated in the image buffer are arranged vertically, then horizontally, so when you set up the tilemap for them, keep that in mind.

    You'd also probably want to set up a double buffer in VRAM to prevent issues. Basically, set up 2 areas in VRAM to store the rendered graphics, and for each buffer, have a tilemap set up. When rendering, just load to a buffer and swap, and you won't be having to deal with any tearing or any weird issues that pop up from not double buffering.

    I hope this helps out some. I also have an example repository that renders a 3D plane. Though, you might wanna check out this older commit, where I just do a basic 2D image scale.
     
    • Informative Informative x 3
    • Like Like x 2
    • List
  5. Chimes

    Chimes

    The One SSG-EG Maniac Member
    651
    492
    63
    This... is a godsend. Thank you! I've been meaning to take a look at the Sega CD for a couple of years now, so this will be super helpful.
     
  6. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,453
    93
    your mom
    After figuring more things out, I'd like to add some clarifications on the purposes of some registers.
    • $FF805C - Image buffer vertical cell size
      • From what I can gather, this more acts like a "stride" for the overall buffer. It is possible to render multiple things to a single buffer, and I believe it's this register that helps with defining the size of the overall canvas that stuff is rendered onto. Also makes sense that the height would define the stride of the image buffer, considering that tiles are arranged from top to bottom, then left to right, like tiles are in Genesis sprites.
      • tl;dr it's how many tiles it would advance to render to the next column of tiles in the image buffer.
    • $FF805E - Image buffer start address
      • This is where it gets to defining where in the image buffer to render to. If you look closely at the bits allowed to be defined, it's in multiples of $20, which is the length of a Genesis tile in bytes. This register effectively acts as both the address within Word RAM to render to, and also the tile offset.
    • $FF8060 - Image buffer offset
      • This is where you would define the fine pixel offset within the tile offset to place the rendered object at.
    • $FF8062/$FF8064 - Image buffer H/V dot size
      • This would be the size of the rendered object that gets placed in the image buffer in pixels.
    The trace table is then used for rendering the object into the image buffer at the specific location.

    So, basically, if you wanted to draw a 64x48 object in a 192px high image buffer at position (10, 33) in the image buffer, you'd set $FF805C to (192/8)-1, set $FF805E to (address of the image buffer + ((((192 / 8) * (10 / 8)) + (33 / 8)) * 32)) / 4, set $FF8060 to (10 & 7) + ((33 & 7) << 3), set $FF8062 to 64, and $FF8062 to 48.

    Image buffer address ($FF805E) is derived from (x * height) + y. The divisions by 8 convert the pixel units to tile units, with the multiplication by 32 converting it into byte units, and the division by 4 shifts the bits into place for the register.
     
    Last edited: Nov 20, 2022
    • Like Like x 2
    • Informative Informative x 1
    • List