Rich Talbot-Watkins wrote:Hmm yeah, the graphics data is pretty bulky!
From my best understanding, the scenery tiles are an awkward size on the Apple (28x63), and are rendered in a complicated way. In particular, what intrigues me is the way the floors are rendered:
That overlapping bit is a true horror! Given that the Apple sprite data is 1bpp, there doesn't seem to be any way you could infer a sprite mask from that. So does it have a separate sprite mask? If so, that's great news as it means that the data size is no different from a 2bpp or 4bpp native format sprite. It would need a mask when plotting B and A; either that or it's a constant triangle mask which is applied when plotting B (so C is not overwritten), and it's an optional mask applied when plotting A (depending on whether it's mostly wall, or just a floor). Any idea how the masking works?
I guess this diagram is just a rough overview though. I'd assume it's more clever than that, and breaks up the tiles even more so that it can avoid storing and rendering large runs of blank.
Everything is drawn with painters algorithm, so back to front and overdrawn where necessary. A screen is made up of 10x3 pieces drawn bottom to top and left to right. Where drawing a piece is: (m versions are for the moveable pieces)
Code: Select all
\*-------------------------------
\*
\* Redraw entire block
\*
\*-------------------------------
.RedBlockSure
{
jsr drawc ;C-section of piece below & to left
jsr drawmc
jsr drawb ;B-section of piece to left
jsr drawmb
jsr drawd ;D-section
jsr drawmd
jsr drawa ;A-section
jsr drawma
jmp drawfrnt ;A-section frontpiece
;(Note: This is necessary in case we do a
;layersave before we get to f.g. plane)
}
Note that this doesn't actually draw the sprites there and then, they are placed into image lists - background, foreground and mid - which are then plotted in later on at the render stage:
Code: Select all
\*-------------------------------
\*
\* D R A W A L L
\*
\* Draw everything in image lists
\*
\* This is the only routine that calls HIRES routines.
\*
\*-------------------------------
.DRAWALL
{
jsr DOGEN ;Do general stuff like cls
lda blackflag ;TEMP
bne label_1 ;
jsr SNGPEEL ;"Peel off" characters
;(using the peel list we
;set up 2 frames ago)
.label_1 jsr ZEROPEEL ;Zero just-used peel list
jsr DRAWWIPE ;Draw wipes
jsr DRAWBACK ;Draw background plane images
jsr DRAWMID ;Draw middle plane images
;(& save underlayers to now-clear peel list)
jsr DRAWFORE ;Draw foreground plane images
jmp DRAWMSG ;Draw messages
}
Each sprite can be plotted at any screen position, with clipping, mirroring and with operand STA, OR, AND (mask) and special (shift & XOR for the "ghost" player character.) As the current plotting is quite slow you can watch it assemble screens in each layer from the SSD's earlier in the thread.
There is no separate sprite mask data. It does infer a mask when required from this table: (note these are Apple II bits so "back to front" with lsb being left-most pixel)
Code: Select all
*-------------------------------
\*
\* MASKTAB
\*
\* Index: byte value w/hibit clr (0-127)
\* Returns mask byte w/hibit set
\*
\*-------------------------------
.MASKTAB
EQUB $FF,$FC,$F8,$F8,$F1,$F0,$F0,$F0
EQUB $E3,$E0,$E0,$E0,$E1,$E0,$E0,$E0
EQUB $C7,$C4,$C0,$C0,$C1,$C0,$C0,$C0
EQUB $C3,$C0,$C0,$C0,$C1,$C0,$C0,$C0
EQUB $8F,$8C,$88,$88,$81,$80,$80,$80
EQUB $83,$80,$80,$80,$81,$80,$80,$80
EQUB $87,$84,$80,$80,$81,$80,$80,$80
EQUB $83,$80,$80,$80,$81,$80,$80,$80
EQUB $9F,$9C,$98,$98,$91,$90,$90,$90
EQUB $83,$80,$80,$80,$81,$80,$80,$80
EQUB $87,$84,$80,$80,$81,$80,$80,$80
EQUB $83,$80,$80,$80,$81,$80,$80,$80
EQUB $8F,$8C,$88,$88,$81,$80,$80,$80
EQUB $83,$80,$80,$80,$81,$80,$80,$80
EQUB $87,$84,$80,$80,$81,$80,$80,$80
EQUB $83,$80,$80,$80,$81,$80,$80,$80
Rich Talbot-Watkins wrote:Here are some thoughts for clawing back some memory (if expanding out the graphics to 2 or 4bpp):
* If using MODE 2, store the graphics 2bpp, thus giving the same memory footprint as the MODE 4 graphics. Then use the 'Exile' type approach of rendering them in any 4 colours (or 3 + transparent) of your choosing - I don't think the graphical style would make this limiting, and you could still get all 8 colours on screen.
I like this idea a lot and will definitely look into it once I've got the B&W version rendering correctly. The input parameters for the sprite plot routines are well documented so just up to me to write the necessary routines (just quite a few variations and fiddly features to consider, including clipping.) Again the code is well structured so it should be possible to just alter the render function that handles conversion of object X coordinates into plot coordinates without affecting the gameplay for different screen widths. NB. Game objects have a different coordinate system from the background tiles.
Rich Talbot-Watkins wrote:* Use the top bit of each pixel as a foreground/background indicator. I think the top bit could be inferred from which section of the scenery tile it is (section A always foreground, the rest background?), so you wouldn't have to store it either. Then you could have the character plotting routines look at the top screen bits as a mask. Just save the screen data prior to plotting the character (there will be shadow RAM spare for that), and erase the character by restoring the screen data - nice and quick, and no need for double buffering.
PoP actually already saves and restores screen data prior to plotting mid-ground objects. The LAYRSAVE and PEEL functions are designed to do this and copy the data out into (double) buffers using the same format as the sprite tables (so can use the same routines to be put back.) I haven't implemented the Beeb equivalents yet but have earmarked the 4K of MOS RAM at &8000 for this purpose.
Rich Talbot-Watkins wrote:* Long shot: LZ77 compress the scenery graphics in an entire block, and expand them out to the screen buffer (blanked) between screens, copying out only the tiles used by that particular screen to a cache. This approach could also work for the NPC graphics which are not all needed simultaneously (I think). I don't think RLE would give big gains. Neither might this approach of course, if the LZ compressed data + required cache is bigger than the unexpanded graphics.
* Break up the scenery tiles into smaller units which allow for some reuse.
Compression and/or caching could be an option. The game already uses two different sprite banks (Dungeon & Palace) for background tiles & selects one per level (there are actually three in the code so note sure if there is technically a third bank made up of a combination of the other two - I need to investigate further later on.) It can also be noted that from any given screen there are only four possible next screens (L/R/U/D) so in theory we can (pre)calculate all possible next tiles/pieces that we haven't already rendered if caching. (Also those that are no longer reachable.)
There is only one NPC (Guard) type permitted per level and again this is selected & loaded on a per-level basis. The Princess & bad guy (Vizer) are only used in cutscenes (apart from the final boss fight.) The nuclear option would be to halve the number of animation frames for the player but that would be a shame.
Rich Talbot-Watkins wrote:* If we have mirrored sprites for the characters, ditch them and use a mirroring table instead to reverse the bytes (and obviously a different routine to plot them backwards).
Not sure how feasible any of that is, but just want to do a brain dump in case anything there sparks any thoughts of your own

Again, PoP already mirrors the sprites in software using a separate set of plot functions in this instance.
There are some great thoughts in here Rich, as ever, thank you! I am continuing to explore just using converted Beeb 1bpp data in MODE4 so I can hopefully get the plotting functionality correct without any additional complications. Once this is working I can explore optimisations and the right approach to colour (MODE1 vs MODE2) and enlist the help of our resident artist if needs be.