Jump to content
IGNORED

tiling engine details


Recommended Posts

Thought I'd start a bit of a thread for information about the tiling engine, as used in Boulder Dash®.

The engine belongs to Thomas and myself. Boulder Dash® is the property of First Star Software, Inc.

 

Well, it's a character-based display system. That's a tile-based engine to some. Same thing.

I won't bother defining what that is; if you don't know, then Mr. Google is your friend.

 

The '2600 doesn't do character graphics. So to build a tile based engine, this must be done in software.

 

The '2600 display is 40 pixels wide. So, any 'character' display system which doesn't use sprites has a limitation of 40 pixels across.

Now, how many characters you get across the screen defines the pixel-width of each character.

 

In BD, the screen is 10 characters wide. So, each character is therefore 4 pixels wide. That's pretty low-res.

OK, but what about the height? Well, we wanted reasonably square characters, and furthermore the characters are build up of "virtual pixels" 3 scanlines high, so we ended up with a character size of 4 pixels (wide) * 7 virtual pixels (high). Since a virtual pixel is 3 scanlines high, the screen real-estate of a character is 4 pixels x 21 scanlines.

 

What's virtual pixel?

 

Early experiments with Interleaved Chronocolour involved the use of separately coloured triads of scanlines organised RGBRGBRGB...

These were "rolled" so that successive frames displayed RGB then GBR then BRG then RGB....

Additionally, the ON pixels were changed based on the colour of the particular scan line being displayed.

 

This was a pretty cool system, but it had a sort of shimmer. Not quite a flicker, but it wasn't a static image, either.

 

BD dropped the "rolling" and simply used a static RGB triplet of lines.

Combinations of RGB and on/off pixels give you 8 "colours"

Consider the RGB triplet of lines as [R][G][b} and I'll use 0 and 1 to indicate if a pixel is on or off..

 

Black = [0][0][0]

REd = [1][0][0]

Green = [0][1][0]

Brown (red+green) = [1][1][0]

 

... etc. 8 colours in all.

 

So, now we can define what a character looks like; it's 21 lines of 4-pixel data - organised into 7 virtual pixels deep (composed of RGB lines) and 4 pixels wide.

 

Now, the playfield format of the '2600 is wonky. PF0, PF1, PF2 but the correspondence of bit-position to pixel-position is inconsistent. So in one of the playfield registers it's mirroed relative to the others. This means that we have two options when displaying a 'character' in that particular position on the screen. We can either load the data from the character 'set' and MIRROR it before putting it on the screen (sloooow!) OR we can store two copies of the character -- one mirrored and the other normal. That's what the BD engine does. There are (optionally) two character definitions for each character.

 

You don't HAVE to have two definitions. If, for example, you're quite happy for a character to be mirrored when in certain columns of the screen and non-mirrored otherwise, then just define one character shape. However, if your characters move around a bit, then this 'flipping' becomes quite noticeable. Early demos of BD did not use mirrored characters, and if you have a look at the early demos on YouTube, you will see the diamonds (for example) flipping as they scroll across those odd playfield sections.

 

Now, when we're copying shape (character) data to the screen, we are copying just 4 pixels but these 4 pixels need to be copied into either the high nybble or low nybble of the appropriate playfield register, based on the column position. Again, totally screwy. Fortunately, we're able to duplicate the 4-pixels into a single byte containing both the LEFT definition of a shape and the RIGHT definition of a shape. Appropriate masking on a draw and we get the correct shape in the correct nybble of the byte, ready to put in the playfield register.

 

So, that about covers how characters are defined.

 

Let's have a look at the basic requirements of a character based display.

 

1) we need a screen buffer. This holds the NAMES of the characters, in a mxn array. For BD, we have a 10(wide) x 8(deep) array. We played with deeper arrays, using more screen space vertically, but eventually settled on 8 characters deep basically for speed, space and aesthetic reasons. The screen buffer is basically a byte per character position indicating which shape is displayed at that position. So, row 3 (starting from 0), column 0 might have #3 it. This would indicate that the character at screen position (0,3) would show shape 4. OK, so we need some system to take the screen buffer and draw the appropriate characters based on the above.

 

