-
-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add "rendering overview" document #501
Changes from 7 commits
11b6276
2512bdf
e7a736d
b716279
5bd1b38
4316faa
eed64bb
07574c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Graphics Overview | ||
|
||
The Game Boy outputs graphics to a 160×144 pixel LCD, using a quite complex | ||
mechanism to facilitate rendering. | ||
|
||
::: warning Terminology | ||
|
||
Sprites/graphics terminology can vary a lot among different platforms, consoles, | ||
users and communities. You may be familiar with slightly different definitions. | ||
Keep also in mind that some definitions refer to lower (hardware) tools | ||
and some others to higher abstractions concepts. | ||
|
||
::: | ||
|
||
## Tiles | ||
|
||
Similarly to other retro systems, pixels are not manipulated | ||
individually, as this would be expensive CPU-wise. Instead, pixels are grouped | ||
in 8×8 squares, called _tiles_ (or sometimes "patterns" or "characters"), often considered as | ||
the base unit in Game Boy graphics. | ||
|
||
A tile does not encode color information. Instead, a tile assigns a | ||
_color ID_ to each of its pixels, ranging from 0 to 3. For this reason, | ||
Game Boy graphics are also called _2bpp_ (2 bits per pixel). When a tile is used | ||
in the Background or Window, these color IDs are associated with a _palette_. When | ||
a tile is used in an object, the IDs 1 to 3 are associated with a palette, but | ||
ID 0 means transparent. | ||
|
||
## Palettes | ||
|
||
A palette consists of an array of colors, 4 in the Game Boy's case. | ||
Palettes are stored differently in monochrome and color versions of the console. | ||
|
||
Modifying palettes enables graphical effects such as quickly flashing some graphics (damage, | ||
invulnerability, thunderstorm, etc.), fading the screen, "palette swaps", and more. | ||
|
||
## Layers | ||
|
||
The Game Boy has three "layers", from back to front: the Background, the Window, | ||
and the Objects. Some features and behaviors break this abstraction, | ||
but it works for the most part. | ||
|
||
### Background | ||
|
||
The background is composed of a _tilemap_. A tilemap is a | ||
large grid of tiles. However, tiles aren't directly written to tilemaps, | ||
they merely contain references to the tiles. | ||
This makes reusing tiles very cheap, both in CPU time and in | ||
required memory space, and it is the main mechanism that helps work around the | ||
paltry 8 KiB of video RAM. | ||
|
||
The background can be made to scroll as a whole, writing to two | ||
hardware registers. This makes scrolling very cheap. | ||
|
||
### Window | ||
|
||
The window is sort of a second background layer on top of the background. | ||
It is fairly limited: it has no transparency, it's always a | ||
rectangle and only the position of the top-left pixel can be controlled. | ||
|
||
Possible usage include a fixed status bar in an otherwise scrolling game (e.g. | ||
_Super Mario Land 2_). | ||
|
||
### Objects | ||
|
||
The background layer is useful for elements scrolling as a whole, but | ||
it's impractical for objects that need to move separately, such as the player. | ||
|
||
The _objects_ layer is designed to fill this gap: _objects_ are made of 1 or 2 stacked tiles (8×8 or 8×16 pixels) | ||
and can be displayed anywhere on the screen. | ||
|
||
::: tip NOTE | ||
|
||
Several objects can be combined (they can be called _metasprites_) to draw | ||
a larger graphical element, usually called "sprite". Originally, the term "sprites" | ||
referred to fixed-sized objects composited together, by hardware, with a background. | ||
Use of the term has since become more general. | ||
|
||
::: | ||
|
||
To summarise: | ||
|
||
- **Tile**, an 8×8-pixel chunk of graphics. | ||
- **Object**, an entry in object attribute memory, composed of 1 or 2 | ||
tiles. Can be moved independently of the background. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,72 @@ | ||
# Rendering Overview | ||
# Rendering overview | ||
|
||
The Game Boy outputs graphics to a 160×144 pixel LCD, using a quite complex | ||
mechanism to facilitate rendering. | ||
## Terminology | ||
|
||
::: warning Terminology | ||
The entire frame is not drawn atomically; instead, the image is drawn by the **<abbr>PPU</abbr>** (Pixel-Processing Unit) progressively, **directly to the screen**. | ||
A frame consists of 154 **scanlines**; during the first 144, the screen is drawn top to bottom, left to right. | ||
|
||
Sprites/graphics terminology can vary a lot among different platforms, consoles, | ||
users and communities. You may be familiar with slightly different definitions. | ||
Keep also in mind that some definitions refer to lower (hardware) tools | ||
and some others to higher abstractions concepts. | ||
The main implication of this rendering process is the existence of **raster effects**: modifying some rendering parameters in the middle of rendering. | ||
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). | ||
|
||
::: | ||
A "**dot**" = one 2<sup>22</sup> Hz (≅ 4.194 MHz) time unit. | ||
Dots remain the same regardless of whether the CPU is in [double speed](<#FF4D — KEY1 (CGB Mode only): Prepare speed switch>), so there are 4 dots per single-speed CPU cycle, and 2 per double-speed CPU cycle. | ||
|
||
## Tiles | ||
::: tip | ||
|
||
Similarly to other retro systems, pixels are not manipulated | ||
individually, as this would be expensive CPU-wise. Instead, pixels are grouped | ||
in 8×8 squares, called _tiles_ (or sometimes "patterns" or "characters"), often considered as | ||
the base unit in Game Boy graphics. | ||
Note that a frame is not exactly one 60<sup>th</sup> of a second: the Game Boy runs slightly slower than 60 Hz, as one frame takes ~16.74 ms instead of ~16.67 (the error is 0.45%). | ||
|
||
A tile does not encode color information. Instead, a tile assigns a | ||
_color ID_ to each of its pixels, ranging from 0 to 3. For this reason, | ||
Game Boy graphics are also called _2bpp_ (2 bits per pixel). When a tile is used | ||
in the Background or Window, these color IDs are associated with a _palette_. When | ||
a tile is used in an object, the IDs 1 to 3 are associated with a palette, but | ||
ID 0 means transparent. | ||
::: | ||
|
||
## Palettes | ||
## PPU modes | ||
|
||
A palette consists of an array of colors, 4 in the Game Boy's case. | ||
Palettes are stored differently in monochrome and color versions of the console. | ||
<figure><figcaption> | ||
|
||
Modifying palettes enables graphical effects such as quickly flashing some graphics (damage, | ||
invulnerability, thunderstorm, etc.), fading the screen, "palette swaps", and more. | ||
During a frame, the Game Boy's PPU cycles between four modes as follows: | ||
|
||
## Layers | ||
</figcaption> | ||
|
||
The Game Boy has three "layers", from back to front: the Background, the Window, | ||
and the Objects. Some features and behaviors break this abstraction, | ||
but it works for the most part. | ||
{{#include imgs/ppu_modes_timing.svg:2:}} | ||
|
||
### Background | ||
</figure> | ||
|
||
The background is composed of a _tilemap_. A tilemap is a | ||
large grid of tiles. However, tiles aren't directly written to tilemaps, | ||
they merely contain references to the tiles. | ||
This makes reusing tiles very cheap, both in CPU time and in | ||
required memory space, and it is the main mechanism that helps work around the | ||
paltry 8 KiB of video RAM. | ||
While the PPU is accessing some video-related memory, [that memory is inaccessible to the CPU](<#Accessing VRAM and OAM>) (writes are ignored, and reads return garbage values, usually $FF). | ||
|
||
The background can be made to scroll as a whole, writing to two | ||
hardware registers. This makes scrolling very cheap. | ||
Mode | Action | Duration | Accessible video memory | ||
----:|--------------------------------------------|--------------------------------------|------------------------- | ||
2 | Searching for OBJs which overlap this line | 80 dots | VRAM, CGB palettes | ||
3 | Sending pixels to the LCD | Between 172 and 289 dots, see below | None | ||
0 | Waiting until the end of the scanline | 376 - mode 3's duration | VRAM, OAM, CGB palettes | ||
1 | Waiting until the next frame | 4560 dots (10 scanlines) | VRAM, OAM, CGB palettes | ||
|
||
### Window | ||
## Mode 3 length | ||
|
||
The window is sort of a second background layer on top of the background. | ||
It is fairly limited: it has no transparency, it's always a | ||
rectangle and only the position of the top-left pixel can be controlled. | ||
During Mode 3, by default the PPU outputs one pixel to the screen per dot, from left to right; the screen is 160 pixels wide, so the minimum Mode 3 length is 160 + 12[^first12] = 172 dots. | ||
|
||
Possible usage include a fixed status bar in an otherwise scrolling game (e.g. | ||
_Super Mario Land 2_). | ||
Unlike most game consoles, the Game Boy does not always output pixels steadily[^crt]: some features cause the rendering process to stall for a couple dots. | ||
Any extra time spent stalling *lengthens* Mode 3; but since scanlines last for a fixed number of dots, Mode 0 is therefore shortened by that same amount of time. | ||
|
||
### Objects | ||
Three things can cause Mode 3 "penalties": | ||
|
||
The background layer is useful for elements scrolling as a whole, but | ||
it's impractical for objects that need to move separately, such as the player. | ||
- **Background scrolling**: At the very beginning of Mode 3, rendering is paused for [`SCX`](<#FF42–FF43 — SCY, SCX: Viewport Y position, X position>) % 8 dots while the same number of pixels are discarded from the leftmost tile. | ||
- **Window**: After the last non-window pixel is emitted, a 6-dot penalty is incurred while the BG fetcher is being set up for the window. | ||
- **Objects**: Each object drawn during the scanline (even partially) incurs a 6- to 11-dot penalty ([see below](<#OBJ penalty algorithm>)). | ||
|
||
The _objects_ layer is designed to fill this gap: _objects_ are made of 1 or 2 stacked tiles (8×8 or 8×16 pixels) | ||
and can be displayed anywhere on the screen. | ||
On DMG and GBC in DMG mode, mid-scanline writes to [`BGP`](<#FF47 — BGP (Non-CGB Mode only): BG palette data>) allow observing this behavior precisely, as any delay shifts the write's effect to the left by that many dots. | ||
|
||
::: tip NOTE | ||
### OBJ penalty algorithm | ||
|
||
Several objects can be combined (they can be called _metasprites_) to draw | ||
a larger graphical element, usually called "sprite". Originally, the term "sprites" | ||
referred to fixed-sized objects composited together, by hardware, with a background. | ||
Use of the term has since become more general. | ||
Only the OBJ's leftmost pixel matters here, transparent or not; it is designated as "The Pixel" in the following. | ||
|
||
1. Determine the tile (background or window) that The Pixel is within. (This is affected by horizontal scrolling and/or the window!) | ||
2. If that tile has **not** been considered by a previous OBJ yet: | ||
1. Count how many of that tile's pixels are to the right of The Pixel. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The wording "to the right" sounds exclusive, but I believe the logic here is inclusive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The subtraction below should have been adjusted to account for that. Is it correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's not, because then the maximum is 7 pixels; minus 3 = 4; plus the "flat" 6 = 40, but the maximum stated above is 11. So probably that the line below should be "Subtract 2." instead? |
||
2. Subtract 3. | ||
3. Incur this many dots of penalty, or zero if negative (from waiting for the BG fetch to finish). | ||
3. Incur a flat, 6-dot penalty (from fetching the OBJ's tile). | ||
|
||
**Exception**: an OBJ with an OAM X position of 0 (thus, completely off the left side of the screen) always incurs a 11-cycle penalty, regardless of `SCX`. | ||
|
||
::: | ||
|
||
To summarise: | ||
[^first12]: The 12 extra cycles come from two tile fetches at the beginning of Mode 3. One is the first tile in the scanline (the one that gets shifted by `SCX` % 8 pixels), the other is simply discarded. | ||
|
||
- **Tile**, an 8×8-pixel chunk of graphics. | ||
- **Object**, an entry in object attribute memory, composed of 1 or 2 | ||
tiles. Can be moved independently of the background. | ||
[^crt]: The Game Boy can afford to "take pauses", because it writes to a LCD it fully controls; by contrast, home consoles like the NES or SNES are on a schedule imposed by the screen they are hooked up to. Taking pauses arguably simplified the PPU's design while allowing greater flexibility to game developers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe the OBJ rendering order has been discussed at this point, so "previous OBJ" may be confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a footnote attached to "previous"?