////////////////////////////////////////////////////////////////////// // // // cpuGraphics.pas: CPU graphics subsystem // // Implements the HV timer system and all of the rendering code // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM Core, released April 1st, 2003 // // The Initial Developer of the Original Code is Bottled Light, // // Inc. Portions created by Bottled Light, Inc. are Copyright // // (C) 2001 - 2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // The code here comes as close to emulating macros in Object // // Pascal as you're ever likely to see, and it isn't pretty. // // The offending code is there to implement the myriad rendering // // combinations for sprites and backgrounds. In all, there are 40 // // combinations for sprites, and 20 for backgrounds, a staggering // // amount of code duplication. Instead, there are 13 gfx*.pas // // files that implement different background or sprite types, and // // two additional files (gfxDrawPixel.pas & gfxDrawPixelInc.pas) // // that implement drawing an individual pixel. These are bound // // together using a bunch of DEFINES and included into this file. // // // // Its nasty, its ugly, its unholy, but its *still* better than // // the alternative. // // // // If I were to write this again, I'd probably play around with // // runtime code generation like some of the newer software 3d // // engines use, where they create a specific shader on the fly // // from component blocks. I could do that every time the mode // // is switched, or just generate all of them at startup. // // // // While we're on the subject of possibly deranged design // // choices, why not look at the sprite system. Its designed to // // render sprites in the correct order, supporting all of the // // beautifully flawed scenarios that might arise, when the sprite // // render quota runs out or multiple blendable sprites overlap. // // The current code fufils its design criteria, but is pretty // // inefficient in the process, with lots of array walking, and a // // nasty amount of data translation from OAM to my own // // TSpriteEntry format, all per scanline. // // // // One option that might be fruitful to look into is caching a // // set of TSpriteEntry's every time OAM is written to, which is // // something I've tried to avoid, but it may pay off considerably // // in terms of speed. I haven't actually profiled the graphics // // code in a long time tho, it may not even be worth pursuing. // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit cpuGraphics; //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses SysUtils, nexus, AddressSpace, cpuSound; ////////////////////////////////////////////////////////////////////// // HBlank() is called every time the HBlankEvent timer elapses procedure HBlank; ////////////////////////////////////////////////////////////////////// // Exported functions //////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // vmGetLaterID() returns a layer ID for the pixel rendered on the // current scanline at offset x. function vmGetLayerID(x: integer): byte; // vmDrawScanline() does just as it suggests. Width can only be 240 // or 256 (other values will probably work, but its only provided for // the VRAM observer to render a 256 swath of the screen) function vmDrawScanline(y, width: integer): Puint16; // vmRenderSprite() renders the y-th scanline of the i-th sprite to // the buffer passed in as line. It is only used for the sprite // observer currently. procedure vmRenderSprite(i: integer; y: integer; line: Puint16); ////////////////////////////////////////////////////////////////////// exports vmGetLayerID, vmDrawScanline, vmRenderSprite; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses Math, cpuMemory, cpuMisc, cpuPeripherals; ////////////////////////////////////////////////////////////////////// type TSpriteEntry = record a, b, c, index: uint16; priority, width, height, run: byte; x, y: integer; rotscale, doublesize: boolean; next, typ: byte; end; PSpriteEntry = ^TSpriteEntry; TSpriteRenderFunc = procedure (y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); TTextRenderFunc = procedure (y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR, scrollX, scrollY: uint16); TRSRenderFunc = procedure (y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); ////////////////////////////////////////////////////////////////////// // Globals shared between the rendering functions //////////////////// ////////////////////////////////////////////////////////////////////// var // The temporary sprite tree (pretty inefficient) spriteTable: array[0..127] of TSpriteEntry; // the last sprite entry to draw (for the scanline limit) lastEntry: integer; // The chain heads for the sprite renderer (god, what was I // thinking when I wrote this crackheaded code) spritePoints: array[0..4] of integer; // Stuff used for a single rendered scanline screenData: array[0..255] of uint16; winData: array[0..255] of byte; idData: array[0..255] of byte; // 240 (256 used for vram viewer) gfxScreenWidth: integer; ////////////////////////////////////////////////////////////////////// {$HINTS OFF} ////////////////////////////////////////////////////////////////////// {$DEFINE SPRITE} ////////////////////////////////////////////////////////////////////// procedure RenderRS256Color1DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D256Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderRS256Color1DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D256Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderRS256Color1DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D256Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderRS256Color1DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D256Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure RenderRS256Color1DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D256Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure RenderRS16Color1DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D16Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderRS16Color1DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D16Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderRS16Color1DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D16Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderRS16Color1DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D16Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure RenderRS16Color1DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS1D16Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure RenderRS256Color2DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D256Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderRS256Color2DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D256Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderRS256Color2DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D256Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderRS256Color2DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D256Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure RenderRS256Color2DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D256Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure RenderRS16Color2DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D16Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderRS16Color2DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D16Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderRS16Color2DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D16Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderRS16Color2DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D16Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure RenderRS16Color2DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDrawRS2D16Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure Render16Color1DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D16Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure Render16Color1DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D16Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure Render16Color1DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D16Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure Render16Color1DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D16Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure Render16Color1DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D16Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure Render256Color1DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D256Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure Render256Color1DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D256Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure Render256Color1DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D256Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure Render256Color1DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D256Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure Render256Color1DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw1D256Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure Render16Color2DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D16Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure Render16Color2DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D16Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure Render16Color2DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D16Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure Render16Color2DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D16Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure Render16Color2DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D16Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// procedure Render256Color2DSprite0(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D256Sprite.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure Render256Color2DSprite1(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D256Sprite.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure Render256Color2DSprite2(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D256Sprite.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure Render256Color2DSprite3(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D256Sprite.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} {$DEFINE OBJWIN} procedure Render256Color2DSprite4(y: integer; entry: PSpriteEntry; line: Puint16; wins, ids: Puint8; mask: byte); {$INCLUDE gfxDraw2D256Sprite.pas} {$UNDEF OBJWIN} ////////////////////////////////////////////////////////////////////// {$UNDEF SPRITE} ////////////////////////////////////////////////////////////////////// const sprite16_1D: array[0..4] of TSpriteRenderFunc = (Render16Color1DSprite0, Render16Color1DSprite1, Render16Color1DSprite2, Render16Color1DSprite3, Render16Color1DSprite4); sprite16_2D: array[0..4] of TSpriteRenderFunc = (Render16Color2DSprite0, Render16Color2DSprite1, Render16Color2DSprite2, Render16Color2DSprite3, Render16Color2DSprite4); sprite256_1D: array[0..4] of TSpriteRenderFunc = (Render256Color1DSprite0, Render256Color1DSprite1, Render256Color1DSprite2, Render256Color1DSprite3, Render256Color1DSprite4); sprite256_2D: array[0..4] of TSpriteRenderFunc = (Render256Color2DSprite0, Render256Color2DSprite1, Render256Color2DSprite2, Render256Color2DSprite3, Render256Color2DSprite4); ////////////////////////////////////////////////////////////////////// {$HINTS ON} ////////////////////////////////////////////////////////////////////// spriteRS16_1D: array[0..4] of TSpriteRenderFunc = (RenderRS16Color1DSprite0, RenderRS16Color1DSprite1, RenderRS16Color1DSprite2, RenderRS16Color1DSprite3, RenderRS16Color1DSprite4); spriteRS16_2D: array[0..4] of TSpriteRenderFunc = (RenderRS16Color2DSprite0, RenderRS16Color2DSprite1, RenderRS16Color2DSprite2, RenderRS16Color2DSprite3, RenderRS16Color2DSprite4); spriteRS256_1D: array[0..4] of TSpriteRenderFunc = (RenderRS256Color1DSprite0, RenderRS256Color1DSprite1, RenderRS256Color1DSprite2, RenderRS256Color1DSprite3, RenderRS256Color1DSprite4); spriteRS256_2D: array[0..4] of TSpriteRenderFunc = (RenderRS256Color2DSprite0, RenderRS256Color2DSprite1, RenderRS256Color2DSprite2, RenderRS256Color2DSprite3, RenderRS256Color2DSprite4); ////////////////////////////////////////////////////////////////////// procedure RenderSprite(i: integer; y: integer; line: Puint16; wins, ids: Puint8); var entry: PSpriteEntry; funcIndex: integer; begin if i >= lastEntry then Exit; entry := @SpriteTable[i]; funcIndex := registers[BLEND_S1] shr 6; // Overrides for forced a-blend sprites and sprite windows if entry^.typ = 1 then funcIndex := 1 else if entry^.typ = 2 then funcIndex := 4; // Call the appropriate sprite renderer Dec(y, entry^.y); if entry^.rotscale then begin if entry^.a and SPRITE_A_256MODE = 0 then begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then spriteRS16_2D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ) else spriteRS16_1D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ); end else begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then spriteRS256_2D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ) else spriteRS256_1D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ); end; end else begin if entry^.a and SPRITE_A_256MODE = 0 then begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then sprite16_2D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ) else sprite16_1D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ); end else begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then sprite256_2D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ) else sprite256_1D[funcIndex](y, entry, line, wins, ids, DISPLAY_ACTIVE_OBJ); end; end; end; ////////////////////////////////////////////////////////////////////// function BuildSpriteEntry(y, i: integer; var cycs: integer; force: boolean): integer; var a, b, c: uint16; t: byte; entry: PSpriteEntry; width, height: integer; begin Result := -1; if force then lastEntry := 0; entry := @spriteTable[lastEntry]; entry^.next := 255; // Read the sprite info entries a := Puint16(@(OAM[i shl 3]))^; b := Puint16(@(OAM[i shl 3 + 2]))^; c := Puint16(@(OAM[i shl 3 + 4]))^; entry^.a := a; entry^.b := b; entry^.c := c; entry^.index := i; // Work out the dimensions t := ((b shr 14) or ((a shr 12) and $C)) shl 1; entry^.width := SpriteSizes[t]; entry^.height := SpriteSizes[t+1]; entry^.run := entry^.width; // Work out the properties entry^.rotscale := a and SPRITE_A_ROTSCALE <> 0; entry^.doublesize := a and SPRITE_A_DOUBLESIZE <> 0; entry^.priority := (c shr 10) and $3; entry^.typ := (a shr 10) and $3; // Work out the height and width width := entry^.width; height := entry^.height; if entry^.doublesize then begin width := width shl 1; height := height shl 1; end; // Work out the positions entry^.x := b and $1FF; entry^.y := a and $FF; if entry^.x + width > 512 then Dec(entry^.x, 512); if entry^.y + height > 256 then Dec(entry^.y, 256); if force then begin entry^.x := 0; entry^.y := 0; end; if (y >= entry^.y) and (y < entry^.y + height) then begin // Decrement the sprite engine clock appropriately if entry^.rotscale then begin if entry^.doublesize then entry^.run := entry^.run*2; Dec(cycs, entry^.run*2 + 10); if cycs < 0 then Dec(entry^.run, cycs shr 1); end else begin Dec(cycs, entry^.width); if cycs < 0 then Dec(entry^.run, cycs); if entry^.doublesize then entry^.run := 0; end; // A touch of horizontal clipping if entry^.x <= -entry^.run then entry^.run := 0; if entry^.x+entry^.run > gfxScreenWidth then entry^.run := gfxScreenWidth - entry^.x; if entry^.run > 0 then begin if entry^.typ = 2 then entry^.priority := 4; // Add the sprite to a priority list entry^.next := spritePoints[entry^.priority]; spritePoints[entry^.priority] := lastEntry; Result := lastEntry; Inc(lastEntry); end; end; end; ////////////////////////////////////////////////////////////////////// procedure vmRenderSprite(i: integer; y: integer; line: Puint16); var entry: PSpriteEntry; funcIndex: integer; wins, ids: array[0..255] of byte; junk: integer; begin junk := 9999; FillChar(wins, 256, 1 shl 4); entry := @SpriteTable[BuildSpriteEntry(y, i, junk, true)]; // Call the appropriate sprite renderer funcIndex := 0; Dec(y, entry^.y); if entry^.rotscale then begin if entry^.a and SPRITE_A_256MODE = 0 then begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then spriteRS16_2D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ) else spriteRS16_1D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ); end else begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then spriteRS256_2D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ) else spriteRS256_1D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ); end; end else begin if entry^.a and SPRITE_A_256MODE = 0 then begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then sprite16_2D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ) else sprite16_1D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ); end else begin if registers[DISPLAY_CR] and DISPLAY_CR_OBJ_1D = 0 then sprite256_2D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ) else sprite256_1D[funcIndex](y, entry, line, @wins, @ids, DISPLAY_ACTIVE_OBJ); end; end; end; ////////////////////////////////////////////////////////////////////// procedure BuildSpriteLists(y: integer); var i, cycs: integer; begin // Initialize the cycle counter to the number of cycles the sprite renderer has left if registers[DISPLAY_CR] or DISPLAY_CR_NOHBLOBJS <> 0 then cycs := 240*4 else cycs := 308*4; Dec(cycs, 6); // Clean up the list for i := 0 to 4 do spritePoints[i] := 255; // Build up to 4 display lists for visible sprites lastEntry := 0; for i := 0 to 127 do BuildSpriteEntry(y, i, cycs, false); end; ////////////////////////////////////////////////////////////////////// procedure RenderSprites(y: integer; priority: byte; line: Puint16; wins, ids: Puint8); var i, j: integer; begin if registers[DISPLAY_ACTIVE] and DISPLAY_ACTIVE_OBJ = 0 then Exit; i := spritePoints[priority]; j := 0; while (i < 255) and (j < 128) do begin RenderSprite(i, y, line, wins, ids); i := spriteTable[i].next; Inc(j); end; end; ////////////////////////////////////////////////////////////////////// {$HINTS OFF} ////////////////////////////////////////////////////////////////////// procedure RenderTextScreen0(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR, scrollX, scrollY: uint16); {$INCLUDE gfxDrawText.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderTextScreen1(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR, scrollX, scrollY: uint16); {$INCLUDE gfxDrawText.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderTextScreen2(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR, scrollX, scrollY: uint16); {$INCLUDE gfxDrawText.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderTextScreen3(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR, scrollX, scrollY: uint16); {$INCLUDE gfxDrawText.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} ////////////////////////////////////////////////////////////////////// procedure RenderRotScaleScreen0(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawRotScale.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderRotScaleScreen1(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawRotScale.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderRotScaleScreen2(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawRotScale.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderRotScaleScreen3(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawRotScale.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} ////////////////////////////////////////////////////////////////////// procedure RenderMode3Screen0(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP3.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderMode3Screen1(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP3.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderMode3Screen2(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP3.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderMode3Screen3(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP3.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} ////////////////////////////////////////////////////////////////////// procedure RenderMode4Screen0(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP4.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderMode4Screen1(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP4.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderMode4Screen2(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP4.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderMode4Screen3(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP4.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} ////////////////////////////////////////////////////////////////////// procedure RenderMode5Screen0(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP5.pas} {$DEFINE BLEND}{$DEFINE ABLEND} procedure RenderMode5Screen1(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP5.pas} {$UNDEF ABLEND}{$DEFINE FADEUP} procedure RenderMode5Screen2(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP5.pas} {$UNDEF FADEUP}{$DEFINE FADEDOWN} procedure RenderMode5Screen3(y: integer; line: Puint16; wins, ids: Puint8; mask: byte; CR: uint16; params: PRotScaleSet); {$INCLUDE gfxDrawBMP5.pas} {$UNDEF FADEDOWN}{$UNDEF BLEND} ////////////////////////////////////////////////////////////////////// {$HINTS ON} ////////////////////////////////////////////////////////////////////// const mode0funcs: array[0..3] of TTextRenderFunc = (RenderTextScreen0, RenderTextScreen1, RenderTextScreen2, RenderTextScreen3); mode1funcs: array[0..3] of TRSRenderFunc = (RenderRotScaleScreen0, RenderRotScaleScreen1, RenderRotScaleScreen2, RenderRotScaleScreen3); mode3funcs: array[0..3] of TRSRenderFunc = (RenderMode3Screen0, RenderMode3Screen1, RenderMode3Screen2, RenderMode3Screen3); mode4funcs: array[0..3] of TRSRenderFunc = (RenderMode4Screen0, RenderMode4Screen1, RenderMode4Screen2, RenderMode4Screen3); mode5funcs: array[0..3] of TRSRenderFunc = (RenderMode5Screen0, RenderMode5Screen1, RenderMode5Screen2, RenderMode5Screen3); ////////////////////////////////////////////////////////////////////// function vmDrawScanline(y, width: integer): Puint16; var i, bg, priority: uint32; color, CR: uint16; // Window stuff coeffa, x0, x1: integer; defaultActive: byte; funcIndex: integer; r, g, b: byte; begin gfxScreenWidth := width; // Only render if the forced blank flag is cleared if registers[DISPLAY_CR] and DISPLAY_CR_NO_DISP = 0 then begin funcIndex := registers[BLEND_S1] shr 6; // Index the current position in the screen data array and clear it coeffa := registers[COEFF_Y]; if coeffa > 15 then coeffa := 16; color := Puint16(@(palette[0]))^; if funcIndex = 2 then begin r := color and $1F; g := (color shr 5) and $1F; b := (color shr 10) and $1F; r := Min(r + ((31 - r)*coeffa) shr 4, 31); g := Min(g + ((31 - g)*coeffa) shr 4, 31); b := Min(b + ((31 - b)*coeffa) shr 4, 31); color := r + g shl 5 + b shl 10; end else if funcIndex = 3 then begin r := color and $1F; g := (color shr 5) and $1F; b := (color shr 10) and $1F; r := Max(r - (r*coeffa) shr 4, 0); g := Max(g - (g*coeffa) shr 4, 0); b := Max(b - (b*coeffa) shr 4, 0); color := r + g shl 5 + b shl 10; end; for i := 0 to gfxScreenWidth-1 do screenData[i] := color; FillChar(idData, gfxScreenWidth, 1 shl 5); // Alias the windows array and clear it defaultActive := (registers[DISPLAY_ACTIVE] and $1F) or $20; if registers[DISPLAY_ACTIVE] shr 5 <> 0 then FillChar(winData, gfxScreenWidth, defaultActive and registers[WIN_OUT]) else FillChar(winData, gfxScreenWidth, defaultActive); // Process the sprites, building a traversal list for later BuildSpriteLists(y); // Draw the sprite windows if registers[DISPLAY_ACTIVE] and DISPLAY_ACTIVE_OBJW <> 0 then RenderSprites(y, 4, @screenData, @winData, @idData); // Plonk in the other windows onto the window array if registers[DISPLAY_ACTIVE] and DISPLAY_ACTIVE_WIN1 <> 0 then if (y >= registers[WIN1_Y0]) and (y < registers[WIN1_Y1]) then begin x0 := registers[WIN1_X0]; x1 := registers[WIN1_X1]; if x1 > x0 then FillChar(winData[x0], x1-x0+1, defaultActive and registers[WIN1_IN]); end; if registers[DISPLAY_ACTIVE] and DISPLAY_ACTIVE_WIN0 <> 0 then if (y >= registers[WIN0_Y0]) and (y < registers[WIN0_Y1]) then begin x0 := registers[WIN0_X0]; x1 := registers[WIN0_X1]; if x1 > x0 then FillChar(winData[x0], x1-x0+1, defaultActive and registers[WIN0_IN]); end; // Render the layers, in order of increasing priority case registers[DISPLAY_CR] and DISPLAY_CR_MODEMASK of 0: begin for priority := 3 downto 0 do begin // Check text BG0..3 for a matching priority for bg := 3 downto 0 do begin CR := Puint16(@(registers[BG0_CR + bg shl 1]))^; if CR and $3 = priority then mode0funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl bg, CR, Puint16(@(registers[BG0_X0 + bg shl 2]))^ {and $1FF}, Puint16(@(registers[BG0_Y0 + bg shl 2]))^ {and $1FF}); end; // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; 1: begin for priority := 3 downto 0 do begin // Check the R/S BG2 for a matching priority CR := Puint16(@(registers[BG2_CR]))^; if CR and $3 = priority then mode1funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 2, CR, @(registers[BG2_A])); // Check text BG0..1 for a matching priority for bg := 1 downto 0 do begin CR := Puint16(@(registers[BG0_CR + bg shl 1]))^; if CR and $3 = priority then mode0funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl bg, CR, Puint16(@(registers[BG0_X0 + bg shl 2]))^{ and $1FF}, Puint16(@(registers[BG0_Y0 + bg shl 2]))^{ and $1FF}); end; // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; 2: begin for priority := 3 downto 0 do begin // Check the R/S BG3 for a matching priority CR := Puint16(@(registers[BG3_CR]))^; if CR and $3 = priority then mode1funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 3, CR, @(registers[BG3_A])); // Check the R/S BG2 for a matching priority CR := Puint16(@(registers[BG2_CR]))^; if CR and $3 = priority then mode1funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 2, CR, @(registers[BG2_A])); // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; 3: begin for priority := 3 downto 0 do begin // Check the bitmap BG2 for a matching priority CR := Puint16(@(registers[BG2_CR]))^; if CR and $3 = priority then mode3funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 2, CR, @(registers[BG2_A])); // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; 4: begin for priority := 3 downto 0 do begin // Check the bitmap BG2 for a matching priority CR := Puint16(@(registers[BG2_CR]))^; if CR and $3 = priority then mode4funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 2, CR, @(registers[BG2_A])); // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; 5: begin for priority := 3 downto 0 do begin // Check the bitmap BG2 for a matching priority CR := Puint16(@(registers[BG2_CR]))^; if CR and $3 = priority then mode5funcs[funcIndex](y, @screenData, @winData, @idData, 1 shl 2, CR, @(registers[BG2_A])); // Check the sprite list for a matching priority RenderSprites(y, priority, @screenData, @winData, @idData); end; end; else UndefinedState('DrawScanline: display mode > 5'); end; end else begin // The screen is white in forced blank for i := 0 to gfxScreenWidth-1 do screenData[i] := $7FFF; end; { funkyness ahead: i := y and 1; while i < gfxScreenWidth do begin screenData[i] := not screenData[i]; Inc(i, 2); end; } Result := @screenData; end; ////////////////////////////////////////////////////////////////////// function vmGetLayerID(x: integer): byte; var test: byte; begin Result := 0; test := idData[x]; repeat test := test shr 1; Inc(Result); until (test = 1) or (result > 5); end; ////////////////////////////////////////////////////////////////////// // Called when entering vertical blank (see ExitHBlank) procedure EnterVBlank; begin // Set the vertical blank status bit registers[DISPLAY_SR] := registers[DISPLAY_SR] or DISPLAY_SR_IN_VBL; // Check for DMA triggers if (Puint16(@(registers[DMA0_CR]))^ shr 12) and $59 = $49 then InitiateDMATransfer(0); if (Puint16(@(registers[DMA1_CR]))^ shr 12) and $59 = $49 then InitiateDMATransfer(1); if (Puint16(@(registers[DMA2_CR]))^ shr 12) and $59 = $49 then InitiateDMATransfer(2); if (Puint16(@(registers[DMA3_CR]))^ shr 12) and $59 = $49 then InitiateDMATransfer(3); // Try to trigger a vertical blank interrupt if registers[DISPLAY_SR] and DISPLAY_SR_VBL_IRQ <> 0 then TriggerIRQ(IRQ_VBLANK); end; ////////////////////////////////////////////////////////////////////// // Called when exiting vertical blank (see ExitHBlank) procedure ExitVBlank; begin // Clear the vertical blank bit in the display status register registers[DISPLAY_SR] := registers[DISPLAY_SR] and not DISPLAY_SR_IN_VBL; // Reset the rotation registers Puint32(@(registers[BG2_X]))^ := Puint32(@(registers[BG2_X_LATCH]))^; Puint32(@(registers[BG2_Y]))^ := Puint32(@(registers[BG2_Y_LATCH]))^; Puint32(@(registers[BG3_X]))^ := Puint32(@(registers[BG3_X_LATCH]))^; Puint32(@(registers[BG3_Y]))^ := Puint32(@(registers[BG3_Y_LATCH]))^; end; ////////////////////////////////////////////////////////////////////// // Called when entering horizontal blank (see HBlank) procedure EnterHBlank; var y: uint32; begin y := registers[DISPLAY_Y]; // Check for the y-line trigger if registers[DISPLAY_YTRIG] = y then begin registers[DISPLAY_SR] := registers[DISPLAY_SR] or DISPLAY_SR_TRIGGERED; if registers[DISPLAY_SR] and DISPLAY_SR_Y_TRIGGER_IRQ <> 0 then TriggerIRQ(IRQ_YLINE); end else registers[DISPLAY_SR] := registers[DISPLAY_SR] and not DISPLAY_SR_TRIGGERED; // See if we are doing visible lines, entering v-blank, or what registers[DISPLAY_SR] := registers[DISPLAY_SR] or DISPLAY_SR_IN_HBL; // Check for DMA triggers if y < 160 then begin if (Puint16(@(registers[DMA0_CR]))^ shr 9) and $59 = $51 then InitiateDMATransfer(0); if (Puint16(@(registers[DMA1_CR]))^ shr 9) and $59 = $51 then InitiateDMATransfer(1); if (Puint16(@(registers[DMA2_CR]))^ shr 9) and $59 = $51 then InitiateDMATransfer(2); if (Puint16(@(registers[DMA3_CR]))^ shr 9) and $59 = $51 then InitiateDMATransfer(3); // Try to trigger a horizontal blank interrupt if registers[DISPLAY_SR] and DISPLAY_SR_HBL_IRQ <> 0 then TriggerIRQ(IRQ_HBLANK); end; end; ////////////////////////////////////////////////////////////////////// // Called when exiting horizontal blank (see HBlank) procedure ExitHBlank; var y: uint32; b, d: uint32; begin // Update the rot/scale registers if registers[BG2_X_DIRTY] = 0 then begin b := Puint16(@(registers[BG2_B]))^; if b and (1 shl 15) <> 0 then b := b or $FFFF0000; Inc(Puint32(@(registers[BG2_X]))^, b); end; if registers[BG2_Y_DIRTY] = 0 then begin d := Puint16(@(registers[BG2_D]))^; if d and (1 shl 15) <> 0 then d := d or $FFFF0000; Inc(Puint32(@(registers[BG2_Y]))^, d); end; if registers[BG3_X_DIRTY] = 0 then begin b := Puint16(@(registers[BG3_B]))^; if b and (1 shl 15) <> 0 then b := b or $FFFF0000; Inc(Puint32(@(registers[BG3_X]))^, b); end; if registers[BG3_Y_DIRTY] = 0 then begin d := Puint16(@(registers[BG3_D]))^; if d and (1 shl 15) <> 0 then d := d or $FFFF0000; Inc(Puint32(@(registers[BG3_Y]))^, d); end; // Reset the rotation registers registers[BG2_X_DIRTY] := 0; registers[BG2_Y_DIRTY] := 0; registers[BG3_X_DIRTY] := 0; registers[BG3_Y_DIRTY] := 0; // Increment the y-counter y := registers[DISPLAY_Y] + 1; if y = 228 then y := 0; registers[DISPLAY_Y] := y; // Clear the H blank bit and check for V blank registers[DISPLAY_SR] := registers[DISPLAY_SR] and not DISPLAY_SR_IN_HBL; if y = 160 then EnterVBlank else if y = 0 then begin ExitVBlank; OnVideoReady(registers[DISPLAY_Y], vmDrawScanline(registers[DISPLAY_Y], 240)); end else if y < 160 then begin OnVideoReady(registers[DISPLAY_Y], vmDrawScanline(registers[DISPLAY_Y], 240)); end; end; ////////////////////////////////////////////////////////////////////// // HBlank() is called twice per scanline, at 960 and 1232 cycles. // It handles all of the state changes needed for entering and exiting // horizontal and vertical blank procedure HBlank; begin if enteringHBlank then begin EnterHBlank; Inc(HBlankEvent, 272); end else begin ExitHBlank; Inc(HBlankEvent, 960); end; enteringHBlank := not enteringHBlank; end; ////////////////////////////////////////////////////////////////////// end. //////////////////////////////////////////////////////////////////////