2) In a game like BD, we have a much larger BOARD. In BD case, this is 40x22 and larger. That's a fair whack of data! But, just like the screen buffer it's just an MxN array of bytes indicating what character (identified by #, called NAME) is in that position. a 40x22 character array equates to a (40x4)x(22x21) PIXEL array... but we don't really think of it like that. We think of characters as our atoms.

 

So, if we have a huge board, and a small screen buffer, holding exactly the same kind of data then the screen buffer is effectively a (moveable!) window into the large board. We change the top left position of that screen buffer "VIEW" into the board and -- wonder of wonders -- we now have scrolling. One of the tasks, then, must be copying the board data into the screen buffer, based on the position of the top left corner of the screen buffer in the board.

 

Now, technically, you don't need the screen buffer. You could do this all directly from the board. However, architectural design and particularly available processing time and bankswitching limitations made it desirable to have one. So, that's how our engine works with that. Copy the relevant board data into the screen buffer.

 

 

So now we have a system that represents a huge playing field (board) and a window onto that board which can change position -- allowing scrolling -- all that is left to do is use the screen buffer to actually draw the screen! This will involve somehow copying the character shapes onto the TV at the appropriate time.

 

As you all will know, there's precious little time in the middle of scanlines to do much of anything. BD uses an asymmetrical playfield, and two sprites and a colour change and a bank switch on every scanline. That's 6 playfield writes, two sprite writes, colour write, bank write. It's jam packed. So we really only have enough time to (quickly) load a byte from somewhere, pump it into a register, and repeat. This means we have to have a pre-set buffer somewhere containing all these data ready to put on the screen.

 

And that's our bitmap array. The bitmap array is effectively a complete RAM copy of the entire screen. In other words, it's the video memory that every other console has. It's our software implementation of hardware. So how big is it? Well, we have 8 character lines deep -- that's 8*21 bytes. And we have -- well, 6 bytes per line because there are 6 playfield writes per lie. That's 8 (deep)*21*6 bytes = 1008 bytes. But, the 6502 only addresses (cheaply) in a single page block. As soon as we have an array > 256 bytes in length, we start to get delays in access due to addressing.

 

Our engine is pretty cool here. We have a single bank (1K) allocated to each character line of the screen. So, there are 8 banks in total. The only data needed in that bank is the bitmap for the characters on that character line. That is, 10 characters wide (=6 bytes) x 21 bytes deep. That's 126 bytes. Now we can use a quicker addressing mode and just load from a pre-set location. So, the display kernel simply consists of 21 lines of loading/storing, followed by a bankswitch (I'm ignoring the sprite stuff which is not really relevant to this discussion).

 

Great, so now we have 8 banks of mingled code and data which copy bitmap data into 21 lines of screen (forming a single character row) and then bankswitching to the NEXT character row drawing bank (or, the last one, ending). Now, for the code and data to co-exist this means that we have to have the code in RAM. That's fine; the bankswitch scheme we use allows for 1K RAM blocks. As long as our draw code AND the bitmap data fits into 1K chunks (for each character row) we're OK. We've used 126 bytes for the bitmap... and can the rest of the draw fit into the remainder? Yes, easily! There's even room left over.

 

