423 lines
32 KiB
Markdown
423 lines
32 KiB
Markdown
# Video: backgrounds, sprites, windows, and effects
|
||
|
||
Rendering behavior implemented in `crates/gba-core/src/video/`, plus button input registers.
|
||
|
||
## Graphics and button input
|
||
|
||
`DISPCNT` at `0x04000000` selects the display mode. The CGB mode bit is forced to zero.
|
||
`GREENSWAP` at `0x04000002` exchanges the green channels of adjacent pixel pairs when bit zero is set.
|
||
`KEYINPUT` at `0x04000130` exposes ten active-low buttons. Unused bits read as zero; writes are ignored.
|
||
`Memory::set_buttons(Buttons)` supplies pressed-high host input. The I/O layer converts it to active-low register bits.
|
||
`KEYCNT` at `0x04000132` selects buttons, OR/AND matching, and keypad interrupt enable.
|
||
Input samples and control writes can latch IF bit 12 and wake HALT without advancing the clock.
|
||
See [keypad interrupt control](timers-irq.md#keypad-interrupt-control) for the sampling policy and limits.
|
||
|
||
Video RAM starts at `0x06000000` and contains 96 KiB.
|
||
Its 128 KiB mirror layout repeats the final 32 KiB in offsets `0x18000..0x1ffff`.
|
||
Palette RAM starts at `0x05000000` and repeats every 1 KiB.
|
||
Halfword and word writes preserve little-endian order.
|
||
Byte writes to palette or background video RAM duplicate the byte into the addressed halfword.
|
||
Byte writes to object video RAM are ignored. Its start is `0x14000` in bitmap modes and `0x10000` in tile modes.
|
||
Object attribute memory (OAM) starts at `0x07000000` and repeats every 1 KiB across the `0x07` region.
|
||
OAM supports byte reads and halfword/word reads and writes. Byte writes do nothing.
|
||
Its 128 eight-byte entries hold three sprite attributes and one affine-parameter halfword each.
|
||
All bytes start at zero. Software must disable unused sprites; zeroed entries describe active 8×8 sprites.
|
||
CPU and DMA accesses share this memory. Active-display access restrictions are not modeled.
|
||
|
||
`Memory::render_frame` creates a snapshot without advancing the emulated clock.
|
||
Mode 3 stores 38,400 RGB555 pixels at `0x06000000..0x06012bff` in row-major order.
|
||
The renderer samples that bitmap through BG2's matrix and reference-point registers.
|
||
Pixel bit 15 and the DISPCNT page-select bit do not affect Mode 3 output. Black pixels are opaque.
|
||
Disabling BG2 leaves palette entry zero behind sprites. Forced blank displays white, regardless of the selected mode.
|
||
All bitmap modes support regular and affine sprites, windows, and color effects.
|
||
Enabling backgrounds other than BG2 in bitmap modes produces errors without changing the output buffer.
|
||
Reserved display modes 6 and 7 return errors. Modes 0 through 5 support snapshots and row capture, not complete hardware implementations.
|
||
|
||
Mode 4 stores 240×160 byte-sized palette indices. Index zero is transparent and can reveal a lower-priority sprite.
|
||
A nonzero palette index remains opaque even when its palette color is black.
|
||
Palette changes affect the next captured row or debug snapshot, not rows already captured.
|
||
Mode 5 stores 160×128 RGB555 pixels. All in-bounds pixels are opaque, including black. Pixel bit 15 is ignored.
|
||
Mode 5 does not automatically stretch to the LCD size. BG2's affine matrix can scale or position the bitmap.
|
||
|
||
Modes 4 and 5 select their visible page with DISPCNT bit 4:
|
||
|
||
| Mode | Page 0 pixel data | Page 1 pixel data |
|
||
| --- | --- | --- |
|
||
| 4 | `0x06000000..0x060095ff` | `0x0600a000..0x060135ff` |
|
||
| 5 | `0x06000000..0x06009fff` | `0x0600a000..0x06013fff` |
|
||
|
||
Both page bases are 40 KiB apart. Mode 4's padding after each image is not pixel data.
|
||
Writes to the hidden page do not change visible pixels until that page is selected.
|
||
Mode 4 byte writes still duplicate the byte into both halves of a halfword, as required by the video memory bus.
|
||
Palette data and sprite tiles are shared between pages. Bitmap-mode sprite tiles start at `0x06014000`.
|
||
|
||
Mode 0 supports all four text backgrounds, with these features:
|
||
|
||
- `BG0CNT..BG3CNT` at `0x04000008..0x0400000f`: priority, character base, palette depth, map base, and size.
|
||
- Horizontal/vertical scroll latches at `0x04000010..0x0400001f`, with byte-write merging and nine-bit offsets.
|
||
- Scroll registers are write-only. Reads return zero as a placeholder; open bus is not modeled.
|
||
- Control masks are `0xdfcf` for BG0/BG1 and `0xffcf` for BG2/BG3.
|
||
- 8×8 tiles with 4-bit palette indices and sixteen banks, or 8-bit indices with one palette.
|
||
- Horizontal and vertical tile flips, and ten-bit tile numbers.
|
||
- 256×256, 512×256, 256×512, and 512×512 maps assembled from 32×32-tile screen blocks.
|
||
- Scroll coordinates wrap at map edges. The affine overflow bit does not change text backgrounds.
|
||
- Index zero is transparent in every palette bank. Nonzero indices can display opaque black.
|
||
- Lower priority numbers win. Equal priorities select BG0 before BG1, BG2, and BG3.
|
||
- Uncovered pixels use palette entry zero. Green swap runs after composition.
|
||
|
||
Mode 0 supports mosaic independently on each background. Rendering errors leave the output buffer unchanged.
|
||
Background tile or map fetches use the 128 KiB VRAM window; offsets `0x18000..0x1ffff`
|
||
mirror `0x10000..0x17fff`. Addresses at or beyond `0x20000` return diagnostics.
|
||
This is not a complete picture processing unit (PPU).
|
||
|
||
Affine background snapshots support these configurations:
|
||
|
||
- Mode 1: text BG0/BG1 and affine BG2.
|
||
- Mode 2: affine BG2/BG3.
|
||
- Modes 3, 4, and 5: affine bitmap BG2, with out-of-bitmap samples always transparent.
|
||
- Enabling a background unavailable in the selected mode returns a diagnostic rather than silently ignoring it.
|
||
|
||
`BG2PA..BG2PD` occupy `0x04000020..0x04000027`. BG2 reference points X/Y occupy `0x04000028..0x0400002f`.
|
||
BG3 uses the corresponding registers at `0x04000030..0x0400003f`.
|
||
Coefficients are signed 16-bit values divided by 256. Reference points are signed 28-bit values divided by 256.
|
||
All registers are write-only; reads return zero as an open-bus placeholder.
|
||
Byte and halfword writes merge into the stored values. Reference-point bits 28–31 are ignored.
|
||
Registers reset to zero, not an identity matrix. Software must initialize PA and PD to 256 for identity sampling.
|
||
The existing Mode 3 demo now performs that initialization through ARM stores.
|
||
|
||
The snapshot source coordinates are `X + PA*x + PB*y` and `Y + PC*x + PD*y`, with fixed-point scaling.
|
||
The renderer floors fractional coordinates after summing, including negative coordinates.
|
||
Mode 0 text backgrounds ignore these affine registers. Affine backgrounds ignore text scroll registers.
|
||
|
||
Affine tile backgrounds use flat maps of byte-sized tile indices, without text-map flips or palette-bank attributes.
|
||
Every tile is 8×8 with eight-bit palette indices, regardless of BGCNT bit 7.
|
||
Map sizes are 128×128, 256×256, 512×512, or 1024×1024 pixels.
|
||
BGCNT bit 13 selects wrapping or transparent edges in Modes 1 and 2. All bitmap modes ignore this bit.
|
||
Palette index zero is transparent in tiled backgrounds and Mode 4. Mode 3/5 black bitmap pixels remain opaque.
|
||
Background priorities, sprite composition, and green swap apply after sampling.
|
||
Map fetches use the same 128 KiB VRAM mirror. Addresses at or beyond `0x20000` return diagnostics.
|
||
|
||
**Timing limit:** debug snapshots use one set of programmed registers for the whole image.
|
||
Row capture instead samples from separate internal X/Y origins.
|
||
At each visible line end, enabled affine backgrounds add the current PB/PD to their origins.
|
||
HBlank DMA coefficient changes therefore affect the next row's increment, not all preceding rows.
|
||
BG2 tracks in Modes 1–5; BG3 tracks in Mode 2. Disabled or unavailable backgrounds do not accumulate.
|
||
Window masking does not pause tracking. This row-level model also continues tracking during forced blank.
|
||
|
||
X/Y writes before HBlank replace the current row's origin for the written axis.
|
||
Writes after row capture set the next row's origin and override that axis's next increment.
|
||
Partial writes merge into programmed values, not accumulated origins. Matrix writes do not reload origins.
|
||
Both axes wrap within signed 28-bit fixed-point storage.
|
||
The next visible frame reloads programmed origins at the line-zero boundary, including values written during VBlank.
|
||
Debug snapshots continue to use programmed origins and absolute screen Y.
|
||
|
||
Vertical background mosaic holds the origin until the shared background counter becomes zero at a line end.
|
||
It then adds the current PB/PD multiplied by the current block height.
|
||
Changing the size does not reset the counter or recompute the origin from absolute screen Y.
|
||
Tracking continues with capture disabled. Fixed-register clock batches remain constant-time.
|
||
|
||
These are explicit row-level rules, not verified hardware latch timing.
|
||
Exact VBlank reload timing, enable delays, forced-blank restart delays, individual sprite fetches, and background pixel-fetch timing remain unmodeled.
|
||
|
||
Sprites are enabled with DISPCNT bit 12 in any supported display mode:
|
||
|
||
- All twelve square, horizontal, and vertical size combinations are supported, from 8×8 to 64×64.
|
||
- X coordinates wrap at 512; Y coordinates wrap at 256. Rendering clips to the 240×160 screen.
|
||
- Regular-sprite horizontal and vertical flips reverse the whole sprite, including multi-tile sprites.
|
||
- Four-bit sprites use sixteen OBJ palette banks. Eight-bit sprites use the complete OBJ palette.
|
||
- OBJ palette data starts at `0x05000200`, separate from background palettes.
|
||
- Index zero is transparent. A nonzero index can display opaque black.
|
||
- DISPCNT bit 6 selects consecutive 1D tile rows or fixed-width 2D tile rows.
|
||
- Tile numbers count 32-byte slots from `0x06010000`. Eight-bit tiles occupy two slots.
|
||
- In 2D mapping, eight-bit base tile numbers ignore bit zero. In 1D mapping, odd bases remain valid.
|
||
- 2D columns wrap within 32 slots. Tile addressing wraps within the 32 KiB OBJ area.
|
||
- Modes 3–5 suppress sprites whose starting tile number is below 512, regardless of the displayed page.
|
||
- Lower priority numbers win object conflicts. Equal priorities retain the earlier OAM entry's color.
|
||
- Transparent texels can change priority and mosaic metadata without replacing the stored color or semi-transparency flag.
|
||
- The selected sprite beats a background at equal priority. A background with a lower priority number covers it.
|
||
- Disabled regular sprites are skipped, regardless of their other attributes.
|
||
|
||
Affine sprites use a two-by-two matrix for rotation, scaling, reflection, or shear:
|
||
|
||
- Attribute 0 bit 8 enables affine sampling. Bit 9 then doubles the drawing area's width and height instead of disabling the sprite.
|
||
- Attribute 1 bits 9–13 select one of 32 matrices. These bits no longer control horizontal or vertical flips.
|
||
- Matrix coefficients PA, PB, PC, and PD occupy OAM offsets `6`, `14`, `22`, and `30`, plus `32 × matrix index`.
|
||
- Each coefficient is a signed 16-bit value divided by 256, also called signed 8.8 fixed point.
|
||
- `[256, 0, 0, 256]` gives an identity transform. `[128, 0, 0, 128]` enlarges the image 2×.
|
||
- The renderer maps drawing-area coordinates back into the source texture, around their respective centers.
|
||
- Fractional coordinates round down after both matrix products are added. Out-of-texture samples are transparent.
|
||
- Double-size mode enlarges only the drawing area, not the source texture or the matrix scale.
|
||
- X/Y still locate the drawing area's upper-left corner. Software must adjust X/Y to preserve the center when changing area size.
|
||
- Sprites can share matrices, including matrices beside disabled sprite entries. Updates affect later preparation events or immediate debug snapshots.
|
||
- Zero matrices sample the center texel throughout the drawing area. Singular matrices are valid; no matrix inversion occurs.
|
||
|
||
Semi-transparent sprites and OBJ windows support both regular and affine sampling.
|
||
Prohibited modes/shapes return explicit diagnostics.
|
||
Unsupported active sprites are checked even when offscreen. Global OBJ disable skips these checks.
|
||
Sprite preparation applies a nominal per-row work allowance. Individual fetch timing and OAM access contention remain unimplemented.
|
||
Sprite priority includes transparent-texel metadata updates, but the core has not run public hardware test ROMs.
|
||
|
||
### Buffered sprite row preparation
|
||
|
||
The core prepares the following visible sprite row at cycle40 of lines0–158 and line227.
|
||
Line227 prepares row0. Lines159–226 do not prepare another visible row.
|
||
This is a row-at-once approximation of the hardware's one-line-ahead engine, not an accurate sequence of OAM/VRAM fetches.
|
||
|
||
Preparation samples OAM attributes, affine matrices, sprite VRAM, tile mapping, bitmap restrictions, and the ahead-of-display OBJ mosaic phase.
|
||
It also samples DISPCNT bit 5 to select the row's nominal sprite work allowance.
|
||
It stores palette indices, priority, mosaic and semi-transparency metadata, and OBJ-window coverage.
|
||
The following row's HBlank composition resolves those indices through the current palette.
|
||
Horizontal OBJ mosaic uses the current width. Window masks, blending coefficients, global display enables, and color effects also remain current.
|
||
|
||
A write before the preceding line's cycle40 can affect preparation. A write after that event cannot change the prepared row.
|
||
Under this boundary policy, a host write at cycle40 is already too late.
|
||
CPU/DMA writes commit before their nominal clock batches, so a write crossing cycle40 participates in preparation.
|
||
For example, an OAM write in row0 HBlank affects row2 or later, not the already-prepared row1.
|
||
|
||
Global OBJ disable makes preparation empty. Disabling OBJ before composition hides an already-prepared row.
|
||
OBJ-window coverage is prepared regardless of its display-enable bit; composition requires both OBJ and OBJ-window enables.
|
||
Forced blank does not stop preparation in this model. Hardware restart delays remain unmodeled.
|
||
|
||
Reset starts at visible row0 rather than a hardware boot phase.
|
||
The first positive clock advance therefore prepares row0 from host setup, using mosaic phase0.
|
||
Later row0 preparation uses line227. Zero-cycle advances do not initialize or refresh the buffers.
|
||
Preparation continues with capture disabled. Capture toggles and debug snapshots do not reset prepared state.
|
||
|
||
Preparation errors are retained for the target row, not returned by CPU/DMA clock advancement.
|
||
Visible composition reports them through the existing deferred frame diagnostic.
|
||
Forced blank and global OBJ disable suppress unused sprite diagnostics. A later valid frame restores presentation.
|
||
Individual fetch timing and video-bus arbitration remain unmodeled.
|
||
|
||
### Nominal sprite work allowance
|
||
|
||
Each prepared row receives 1,210 nominal sprite-rendering cycles, or 954 when DISPCNT bit 5 (`HBlank Interval Free`) is set.
|
||
Debug snapshots apply the same allowance independently to each row, using current registers.
|
||
This is an aggregate rendering limit. It does not advance CPU or display clocks.
|
||
|
||
Objects consume work in OAM order, regardless of display priority:
|
||
|
||
- An active regular sprite costs its drawing-canvas width.
|
||
- An active affine sprite costs `10 + 2 × drawing-canvas width`.
|
||
- Double-size affine mode charges the enlarged canvas, not the source texture width.
|
||
- These active costs include inspection. Disabled, vertically excluded, and entirely horizontally offscreen entries cost two inspection cycles instead.
|
||
- A sprite that exceeds the remaining allowance prepares only a left-to-right canvas prefix. Later objects produce no samples.
|
||
- An incomplete affine setup or pixel consumes the remaining allowance. A later regular sprite cannot reuse it.
|
||
- There is no separate 32-object-per-row limit.
|
||
|
||
Transparency, occlusion, window masks, mosaic, out-of-texture affine samples, and bitmap-restricted tile numbers do not refund work.
|
||
Partially clipped sprites charge their full canvas width, including clipped columns. Hardware left-clipping savings are not modeled.
|
||
Invalid active OAM entries still produce development diagnostics after allowance exhaustion, preserving the existing validation policy.
|
||
|
||
DISPCNT bit 5 is sampled during whole-row preparation, not during later composition.
|
||
Changing it after the preceding line's cycle40 therefore cannot change that prepared row's allowance.
|
||
The reduced allowance does not implement HBlank OAM access arbitration.
|
||
These nominal costs combine documented limits with emulator-reference inspection costs; exact hardware cutoffs remain unverified.
|
||
|
||
### Mosaic snapshots and vertical counters
|
||
|
||
`MOSAIC` at `0x0400004c` is write-only. Reads return zero as an open-bus placeholder.
|
||
Byte writes merge into its 16-bit latch. The unused upper halfword at `0x0400004e` reads zero and ignores writes.
|
||
CPU and DMA halfword/word writes use the same path.
|
||
|
||
| Bits | Dimension, stored as size minus one |
|
||
| --- | --- |
|
||
| 0–3 | Background horizontal size |
|
||
| 4–7 | Background vertical size |
|
||
| 8–11 | Sprite horizontal size |
|
||
| 12–15 | Sprite vertical size |
|
||
|
||
Each dimension ranges from one to sixteen pixels. Static zero fields give ordinary 1×1 sampling.
|
||
A mid-frame write of zero preserves any existing vertical phase until the counter next resets.
|
||
BGCNT bit 6 enables mosaic for an individual background. OBJ attribute 0 bit 12 enables sprite mosaic.
|
||
The register alone does not enable a layer's mosaic effect.
|
||
|
||
Debug snapshots align background blocks to the screen origin, not tile boundaries or scroll offsets.
|
||
Their upper-left samples enter text scrolling or affine mapping before palette lookup and composition.
|
||
Captured rows instead use live vertical phase; their horizontal blocks remain screen-aligned.
|
||
This applies to Modes 0–5, including both Mode 4/5 pages, transparent indices, and transformed bitmap edges.
|
||
Window masks use the actual output coordinate, so a window can cut through a mosaic block.
|
||
|
||
Sprite snapshots use the screen's mosaic row. Prepared sprite rows subtract the latched ahead-of-display counter from the local row.
|
||
Both paths clamp a partial first block to the sprite's first row.
|
||
Flips and affine transforms follow that row selection. Vertical mosaic does not enlarge the drawing area.
|
||
Horizontal sprite mosaic latches the selected object sample after object priority resolution.
|
||
The latch updates at a block boundary, at transitions to or from non-mosaic samples, or for a lower priority number.
|
||
Transparent samples participate through their priority and mosaic flags. Each scanline starts with an empty latch.
|
||
The latch runs before window masking and alpha blending; this is not a final-frame pixelation filter.
|
||
OBJ-window coverage ignores both mosaic dimensions. Transparent window texels can still affect object metadata.
|
||
|
||
Captured rows use one four-bit vertical counter for all backgrounds and another for sprites.
|
||
The background counter follows displayed rows. The OBJ counter follows the row being prepared, one row ahead.
|
||
At each applicable line end, a counter increments by one.
|
||
If the incremented value equals the current height, the counter resets to zero. Otherwise it wraps to four bits.
|
||
The background counter resets at entry to row160. The OBJ counter resets one displayed line earlier and stays zero through line227.
|
||
At frame wrap, OBJ advances to the phase for preparing row1; row0 has already retained phase0.
|
||
|
||
Size writes preserve the current counters. Shrinking below the current phase can delay the next block until four-bit wraparound.
|
||
For example, changing height8 to height2 at phase6 produces phases7 through15 before zero.
|
||
Text backgrounds then sample `screen_y - background_counter`, followed by current scroll and tile lookup.
|
||
Affine backgrounds instead retain their origin and add `height * PB/PD` when the counter becomes zero.
|
||
After that example's delayed reset, an affine origin advances by two rows, not sixteen.
|
||
|
||
Layer enable bits, per-layer mosaic flags, forced blank, and host capture enable do not pause or reset these counters.
|
||
Mosaic-disabled layers ignore the counters. OBJ-window coverage also ignores them.
|
||
Background VRAM samples at capture; sprite OAM/VRAM samples during preparation. Palette lookup remains current at composition.
|
||
Vertical mosaic does not copy a cached row of final RGB pixels.
|
||
Snapshots, presentation, failed batched writes, and zero-cycle advances do not change counter state.
|
||
Fixed-register clock batches remain constant-time, including frame wrap.
|
||
|
||
**Timing limit:** counter updates use line boundaries, and sprite preparation samples a whole row at cycle40.
|
||
Individual sprite fetches, per-access size latching, and horizontal counter timing remain unmodeled.
|
||
The selected counter rules follow reference implementations. They have not been verified here against GBA hardware test programs.
|
||
The mosaic demo shares the tile scene's ARM program builder and writes MOSAIC during each VBlank update.
|
||
|
||
### Window masks and color effects
|
||
|
||
| Registers | Addresses | Access and stored masks |
|
||
| --- | --- | --- |
|
||
| WIN0H, WIN1H | `0x04000040..0x04000043` | Write-only, eight-bit horizontal bounds |
|
||
| WIN0V, WIN1V | `0x04000044..0x04000047` | Write-only, eight-bit vertical bounds |
|
||
| WININ, WINOUT | `0x04000048..0x0400004b` | Read/write, `0x3f3f` |
|
||
| BLDCNT | `0x04000050..0x04000051` | Read/write, `0x3fff` |
|
||
| BLDALPHA | `0x04000052..0x04000053` | Read/write, `0x1f1f` |
|
||
| BLDY | `0x04000054..0x04000055` | Write-only, `0x001f` |
|
||
|
||
Byte writes merge into the stored halfwords. CPU and DMA writes use the same register path.
|
||
Write-only reads return zero, not hardware open-bus values. Unused bytes at `0x04000056..0x04000057` read zero and ignore writes.
|
||
MOSAIC at `0x0400004c` and its unused upper halfword are described above.
|
||
|
||
DISPCNT bits 13–15 enable WIN0, WIN1, and OBJ windows.
|
||
When no window is enabled, every globally enabled layer can display and color effects are permitted.
|
||
Otherwise WIN0 takes precedence over WIN1, then OBJ window, then the outside region.
|
||
Masks are selected by region; overlapping windows do not combine their masks.
|
||
Each region independently enables BG0–BG3, OBJ, and effects. A window cannot enable a globally disabled layer.
|
||
The palette backdrop remains visible when every layer is masked out.
|
||
|
||
Bounds store the start in the high byte and the excluded end in the low byte.
|
||
Debug snapshots use wrapping eight-bit intervals. Equal bounds select an empty interval.
|
||
Captured rows instead use persistent vertical flags for WIN0 and WIN1:
|
||
|
||
- At each scanline start, matching the top edge sets the flag; matching the bottom edge clears it.
|
||
- The bottom edge wins when both bounds match the same scanline.
|
||
- With no matching edge, the flag keeps its previous value, including across frame boundaries.
|
||
- Hidden rows 160–227 run the same comparisons. VCOUNT never reaches bounds 228–255.
|
||
- WIN0V/WIN1V writes change future comparisons. They do not replay an edge from an already-entered row.
|
||
- Window enable bits and forced blank affect composition, not vertical tracking.
|
||
|
||
Moving the top behind VCOUNT does not open an inactive window.
|
||
Moving the bottom behind VCOUNT can leave an active window open until a later matching edge.
|
||
Equal unreachable bounds can retain an active flag even though the debug snapshot shows an empty interval.
|
||
A wrapping window can differ on the first frame because its top edge has not occurred yet.
|
||
|
||
Both flags start clear. Host setup before the first positive clock advance participates in the initial line-zero comparison.
|
||
After that startup step, even a write at cycle zero cannot repeat that line's comparison.
|
||
Zero-cycle advances, snapshots, and capture toggles do not update the flags.
|
||
Tracking also runs with capture disabled. Fixed-register bulk advancement remains constant-time.
|
||
|
||
Captured rows also retain horizontal comparator history for each window:
|
||
|
||
- X runs from 0 through 255, with comparisons at line cycles 0, 4, …, 1020.
|
||
- Matching the left edge sets the flag. Matching the right edge clears it; right wins when both edges match.
|
||
- Visible columns 0–239 retain their flags for later row composition. Offscreen columns still update the persistent flag.
|
||
- Flags carry across line and frame boundaries. Hidden lines, disabled windows, and forced blank do not stop tracking.
|
||
- WIN0H/WIN1H writes affect future comparisons, not already-recorded columns.
|
||
|
||
Advancing clocks consumes the half-open interval from the current cycle to the ending cycle.
|
||
A write at cycle `4*x` therefore precedes X's comparison; a write at `4*x+1` is too late.
|
||
A zero-cycle advance does not perform a comparison.
|
||
Moving an edge behind the current column can miss it and retain the old flag into the following line.
|
||
Wrapping horizontal bounds can differ on the first row because no previous line has set the flag.
|
||
|
||
HBlank starts at cycle1006, after all visible columns have been recorded.
|
||
Columns252–255 occur later, so HBlank DMA can still set or cancel an offscreen edge before the next line.
|
||
Already-captured rows remain unchanged. The horizontal history records window coverage, not pixel colors.
|
||
|
||
Region masks, enable bits, effects, palette, and background VRAM remain current-row inputs sampled at HBlank.
|
||
Sprite OAM/VRAM and OBJ-window coverage come from the preceding line's preparation.
|
||
CPU/DMA writes still commit before their nominal cycle batch. Per-access bus timing and the LCD merge pipeline remain unimplemented.
|
||
This comparator model follows a reference implementation; it has not been verified here against GBA hardware test programs.
|
||
|
||
Horizontal history is retained even with capture disabled. Capture toggles and debug snapshots do not reset it.
|
||
Bulk clock advancement processes at most three line segments, regardless of the number of crossed frames.
|
||
This preserves constant-time advancement relative to emulated cycles, with fixed 240-column history buffers.
|
||
|
||
OBJ window coverage comes from nonzero sprite texels, regardless of their palette colors or background priorities.
|
||
Window sprites contribute no color pixels. Both DISPCNT bits 12 and 15 must be set for OBJ window coverage.
|
||
Normal sprites do not occlude window coverage, and the regional OBJ display bit does not disable the window mask.
|
||
|
||
The renderer selects the two frontmost unmasked, non-transparent layers before applying effects.
|
||
Alpha blending requires the top layer to be a first target and the immediate lower layer to be a second target.
|
||
A non-target layer between them prevents blending; the renderer does not search farther back for a target.
|
||
The backdrop can be a target but cannot blend with itself. Only one OBJ color pixel participates, so OBJ-to-OBJ blending is unavailable.
|
||
Coefficients are stored as five-bit values and clamped to 16 when used.
|
||
Each RGB555 channel uses `min(31, (first*EVA + second*EVB) / 16)`, with integer division after summing.
|
||
Brightness uses `color + (31-color)*EVY/16` or `color - color*EVY/16` on selected top-layer channels.
|
||
No layer receives brightness changes before it becomes a blend target.
|
||
|
||
Semi-transparent OBJ pixels request alpha blending regardless of BLDCNT's mode and first-target selection.
|
||
They still require an eligible second target and the region's effect-enable bit.
|
||
Without a second target, a selected brightness effect can apply instead.
|
||
Green swap runs after color effects. Forced blank bypasses all composition and effects.
|
||
|
||
The effects demo reuses the tile scene and ARM instruction builder.
|
||
Its CPU writes a 112×80 WIN0 rectangle, disables effects inside it, and enables effects outside it.
|
||
BLDY and both alpha coefficients are eight. Z selects BG0/OBJ as first targets and BG1 as the second target.
|
||
Three debug words at `0x02000000` retain the update count and nine-bit horizontal/vertical scroll.
|
||
The demo updates registers during VBlank. Vertical window state still depends on edges reached in previous rows and frames.
|
||
Moving its bottom beyond row227 can keep the unchanged-color region active at the next frame's top.
|
||
The smoke test tracks this state instead of assuming a geometric rectangle on every frame.
|
||
|
||
### CPU-driven tile and background demos
|
||
|
||
The tile demo copies 1 KiB palette and OAM images with `CpuSet`, and 96 KiB video data with `CpuFastSet`.
|
||
Its ARM program configures a 4-bit foreground, an 8-bit 512×512 terrain background, and one 16×16 sprite.
|
||
Unused sprite entries are explicitly disabled.
|
||
It installs a BIOS IRQ callback and uses `VBlankIntrWait` before updating scroll registers and OAM.
|
||
Z sets the regular sprite's horizontal-flip bit. X changes its background priority from zero to one.
|
||
Q and W select affine double-size mode and write matrix zero through real ARM halfword stores.
|
||
Q uses `[181, 181, -181, 181]` for a quantized 45-degree clockwise rotation.
|
||
W halves the coefficients. With Q held, the matrix becomes `[90, 90, -90, 90]`.
|
||
The demo moves the drawing-area origin from `(112, 72)` to `(104, 64)` to preserve the center at `(120, 80)`.
|
||
Releasing Q and W restores regular mode. Z has no effect while affine mode is active.
|
||
Opposite directions cancel. Enter resets both offsets. Offsets wrap to nine bits.
|
||
Three debug words at `0x02000000` hold the update count, horizontal scroll, and vertical scroll.
|
||
The tile, effects, mosaic, raster, bitmap, and affine background demos share row capture, bounded frame execution, and keyboard handling.
|
||
|
||
The Mode 4/5 and affine raster demos reuse the affine background demo's ARM instruction builder.
|
||
The affine raster program builds 160 halfwords at `0x02001000` during each VBlank.
|
||
It rearms DMA0 to copy one halfword to BG2PB at every visible HBlank.
|
||
Sixteen positive offsets alternate with sixteen negative offsets. Z uses the base transform without distortion.
|
||
The CPU writes all assets, tables, registers, and DMA configuration. The host only supplies buttons and presents captured frames.
|
||
Their CPU copies a 512-byte palette with `CpuSet` and an 80 KiB two-page video image with `CpuFastSet`.
|
||
During each VBlank update, ARM code stores the transform and selects the page from bit 5 of its update counter.
|
||
Z overrides that selection with page 1. Three debug words at `0x02000000` hold the update count and signed pan X/Y.
|
||
The host does not use the counter as a breakpoint or write DISPCNT.
|
||
|
||
The affine background demo uses Mode 2 BG2 with a 256×256 map and four original eight-bit tiles.
|
||
Its CPU copies a 512-byte palette with `CpuSet` and a 64 KiB video image with `CpuFastSet`.
|
||
Each update stores the matrix and computes the reference point as `(map center + pan)*256 - matrix*(screen center)`.
|
||
Q uses `[181, 181, -181, 181]`; W halves the coefficients. Z clears BGCNT's overflow bit.
|
||
The CPU uses `VBlankIntrWait` before each update. Three debug words at `0x02000000` hold update count and signed pan X/Y.
|
||
|
||
The graphics demo's ARM code initializes DISPCNT and configures DMA3 to fill all 38,400 background pixels under forced blank.
|
||
DMA3 reads a fixed halfword at `0x02000010`; the CPU supplies its color and waits while DMA fills video RAM.
|
||
ARM code then polls KEYINPUT and erases/redraws the square.
|
||
It clamps movement to the screen. Opposite directions cancel, including at screen edges.
|
||
The host supplies input and renders the result; it does not write the demo's pixels or position.
|
||
The ARM program installs a ROM-side IRQ callback and enables VBlank requests in DISPSTAT and IE.
|
||
Before each redraw, it invokes `SWI 0x050000` for VBlankIntrWait.
|
||
The service discards the old BIOS flag and uses HALT while waiting for the next VBlank request.
|
||
After wake-up, IRQ dispatch calls the ROM handler to acknowledge IF and record the BIOS flag.
|
||
The service consumes that flag and restores the caller's status before ARM code redraws during VBlank.
|
||
It stores an update counter, x, y, and color at four words starting at `0x02000000`, for tests and inspection only.
|
||
The host no longer uses this counter as a breakpoint.
|
||
|
||
The first call runs background initialization under forced blank, waits for Mode 3 to become active, and primes the VBlank wait.
|
||
The host then runs between consecutive VBlank-entry events and presents the captured image from the preceding visible period.
|
||
A 200,000-machine-step limit bounds startup and frame execution, including DMA units and HALT idle intervals.
|
||
Errors retain completed steps but do not replace the output image.
|
||
Normal demo calls span one display frame and stop at the exact VBlank edge during HALT.
|
||
Other programs can still overshoot by an instruction or DMA-unit cost.
|
||
The graphics window limits presentation to approximately 59.73 Hz using the GBA clock ratio.
|
||
Host sleeps do not advance emulated time. Slow hosts do not skip emulated frames.
|
||
The original host-generated display test keeps its independent 60 Hz cap.
|