Jump to content
IGNORED

Looking for a better kernel example


Recommended Posts

Hello there,

 

I'm new here and new to Atari dev, but not new to 6502 assembly. That being the case, I am focusing a lot on the architecture of the TIA and the complexities of writing a decent kernel.

 

After reviewing this site's 2600 101 tutorial, I am a bit confused on the "dos and dont's" of when exactly to perform certain functions.

 

The examples given define what the TIA is doing, but I am hoping for a more comprehensive workflow so that I can visualize when it is best to perform game logic.

 

In the NES world, you are a bit more shileded from TV protocol. For instance, when the TV hits VBLANK, you automatically go into an NMI. I use this time to handle all of my drawing and the main game loop for my logic.

 

I'm happy to not use interrupts for a change, but I don't fully grasp WSYNC or HBLANK and what have you. I guess what I'm asking for is a better kernel example with documentation that provides best practices. Later down the line, I will develop my own tricks, but I would rather not reinvent the 30 year old wheel :)

Link to comment
Share on other sites

After reviewing this site's 2600 101 tutorial, I am a bit confused on the "dos and dont's" of when exactly to perform certain functions.

 

I'm happy to not use interrupts for a change, but I don't fully grasp WSYNC or HBLANK and what have you. I guess what I'm asking for is a better kernel example with documentation that provides best practices. Later down the line, I will develop my own tricks, but I would rather not reinvent the 30 year old wheel :)

 

The best thing to do is to understand how much time is available, and where.

When you're busy feeding data to the TIA, you really don't have a lot of available time. A few cycles per scanline, perhaps... depending on how sophisticated you get. Many games have no cycles spare at all during the scanline draw. But most games tend to use the timers to wait for particular time periods... so they set a timer (say, TIM64T) with a value and then do "some stuff" and then wait for INTIM to go to zero. THOSE are the places where you have "heaps" of time to really do game logic. And the amount of time depends on the value written to the timer in the first place (in TIM64T case, 64* the value written = cycles available to "do stuff").

It varies from game to game, but that's the gist of it. You do stuff when you can, and you do it as quickly as you can.

If you look at any kernel, you should be able to visualise the phases and how 'busy' they are. Where there are waits, and how much time is actually available in those 'slots' or 'blocks' or whatever you want to call them.