Now, when we're actually putting data INTO the bitmap buffers (that is, taking the character NAME from the screen buffer and looking up the definition (shape) of the character) well we need to have both the bank containing the shape data AND the bank containing the RAM bitmap buffer accessible. Here's where we ran into huge limitations of the bankswitch scheme I chose. 3E. Basically you have to either have your character shapes in the fixed bank AND/OR in the row bank(s). If you put a character in the row banks, then you have to duplicate it in all 8 row banks. If you put it in the fixed bank (there's only ONE 2K bank for this!) then you soon run into severe limitations related to available space because just about EVERYTHING seems to need the fixed bank!

 

But, that's about it in a nutshell. We have shapes defined as characters. These are referenced by # (=NAME). The large board is built as an array of character NAMES. We have a screen which is a window into the large board, and it can scroll. We have a system to look at the screen and, based on the NAME, copy character shape data into the row bitmaps. And we have a self-modifying bankswitching draw system that copies this bitmap data onto the TV display.

 

All of this requires a very very large amount of processing time. So to make it all work you have to have a good think about how to optimise things. For example, copying shape data into the row bitmaps involves moving at LEAST 21 bytes per character. But you can't just move because a character is only 4 pixels (half a nybble) of the byte. You don't want to destroy the other nybble, so you have to mask. Masking means you need to load the existing byte from the bitmap buffer, mask out the pixels you want to keep, then load the shape byte and mask out the pixels you want to keep (i.e, the other nybble) then combine them together, then write the data back to the bitmap buffer. So, that's roughly 25 cyles (if you're lucky!!) per byte of every character, just to do the draw. Given 21 bytes per character and 10 characters per row and 8 rows per frame that's 42000 cycles, ballpark.

 

But, with all the onscreen processing time taken doing the actual playfield writes, the only available time to do this task is in the vertical blank. I forget how many cycles are available there. Something like 2500? That would mean we'd need roughly 17 frames for every game cycle. Maximum speed, then, of 3 frames per second. But hey, we haven't even begun to include game logic! Just a simple, minimal, scrolling character-based display system would, according to the above, run at something approximating dog slow.

 

So, that's where the magic starts to happen. How to make this fast? And that's what Thomas and I spent quite a few years working at. We optimised the hell out of it, found boundary conditions which allowed assumptions and simplifcations to be made, and generally designed very efficient multi-tasking subsystems to allow the whole process to work in lock-step with the game logic to produce a working character-based tiling engine.

 

Questions?

  • Like 5
Link to comment
Share on other sites

Just a few remarks from me:

1. While we could, we decided not to use exactly RGB colors. Instead we defined 3 colors/cave which alone or mixed gave the best results to recreate the colors of the original (C64 for me ;)) or whatever colors we preferred (lots of discussion between me and Andrew here :)).

 

2. When designing the character graphics, the "virtual pixels" are not fixed at 3 pixels height. You can play here with 1 or 2 pixel rows and leave the other ones black (background) or even vertically merge pixels (e.g. 1 or 2 rows at the bottom of a pixel and 1 or 2 rows at the top of the pixel below). Whatever looks best. If you look at the boulder graphics you can see what I mean.

Link to comment
Share on other sites

Forgot to mention; all interaction in the game happens in the "big" board. When a character moves, then it's the character name in the big board that's moved from one location in the x,y grid into another location in the x,y grid. That's all the game logic has to do. All the rest is the display engine. If the character moving is onscreen, then that's because the screen window overlaps the area in the big board where that character is. And so it will be drawn. None of the game logic needs to worry or know about the display engine. Completely independent systems.

Link to comment
Share on other sites

I admit without pictures, I may not be understanding it as much as I think I am.

What pictures would help you? Have you checked the demo?

I don't need the demo, I bought the game, and I am very glad that I did. Not just to have and enjoy it, but to support the effort.

As for pictures, maybe mockup a couple of "cells" or "Characters" in each of the iterations of colors.

And maybe a large grid for the board, small one for the screen window.

Edited by Pioneer4x4
Link to comment
Share on other sites

As for pictures, maybe mockup a couple of "cells" or "Characters" in each of the iterations of colors.

And maybe a large grid for the board, small one for the screen window.

Not sure if i get you right here. Can you elaborate what you want a bit more detailed, please?

Link to comment
Share on other sites

Forgot to mention; all interaction in the game happens in the "big" board. When a character moves, then it's the character name in the big board that's moved from one location in the x,y grid into another location in the x,y grid. That's all the game logic has to do. All the rest is the display engine. If the character moving is onscreen, then that's because the screen window overlaps the area in the big board where that character is. And so it will be drawn. None of the game logic needs to worry or know about the display engine. Completely independent systems.

