Jump to content
IGNORED

IntyBASIC - Smooth Scrolling


awhite2600

Recommended Posts

I'm starting to write a game in IntyBASIC. I'd like to keep the game secret for the time being. I may release the game once it reaches a playable state. For now, it's just an experimental concept.

 

I'm new to the world of Intellivision programming - despite owning an Intellivision since 1982 and writing programs since 1980. I'm trying to get my head around the hardware and some of the limitations.

 

The game requires smooth horizontal scrolling. I can draw the screen and then scroll it back and forth 8 pixels without any issues. My scroll routine uses the hardware to to the fine scrolling. After 8 pixels I essentially redraw the entire screen shifted by one card. (I may be able to optimize this, but for now I update all of the cards by poking directly into the BACKTAB area.) The screen becomes corrupt after a while. This might just be a bug in my scrolling code.

 

Another thread mentions updating less than 20 cards at a time. Is this a limitation of the hardware (or timing)? Should I try to optimize the scrolling to do more intelligent redrawing?

Edited by awhite2600
  • Like 1
Link to comment
Share on other sites

The "20 card" limitation is for bitmaps in GRAM. That is, you only have 3900 cycles to access GRAM during an interrupt. For a fully unrolled copy loop, you could update about 29 tiles in GRAM in a single interrupt, if you did nothing else. Realistically, anything above 20 is probably pushing it. The good news is, you don't need to update GRAM to scroll the screen horizontally with the hardware scroll registers.

 

Rather, you just need to slide the BACKTAB over. You have plenty of cycles to scroll the entire BACKTAB. To do it without artifacts, you want to start that as early in the frame as possible, starting before active display begins.

 

It takes the STIC 912 cycles to display a full row of cards from BACKTAB. That means your horizontal scroll routine needs to slide each row over by 1 (whether to the left or right) in fewer than 912 cycles to stay ahead of the STIC.

 

Your code also needs to start ahead of active display. Active display starts approximately 3950 cycles after the retrace interrupt (somewhere I have the exact value), so however long your code takes to slide one row, it needs to start at least that much before active display. For example, a simple, fully unrolled memmove takes about 323 cycles to move 19 cards. Add a few more cycles for set up and a write to fill in the newly exposed column and you're around 400 cycles. So, you need to start the scroll process no later than 3950 - 400 cycles before active display to avoid display artifacts.

 

I'll leave "how to do that" to the IntyBASIC experts. I'm just explaining the hardware. :-)

Edited by intvnut
  • Like 1
Link to comment
Share on other sites

Thanks for the detailed explanation intvnut. It was quite helpful.

 

As I said, my code is not even close to being optimized. That's likely a big part of the problem. Currently, I recalculate and redraw the screen - shifted one card to the left. There are a couple of nested loops and some other code required to do the "recalc". So I'm likely way over the number of cycles used by the STIC to draw the screen. Artifacts aren't even the issue, the screen is getting corrupted the more it scrolls.

 

I'm going to rewrite the scroll routine to do a peek/poke memory copy. That should shorten the number of cycles considerably. I'd take a stab at writing the scroller in assembly, but I want to walk before I run.

 

Due to the fact that I'm using IntyBASIC I have no idea how many cycles are being consumed by various sections of my code.

 

As I write this reply I've come up with some ideas to optimize the screen draw routines. It's unnecessarily complex at the moment. If I can reduce the complexity and write a better memcopy style scroller I should be in better shape.

Link to comment
Share on other sites

BTW, one way you can make this work more smoothly, if you have the RAM, is to compute the new column way ahead of time so it's ready to go at a moment's notice. If you're using cart.mac and the 42K memory map, you have 8K words of RAM available to you, so you could use that.

 

That said, the display is only 12 rows tall, so burning 12 words of System RAM (or 24 words of Scratch RAM) may be acceptable also.

 