Now, WSYNC is at the single scanline level. If you've already set up everything for a scanline, you can do one of two things; manually do a delay loop until you hit 76 cycles at which point it's time to do stuff for the next scan line (because that's what the TV is now doing). OR, you can write to WSYNC and *presto* you've already hit 76 cycles. That's all it does.

The HBLANK is the 68 cycle period immediately after WSYNC is hit -- i.e., the first 68 cycles on a scan line, off the left edge of the screen if you like. This is before the TIA starts drawing stuff to the TV, if you want to think of it like that. You can set up TIA registers (e.g., playfield) in this period. That's all HBLANK is... it's not magical. 68 cycles isn't much.

Mostly, you're going to be using the vertical blank for your game logic, as that's where the most cycles are.

Cheers

A

Link to comment
Share on other sites

Now, WSYNC is at the single scanline level. If you've already set up everything for a scanline, you can do one of two things; manually do a delay loop until you hit 76 cycles at which point it's time to do stuff for the next scan line (because that's what the TV is now doing). OR, you can write to WSYNC and *presto* you've already hit 76 cycles. That's all it does.

The HBLANK is the 68 cycle period immediately after WSYNC is hit -- i.e., the first 68 cycles on a scan line, off the left edge of the screen if you like. This is before the TIA starts drawing stuff to the TV, if you want to think of it like that. You can set up TIA registers (e.g., playfield) in this period. That's all HBLANK is... it's not magical. 68 cycles isn't much.

Mostly, you're going to be using the vertical blank for your game logic, as that's where the most cycles are.

Just to clarify, the 76 cycles Andrew referred to are *machine* or *CPU* cycles, whereas the 68 cycles are *color* or *pixel* cycles. Since 1 CPU cycle equals 3 color cycles, 68 color cycles are only 22 and 2/3 CPU cycles-- not much time at all!

Edited by SeaGtGruff
Link to comment
Share on other sites

Ok, that sort of makes sense to me. I am about halfway through Andrew's '2600 for newbies now. Here is what I don't understand yet:

 

All the build up about the TIA sort of makes it sounds horrible to use. I still can't figure out what is my responsibility as the programmer.

Do I need to try to cram stuff into WSYNC and HBLANK or not? Maybe I can explain how I would handle things in NES dev and maybe you

guys can help me understand better from that perspective. During VBLANK, I wouldn't handle any logic other than controller latches and

of course DRAWING. However, I would determine what needed to be drawn during non VBLANK time. I created a buffer that I would write

certain op codes, so to speak. These would be reviewed by the drawing handler that was triggered during NMI only if something needed to

be drawn in the first place. Granted, I had plenty of zero-page registers and other RAM to work with. But it made TV protocol a sinch. I never

had to fret over coliding with the PPU (the NES's graphics chip).

 

Now that I'm in Atari territory, I'm just not sure yet when to handle game logic because it seems that the majority of my working time is in VBLANK,

as where it was the opposite in NES dev. VBLANK was a sacred time slot that you didn't want to fool around with.

I think I understand all the 76 cycles and calculating color cycles vs. machine cycles. And I'm fluent in 6502 assembly.

I've even written my own javascript assembler. I just don't quite grasp the TIA yet.

Edited by johnnystarr
Link to comment
Share on other sites

Ok,

 

So you're saying:

 

VBLANK: Game Logic

HBLANK: Draw Screen

Overscan: Game Logic

 

I'm glad to see where this is going. But I would like to understand HBLANK a bit more. Should I view it like, during VBLANK decide what will be drawn at HBLANK, fill some RAM with the details and

then halt the system and let HBLANK draw what was determined in Overscan or VBLANK?

 

Or are you saying that I need to write the changes to the TIA druing HBLANK?

 

Or, am I overthinking this? Is it that during HBLANK, we just need to waste some cycles in order for it to draw so I have the choice of running some gamelogic during HBLANK and count all the machine cycles

and calculate the exact time that the screen needs to be drawn so that I can kill 2 birds with one stone?

Link to comment
Share on other sites

As you have probably figured out by now on the 2600 the TIA handles HBLANK while it is the programmer's responsibility to handle VBLANK and Overscan. Thus the programmer can adjust the amount of scanlines, speeding up or slowing down the framerate. PAL is 50 fps, NTSC 60.

 

During the kernel HBLANK becomes important because it a small amount of time available to the programmer to make update player graphics and other objects before the scanline gets drawn. However, often objects get updated while the scanline is getting drawn, not in the HBLANK period!

 

 

An example is the six digit score found in almost every Atari game. Multiple copies of the player sprites are used (three copies close), but left to their own devices each copy would look the same. A couple of tricks are used to update the player graphics so each copy is unique, but in short it does involve updating each copy after the previous is drawn.

Link to comment
Share on other sites

Also, if you want to draw an "asymmetrical" playfield-- where the right half is neither a repetition nor reflection of the left half-- then you must update each playfield register while the line is being drawn, and (as with updating the sprites for the score display) the timing is important-- first set the playfield registers for the left half of the screen, then update each one for the right half of the screen *after* the left copy has been drawn but *before* the right copy gets drawn.

 

Even if you aren't drawing an asymmetrical playfield, sometimes it's necessary to update the playfield registers during the scan line so you have more time to update other things during the HBLANK. As long as you update each register *before* it gets drawn, life is good. For example, you might want to arrange your main drawing loop such that it begins sometime after the middle of the scan line, so you can start updating the playfield registers *before* HBLANK-- i.e., you're setting up the playfield for the *next* line-- as long as each playfield register doesn't get updated until *after* its right copy has been drawn. If you're using the "repeated" playfield mode, you can update PF0 shortly after the middle of the screen, then update PF1, then update PF2 during HBLANK or later:

 

begin the loop (in the middle of a scan line)

write PF0 for the "next" scan line (after the right copy of PF0 has been drawn)

write PF1 for the "next" scan line (after the right copy of PF1 has been drawn)

HBLANK begins

write other graphics registers during HBLANK (sprites and colors-- things that can't be updated while the line's being drawn)

HBLANK ends

write PF2 (before the left copy of PF2 gets drawn)

go back to the beginning of the loop

 

Also, if you construct the timing of your loop just right-- exactly 76 machine cycles-- you don't need to use WSYNC, which lets you use every possible cycle for making graphics updates (and of course for updating and checking the loop counter).

 

Of course, you can update anything while the line's being drawn, even sprite colors and sprite graphics, but that's tricky because it can lead to "tearing" (things getting changed *while* they're being drawn) or things getting updated too soon or too late (such that the vertical positioning of the sprite ends up being a line off from what you wanted). But if you know for a fact where a particular sprite is, you might be able to update that sprite's color and graphics during the scan line. For example, you could have different variations of the drawing loop, and perform one or the other of the variations depending on whether a given sprite is on the left side of the screen or on the right side of the screen-- for example:

 

variation 1-- we know that player0's horizontal position is past a certain point on the right side of the screen

so we can update player0's color and graphics while the line's being drawn, sometime before player0 gets drawn

 

variation 2-- we know that player0's horizontal position is before a certain point on the left side of the screen

so we can update player0's color and graphics for the "next" line while the "current" line's being drawn, sometime after player0 has already been drawn

 

Of course, the color registers aren't just for the players and playfield-- the playfield color is also used for the ball, and each player's color is also used for its missile-- so you'd need to take the missiles' horizontal positions into account, too. And since the whole point would be to squeeze more changes onto each line by arranging the order and timing of each change just so, you wouldn't want to choose between loop variations *while* the loop's being performed-- you'd want to make that determination during VBLANK so you can choose one loop or another as appropriate, and that means you'd have only one copy of the sprite on the screen. On the other hand, if you've split the screen into horizontal bands so you can reposition the sprites from one band to the other (as in the rows of vehicles, logs, etc. in Frogger), you could choose between loop variations from one band to the next.

 

I didn't mean to confuse the issue by throwing in all those ideas, but the main point is that if you time things just right-- including the order in which things get updated-- you can get a lot more done during the scan line.

Edited by SeaGtGruff
Link to comment
Share on other sites

Use HBLANK time to update TIA registers for items that can be on any position of the scanline BEFORE the scanline is drawn.

 

If you update registers too late you'll get shearing, like seen in shazz's topic. In the screenshots he posted, the frowny face that's shifted down 1 scanline from the other sprites is an example of shearing. This occur because the update to GRP1 was too late, it occurred after that copy of the player (sprite) was output to the screen.

 

If you have items that are only on the right side of the screen you can update them after HBLANK, but you must still update their registers before the item is drawn.

 

There are some techniques using the VDELxx registers that let you update registers later in the scanline, but that's a bit more advanced than is suitable for a "newbie topic".

Link to comment
Share on other sites

There are some techniques using the VDELxx registers that let you update registers later in the scanline, but that's a bit more advanced than is suitable for a "newbie topic".

 

I think I'm starting to get the idea now. I am probably getting ahead of myself anyway. I'm going to follow along with the newbie guide and do all the exercises.

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...