Andrew and Tom,

that's an awesome engine you've built in BoulderDash!

 

I'm building a tile based scrolling engine that reads a 3 byte wide table (30 bytes if there is no vertical scrolling done) into a 6 byte wide RAM buffer (60 bytes) that gets loaded into the playfield;

each bit of the source bytes gets doubled to create a 20x10 board of rectangular tiles;

it also mirrors the bits as required in the RAM buffer so the images are depicted

correctly in the 3 byte wide source table.

I'm working on the side scrolling aspect now and plan to implement it using a bit index and a 12 byte wide source table:

 

the bit index allows me to start building the RAM playfield at any point - I'm still building it out of 3 bytes per row I just slide my bit pointer around within the the 12 byte wide ROW. A 3 byte wide screen (20 bits) makes the play area four screen widths worth of scrolling which is fine for the game I have in mind; I want the play area to also scroll two screens vertically so that puts me at 240 bytes which I can just fit it in one table.

 

I prefer not to set up multiple boundries for the bit index as I would using mutiple 3 byte source image tables as screen-wide (20 virtual Pixel) vertical strip sections of the larger play area; With a 10x8 tile grid I imagine you are also sliding a bit index across a larger row in it's entirety; could you fit a complete BD tile world in a 256 byte table given the large tile size or did you split the worlds across tables on the Y axis for more room so as to leave row size intact for speed?

Link to comment
Share on other sites

could you fit a complete BD tile world in a 256 byte table given the large tile size or did you split the worlds across tables on the Y axis for more room so as to leave row size intact for speed?

 

All of the tables used in our tiling engine are auto-generated with macros, so that the size could be changed without having to worry about things

The actual code is...

 

		 NEWRAMBANK BANK_BOARD

; Now the interesting 'BOARD' -- which in reality is a free-form system of M*N
; rows and columns. We need to reserve enough RAM for the board's entirety, but
; don't really care much how it overlaps the 1K bank limit. The code accessing
; the board *MUST* calculate and take account of the correct RAM bank to switch
; when accessing.

; The system is fairly free-form, in that it rearranges the memory and tables
; automatically based on the sizes set in these constants. The board may overlay
; MULTIPLE banks -- just as long as any particular LINE does not cross a bank
; we're doing OK.