My understanding is that IntyBASIC outputs assembly code. (Shamefully, I admit I haven't tried playing with it yet.) So, you should be able to find your copy loop and count cycles there. For something as basic as a "slide row left 1" and "slide row right 1", there's not much advantage to writing that in BASIC, so it's probably worth figuring out how to patch those in as unrolled assembly functions.

 

Here's a couple routines I just whipped up that you're welcome to use. Untested, but they look correct.

;; Slides a row of cards left by 1.  Does not modify rightmost card.
;; Pointer to start of row in R4.
;; Modifies R0, R1, R4, R5.
;; 348 cycles.
MV_LFT      PROC
            MOVR    R5,     R1  ;   6    save return address
            MOVR    R4,     R5  ;   6
            INCR    R5          ;   6
                                ;----
                                ;  18
            REPEAT  19
            MVI@    R5,     R0  ;   8
            MVO@    R0,     R4  ;   9
                                ;----
            ENDR                ;  17
                                ;----
                                ; 323  (17 * 19)

                                ;  18  (carried forward)
            JR      R1          ;   7  return
                                ;----
            ENDP                ; 348

;; Slide a row of cards right by 1.  Does not modify leftmost card.
;; Pointer to start of row in R4.
;; Modifies R0, R1, R2, R4, R5
;; 348 cycles.
MV_RGT      PROC
            MOVR    R5,     R2  ;   6
            MOVR    R4,     R5  ;   6
            INCR    R5          ;   6   advance past first card
            MVI@    R4,     R0  ;   8   read column 0
            MVI@    R4,     R1  ;   8   read column 1
                                ;----
                                ;  34

            REPEAT  8           ; write to columns 0 .. 15
            MVO@    R0,     R5  ;   9   write column 1 + 2*k
            MVI@    R4,     R0  ;   8   read  column 2 + 2*k
            MVO@    R1,     R5  ;   9   write column 2 + 2*k
            MVI@    R4,     R1  ;   8   read  column 3 + 2*k
                                ;----
            ENDR                ;  34
                                ;----
                                ; 272 (34 * 

                                ;  34 (carried forward)
                                ;----
                                ; 306

            MVO@    R0,     R5  ;   9   write column 16
            MVI@    R4,     R0  ;   8   read  column 17
            MVO@    R1,     R5  ;   9   write column 17
            MVO@    R0,     R5  ;   9   write column 18
            JR      R2          ;   7   return
                                ;----
            ENDP                ; 348

  • Like 1
Link to comment
Share on other sites

I'm working to put a SCROLL statement in IntyBASIC v0.5 that will include optimized assembly code for scrolling in four directions. :)

 

So programmers only will have to redraw the newly introduced zone.

 

Cool. You're welcome to use my code above as you see fit.

 

To have artifact-free scrolling there are some challenges involved, if you want scrolling to be completely artifact free. I guess how you attack them depends in part on IntyBASIC itself.

 

What are your design goals?

Link to comment
Share on other sites

 

BTW, one way you can make this work more smoothly, if you have the RAM, is to compute the new column way ahead of time so it's ready to go at a moment's notice. If you're using cart.mac and the 42K memory map, you have 8K words of RAM available to you, so you could use that.

 

That said, the display is only 12 rows tall, so burning 12 words of System RAM (or 24 words of Scratch RAM) may be acceptable also.

 

My understanding is that IntyBASIC outputs assembly code. (Shamefully, I admit I haven't tried playing with it yet.) So, you should be able to find your copy loop and count cycles there. For something as basic as a "slide row left 1" and "slide row right 1", there's not much advantage to writing that in BASIC, so it's probably worth figuring out how to patch those in as unrolled assembly functions.

 

Here's a couple routines I just whipped up that you're welcome to use. Untested, but they look correct.

;; Slides a row of cards left by 1.  Does not modify rightmost card.
;; Pointer to start of row in R4.
;; Modifies R0, R1, R4, R5.
;; 348 cycles.

......

Thanks for the code intvnut. I'll likely try some test scrolling code in BASIC. If the scrolling "works" (scrolls without corruption) then I may attempt to wedge your assembly version into my game. Using a true scroll routine vs. a complete redraw will require me to rethink some of my code. The screen is constructed algorithmicly at this point. While it will be more efficient to just draw the new zone after a scroll, it will necessitate some major code changes. The good thing is that the changes will likely allow me to drop using a couple of arrays to store/calc the screen state.

Nanochess has indicated that he might include a scrolling routine in his next release - so that could end up being an option too. Nanochess, do you have a plan for your next release? Any other features, changes? Do you have an approximate timeline in mind? (I'm not pushing. I think IntyBASIC is great. )

Edited by awhite2600
Link to comment
Share on other sites

The SCROLL command will be "pipelined" until the next interrupt (WAIT), and inside the interrupt service just after MOB updating will be done the scrolling. And user should add his/her screen update just after WAIT.

 

Of course, that is the plan, but still to be checked if it works.

 

The syntax will be SCROLL [offsetx],[offsety],[move]

 

Where move 0=up, 1=left, 2=right, 3=down

 

Also a there will be BORDER ,[flags] for hiding top/left right parts of screen.

  • Like 1
Link to comment
Share on other sites

Thanks for the code intvnut. I'll likely try some test scrolling code in BASIC. If the scrolling "works" (scrolls without corruption) then I may attempt to wedge your assembly version into my game. Using a true scroll routine vs. a complete redraw will require me to rethink some of my code. The screen is constructed algorithmicly at this point. While it will be more efficient to just draw the new zone after a scroll, it will necessitate some major code changes. The good thing is that the changes will likely allow me to drop using a couple of arrays to store/calc the screen state.

 

Another option, if you have the RAM for it, would be to construct your entire "world" in an off-screen buffer. Then, instead of scrolling, you're copying a window of the world onto the display. That can be cleaner to code and still very quick. Even if you have to copy the window to the display every frame, that only costs around 4500 cycles from assembly code, IIRC.

 

To make that practical, you probably have to use a memory map that adds more RAM (such as the 42K map in cart.mac).

 

 

The SCROLL command will be "pipelined" until the next interrupt (WAIT), and inside the interrupt service just after MOB updating will be done the scrolling. And user should add his/her screen update just after WAIT.

 

Of course, that is the plan, but still to be checked if it works.

 

It will work, but it will not be artifact free. Scrolling the screen in-place takes over 4000 cycles. Assuming you have other work to do in your ISR (such as update MOBs, any sound effects/music if you're doing that, etc.) that means you're a couple rows into active display by the time the scroll completes. So, you will have artifacts along the top of the screen I'd imagine.

 

It won't be too bad for the top row during vertical scrolling due to the border extension, unless you are scrolling by more than 1px per frame (which you're going to want to be able to do). Similar for the left edge. Most artifacts will be visible in the upper right corner.

 

You can avoid this by requiring the programmer to provide the update in an offscreen buffer ahead of time. Or, have some ability to let the user's code fill incrementally during the scrolling process. (That starts to get complicated quickly.)

 

 

 

The syntax will be SCROLL [offsetx],[offsety],[move]

 

Where move 0=up, 1=left, 2=right, 3=down

 

Why are there separate offsetx/offsety if you have a direction flag also? Also, is it possible to scroll diagonally, or do you need to alternate between horizontal and vertical on alternate frames?

Link to comment
Share on other sites

 

Another option, if you have the RAM for it, would be to construct your entire "world" in an off-screen buffer. Then, instead of scrolling, you're copying a window of the world onto the display. That can be cleaner to code and still very quick. Even if you have to copy the window to the display every frame, that only costs around 4500 cycles from assembly code, IIRC.

 

To make that practical, you probably have to use a memory map that adds more RAM (such as the 42K map in cart.mac).

 

I'm not certain what memory maps are supported by IntyBASIC. I'd also like the option to be able to have carts produced. (I realize that this is not easy.) I believe that larger carts or those with RAM are more difficult/costly to produce.

 

There is no "world" to the game that I am designing, I'll describe it for now as a simple side scroller. New elements will enter as the screen scrolls. As some of the objects entering the screen are large, I just need to keep track so that they can be drawn over multiple columns of cards. Nothing too difficult, just takes some thought into the best way to do it. I haven't written a game since the early 90's, so my programming skills are a bit rusty.

Link to comment
Share on other sites

Another option, if you have the RAM for it, would be to construct your entire "world" in an off-screen buffer. Then, instead of scrolling, you're copying a window of the world onto the display. That can be cleaner to code and still very quick. Even if you have to copy the window to the display every frame, that only costs around 4500 cycles from assembly code, IIRC.

 

To make that practical, you probably have to use a memory map that adds more RAM (such as the 42K map in cart.mac).

To be seen. I'll start with support for simple games, Defender-like

 

It will work, but it will not be artifact free. Scrolling the screen in-place takes over 4000 cycles. Assuming you have other work to do in your ISR (such as update MOBs, any sound effects/music if you're doing that, etc.) that means you're a couple rows into active display by the time the scroll completes. So, you will have artifacts along the top of the screen I'd imagine.

 

It won't be too bad for the top row during vertical scrolling due to the border extension, unless you are scrolling by more than 1px per frame (which you're going to want to be able to do). Similar for the left edge. Most artifacts will be visible in the upper right corner.

 

You can avoid this by requiring the programmer to provide the update in an offscreen buffer ahead of time. Or, have some ability to let the user's code fill incrementally during the scrolling process. (That starts to get complicated quickly.)

Hmmm...

 

Why are there separate offsetx/offsety if you have a direction flag also? Also, is it possible to scroll diagonally, or do you need to alternate between horizontal and vertical on alternate frames?

It's for setting directly the registers and other one to select the scrolling routine in the required moment.

Link to comment
Share on other sites

I'm not certain what memory maps are supported by IntyBASIC. I'd also like the option to be able to have carts produced. (I realize that this is not easy.) I believe that larger carts or those with RAM are more difficult/costly to produce.

 

FWIW, with JLP-based cartridges, there is no cost difference for games with RAM vs. games without. JLP supports games up to approximately 120K words (through bankswitching), with 8K words of RAM. All of this is integrated in the single chip on-board.

 

 

 

There is no "world" to the game that I am designing, I'll describe it for now as a simple side scroller. New elements will enter as the screen scrolls. As some of the objects entering the screen are large, I just need to keep track so that they can be drawn over multiple columns of cards. Nothing too difficult, just takes some thought into the best way to do it. I haven't written a game since the early 90's, so my programming skills are a bit rusty.

 

The way the NES handles this is to keep a larger-than-screen buffer in RAM, and draw everything there. The hardware scrolling registers on the NES's PPU control what subset of this larger window gets displayed. The window isn't as large as the entire "universe," rather, it's something like 2x the screen's normal dimensions. (I realize I'm abstracting some important details that are relevant to programming the PPU. Those details aren't as interesting here.)

 

So, for example, you could declare a 32 x 12 "screen" in RAM, to give you some buffer area to draw items that might be wider than one column when they become "visible", even if the object isn't entirely visible onscreen yet. The BACKTAB updating code just has to keep track of how to copy from this 32x12 off-screen buffer to the actual BACKTAB. But, that approach does require some cartridge RAM, as a 32x12 word window is 384 words (768 bytes). The other challenge with that approach is that you need to draw things in a "circular" manner in the off-screen buffer.

 

FWIW, this mechanism is approximately how games like Super Mario Bros work. They decompress the world to a buffer that's wider than the visible screen, and keep shifting the "start of image" circularly as the player runs to the right.

 

 

It's for setting directly the registers and other one to select the scrolling routine in the required moment.

 

Ah, ok. Perhaps you should break this into two pieces, since you'll likely want to update the horizontal and vertical delay registers every time, but only actually move BACKTAB when hdly or vdly "wrap".

 

For example, imagine a Super Mario Bros. type game where you're running to the right at 1px per frame. HDLY goes 7, 6, 5, 4, 3, 2, 1, 0 and then wraps back to 7. You only want to move the BACKTAB when HDLY goes from 0 back to 7. Your current syntax assumes you're scrolling BACKTAB every time.

  • Like 1
Link to comment
Share on other sites

Ah, ok. Perhaps you should break this into two pieces, since you'll likely want to update the horizontal and vertical delay registers every time, but only actually move BACKTAB when hdly or vdly "wrap".

 

For example, imagine a Super Mario Bros. type game where you're running to the right at 1px per frame. HDLY goes 7, 6, 5, 4, 3, 2, 1, 0 and then wraps back to 7. You only want to move the BACKTAB when HDLY goes from 0 back to 7. Your current syntax assumes you're scrolling BACKTAB every time.

The syntax will allow optional parameters, like this:

 

SCROLL 3
SCROLL ,3
SCROLL 0,,1
Link to comment
Share on other sites

Thanks nanochess. I look forward to installing IntyBASIC v0.5 and testing it with my game code. Native SCROLL support will be a big help.

 

Does the native scroll support keep MOBs stationary on screen or do they "scroll" with the background? My game currently requires that the MOBs stay still. If the SCROLL command moves the MOBs then I will just update them to appear in place.

 

I haven't had a chance to work on my game since the weekend. Going to try to get back to it again tonight.

Link to comment
Share on other sites

Thanks nanochess. I look forward to installing IntyBASIC v0.5 and testing it with my game code. Native SCROLL support will be a big help.

 

Does the native scroll support keep MOBs stationary on screen or do they "scroll" with the background? My game currently requires that the MOBs stay still. If the SCROLL command moves the MOBs then I will just update them to appear in place.

 

I haven't had a chance to work on my game since the weekend. Going to try to get back to it again tonight.

 

Hmmmm... I don't have tested it. I suppose you should keep track of position.

 

I was so happy that it worked that I forget completely about MOB.

Link to comment
Share on other sites

 

Hmmmm... I don't have tested it. I suppose you should keep track of position.

 

I was so happy that it worked that I forget completely about MOB.

 

Not a big deal. I would figure it out quickly enough.

 

My (bug filled) code already does smooth scrolling and MOB updates via pokes. Based on your comment I guess the MOBs will scroll with the screen if left untouched. I'll just keep my existing MOB position code in place and sub the new SCROLL command in where my bug filled scroll routine is.

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

I just heard on Intellivisionaries Podcast (yes I am behind I know) 6 in the interview of the developer of Dreadnaught Factor discovered it was faster to modify the (memory location of) cards of on-screen objects that were moving, rather than making the system work to find the actual object on-screen and then change it. Not directly related to scrolling, but I thought it might be a helpful concept when moving things that have to "work" on the scrolling field.

Link to comment
Share on other sites

I just heard on Intellivisionaries Podcast (yes I am behind I know) 6 in the interview of the developer of Dreadnaught Factor discovered it was faster to modify the (memory location of) cards of on-screen objects that were moving, rather than making the system work to find the actual object on-screen and then change it. Not directly related to scrolling, but I thought it might be a helpful concept when moving things that have to "work" on the scrolling field.

 

What do you mean by "making the system work to find the actual object on-screen"?

  • Like 1
Link to comment
Share on other sites

What do you mean by "making the system work to find the actual object on-screen"?

 

That had me scratching my head also.

 

I was thinking that perhaps it was a telephone-gamed explanation of changing pictures in GRAM to get animation all over the screen, rather than modifying many locations in BACKTAB to show different static images from a fixed set of cards in GRAM.

 

A quick trace in jzIntv's debugger suggests that all the "blinky" things on the Dreadnaughts, for example, are animated by modifying GRAM, not BACKTAB. To make all these different things animate, you just change their pictures in GRAM, and wherever they are on the screen, their pictures change to match. That's way easier than keeping a list of screen items somewhere that you have to walk through, or worse, actually scanning BACKTAB looking for instances of the thing you want to modify.

Link to comment
Share on other sites

 

That had me scratching my head also.

 

I was thinking that perhaps it was a telephone-gamed explanation of changing pictures in GRAM to get animation all over the screen, rather than modifying many locations in BACKTAB to show different static images from a fixed set of cards in GRAM.

 

A quick trace in jzIntv's debugger suggests that all the "blinky" things on the Dreadnaughts, for example, are animated by modifying GRAM, not BACKTAB. To make all these different things animate, you just change their pictures in GRAM, and wherever they are on the screen, their pictures change to match. That's way easier than keeping a list of screen items somewhere that you have to walk through, or worse, actually scanning BACKTAB looking for instances of the thing you want to modify.

 

Gotcha! That's the technique I use to animate the Snowflakes in Christmas Carol: they all share a single GRAM card that is animated by cycling through a set of "twinkling" frames. Once Carol "eats" a Snowflake, its BACKTAB card is changed to another GRAM slot, reserved for cycling the "disappearing" animation.

 

I can see how these techniques may have been new back then, and so felt more novel. I know that when I thought of doing them, to me it was like a "Eureka" moment, though one which was immediately followed by the thought of, "this must have been how they did it back then!"

 

-dZ.

  • Like 1
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...