From 23df373af5e2b89db5a21ed6e634a6597534fd36 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Sat, 1 Jul 2023 16:40:45 +0200 Subject: [PATCH] Convert some more bit tables Recovered from a very old stash, hopefully I "rebased" correctly. Since this is changing the structure of the bit explanations, some modifications to the content have been made as well. --- DEPLOY.md | 28 +++++++ custom/style.css | 10 +++ preproc/src/preproc.rs | 166 ++++++++++++++++++++++++++++++++--------- src/Audio_Registers.md | 30 ++++---- src/LCDC.md | 28 +++---- src/OAM.md | 10 +-- src/Palettes.md | 49 ++++++------ src/STAT.md | 31 +++----- src/Tile_Data.md | 13 ++-- src/Tile_Maps.md | 23 +++--- 10 files changed, 253 insertions(+), 135 deletions(-) diff --git a/DEPLOY.md b/DEPLOY.md index 6857e6a8..ba20246e 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -130,6 +130,34 @@ Note that the angle brackets [are only required if there are spaces in the URL]( In effect, this means that linking to a section is as simple as copy-pasting its name in the URL field, prepending a `#`, and wrapping everything in `<>` if the name contains a space. +### Bit descriptions + +```markdown +{{#bits 8 > SB 7-0:"Serial data"; SC 7:"Transfer start" 1:"Clock speed" 0:"Clock source"}} +``` + +Pan Docs describes a lot of hardware registers, and [it has been agreed upon that tables are the best format for this](https://github.com/gbdev/pandocs/issues/318). +However, the best formatting requires `colspan`, which requires HTML tables, which are quite tedious to write; hence, a shorthand syntax was developed. +This is typically used for bit descriptions (hence the name), but is generic enough to work e.g. for byte descriptions as well. + +The first argument is the number of columns (not counting the title one). + +The second argument is the direction of the indices: `<` for increasing, `>` for decreasing. +Decreasing is preferred for bit descriptions, and increasing for byte descriptions. + +The following arguments can be repeated for as many rows as desired, separated by semicolons `;`: +- One argument (which may not start with a digit) names the row; if exactly "\_", it will be ignored. +- Any amount of arguments (even zero) name the individual fields, which must be ordered as in the example. Fields may span several bits, as shown above. + +Note: these are usually followed by more detailed descriptions of the fields. +The format of those is documented [in the style guide](https://github.com/gbdev/pandocs/wiki/Document-Style#ANCHOR_FOR_ADDITION_BELOW). + +\[THIS WILL NOT BE ADDED TO THE PR, BUT TO THE STYLE GUIDE ON THE WIKI. IT'S MERELY HERE FOR REVIEW.\] + +The format of bit description lists is as follows: `- **Field name** [*Additional notes*] (*Read/Write*): Description`. +Additional notes are, for example, to note CGB exclusivity; they are not required. +The "Read/Write" part may be omitted if all fields within the byte are readable and writable; otherwise, it must be indicated for all fields, and both words must be fully spelled out, or spelled exactly "Read-only"/"Write-only". + ## Syntax highlighting Syntax highlighting is provided within the browser, courtesy of [`highlight.js`](https://github.com/highlightjs/highlight.js). diff --git a/custom/style.css b/custom/style.css index 3052e518..ad2c7380 100644 --- a/custom/style.css +++ b/custom/style.css @@ -150,3 +150,13 @@ math[display="block"], mfrac { mfrac msup :not(:first-child) { font-size: 1.8rem; } + + +table.bit-descrs th { + padding: 3px 10px; +} + +/* mdBook aligns the first column, but this is not desirable for those tables */ +table.bit-descrs.nameless td:first-child { + text-align: center; +} diff --git a/preproc/src/preproc.rs b/preproc/src/preproc.rs index 7117af6b..39a76a41 100644 --- a/preproc/src/preproc.rs +++ b/preproc/src/preproc.rs @@ -14,8 +14,10 @@ use mdbook::errors::Error; use mdbook::preprocess::{Preprocessor, PreprocessorContext}; use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; use regex::Regex; +use std::borrow::Cow; use std::collections::HashMap; use std::io::Write; +use std::iter; use std::process::{Command, Stdio}; use std::str; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; @@ -325,30 +327,52 @@ impl Pandocs { let mut replaced = String::with_capacity(chapter.content.len()); for result in find_bit_descrs(&chapter.content) { - let (start, end, attrs) = result?; + let (cap_start, cap_end, attrs) = result?; - replaced.push_str(&chapter.content[previous_end_index..start]); - replaced.push_str(""); - for i in (0..attrs.width).rev() { + replaced.push_str(&chapter.content[previous_end_index..cap_start]); + + let (start, end) = if attrs.increasing { + (0, attrs.width - 1) + } else { + (attrs.width - 1, 0) + }; + + // Generate the table head + if !attrs.rows[0].0.is_empty() { + // If names are present, add an empty cell for the name column + replaced.push_str("
"); + } else { + // Otherwise, add a class to force correct styling of first column + replaced.push_str("
"); + } + // Start at `start`, and step each time + for i in iter::successors(Some(start), |i| { + (*i != end).then(|| if attrs.increasing { i + 1 } else { i - 1 }) + }) { replaced.push_str(&format!("", i)); } replaced.push_str(""); for (name, row) in &attrs.rows { - replaced.push_str(&format!("", name)); - let mut pos = attrs.width; + replaced.push_str(""); + // If a name is present, add it + if !name.is_empty() { + replaced.push_str(&format!("", name)); + } + let mut pos = 0; let mut fields = row.iter().peekable(); - while pos != 0 { - let (start, unused, name) = match fields.peek() { + while pos < attrs.width { + let (len, is_unused, name) = match fields.peek() { // If we are at the edge of a "used" field, use it - Some(field) if field.end == pos - 1 => (field.start, false, field.name), + Some(field) if field.start == pos => (field.len, false, field.name), // If in an unused field, end at the next field, or the width if none such - res => (res.map_or(0, |field| field.end + 1), true, ""), + res => (res.map_or(attrs.width, |field| field.start) - pos, true, ""), }; + replaced.push_str(&format!( "", - pos - start, - if unused { + len, + if is_unused { " class=\"unused-field\"" } else { "" @@ -356,16 +380,16 @@ impl Pandocs { name )); - if !unused { + if !is_unused { fields.next(); } - pos = start; + pos += len; } replaced.push_str(""); } replaced.push_str("
{}
{}
{}{}
"); - previous_end_index = end; + previous_end_index = cap_end; } replaced.push_str(&chapter.content[previous_end_index..]); @@ -394,10 +418,12 @@ fn find_bit_descrs( .map(|caps| { // Must use `.get()`, as indexing ties the returned value's lifetime to `caps`'s. let contents = caps.get(1).unwrap().as_str(); - BitDescrAttrs::from_str(contents).map(|attrs| { - let all = caps.get(0).unwrap(); // There is always a 0th capture. - (all.start(), all.end(), attrs) - }) + BitDescrAttrs::from_str(contents) + .map(|attrs| { + let all = caps.get(0).unwrap(); // There is always a 0th capture. + (all.start(), all.end(), attrs) + }) + .context(format!("Failed to parse \"{contents}\"")) }) } @@ -405,6 +431,7 @@ fn find_bit_descrs( struct BitDescrAttrs<'input> { width: usize, rows: Vec<(&'input str, Vec>)>, + increasing: bool, } impl<'input> BitDescrAttrs<'input> { @@ -421,8 +448,34 @@ impl<'input> BitDescrAttrs<'input> { ))?; let s = contents[width_len..].trim_start(); + // Then, parse the direction + let mut chars = s.chars(); + // Angle brackets have a tendency to get escaped, so account for that + let (base_len, next) = match chars.next() { + Some('\\') => ('\\'.len_utf8(), chars.next()), + c => (0, c), + }; + let increasing = match next { + Some('<') => true, + Some('>') => false, + c => { + bail!( + "Expected width to be followed by '<' or '>' for direction, found {}", + c.map_or(Cow::from("nothing"), |c| format!( + "'{}'", + &s[..base_len + c.len_utf8()] + ) + .into()), + ); + } + }; + debug_assert_eq!('<'.len_utf8(), 1); + debug_assert_eq!('>'.len_utf8(), 1); + let s = s[base_len + 1..].trim_start(); + // Next, parse the rows! let mut rows = Vec::new(); + let mut name_type = None; for row_str in s.split_terminator(';') { let row_str = row_str.trim(); @@ -452,45 +505,86 @@ impl<'input> BitDescrAttrs<'input> { let Some(cap) = RE.captures(row_str) else { bail!("Failed to parse field for \"{}\"", row_str); }; - let end = cap[1].parse().unwrap(); - let start = cap + let left: usize = cap[1].parse().unwrap(); + let right = cap .get(2) - .map_or(end, |end_match| end_match.as_str().parse().unwrap()); + .map_or(left, |end_match| end_match.as_str().parse().unwrap()); let name = &cap.get(3).unwrap().as_str(); - // Perform sanity checks. - if start > end { - bail!( - "Field must end after it started (expected {} <= {})", - start, - end, - ); + // Perform some sanity checks. + let Some((mut start, len)) = if increasing { + right.checked_sub(left) + } else { + left.checked_sub(right) } + .map(|len| (left, len + 1)) else { + bail!( + "Field must end after it started ({}-{})", + left, right + )}; + if let Some(field) = fields.last() { - if field.end <= start { + if !increasing { + // Cancel the massaging to get back what was input + let prev_end = width - field.end(); + + if prev_end < start { + bail!( + "Field must start after previous ended (expected {} > {})", + prev_end - 1, + start + ); + } + } else if field.end() > start { bail!( - "Field must start after previous ended (expected {} > {})", - field.end, - start, + "Field must start after previous ended (expected {} < {})", + field.end() - 1, + start ); } } - fields.push(BitDescrField { start, end, name }); + // If in decreasing order, still store positions in increasing order to simplify processing. + if !increasing { + start = width - 1 - start; + } + + fields.push(BitDescrField { start, len, name }); + // Advance by the match's length, plus any whitespace after it. row_str = row_str[cap[0].len()..].trim_start(); } + // Check the name type. + let new_name_type = name.is_empty(); + if let Some(name_type) = name_type { + if new_name_type != name_type { + return Err(Error::msg("Row names must all be omitted, or none may be")); + } + } else { + name_type = Some(new_name_type); + } + rows.push((name, fields)); } - Ok(BitDescrAttrs { width, rows }) + Ok(BitDescrAttrs { + width, + rows, + increasing, + }) } } #[derive(Debug)] struct BitDescrField<'a> { start: usize, - end: usize, + len: usize, name: &'a str, } + +impl BitDescrField<'_> { + fn end(&self) -> usize { + self.start + self.len + } +} diff --git a/src/Audio_Registers.md b/src/Audio_Registers.md index 992e52b8..6458ba32 100644 --- a/src/Audio_Registers.md +++ b/src/Audio_Registers.md @@ -88,29 +88,25 @@ Bit 2-0 - Right output volume (0-7) This register controls CH1's period sweep functionality. -``` -Bit 6-4 - Sweep pace -Bit 3 - Sweep increase/decrease - 0: Addition (period increases) - 1: Subtraction (period decreases) -Bit 2-0 - Sweep slope control (n: 0-7) -``` +{{#bits 8 > + "NR10" 6-4:"Pace" 3:"Direction" 2-0:"Individual step"; +}} -The sweep pace dictates how often the period gets changed, in units of 128 Hz ticks[^div_apu] (7.8 ms). -The pace is only reloaded after the following sweep iteration, or when (re)triggering the channel. -However, if bits 4–6 are all set to 0, then iterations are instantly disabled, and the pace will be reloaded immediately if it's set to something else. +- **Pace**: This dictates how often sweep "iterations" happen, in units of 128 Hz ticks[^div_apu] (7.8 ms). + Note that the value written to this field is not re-read by the hardware until a sweep iteration completes, or the channel is [(re)triggered](<#Triggering>). -On each sweep iteration, the period in [`NR13`](<#FF13 — NR13: Channel 1 period low \[write-only\]>) and [`NR14`](<#FF14 — NR14: Channel 1 period high & control>) is modified and written back. -That is, unless n (the slope) is 0, in which case iterations do nothing (in this case, subtraction mode should be set, see below). + However, if `0` is written to this field, then iterations are instantly disabled (but see below), and it will be reloaded as soon as it's set to something else. +- **Direction**: `0` = Addition (period increases); `1` = Subtraction (period decreases) +- **Individual step**: On each iteration, the new period Lt+1 is computed from the current one Lt as follows: -On each tick, the new period Lt+1 is computed from the current one Lt as follows: + + Lt+1 = Lt ± Lt2step + - - Lt+1 = Lt ± Lt2n - +On each sweep iteration, the period in [`NR13`](<#FF13 — NR13: Channel 1 period low \[write-only\]>) and [`NR14`](<#FF14 — NR14: Channel 1 period high & control>) is modified and written back. In addition mode, if the period value would overflow (i.e. Lt+1 is strictly more than $7FF), the channel is turned off instead. -**This occurs even if sweep iterations are disabled** by n = 0. +**This occurs even if sweep iterations are disabled** by the pace being 0. Note that if the period ever becomes 0, the period sweep will never be able to change it. For the same reason, the period sweep cannot underflow the period (which would turn the channel off). diff --git a/src/LCDC.md b/src/LCDC.md index 252c5c61..2a3a20ed 100644 --- a/src/LCDC.md +++ b/src/LCDC.md @@ -5,16 +5,19 @@ **LCDC** is the main **LCD C**ontrol register. Its bits toggle what elements are displayed on the screen, and how. -Bit | Name | Usage notes -----|--------------------------------|------------------------- - 7 | LCD and PPU enable | 0=Off, 1=On - 6 | Window tile map area | 0=9800-9BFF, 1=9C00-9FFF - 5 | Window enable | 0=Off, 1=On - 4 | BG and Window tile data area | 0=8800-97FF, 1=8000-8FFF - 3 | BG tile map area | 0=9800-9BFF, 1=9C00-9FFF - 2 | OBJ size | 0=8×8, 1=8×16 - 1 | OBJ enable | 0=Off, 1=On - 0 | BG and Window enable/priority | 0=Off, 1=On +{{#bits 8 > + "" 7:"LCD & PPU enable" 6:"Window tile map" 5:"Window enable" 4:"BG & Window tiles" + 3:"BG tile map" 2:"OBJ size" 1:"OBJ enable" 0:"BG & Window enable / priority"; +}} + +- **[LCD & PPU enable](<#LCDC.7 - LCD enable>)**: `0` = Off; `1` = On +- **[Window tile map area](<#LCDC.6 - Window tile map area>)**: `0` = 9800–9BFF; `1` = 9C00–9FFF +- **[Window enable](<#LCDC.5 - Window enable>)**: `0` = Off; `1` = On +- **[BG & Window tile data area](<#LCDC.4 - BG and Window tile data area>)**: `0` = 8800–97FF; `1` = 8000–8FFF +- **[BG tile map area](<#LCDC.3 - BG tile map area>)**: `0` = 9800–9BFF; `1` = 9C00–9FFF +- **[OBJ size](<#LCDC.2 - OBJ size>)**: `0` = 8×8; `1` = 8×16 +- **[OBJ enable](<#LCDC.1 - OBJ enable>)**: `0` = Off; `1` = On +- **[BG & Window enable / priority](<#LCDC.0 - BG and Window enable/priority>)** *\[Different meaning in CGB Mode\]*: `0` = Off; `1` = On ### LCDC.7 — LCD enable @@ -73,7 +76,6 @@ Objects (sprites) aren't affected by this, and will always use the \$8000 addres This bit works similarly to [LCDC bit 6](<#LCDC.6 — Window tile map area>): if the bit is clear (0), the BG uses tilemap $9800, otherwise tilemap $9C00. - ### LCDC.2 — OBJ size This bit controls the size of all objects (1 tile or 2 stacked vertically). @@ -128,8 +130,8 @@ A problem often seen in 8-bit games is objects rendering on top of the textbox/status bar. It's possible to prevent this using LCDC if the textbox/status bar is "alone" on its scanlines: -- Set LCDC.1 to 1 for gameplay scanlines -- Set LCDC.1 to 0 for textbox/status bar scanlines +- Set LCDC.1 to 1 for gameplay scanlines +- Set LCDC.1 to 0 for textbox/status bar scanlines Usually, these bars are either at the top or bottom of the screen, so the bit can be set by the VBlank and/or STAT handlers. diff --git a/src/OAM.md b/src/OAM.md index 4c39a537..cb172a19 100644 --- a/src/OAM.md +++ b/src/OAM.md @@ -49,16 +49,16 @@ tile is "NN & \$FE", and the bottom 8×8 tile is "NN | \$01". ## Byte 3 — Attributes/Flags -{{#bits 8 +{{#bits 8 > "Attributes" 7:"Priority" 6:"Y flip" 5:"X flip" 4:"DMG palette" 3:"Bank" 2-0:"CGB palette"; }} -- **Priority**: `0` = No, `1` = BG and Window colors 1-3 over this OBJ +- **Priority**: `0` = No, `1` = BG and Window colors 1–3 are drawn over this OBJ - **Y flip**: `0` = Normal, `1` = Entire OBJ is vertically mirrored - **X flip**: `0` = Normal, `1` = Entire OBJ is horizontally mirrored - **DMG palette** *\[Non CGB Mode only\]*: `0` = OBP0, `1` = OBP1 -- **Bank** *\[CGB Mode Only\]*: `0` = Fetch tile in VRAM bank 0, `1` = Fetch tile in VRAM bank 1 -- **CGB palette** *\[CGB Mode Only\]*: Use OBP0-7 +- **Bank** *\[CGB Mode Only\]*: `0` = Fetch tile from VRAM bank 0, `1` = Fetch tile from VRAM bank 1 +- **CGB palette** *\[CGB Mode Only\]*: Which of OBP0–7 to use ## Writing data to OAM @@ -110,7 +110,7 @@ differently when in CGB mode. ::: tip Interaction with "BG over OBJ" flag -Object drawing priority and "BG over OBJ" interact in a non-intuitive way. +Object drawing priority and ["BG over OBJ"](<#BG Map Attributes (CGB Mode only)>) interact in a non-intuitive way. Internally, the PPU first resolves priority between objects to pick an "object pixel", which is the first non-transparent pixel encountered diff --git a/src/Palettes.md b/src/Palettes.md index 2fdc2c9c..2c421209 100644 --- a/src/Palettes.md +++ b/src/Palettes.md @@ -5,15 +5,13 @@ ### FF47 — BGP (Non-CGB Mode only): BG palette data -This register assigns gray shades to the color indexes of the BG and -Window tiles. +This register assigns gray shades to the [color IDs](./Tile_Data.md) of the BG and Window tiles. -``` -Bit 7-6 - Color for index 3 -Bit 5-4 - Color for index 2 -Bit 3-2 - Color for index 1 -Bit 1-0 - Color for index 0 -``` +{{#bits 8 > + "Color for..." 7-6:"ID 3" 5-4:"ID 2" 3-2:"ID 1" 1-0:"ID 0"; +}} + +Each of the two-bit values map to a color thusly: Value | Color ------|------- @@ -28,7 +26,7 @@ instead. ### FF48–FF49 — OBP0, OBP1 (Non-CGB Mode only): OBJ palette 0, 1 data These registers assigns gray shades to the color indexes of the OBJs that use the corresponding palette. -They work exactly like BGP, except that the lower two bits are ignored because color index 0 is transparent for OBJs. +They work exactly like [`BGP`](<#FF47 — BGP (Non-CGB Mode only): BG palette data>), except that the lower two bits are ignored because color index 0 is transparent for OBJs. ## LCD Color Palettes (CGB only) @@ -42,34 +40,31 @@ This register is used to address a byte in the CGB's background palette RAM. Since there are 8 palettes, 8 palettes × 4 colors/palette × 2 bytes/color = 64 bytes can be addressed. -``` -Bit 7 Auto Increment (0=Disabled, 1=Increment after Writing) -Bit 5-0 Address ($00-3F) -``` - First comes BGP0 color number 0, then BGP0 color number 1, BGP0 color number 2, BGP0 color number 3, BGP1 color number 0, and so on. Thus, address $03 allows accessing the second (upper) byte of BGP0 color #1 via BCPD, which contains the color's blue and upper green bits. -data can be read from or written to the specified CRAM address through -BCPD/BGPD. If the Auto Increment bit is set, the index gets -incremented after each **write** to BCPD. Auto Increment has -no effect when **reading** from BCPD, so the index must be manually -incremented in that case. Writing to BCPD during rendering still causes -auto-increment to occur, despite the write being blocked. +{{#bits 8 > + "BCPS / OCPS" 7:"Auto-increment" 5-0:"Address"; +}} + +- **Auto-increment**: `0` = Disabled; `1` = Increment "Address" field after **writing** to + [`BCPD`](<#FF69 — BCPD/BGPD (CGB Mode only): Background color palette data / Background palette data>) / + [`OCPD`](<#FF6A - OCPS/OBPI (Object Color Palette Specification or Sprite Palette Index), FF6B - OCPD/OBPD (Object Color Palette Data or Sprite Palette Data) - Both CGB Mode Only>) + (even during [Mode 3](<#Properties of STAT modes>), despite the write itself failing), reads *never* cause an increment +- **Address**: Specifies which byte of BG Palette Memory can be accessed through + [`BCPD`](<#FF69 — BCPD/BGPD (CGB Mode only): Background color palette data / Background palette data>) Unlike BCPD, this register can be accessed outside VBlank and HBlank. ### FF69 — BCPD/BGPD (CGB Mode only): Background color palette data / Background palette data -This register allows to read/write data to the CGBs background palette memory, -addressed through BCPS/BGPI. Each color is stored as little-endian RGB555: +This register allows to read/write data to the CGBs background palette memory, addressed through [BCPS/BGPI](<#FF68 — BCPS/BGPI (CGB Mode only): Background color palette specification / Background palette index>). +Each color is stored as little-endian RGB555: -``` -Bit 0-4 Red Intensity ($00-1F) -Bit 5-9 Green Intensity ($00-1F) -Bit 10-14 Blue Intensity ($00-1F) -``` +{{#bits 16 < + "One color" 0-4:"Red intensity" 5-9:"Green intensity" 10-14:"Blue intensity"; +}} Much like VRAM, data in palette memory cannot be read or written during the time when the PPU is reading from it, that is, [Mode 3](<#STAT modes>). diff --git a/src/STAT.md b/src/STAT.md index a657073c..9e215ec6 100644 --- a/src/STAT.md +++ b/src/STAT.md @@ -20,25 +20,16 @@ is set, and (if enabled) a STAT interrupt is requested. ## FF41 — STAT: LCD status -``` -Bit 6 - LYC=LY STAT Interrupt source (1=Enable) (Read/Write) -Bit 5 - Mode 2 OAM STAT Interrupt source (1=Enable) (Read/Write) -Bit 4 - Mode 1 VBlank STAT Interrupt source (1=Enable) (Read/Write) -Bit 3 - Mode 0 HBlank STAT Interrupt source (1=Enable) (Read/Write) -Bit 2 - LYC=LY Flag (0=Different, 1=Equal) (Read Only) -Bit 1-0 - Mode Flag (Mode 0-3, see below) (Read Only) - 0: HBlank - 1: VBlank - 2: Searching OAM - 3: Transferring Data to LCD Controller -``` - -The two lower STAT bits show the current status of the PPU. - -Bit 2 is set when [LY](<#FF44 — LY: LCD Y coordinate \[read-only\]>) contains the same value as [LYC](<#FF45 — LYC: LY compare>). -It is constantly updated. +{{#bits 8 > + "" 6:"LYC int select" 5:"Mode 2 int select" 4:"Mode 1 int select" 3:"Mode 0 int select" 2:"LYC == LY" 1-0:"PPU mode"; +}} -Bits 3-6 select which sources are used for [the STAT interrupt](<#INT $48 — STAT interrupt>). +- **LYC int select** (*Read/Write*): If set, selects the `LYC` == `LY` condition for [the STAT interrupt](<#INT 48 - STAT Interrupt>) +- **Mode 2 int select** (*Read/Write*): If set, selects the Mode 2 condition for [the STAT interrupt](<#INT 48 - STAT Interrupt>) +- **Mode 1 int select** (*Read/Write*): If set, selects the Mode 1 condition for [the STAT interrupt](<#INT 48 - STAT Interrupt>) +- **Mode 0 int select** (*Read/Write*): If set, selects the Mode 0 condition for [the STAT interrupt](<#INT 48 - STAT Interrupt>) +- **LYC == LY** (*Read-only*): Bit 2 is set when [LY](<#FF44 — LY: LCD Y coordinate \[read-only\]>) contains the same value as [LYC](<#FF45 — LYC: LY compare>); it is constantly updated +- **PPU mode** (*Read-only*): Indicates what actions the PPU is currently taking; read more [below](<#STAT modes>) ## STAT modes @@ -63,8 +54,8 @@ to the CPU: writes are ignored, and reads return garbage values (usually $FF). - During mode 3, the CPU cannot access VRAM or [CGB palette data registers](<#LCD Color Palettes (CGB only)>) ($FF69,$FF6B). -Mode | Action | Duration | Accessible video memory ------|------------------------------------------------------------------|--------------------------------------------------------------------|------------------------- +Mode | Action | Duration | Accessible video memory +-----|-------------------------------------------------------------|-------------------------------------------------------|------------------------- 2 | Searching OAM for OBJs whose Y coordinate overlap this line | 80 dots | VRAM, CGB palettes 3 | Reading OAM and VRAM to generate the picture | 168 to 291 dots, depending on object count | None 0 | Nothing (HBlank) | 85 to 208 dots, depending on previous mode 3 duration | VRAM, OAM, CGB palettes diff --git a/src/Tile_Data.md b/src/Tile_Data.md index 1b44268f..63a383ae 100644 --- a/src/Tile_Data.md +++ b/src/Tile_Data.md @@ -70,11 +70,14 @@ mode, controlled by [LCDC bit 4](<#LCDC.4 — BG and Window tile data area>). Each tile occupies 16 bytes, where each line is represented by 2 bytes: -``` -Byte 0-1 Topmost Line (Top 8 pixels) -Byte 2-3 Second Line -etc. -``` + + + + + + + +
Byte1st2nd3rd4th...
ExplanationTopmost line (top 8 pixels)Second lineEtc.
For each line, the first byte specifies the least significant bit of the color ID of each pixel, and the second byte specifies the most significant bit. In diff --git a/src/Tile_Maps.md b/src/Tile_Maps.md index 7a94dc39..c58f17e8 100644 --- a/src/Tile_Maps.md +++ b/src/Tile_Maps.md @@ -24,18 +24,17 @@ In CGB Mode, an additional map of 32×32 bytes is stored in VRAM Bank 1 entry in VRAM Bank 0, that is, 1:9800 defines the attributes for the tile at 0:9800): -``` -Bit 7 BG-to-OAM Priority (0=Use OAM Priority bit, 1=BG Priority) -Bit 6 Vertical Flip (0=Normal, 1=Mirror vertically) -Bit 5 Horizontal Flip (0=Normal, 1=Mirror horizontally) -Bit 4 Not used -Bit 3 Tile VRAM Bank number (0=Bank 0, 1=Bank 1) -Bit 2-0 Background Palette number (BGP0-7) -``` - -Note that, if the map entry at `0:9800` is tile \$2A, the attribute at -`1:9800` doesn't define properties for ALL tiles \$2A on-screen, but only -the one at `0:9800`! +{{#bits 8 > "BG attributes" 7:"Priority" 6:"Y flip" 5:"X flip" 3:"Bank" 2-0:"Color palette"}} + +- **Priority**: `0` = No; `1` = Colors 1–3 of the corresponding BG/Window tile are drawn over OBJ, regardless of [OBJ priority](<#Byte 3 - Attributes/Flags:>) +- **Y flip**: `0` = Normal; `1` = Tile is drawn vertically mirrored +- **X flip**: `0` = Normal; `1` = Tile is drawn horizontally mirrored +- **Bank**: `0` = Fetch tile from VRAM bank 0; `1` = Fetch tile from VRAM bank 1 +- **Color palette**: Which of BGP0–7 to use + +Bit 4 is ignored by the hardware, but can be written to and read from normally. + +Note that, for example, if the byte at `0:9800` is \$2A, the attribute at `1:9800` doesn't define properties for ALL tiles \$2A on-screen, but only the one at `0:9800`! ### BG-to-OBJ Priority in CGB Mode