Quantcast
Channel: nesdev.org
Viewing all articles
Browse latest Browse all 746

A Curious Case of Display List Terminators

$
0
0
So I've been thoroughly entrenched in drawing parallels between various disasms I've been cooking on as of late and have noticed something that may or may not matter. Still, I'm nuts about these sorts of inane trivia so figured I'd share.

Nintendo titles typically use a common display list format, with the PPU destination address (i.e. nametable coordinates) followed by either a length and subsequent payload or in some special cases the high bits of the first byte can request other behaviors like vertical placement of a tile. The display list is any number (probably 128 or 256 max really) of entries finally terminated by a single byte after the last entry. This byte has an important job but is easy to overlook and take for granted.

Well, I've found that the byte used in practice differs in some interesting ways across some different titles. The earliest I've examined is Super Mario Bros., in which the terminator is generally $00. However, several pieces of text which are displayed using an alternate routine use a different terminator of $FF. These bits include the header text, game over, and the warp zone text. The warp zone text in particular is switched to use the NMI display list routine instead between the V1 and V2 engines, so the closing byte is changed accordingly. The relevant bits are here:

Code:

.export ppu_displist_write, ppu_displist_write_scc_h_vppu_displist_write:ldxPPU_SRldy#0lda(ppu_displist_write_addr), ybneppu_displist_write_do ; if (ppu_displist_write_addr[0] == 0) {.export ppu_displist_write_scc_h_vppu_displist_write_scc_h_v:staPPU_SCC_H_VstaPPU_SCC_H_Vrts; } else ppu_displist_write_do();
This is the general routine in which a value of $00 returned from the display list indicates an end to the list and for scroll to be reset for display. However, those particular bits of screen text that end with $FF use this routine instead (where BG_TEXT_END_VAL is $FF):

Code:

.exportbg_text_drawbg_text_draw:phatayldxbg_text_idx_tbl, yldy#0: ; for (x = bg_text_idx_tbl[a], y = 0; bg_text[x] != BG_TEXT_END_VAL && y < 0x100; x++, y++) {ldabg_text, xcmp#BG_TEXT_END_VALbeq:+ ; if (bg_text[x] != BG_TEXT_END_VAL) {stappu_displist_data, yinxinybne:-; } else break;: ; }lda#NMI_LIST_ENDstappu_displist_data, ypla
In practice this is just a secondary list format sitting on top of the first, the text is looped over until a $FF is found, put in the display list buffer, but with a $00 terminator like the rest of the text in the game. The reason for this ritual currently eludes me. Comparing this with a few other codebases only thickens the mystery.

The FDS boot ROM includes baked-in versions of routines for handling these display lists. The routine is largely unchanged, although a few items are rearranged and a feature is added in which rts and jmp can be used in display lists, the loop actually has specific branch cases for the rts and jmp opcodes and they behave somewhat how you'd expect in a display list, which allowed recycling chunks and slightly more dynamic loading from small pieces.

Anywho, one marked change in this routine in the FDS boot ROM is that it now uses $FF as a terminator by default. Well, at least that is what is found in display lists in the boot ROM itself. The comparable snippet of the display list routine here is:

Code:

; while (ppu_displist_write_addr[0] >= 0) {fds_ppu_displist_write_loop:...ldxPPU_SRldy#0lda(ppu_displist_write_addr), ybpl:+ ; if (ppu_displist_write_addr[0] < 0) {rts: cmp#CPU_OPCODE_RTSbne:+ ; } else if (ppu_displist_write_addr[0] == rts) {plastappu_displist_write_addr+1plastappu_displist_write_addrldy#WORD_SIZEbne:--: cmp#CPU_OPCODE_JMPbnefds_ppu_il_proc ; } else if (ppu_displist_write_addr[0] == jmp) {ldappu_displist_write_addrphaldappu_displist_write_addr+1phainylda(ppu_displist_write_addr), ytaxinylda(ppu_displist_write_addr), ystappu_displist_write_addr+1stxppu_displist_write_addrbcsfds_ppu_displist_write_loop; }; }
You'll notice here now that the behavior has been reworked. First the routine is terminated in the event *any* negative value is observed. While $FF is being used in practice, this routine will respond to any value with the high bit set with termination (at the end of a string, $FFs are fine in the middle of one). Following are the special cases of rts, which pops the last display list address off the stack and restores that position, and jmp, which pushes the current address to the stack and transfers the next word in the display list in as the next address. In reality it's a "jsr" since it pushes the current address...but they used the jmp opcode for some reason. The significance of the difference, again, eludes me, they could've just as well done a bne around the rts and still had their special cases. Note, an important factor here is that many FDS boot ROM routines are used in titles for that platform. Presumably Nintendo provided some sort of documentation to developers at least describing what the registers were and what entrypoints were available in the boot ROM. Indeed these services are used in numerous places. Essentially what this means is the "standard" format enforced by this platform is the one with $FF (or negative...) terminators and supporting "jmp" and "rts" opcodes.

Enter The Legend of Zelda. The Legend of Zelda depends heavily on the FDS routines, using the baked in versions of OAM initialization, timer and entropy management, controller reading, and several PPU memory operations. Naturally, using the boot ROM display list handler, the display lists in Legend of Zelda are terminated with $FF and support the jmp/rts mechanism. This seems all fine and good. Makes sense that they'd save on space by using the resident stuff. Taking a look at the US release, it has an included routine since the FDS boot ROM isn't in the picture. It similarly uses a bpl on the display list byte to jump back into the routine, retaining this same behavior that any negative value will signal end. However, the jmp/rts behavior appears to be lost in the US release of The Legend of Zelda, at least I don't spot it in the existing disassembly, the routine otherwise much more closely resembles the copy in Super Mario Bros.

Finally there is Doki Doki Panic. Unlike The Legend of Zelda, Doki Doki Panic packs in quite a number of the routines the FDS boot ROM has just sitting there ripe for the plucking. The game includes its own copies of the display list routine among other bits. This copy again is pretty much the same bits as the others rearranged ever so slightly. The important bit is here:

Code:

ppu_displist_write:; for (y; ppu_displist_write_addr[y] != 0; y++) {ldy#0lda(ppu_displist_write_addr), ybeqppu_base_doneldxPPU_SR...; }
Once again we've returned to the Super Mario Bros. behavior in that a zero rather than a negative byte signals termination of the display list. This implies in parallel titles being developed at Nintendo may have been using either format, raising the question of how much of a problem this was in practice in their generation of display lists, however that was handled. This also raises the fact that unless a constant was being used for this terminator, having already setup a fair deal of content with $00 may have a hand in not using the FDS version of the routines for an FDS title. Just speculation. Further speculation then suggests some potential genealogical factors involved in Doki Doki Panic using the $00 style display lists well past a time when the SRD folks were familiar with the boot ROM routine from using it in Zelda.

So yeah, like I said, inane trivia to the max, but one of those little details that demonstrates some of the peculiarities one can find between code by the same folks. In any case, I want to shout out the jmp and rts concept, that's quite clever to allow display lists to have a bit more reusability for common elements. I wonder what other titles outside the FDS boot ROM incorporate that particular feature, as it's not in the others. You'd think something like that would make the rounds a bit more, but maybe it wasn't useful in enough situations.

Statistics: Posted by segaloco — Thu Jan 02, 2025 9:33 pm — Replies 5 — Views 160



Viewing all articles
Browse latest Browse all 746

Trending Articles