; NOTE: Assumption is that board lines CANNOT CROSS page boundaries.
; now fits into one single bank (if we don't reserve too much space for code)

SIZE_BOARD_X = 40
SIZE_BOARD_Y = 22

; have to precalculate it here, else DASM freaks out:

.BOARD_SIZE SET 0
.BOARD_LOCATION SET 0
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_SIZE SET .BOARD_SIZE - .BOARD_LOCATION
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
.BOARD_SIZE SET .BOARD_SIZE + .BOARD_LOCATION
		 ENDIF
.BOARD_SIZE SET .BOARD_SIZE + SIZE_BOARD_X
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND
SIZE_BOARD	 = .BOARD_SIZE

IF SIZE_BOARD > RAM_SIZE
MULTI_BANK_BOARD = YES
ELSE
MULTI_BANK_BOARD = NO					 ; timings: [-..]
ENDIF

Board		 ds SIZE_BOARD			 ; Note, we can only access this in
													 ; 1024 byte chunks, switching RAM
													 ; banks as we go. In other words,
													 ; this overlaps multiple banks!

 

What this is essentially doing is making sure that we have a large array of lines, but also that none of those lines crosses a page boundary. We then refer to any particular line (y) by first looking up its start address and bank from a table, then indexing to retrieve the character number (x). Those lookup tables are...

 

	 NEWBANK INITBANK

.byte 0 ; to avoid extra cycle when accessing via BoardLineStartLO-1,y

DEFINE_SUBROUTINE BoardLineStartLO
; Gives the start address (LO) of each board line

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte <.BOARD_LOCATION
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND
CHECKPAGE BoardLineStartLO

SIZE_BOARD = .BOARD_LOCATION-Board ; verify calculated value

;------------------------------------------------------------------------------

BoardLineStartHiR

; Gives the start address (HI) of each board line
; Note this caters for the memory wrapping when we go from bank to bank, as
; the board overlays multiple banks!

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte >( .BOARD_LOCATION & $13FF )	 ; cater for mirroring of memory images
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND

CHECKPAGE BoardLineStartHiR

;------------------------------------------------------------------------------

BoardLineStartHiW

; Gives the start address (HI) of each board line
; Note this caters for the memory wrapping when we go from bank to bank, as
; the board overlays multiple banks!

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte >( ( .BOARD_LOCATION & $13FF ) + RAM_WRITE )	 ; cater for mirroring of memory images
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND

CHECKPAGE BoardLineStartHiW

;------------------------------------------------------------------------------

IF MULTI_BANK_BOARD = YES
BoardBank
ENDIF

; Gives the RAM bank of the start of the board row for a given row.
.BOARD_LOCATION SET Board - RAM_3E
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
IF MULTI_BANK_BOARD = YES
		 .byte BANK_BOARD + (.BOARD_LOCATION / RAM_SIZE)		 ; actual bank #
ENDIF

.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X	 ; note, we CANNOT cross a page boundary within a row
	 REPEND
IF MULTI_BANK_BOARD = YES
CHECKPAGE BoardBank
ENDIF

 

As you can see, we make fairly extensive use of comments, macros and auto-calculation here.

The short answer to your question is that we do not allow rows to cross page boundaries, so we can get efficient access.

Cheers

A

Link to comment
Share on other sites

I'm building a tile based scrolling engine...

From you description it seems pretty elaborated. But I don't think it is feasible, because of too many CPU cycles required.

 

So before you invest too much time into planning, maybe you should do some CPU cycle calculations.

Edited by Thomas Jentzsch
Link to comment
Share on other sites

I'm building a tile based scrolling engine...

From you description it seems pretty elaborated. But I don't think it is feasible, because of too many CPU cycles required.

 

So before you invest too much time into planning, maybe you should do some CPU cycle calculations.

Tom,

good point, it's pretty tight but I think I can optimize it more. Here's a pic of the engine rendering a 20 pixel grid right ways up on the playfield by mapping 3 byte rows (1 bit per pixel) into 6 byte rows (2 bits per pixel) of RAM for the playfield to display; a few of the 3 byte rows are shown on the inset:

 

post-30777-0-87194300-1343588654_thumb.jpg

 

I'm plannig to extend this bitmap as much as desired (blue arrow) and use a bit index as the literal Y value to float a 3 byte section anywhere within the row to scroll the image back and forth - the vertical blank section at the bottom where I've got some more time seems like a good place to precalc it and fill 1/2 the RAM buffer reserved for the playfield with the bit shifted 3 byte target rows to build the playfield with and then just point the engine there without changing the routine so it can expand it to six bytes :)

Link to comment
Share on other sites

could you fit a complete BD tile world in a 256 byte table given the large tile size or did you split the worlds across tables on the Y axis for more room so as to leave row size intact for speed?

 

All of the tables used in our tiling engine are auto-generated with macros, so that the size could be changed without having to worry about things

The actual code is...

 

		 NEWRAMBANK BANK_BOARD

; Now the interesting 'BOARD' -- which in reality is a free-form system of M*N
; rows and columns. We need to reserve enough RAM for the board's entirety, but
; don't really care much how it overlaps the 1K bank limit. The code accessing
; the board *MUST* calculate and take account of the correct RAM bank to switch
; when accessing.

; The system is fairly free-form, in that it rearranges the memory and tables
; automatically based on the sizes set in these constants. The board may overlay
; MULTIPLE banks -- just as long as any particular LINE does not cross a bank
; we're doing OK.

