amiga-bootcamp/08_graphics/bitmap.md

599 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Graphics](README.md)
# BitMap — Planar Layout, AllocBitMap, Interleaving, and the RastPort Relationship
## Overview
The Amiga's display system is built on **planar bitmaps**: rather than storing each pixel's color in a contiguous byte or word (chunky), the display hardware reads one bit from each of several independent memory regions called **bitplanes**. A pixel's color index is the concatenation of bits at the same (x, y) coordinate across all planes. This design was chosen in 1985 because it minimizes DMA bandwidth: a 16-color screen needs only 4 bits per pixel across the bus, and the Copper can manipulate individual bitplanes with simple pointer changes. The trade-off is software complexity — drawing a single pixel requires read-modify-write across multiple planes, and modern developers accustomed to RGB framebuffers must rethink their mental model. The `struct BitMap` is the central data structure that describes this layout, and `graphics.library` provides `AllocBitMap()` (OS 3.0+) to manage its allocation, alignment, and Chip RAM requirements automatically.
---
## Architecture
### BitMap in the Display Pipeline
```mermaid
graph LR
subgraph "Software"
APP["Application\nDraw into BitMap"]
RP["RastPort\nDrawing context"]
end
subgraph "graphics.library"
AB["AllocBitMap()\nChip RAM allocation\nAlignment enforcement"]
BL["BltBitMap()\nBlitter DMA copy"]
end
subgraph "Hardware"
BP["BitPlane DMA\n(Agnus/Alice)"]
DEN["Denise/Lisa\nPixel compositor"]
OUT["Video output"]
end
APP --> RP
RP --> AB
AB --> BL
BL --> BP
BP --> DEN
DEN --> OUT
style BP fill:#e8f4fd,stroke:#2196f3,color:#333
style DEN fill:#fff3e0,stroke:#ff9800,color:#333
```
### Standard vs Interleaved Layout
```mermaid
graph TB
subgraph "Standard Planar"
SP0["Plane 0\n10,240 bytes"]
SP1["Plane 1\n10,240 bytes"]
SP2["Plane 2\n10,240 bytes"]
SP3["Plane 3\n10,240 bytes"]
end
subgraph "Interleaved"
I0["Row 0: P0 P1 P2 P3\n160 bytes"]
I1["Row 1: P0 P1 P2 P3\n160 bytes"]
I2["Row 2: P0 P1 P2 P3\n160 bytes"]
end
style I0 fill:#e8f5e9,stroke:#4caf50,color:#333
```
In **standard planar**, each plane is a contiguous block. In **interleaved**, planes are striped row-by-row within a single allocation. Interleaving improves cache locality and allows the Blitter to fetch all planes for a given row in one pass.
---
## Data Structures
### struct BitMap
```c
/* graphics/gfx.h — NDK 3.9 */
struct BitMap {
UWORD BytesPerRow; /* bytes per row per plane (must be even) */
UWORD Rows; /* height in pixels */
UBYTE Flags; /* BMF_* flags */
UBYTE Depth; /* number of bitplanes (18) */
UWORD pad;
PLANEPTR Planes[8]; /* pointers to each bitplane buffer */
};
```
### Field Reference
| Field | Type | Description | Constraints |
|---|---|---|---|
| `BytesPerRow` | `UWORD` | Bytes per scanline **per plane** | Must be even; minimum 2; typically `width / 8` rounded up to even |
| `Rows` | `UWORD` | Height in pixels | Maximum 1024 on OCS/ECS; 2048+ on AGA with large-modulo tricks |
| `Flags` | `UBYTE` | `BMF_*` allocation flags | Set by `AllocBitMap`; do not modify directly |
| `Depth` | `UBYTE` | Number of bitplanes | 18 (AGA); 16 (OCS/ECS practical limit) |
| `Planes[]` | `PLANEPTR` | Pointers to each plane's base address | All must be in Chip RAM if displayable; `NULL` for unused planes |
### BMF_ Flags
```c
#define BMF_CLEAR (1<<0) /* Zero-fill planes on allocation */
#define BMF_DISPLAYABLE (1<<1) /* Allocate in Chip RAM (DMA-visible) */
#define BMF_INTERLEAVED (1<<2) /* Row-interleaved plane layout */
#define BMF_STANDARD (1<<3) /* Use standard (non-super) allocation */
#define BMF_MINPLANES (1<<4) /* Allocate minimum planes; rest NULL */
```
---
## Planar Memory Layout
### Standard Planar
For a 320×256×4 display (16 colors):
```
BytesPerRow = 320/8 = 40 bytes
Rows = 256
Depth = 4
Plane 0: 40 × 256 = 10,240 bytes (bit 0 of color index)
Plane 1: 40 × 256 = 10,240 bytes (bit 1)
Plane 2: 40 × 256 = 10,240 bytes (bit 2)
Plane 3: 40 × 256 = 10,240 bytes (bit 3)
Total = 4 × 10,240 = 40,960 bytes
```
Pixel color at (x, y):
```c
UBYTE byte =Planes[p][y * bm->BytesPerRow + x / 8];
UBYTE bit = (byte >> (7 - (x & 7))) & 1;
```
> **Big-Endian note**: The 68000 stores the leftmost pixel of each byte in bit 7, not bit 0. `(7 - (x & 7))` extracts the correct bit. Modern little-endian developers often get this backwards.
### Interleaved Planar
With `BMF_INTERLEAVED`, memory is organized as:
```
BytesPerRow = 40 * 4 = 160 /* covers all planes for one row */
Row 0: [Plane0 bytes][Plane1 bytes][Plane2 bytes][Plane3 bytes]
Row 1: [Plane0 bytes][Plane1 bytes][Plane2 bytes][Plane3 bytes]
...
```
Pointer arithmetic:
```c
/* Planes[i] points to Plane i's data within the interleaved block */
Planes[0] = base;
Planes[1] = base + 40;
Planes[2] = base + 80;
Planes[3] = base + 120;
/* Accessing pixel (x, y) in plane p: */
UBYTE *row = base + y * 160;
UBYTE byte = row[p * 40 + x / 8];
```
**Why interleave?**
- **Cache efficiency**: All planes for a row are contiguous
- **Blitter speed**: Single modulo value advances to next row; fewer setup registers
- **ScrollRaster**: Hardware scroll works correctly with interleaved layout
**Trade-off**: Interleaved BitMaps are harder to manipulate with custom CPU rendering because plane pointers are not independent.
---
## API Reference
### Allocation
```c
/* OS 3.0+ — preferred method */
struct BitMap *AllocBitMap(ULONG width, ULONG height, ULONG depth,
ULONG flags, struct BitMap *friend);
/* Free */
void FreeBitMap(struct BitMap *bm);
```
| Parameter | Description |
|---|---|
| `width` | Width in pixels |
| `height` | Height in pixels |
| `depth` | Number of bitplanes (18) |
| `flags` | `BMF_*` flags |
| `friend` | Optional "friend" BitMap for compatibility (usually `NULL` or a screen's BitMap) |
> [!WARNING]
> **Requires Chip RAM**: When `BMF_DISPLAYABLE` is set, `AllocBitMap()` allocates from Chip RAM (`MEMF_CHIP`). The Blitter, Copper, bitplane DMA, and sprite DMA cannot access Fast RAM. Pointing display hardware at a Fast RAM BitMap produces silent corruption.
### Manual Allocation (OS 1.x Compatible)
```c
struct BitMap bm;
InitBitMap(&bm, 4, 320, 256);
for (int i = 0; i < 4; i++)
bm.Planes[i] = AllocRaster(320, 256); /* AllocMem(..., MEMF_CHIP) */
/* Free: */
for (int i = 0; i < 4; i++)
FreeRaster(bm.Planes[i], 320, 256);
```
### RastPort Relationship
A `RastPort` is the drawing context; it contains a pointer to a `BitMap` plus pen, draw mode, and layer state:
```c
/* graphics/rastport.h — NDK 3.9 */
struct RastPort {
struct Layer *Layer;
struct BitMap *BitMap; /* Target bitmap for drawing */
UWORD cp_x, cp_y; /* Current pen position */
UBYTE DrawMode; /* JAM1, JAM2, COMPLEMENT, INVERSVID */
UBYTE AreaPtrn; /* Areafill pattern pointer */
UBYTE linpatcnt;
UBYTE dummy;
UWORD Flags;
UWORD LinePtrn;
SHORT cp_minx, cp_maxx;
SHORT cp_miny, cp_maxy;
UBYTE APen, BPen;
UBYTE AlphaThreshold;
/* ... additional fields ... */
};
```
```c
/* Initialize a RastPort for a BitMap */
struct RastPort rp;
InitRastPort(&rp);
rp.BitMap = myBitMap;
/* Now draw: */
SetAPen(&rp, 3);
Move(&rp, 10, 10);
Draw(&rp, 100, 50); /* Line rendered into myBitMap */
```
---
## Decision Guide: Standard vs Interleaved
| Criterion | Standard Planar | Interleaved |
|---|---|---|
| **When to use** | Custom CPU rendering, per-plane effects, easy pointer math | Blitter-heavy code, scrolling, OS-friendly rendering |
| **BytesPerRow** | `width / 8` (rounded up) | `width / 8 * depth` |
| `AllocBitMap()` flag | None (default) | `BMF_INTERLEAVED` |
| `ScrollRaster()` | Requires manual plane loop | Works with single call |
| `BltBitMap()` | Multiple blits or per-plane loops | Single blit with modulo |
| **CPU pixel access** | Simple: `Planes[p][offset]` | Complex: `base + row * bpr * depth + p * bpr` |
| **Memory fragmentation** | `depth` separate allocations | One contiguous block |
| **Display hardware** | Identical — DMA doesn't care | Identical — DMA doesn't care |
---
## Practical Examples
### Example 1: Allocate and Clear a Displayable BitMap
```c
#include <graphics/gfx.h>
#include <proto/graphics.h>
struct BitMap *CreateDisplayBitMap(ULONG width, ULONG height, ULONG depth)
{
struct BitMap *bm = AllocBitMap(width, height, depth,
BMF_CLEAR | BMF_DISPLAYABLE,
NULL);
if (!bm)
{
/* Out of Chip RAM — this is common on stock A500 */
return NULL;
}
/* Verify allocation succeeded for all requested planes */
for (int i = 0; i < depth; i++)
{
if (!bm->Planes[i])
{
FreeBitMap(bm);
return NULL;
}
}
return bm;
}
```
### Example 2: CPU Pixel Plot (Standard Planar)
```c
void PutPixel(struct BitMap *bm, WORD x, WORD y, UBYTE color)
{
if (x < 0 || x >= bm->BytesPerRow * 8 || y < 0 || y >= bm->Rows)
return;
UWORD byteOffset = y * bm->BytesPerRow + (x >> 3);
UBYTE bitMask = 0x80 >> (x & 7); /* bit 7 = leftmost pixel */
for (int p = 0; p < bm->Depth; p++)
{
if (color & (1 << p))
bm->Planes[p][byteOffset] |= bitMask; /* Set bit */
else
bm->Planes[p][byteOffset] &= ~bitMask; /* Clear bit */
}
}
```
### Example 3: CPU Pixel Plot (Interleaved)
```c
void PutPixelInterleaved(struct BitMap *bm, WORD x, WORD y, UBYTE color)
{
if (!(bm->Flags & BMF_INTERLEAVED)) return;
UWORD rowBytes = bm->BytesPerRow; /* total per row, all planes */
UWORD planeBytes = rowBytes / bm->Depth; /* bytes per plane per row */
UWORD byteOffset = y * rowBytes + (x >> 3);
UBYTE bitMask = 0x80 >> (x & 7);
for (int p = 0; p < bm->Depth; p++)
{
UBYTE *planePtr = bm->Planes[0] + byteOffset + p * planeBytes;
if (color & (1 << p))
*planePtr |= bitMask;
else
*planePtr &= ~bitMask;
}
}
```
### Example 4: Blitter Copy Between BitMaps
```c
/* Copy a rectangle from source to destination */
void CopyRect(struct BitMap *src, WORD sx, WORD sy,
struct BitMap *dst, WORD dx, WORD dy,
WORD width, WORD height)
{
BltBitMap(src, sx, sy, dst, dx, dy, width, height,
0xC0, /* minterm: D = C (straight copy) */
0x01, /* mask: all planes */
NULL); /* no temporary mask */
}
```
### Example 5: Attach BitMap to ViewPort
```c
struct ViewPort vp;
struct BitMap *bm = CreateDisplayBitMap(320, 256, 5);
InitVPort(&vp);
vp.DWidth = 320;
vp.DHeight = 256;
vp.DxOffset = 0;
vp.DyOffset = 0;
vp.RasInfo = &ri;
vp.Modes = HIRES | SPRITES;
ri.BitMap = bm;
ri.RxOffset = 0;
ri.RyOffset = 0;
```
---
## When to Use / When NOT to Use
### When to Use AllocBitMap
| Scenario | Why It Fits |
|---|---|
| **OS 3.0+ application** | `AllocBitMap()` handles alignment, Chip RAM, and friend-BitMap compatibility |
| **Off-screen buffers** | Allocate non-displayable BitMaps for pre-rendering, then blit to screen |
| **Double buffering** | Two displayable BitMaps swapped per frame via `ChangeVPBitMap()` |
| **Interleaved scrolling** | `BMF_INTERLEAVED` + `ScrollRaster()` is the correct path for smooth scroll |
### When NOT to Use AllocBitMap / Manual BitMaps
| Scenario | Problem | Better Approach |
|---|---|---|
| **Direct hardware banging** | `AllocBitMap()` may allocate structures you don't need | Direct `AllocMem(MEMF_CHIP)` and manual `Planes[]` setup |
| **Copper-only displays** | If the CPU never draws, a raw bitplane array is sufficient | Manual `AllocRaster()` per plane |
| **Chunky-to-planar rendering** | C2P output needs specific plane alignment; `AllocBitMap` may not match | Allocate manually with `MEMF_CHIP` and verify alignment |
| **Custom DMA tricks** | Some demo effects need non-standard `BytesPerRow` or plane spacing | Manual allocation with exact sizes |
---
## Best Practices & Antipatterns
### Best Practices
1. **Always check `AllocBitMap()` return value** — Chip RAM exhaustion is common on stock machines.
2. **Verify all `Planes[]` are non-NULL**`BMF_MINPLANES` can leave upper planes unset.
3. **Use `TypeOfMem(bm->Planes[0])` to confirm Chip RAM** when debugging DMA issues.
4. **Round width up to 16-pixel boundaries** for Blitter efficiency: `width = ((width + 15) / 16) * 16`.
5. **Prefer interleaved for scrolling games**`ScrollRaster()` and the Blitter work optimally.
6. **Use standard planar for per-plane effects** — color cycling, parallax, and palette tricks are easier.
7. **Free with `FreeBitMap()`** if allocated with `AllocBitMap()` — do not mix manual and automatic free.
8. **Set `friend` BitMap when possible** — improves compatibility with graphics cards and RTG systems.
### Antipatterns
#### 1. The Odd-Width Trap
```c
/* ANTIPATTERN — BytesPerRow not even */
struct BitMap bm;
InitBitMap(&bm, 4, 321, 200); /* 321 pixels = 41 bytes (odd!) */
/* Blitter requires even alignment; DMA may corrupt adjacent memory */
/* CORRECT — round up to even bytes */
InitBitMap(&bm, 4, 320, 200); /* 40 bytes — even, safe */
```
#### 2. The Fast RAM BitMap
```c
/* ANTIPATTERN — allocating display BitMap in Fast RAM */
struct BitMap bm;
InitBitMap(&bm, 4, 320, 256);
for (int i = 0; i < 4; i++)
bm.Planes[i] = AllocMem(10240, MEMF_ANY); /* May return Fast RAM! */
/* CORRECT — force Chip RAM for displayable bitmaps */
for (int i = 0; i < 4; i++)
bm.Planes[i] = AllocMem(10240, MEMF_CHIP | MEMF_CLEAR);
```
#### 3. The Uninitialized RastPort
```c
/* ANTIPATTERN — using a RastPort without initialization */
struct RastPort rp;
rp.BitMap = myBitMap;
Move(&rp, 0, 0); /* rp contains garbage pen, mode, layer ptr → crash */
/* CORRECT — always InitRastPort() */
struct RastPort rp;
InitRastPort(&rp);
rp.BitMap = myBitMap;
```
---
## Pitfalls & Common Mistakes
### 1. Modulo Misalignment in Blitter Operations
The Blitter uses **word-aligned** addressing. If `BytesPerRow` is odd, or if you compute offsets incorrectly for interleaved BitMaps, the Blitter wraps to the wrong memory location.
```c
/* PITFALL — wrong modulo for interleaved BitMap */
struct BitMap *bm = AllocBitMap(320, 256, 4, BMF_INTERLEAVED, NULL);
/* BytesPerRow = 40 * 4 = 160 */
/* If you tell the Blitter modulo = 40 (per-plane), it advances
by 40 bytes per row — landing in the middle of the next plane. */
/* CORRECT — modulo for interleaved is total row bytes */
UWORD modulo = bm->BytesPerRow; /* 160, not 40 */
```
### 2. Depth Mismatch in BltBitMap
```c
/* PITFALL — copying from 5-plane to 3-plane BitMap */
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, 0xC0, 0x1F, NULL);
/* If dst->Depth < 5, BltBitMap writes past Plane[] array → crash */
/* CORRECT — ensure destination depth >= source depth, or mask
to only the planes that exist: */
UBYTE planeMask = (1 << dst->Depth) - 1;
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, 0xC0, planeMask, NULL);
```
### 3. Forgetting PlaneClear on Manual Allocations
```c
/* PITFALL — uninitialized planes contain garbage */
struct BitMap bm;
InitBitMap(&bm, 4, 320, 256);
for (int i = 0; i < 4; i++)
bm.Planes[i] = AllocRaster(320, 256);
/* Planes contain random data → visual garbage on first display */
/* CORRECT — clear after allocation */
for (int i = 0; i < 4; i++)
memset(bm.Planes[i], 0, bm.BytesPerRow * bm.Rows);
```
### 4. Width vs BytesPerRow Confusion
```c
/* PITFALL — using width in bytes where pixels are expected */
UWORD widthInBytes = bm->BytesPerRow; /* 40 bytes = 320 pixels */
BltBitMap(src, 0, 0, dst, 0, 0, widthInBytes, 200, ...);
/* BltBitMap expects PIXELS, not bytes! Copies only 40 pixels. */
/* CORRECT */
BltBitMap(src, 0, 0, dst, 0, 0, 320, 200, ...);
```
---
## Use Cases
| Software Pattern | BitMap Approach | Why |
|---|---|---|
| **Scrolling platformer** | Interleaved, `ScrollRaster()` | Single hardware scroll register update per frame |
| **Double-buffered animation** | Two standard BitMaps + `ChangeVPBitMap()` | Clean VBlank swap with no tearing |
| **Parallax background** | Multiple standard BitMaps at different depths | Independent scroll per layer via separate ViewPorts |
| **Off-screen sprite preshift** | Standard planar, 16 copies per frame | CPU pre-renders shifted frames for fast Blitter copy |
| **Chunky-rendered 3D** | Standard planar + C2P conversion | Render in Fast RAM chunky buffer, convert to BitMap planes |
| **Color-cycling water/sky** | Standard planar, modify one plane only | Animate palette index via plane manipulation |
---
## Historical Context & Modern Analogies
### Why Planar?
In 1985, DRAM bandwidth was the bottleneck. A 320×200 display at 60 Hz requires:
| Format | Bits/Pixel | Bytes/Frame | DMA Bandwidth |
|---|---|---|---|
| **Chunky 256-color** | 8 | 64,000 | ~3.8 MB/s |
| **Planar 32-color (5 planes)** | 5 | 40,000 | ~2.4 MB/s |
| **Planar 16-color (4 planes)** | 4 | 32,000 | ~1.9 MB/s |
| **Planar 8-color (3 planes)** | 3 | 24,000 | ~1.4 MB/s |
Planar layout reduces DMA bandwidth proportionally to color depth. It also enables tricks impossible in chunky:
- **Color cycling**: Change palette entries, not pixels
- **Parallax**: Scroll one plane while others stay fixed
- **Transparency**: Omit a plane to see through to background
### Modern Analogies
| Amiga Planar Concept | Modern Equivalent | Shared Concept |
|---|---|---|
| `BitMap` + `Planes[]` | **OpenGL texture array / Vulkan image layers** | Multiple memory planes composited into final pixel |
| Interleaved planar | **GPU tile-based render target** | Contiguous per-row layout for cache efficiency |
| `BytesPerRow` | **Vulkan ` VkSubresourceLayout.rowPitch`** | Stride between scanlines, often larger than width |
| `AllocBitMap(BMF_DISPLAYABLE)` | **`vkAllocateMemory` with `DEVICE_LOCAL_BIT`** | Explicit allocation in GPU-visible (DMA-able) memory |
| `BltBitMap()` | **OpenGL `glBlitFramebuffer`** | Hardware-accelerated rectangular copy between surfaces |
| Planar color cycling | **Palette-based texture animation** | Modify lookup table instead of texels for animated effects |
### Where Analogies Break Down
- **No alpha channel**: Planar pixels are color indices, not RGBA. Transparency requires hold-and-modify (HAM) or sprite overlay.
- **No arbitrary pixel writes**: Drawing a single pixel requires RMW across all planes — no simple `framebuffer[y*w+x] = color`.
- **Blitter as coprocessor**: Unlike modern GPUs, the Blitter is a fixed-function DMA engine with no shader programmability.
---
## FAQ
**Q: Can I use `AllocBitMap()` on OS 1.3?**
> No. `AllocBitMap()` was introduced in OS 2.0 (V36). On 1.3, use `InitBitMap()` + `AllocRaster()` / `AllocMem(MEMF_CHIP)`.
**Q: How do I detect if a BitMap is interleaved?**
> Check `bm->Flags & BMF_INTERLEAVED`. If set, `bm->BytesPerRow` is the total bytes per row across all planes.
**Q: Why does my Blitter copy look garbled?**
> Most likely causes: (1) `BytesPerRow` is odd, (2) source/destination BitMaps are not in Chip RAM, (3) modulo values are wrong for interleaved layout, (4) plane mask includes nonexistent planes.
**Q: Can I draw directly into a screen's BitMap?**
> Yes, but only through a `RastPort` obtained from the window (`win->RastPort`) or by creating your own RastPort pointing to the screen's BitMap. Never write to `screen->RastPort.BitMap` directly without proper locking — it bypasses layers clipping.
**Q: What is the maximum BitMap size?**
> Theoretical: 32767×32767 (16-bit Rows/BytesPerRow). Practical: Chip RAM limits. A 640×512×8 BitMap consumes 320 KB — large but feasible on a 2 MB Chip RAM AGA machine.
---
## Impact on FPGA/Emulation
For MiSTer and emulator developers, planar BitMap emulation has specific requirements:
- **Bitplane DMA must respect `BytesPerRow`**: The Agnus/Alice DMA controller fetches one row per bitplane per scanline using `BytesPerRow` as the stride. Emulators must implement this correctly, not assume contiguous layout.
- **Interleaved is a software convention**: The hardware does not distinguish interleaved from standard — it only sees plane pointers. Interleaving is achieved by software setting `Planes[i]` to offsets within a single block.
- **Alignment enforcement**: `AllocBitMap()` ensures even `BytesPerRow` and proper Chip RAM alignment. Emulators need not enforce this for manually constructed BitMaps, but should document that misaligned BitMaps produce undefined behavior on real hardware.
- **AGA 64-bit fetches**: Alice can fetch 64 bits (8 bytes) per DMA cycle when `FMODE` is set. Emulators must support wider fetches for correct AGA high-resolution modes.
---
## References
- NDK 3.9: `graphics/gfx.h`, `graphics/rastport.h`
- ADCD 2.1: `AllocBitMap()`, `FreeBitMap()`, `InitBitMap()`, `InitRastPort()`, `BltBitMap()`
- *Amiga Hardware Reference Manual* — Bitplane DMA chapter
- See also: [memory_types.md](../01_hardware/common/memory_types.md) — Chip RAM requirements for DMA-visible BitMaps
- See also: [blitter.md](blitter/blitter.md) — Blitter DMA operations on BitMaps
- See also: [blitter_programming.md](blitter/blitter_programming.md) — Advanced Blitter minterms and cookie-cut
- See also: [views.md](views.md) — Attaching BitMaps to ViewPorts for display
- See also: [rastport.md](rastport.md) — RastPort drawing context and primitives
- See also: [dma_architecture.md](../01_hardware/common/dma_architecture.md) — bitplane DMA slot budget, DDFSTRT/DDFSTOP registers, bandwidth calculations