Skip to content

Commit 584131b

Browse files
committed
Document more of the Window
1 parent 4a03724 commit 584131b

File tree

8 files changed

+50
-33
lines changed

8 files changed

+50
-33
lines changed

src/Interrupts.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- **`ei`**: Enables interrupt handling (that is, `IME := 1`)
99
- **`di`**: Disables interrupt handling (that is, `IME := 0`)
1010
- **`reti`**: Enables interrupts and returns (same as `ei` immediately followed by `ret`)
11-
- **When an [interrupt handler](<#Interrupt Handling>) is executed**: Disables interrupts before `call`ing the interrupt handler
11+
- **When an [interrupt handler](<#Interrupt handling>) is executed**: Disables interrupts before `call`ing the interrupt handler
1212

1313
`IME` is unset (interrupts are disabled) [when the game starts running](<#0100-0103 — Entry point>).
1414

@@ -53,7 +53,7 @@ may still do that in order to manually request (or discard) interrupts.
5353
Just like real interrupts, a manually requested interrupt isn't serviced
5454
unless/until `IME` and `IE` allow it.
5555

56-
## Interrupt Handling
56+
## Interrupt handling
5757

5858
1. The `IF` bit corresponding to this interrupt and the `IME` flag are reset by the CPU.
5959
The former "acknowledges" the interrupt, while the latter prevents any further interrupts
@@ -71,7 +71,7 @@ This consumes one last M-cycle.
7171

7272
The entire process [lasts 5 M-cycles](https://gist.github.com/SonoSooS/c0055300670d678b5ae8433e20bea595#user-content-isr-and-nmi).
7373

74-
## Interrupt Priorities
74+
## Interrupt priorities
7575

7676
In the following circumstances it is possible that more than one bit in the IF register is set, requesting more than one interrupt at once:
7777

@@ -85,7 +85,7 @@ is serviced first. The priorities follow the order of the bits in the IE
8585
and IF registers: Bit 0 (VBlank) has the highest priority, and Bit 4
8686
(Joypad) has the lowest priority.
8787

88-
## Nested Interrupts
88+
## Nested interrupt handling
8989

9090
The CPU automatically disables all the other interrupts by setting IME=0
9191
when it services an interrupt. Usually IME remains zero until the

src/Memory_Map.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ $FF04 | $FF07 | DMG | [Timer and divider](<#Timer and Divider Reg
2929
$FF0F | | DMG | [Interrupts](<#FF0F — IF: Interrupt flag>)
3030
$FF10 | $FF26 | DMG | [Audio](<#Audio Registers>)
3131
$FF30 | $FF3F | DMG | [Wave pattern](<#FF30–FF3F — Wave pattern RAM>)
32-
$FF40 | $FF4B | DMG | LCD [Control](<#FF40 — LCDC: LCD control>), [Status](<#FF41 — STAT: LCD status>), [Position, Scrolling](<#LCD Position and Scrolling>), and [Palettes](<#Palettes>)
32+
$FF40 | $FF4B | DMG | LCD [Control](<#FF40 — LCDC: LCD control>), [Status](<#FF41 — STAT: LCD status>), [Position, Scrolling](<#Viewport position (Scrolling)>), and [Palettes](<#Palettes>)
3333
$FF4F | | CGB | [VRAM Bank Select](<#FF4F — VBK (CGB Mode only): VRAM bank>)
3434
$FF50 | | DMG | [Boot ROM mapping control](<#Power-Up Sequence>)
3535
$FF51 | $FF55 | CGB | [VRAM DMA](<#LCD VRAM DMA Transfers>)

src/Rendering.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The entire frame is not drawn atomically; instead, the image is drawn by the **<
66
A frame consists of 154 **scanlines**; during the first 144, the screen is drawn top to bottom, left to right.
77

88
The main implication of this rendering process is the existence of **raster effects**: modifying some rendering parameters in the middle of rendering.
9-
The most famous raster effect is modifying the [scrolling registers](<#LCD Position and Scrolling>) between scanlines to create a ["wavy" effect](https://gbdev.io/guides/deadcscroll#effects).
9+
The most famous raster effect is modifying the [scrolling registers](<#Viewport position (Scrolling)>) between scanlines to create a ["wavy" effect](https://gbdev.io/guides/deadcscroll#effects).
1010

1111
A "**dot**" = one 2<sup>22</sup> Hz (≅ 4.194 MHz) time unit.
1212
Dots remain the same regardless of whether the CPU is in [Double Speed mode](<#FF4D — KEY1/SPD (CGB Mode only): Prepare speed switch>), so there are 4 dots per Normal Speed M-cycle, and 2 per Double Speed M-cycle.

src/Scrolling.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Viewport Position (Scrolling)
1+
# Viewport position (Scrolling)
22

33
These registers can be accessed even during Mode 3, but modifications may not take
44
effect immediately (see further below).
@@ -23,6 +23,6 @@ Example from the homebrew game *Mindy's Hike*:
2323

2424
## Mid-frame behavior
2525

26-
The scroll registers are re-read on each [tile fetch](<#Get Tile>), except for the low 3 bits of SCX, which are only read at the beginning of the scanline (for the initial shifting of pixels).
26+
The scroll registers are re-read on each [tile fetch](<#Get Tile>), except for the low 3 bits of `SCX`, which are only read at the beginning of the scanline (for the initial shifting of pixels).
2727

28-
All models before the CGB-D read the Y coordinate once for each bitplane (so a very precisely timed SCY write allows "desyncing" them), but CGB-D and later use the same Y coordinate for both no matter what.
28+
All models before the CGB-D read the Y coordinate once for each bitplane (so a very precisely timed `SCY` write allows "desyncing" them), but CGB-D and later use the same Y coordinate for both no matter what.

src/Tile_Maps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ for the definition of "Window visibility".)
106106

107107
:::tip Window Internal Line Counter
108108

109-
The window keeps an internal line counter that's functionally similar to `LY`, and increments alongside it. However, it only gets incremented when the window is visible, as described [here](<#FF4A–FF4B — WY, WX: Window Y position, X position plus 7>).
109+
The window keeps an internal line counter that's functionally similar to `LY`, and increments alongside it. However, it only gets incremented when the window is visible, as described [here](<#Window rendering criteria>).
110110

111111
This line counter determines what window line is to be rendered on the current scanline.
112112

src/Window.md

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,54 @@
1-
# Window
1+
# Window behavior
22

33
## FF4A–FF4B — WY, WX: Window Y position, X position plus 7
44

5-
These two registers specify the on-screen coordinates of [the Window](#Window)'s top-left pixel.
5+
These two registers specify the on-screen coordinates of [the Window]'s top-left pixel.
66

7-
The Window is visible (if enabled) when both coordinates are in the ranges
8-
WX=0..166, WY=0..143 respectively. Values WX=7, WY=0 place the Window at the
9-
top left of the screen, completely covering the background.
7+
The Window is visible (if enabled) when `WX` and `WY` are in the range \[0; 166\] and \[0; 143\] respectively.
8+
Values `WX`=7, `WY`=0 place the Window at the top left of the screen, completely covering the background.
109

11-
:::warning Warning
10+
## Window mid-frame behavior
11+
12+
While the Window should work as just mentioned, writing to `WX`, `WY` etc. mid-frame displays more articulated behavior.
13+
There are several aspects of the window that respond differently to various mid-frame interactions; the **tl;dr** is this:
14+
15+
- For the least glitchy results, only write to `WX`, `WY`, and `LCDC` during VBlank (possibly in your [VBlank interrupt handler]); if mid-frame writes are required, prefer writing during HBlank.
16+
- If intending to hide the Window for part of the screen (e.g. to have a status bar at the *top* of the screen instead of the bottom), hide it by setting `WX` to a high value rather than writing to `LCDC`.
17+
18+
### Window rendering criteria
1219

13-
WX values 0 and 166 are unreliable due to hardware bugs.
20+
The PPU keeps track of a “**Y condition**” throughout a frame.
1421

15-
If WX is set to 0, the window will "stutter" horizontally when SCX changes
16-
(depending on SCX % 8).
22+
- On each VBlank, the *Y condition* is cleared (becomes false).
23+
- At the beginning of each scanline, if the value of `WY` is equal to [`LY`], the *Y condition* becomes true (and remains so for subsequent scanlines).
1724

18-
If WX is set to 166, the window will span the entirety of the following
19-
scanline.
25+
:::tip Erratum
26+
27+
On GBC, clearing the [Window enable bit] in `LCDC` resets the *Y condition*; `WY` must be set to `LY` or greater for the Window to display again in the current frame.
2028

2129
:::
2230

23-
## Window mid-frame behavior
31+
Additionally, the PPU maintains a counter, initialized to 0 at the beginning of each scanline.
32+
The counter is incremented for each pixel rendered; however, it also increments 7 times before the first pixel is actually rendered (this covers pixels discarded during the initial “fine scroll” adjustment).
33+
34+
When this counter is equal to `WX`, if the *Y condition* is true and the [Window enable bit] is set in `LCDC`, background rendering is reset, beginning anew from the active row of the Window's tilemap.
35+
The coordinate of the active Window row is then incremented.
2436

25-
While the Window should work as just mentioned, writing to WX, WY etc. mid-frame shows a more articulated behavior.
37+
- This process can happen more than once per scanline, making the Window's “tilemap Y coordinate” increase more than once in the scanline.
38+
(This is demonstrated by the TODO test ROM.)
2639

27-
For the window to be displayed on a scanline, the following conditions must be met:
40+
However, this requires “disabling” the Window by briefly clearing its enable bit from `LCDC` first.
41+
- If this process doesn't happen, the Window's “tilemap Y coordinate” does not increase; so, if the Window is hidden (by any means) on a given scanline, the row of pixels rendered the next time it's shown will be the same as if it had not been hidden in the first place, producing a sort of vertical striped stretching:
2842

29-
- **WY condition was triggered**: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)
30-
- **WX condition was triggered**: i.e. the current X coordinate being rendered + 7 was equal to WX
31-
- Window enable bit in LCDC is set
43+
![Visual demonstration](https://github.com/mattcurrie/mealybug-tearoom-tests/raw/master/expected/DMG-blob/m2_win_en_toggle.png?raw=true)
44+
- If `WX` is equal to 0, the Window is switched to before the initial “fine scroll” adjustment, causing it to be shifted left by <math><mi>SCX</mi> <mo>%</mo> <mn>8</mn></math> pixels.
45+
- On monochrome systems, `WX` = 166 (which would normally show a single Window pixel, along the right edge of the screen) exhibits a bug: the Window spans the entire screen, but offset vertically by one scanline.
46+
- On monochrome systems, if the Window is disabled via `LCDC`, but the other conditions are met *and* it would have started rendering exactly on a BG tile boundary, then where it would have started rendering, a single pixel with ID 0 (i.e. drawn as the first entry in [the BG palette]) is inserted; this offsets the remainder of the scanline.[^star_trek]
3247

33-
If the WY condition has already been triggered and at the start of a row the window enable bit was set,
34-
then resetting that bit before the WX condition gets triggered on that row yields a nice window glitch pixel where the window would have been activated.
48+
[^star_trek]: This was discovered as affecting the game *Star Trek 25th anniversary*; more information and a test ROM are available [in this thread](https://github.com/LIJI32/SameBoy/issues/278#issuecomment-1189712129).
3549

36-
The way the Window selects which line of its tilemap to render may be surprising: the Y position is selected by an internal counter, which is reset to 0 during VBlank and **only** incremented when the Window starts being rendered on a given scanline.
37-
In particular, this means that hiding the Window mid-frame in any way (via either `WX` or `LCDC`, usually to display a status bar at the top *and* bottom of the screen) will also inhibit incrementing that Y-position counter.
50+
[the Window]: #Window
51+
[VBlank interrupt handler]: <#INT $40 — VBlank interrupt>
52+
[Window enable bit]: <#LCDC.5 — Window enable>
53+
[`LY`]: <#FF44 — LY: LCD Y coordinate \[read-only\]>
54+
[the BG palette]: <#FF47 — BGP (Non-CGB Mode only): BG palette data>

src/halt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and [`IF`](<#FF0F — IF: Interrupt flag>) is non-zero.
77

88
Most commonly, [`IME`](<#IME: Interrupt master enable flag \[write only\]>) is
99
set. In this case, the CPU simply wakes up, and before executing the instruction
10-
after the `halt`, the [interrupt handler is called](<#Interrupt Handling>)
10+
after the `halt`, the [interrupt handler is called](<#Interrupt handling>)
1111
normally.
1212

1313
If `IME` is *not* set, there are two distinct cases, depending on whether an

src/othermbc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ the mapper consists of a single standard 74 series logic chip, it has
5555
two unusual properties:
5656

5757
First, unlike a usual MBC, it switches the whole 32 KiB ROM area instead
58-
of just the $4000-$7FFF area. Therefore, if you want to use [the interrupt vectors](<#Interrupt Handling>)
58+
of just the $4000-$7FFF area. Therefore, if you want to use [the interrupt vectors](<#Interrupt handling>)
5959
with this cart, you should duplicate them across all banks.
6060
Additionally, since the 74LS377's contents can't be guaranteed when powering on,
6161
the ROM header and some code for switching to a known bank should also

0 commit comments

Comments
 (0)