Keep what a program draws, instead of making it a sprite
A drawing lasted exactly one frame. The verbs are immediate, they went to the back buffer, SDL double-buffers and the frontend never clears -- so the only way to keep a picture was to capture it with `SSHAPE` and install it as a sprite, which is what `examples/breakout/sprites/breakout.bas` spends two of its eight sprites doing. That was TODO.md section 9 item 9. The drawing verbs now render into a layer texture the frame composites under the text and the sprites. Draw once; it is there on every frame after. **Bracketed around the step phase, not around each verb.** One pair of `SDL_SetRenderTarget` calls a frame instead of one per `DRAW`, and it is also what makes `SSHAPE` read back what the program has just drawn rather than whatever the last frame left. **The layer is transparent where nothing was drawn.** It covers the whole window and composites underneath, so an opaque one would black out the frame the moment a program issued a single `DRAW`. And a fresh SDL target texture's contents are undefined, so it is cleared on creation -- skipping that puts uninitialised memory under the first frame's text and looks like a driver bug rather than a missing memset. **The line editor forced a wrinkle worth naming.** `akbasic_frontend_akgl_pump()` is called from two places with different answers to "is a render target current": the frame loop calls it between steps, and the sink's editor calls it from *inside* a step, borrowing a frame while it waits for a typed line. SDL refuses to present while a target is current, so the pump ends the layer, presents, and puts it back only if it was the one that ended it. `akgl_frontend` caught this -- it drives a REPL session, and it failed with "You can't present on a render target" the first time the brackets went in. This does not make a drawing *visible* on its own. The text layer still repaints every row it owns, opaque, every frame, and by default it owns the whole window; `WINDOW` shrinks it and that half was already fixed. The two together are what a picture needed, and the tests assert both -- a pixel still there a frame later with nothing redrawn, and a pixel below a shrunk text area surviving the text repaint. The second assertion wipes to a non-black colour first, because against black it could not tell a transparent layer from an opaque one. The tests found two of their own bugs on the way: `stop_runtime()` was not tearing the graphics backend down, so re-initialising it dropped a live texture on the floor; and a first draft called `begin()` before `start_runtime()`, which re-inits the backend, so the assertion read back off an orphaned render target and passed while proving nothing. Chapters 6 and 13 stop saying a drawing has to be redrawn every frame, because it does not. The batch-boundary tear stays documented -- it bites an `SSHAPE` capture, which matters much less now that capturing is not the only way to keep a picture. Both games still run clean. 111 with akgl, 110 without. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
This commit is contained in:
@@ -240,10 +240,23 @@ does with one, but you cannot store it or measure it.
|
||||
|
||||
`FILTER` parses and then refuses: there is no filter stage to configure. See Chapter 7.
|
||||
|
||||
The graphics verbs draw straight to the renderer rather than into a display list, so
|
||||
anything drawn is overwritten by the text layer on the next frame. A program that wants
|
||||
its drawing to persist has to redraw it. This is recorded as a defect rather than a
|
||||
design; see Chapter 13.
|
||||
**A drawing stays.** The verbs render into a layer the frame composites underneath the
|
||||
text and the sprites, so a program draws its picture once and it is there on every frame
|
||||
after. It does not have to redraw it, and it does not have to capture it into a sprite.
|
||||
|
||||
What covers it is the text layer, which repaints every row it owns — opaque, every frame,
|
||||
for a reason `akbasic_sink_akgl_render()` explains — and by default it owns the whole
|
||||
window. `WINDOW` shrinks it:
|
||||
|
||||
```basic norun
|
||||
10 WINDOW 0, 0, 39, 1
|
||||
20 GRAPHIC 1, 1
|
||||
30 COLOR 1, 3
|
||||
40 BOX 1, 20, 40, 300, 180
|
||||
```
|
||||
|
||||
Two rows of text at the top, the rest of the window for drawing, and the box is still
|
||||
there a thousand frames later.
|
||||
|
||||
**A redraw also has to fit inside one batch.** The host runs a fixed number of source
|
||||
lines and then presents, and presenting throws the drawing buffer away — so a run of
|
||||
|
||||
@@ -142,19 +142,21 @@ interpreter's error code, which bears no relation to a Commodore error number. P
|
||||
- **`SSHAPE` puts a handle in the string, not the pixels.** You can pass it to `GSHAPE`
|
||||
and `SPRSAV`; you cannot save it or take its `LEN`.
|
||||
- **`WIDTH` is emulated** by drawing parallel passes.
|
||||
- **Drawing does not persist across frames.** The verbs draw straight to the renderer,
|
||||
so anything drawn is overwritten on the next frame unless the program redraws it.
|
||||
This is a defect, not a design.
|
||||
- **And a redraw has to *fit*.** The host runs a fixed number of source lines and then
|
||||
presents the frame, and presenting discards the drawing buffer — so a sequence of
|
||||
drawing verbs longer than one batch comes back **half drawn**, with an `SSHAPE` at the
|
||||
end capturing only what was issued since the present. "Redraw it every frame" is
|
||||
therefore not sufficient advice on its own. Measured against the standalone frontend's
|
||||
256 lines a batch: after synchronising to a jiffy edge, 220 lines of drawing survive a
|
||||
capture and 250 do not. The only way a program can see the boundary is to watch `TI#`,
|
||||
which is refreshed once per batch — so the step on which it changes is the first step
|
||||
of one. [Chapter 18](18-tutorial-breakout-artwork.md#step-5-find-the-frame-boundary)
|
||||
builds a pacing routine out of exactly that.
|
||||
- **Drawing persists, and the text layer covers it.** A drawing goes into a layer that
|
||||
survives the frame, so a program draws its picture once and it stays — no redrawing, no
|
||||
capturing it into a sprite. What is still true is that the text layer repaints every row
|
||||
it owns, opaque, every frame, and by default it owns the whole window. `WINDOW` shrinks
|
||||
it and hands the rest over. The two together are what makes a picture usable:
|
||||
`WINDOW 0, 0, 39, 1` keeps a one-row status line and gives the drawing verbs everything
|
||||
below it.
|
||||
- **A drawing still has to fit in one batch.** The host runs a fixed number of source lines
|
||||
and then presents, and an `SSHAPE` capture spanning that boundary comes back **half
|
||||
drawn** — the part issued since the present, over whatever was there before. Measured
|
||||
against the standalone frontend's 256 lines a batch: after synchronising to a jiffy edge,
|
||||
220 lines of drawing survive a capture and 250 do not. This bites a *capture*, not the
|
||||
drawing itself, so it matters far less than it did when capturing was the only way to
|
||||
keep a picture. The only way a program can see the boundary is to watch `TI#`, which is
|
||||
refreshed once per batch.
|
||||
- **`CHAR` ignores its colour argument** and needs a text device with a cursor.
|
||||
|
||||
## Sound
|
||||
|
||||
Reference in New Issue
Block a user