; NOTE: Assumption is that board lines CANNOT CROSS page boundaries.
; now fits into one single bank (if we don't reserve too much space for code)

SIZE_BOARD_X = 40
SIZE_BOARD_Y = 22

; have to precalculate it here, else DASM freaks out:

.BOARD_SIZE SET 0
.BOARD_LOCATION SET 0
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_SIZE SET .BOARD_SIZE - .BOARD_LOCATION
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
.BOARD_SIZE SET .BOARD_SIZE + .BOARD_LOCATION
		 ENDIF
.BOARD_SIZE SET .BOARD_SIZE + SIZE_BOARD_X
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND
SIZE_BOARD	 = .BOARD_SIZE

IF SIZE_BOARD > RAM_SIZE
MULTI_BANK_BOARD = YES
ELSE
MULTI_BANK_BOARD = NO					 ; timings: [-..]
ENDIF

Board		 ds SIZE_BOARD			 ; Note, we can only access this in
													 ; 1024 byte chunks, switching RAM
													 ; banks as we go. In other words,
													 ; this overlaps multiple banks!

 

What this is essentially doing is making sure that we have a large array of lines, but also that none of those lines crosses a page boundary. We then refer to any particular line (y) by first looking up its start address and bank from a table, then indexing to retrieve the character number (x). Those lookup tables are...

 

	 NEWBANK INITBANK

.byte 0 ; to avoid extra cycle when accessing via BoardLineStartLO-1,y

DEFINE_SUBROUTINE BoardLineStartLO
; Gives the start address (LO) of each board line

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte <.BOARD_LOCATION
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND
CHECKPAGE BoardLineStartLO

SIZE_BOARD = .BOARD_LOCATION-Board ; verify calculated value

;------------------------------------------------------------------------------

BoardLineStartHiR

; Gives the start address (HI) of each board line
; Note this caters for the memory wrapping when we go from bank to bank, as
; the board overlays multiple banks!

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte >( .BOARD_LOCATION & $13FF )	 ; cater for mirroring of memory images
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND

CHECKPAGE BoardLineStartHiR

;------------------------------------------------------------------------------

BoardLineStartHiW

; Gives the start address (HI) of each board line
; Note this caters for the memory wrapping when we go from bank to bank, as
; the board overlays multiple banks!

.BOARD_LOCATION SET Board
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
		 .byte >( ( .BOARD_LOCATION & $13FF ) + RAM_WRITE )	 ; cater for mirroring of memory images
.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X
	 REPEND

CHECKPAGE BoardLineStartHiW

;------------------------------------------------------------------------------

IF MULTI_BANK_BOARD = YES
BoardBank
ENDIF

; Gives the RAM bank of the start of the board row for a given row.
.BOARD_LOCATION SET Board - RAM_3E
	 REPEAT SIZE_BOARD_Y
		 IF >.BOARD_LOCATION != >(.BOARD_LOCATION + SIZE_BOARD_X-1)
.BOARD_LOCATION SET .BOARD_LOCATION - <.BOARD_LOCATION + 256
		 ENDIF
IF MULTI_BANK_BOARD = YES
		 .byte BANK_BOARD + (.BOARD_LOCATION / RAM_SIZE)		 ; actual bank #
ENDIF

.BOARD_LOCATION SET .BOARD_LOCATION + SIZE_BOARD_X	 ; note, we CANNOT cross a page boundary within a row
	 REPEND
IF MULTI_BANK_BOARD = YES
CHECKPAGE BoardBank
ENDIF

 

As you can see, we make fairly extensive use of comments, macros and auto-calculation here.

The short answer to your question is that we do not allow rows to cross page boundaries, so we can get efficient access.

Cheers

A

Andrew,

I follow what you're saying on the precalculations, look up tables and page boundries - very cool design, the performance is tremendous! I like your queue implementation as well for throttling the time intensive events that pile up!

 

My playfield image expander and bitshifter engines crunch the 6502 quite a bit as Tom pointed out but I've got them working and now optimising to fit them in the blanks. Here is the thread with the details and a bin demo if you or Tom would like to take a look and discuss (either here or there):

 

http://www.atariage.com/forums/topic/200972-tile-based-scrolling-and-expanding-engines/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...