Jump to content



1

Session 11: Colourful Colors


34 replies to this topic

#26 Cybergoth OFFLINE  

Cybergoth

    Quadrunner

  • 8,207 posts
  • This is Sparta!
  • Location:Bavaria

Posted Sun Jan 9, 2005 4:54 AM

Hi there!

xucaen said:

I noticed Andrew's example begins with vertical sync, but yours begins with vertical blank. I've seen other examples that begin with overscan. That's enough to confuse me.   :? So my biggest misunderstanding at the moment is... how do I know where to begin? Do I control the tv or do I try to keep up with it?

You control the TV yes. That's one of the coolest featrures of the VCS. It doesn't matter how you begin your loop, because after the first VSYNC sequence - you're synced. The order does matter though:

NextScreen
VSYNC
VBLANK
Kernel
Overscan
JMP NextScreen

is as fine as

NextScreen
VBLANK
Kernel
Overscan
VSYNC
JMP NextScreen

The layout I'd personally recommend though is starting the Kernel code right after the first ORG F000:


     ORG $F000



DoKernel

     Kernel      ; Kernel Code here

     JMP Overscan



CartridgeEntry

     CLEAN_START

     Init Code  ; Initialisation code here



NextScreen

     VSYNC

     VBLANK    ; VBLANK calculations here

     JMP DoKernel



     Overscan ; Overscan calculations here

     JMP NextScreen



     Subroutines; Subroutines and data fill the rest

     Data            ;



     ORG FFFC

     .word CartridgeEntry


That way, with your kernel code being on top of the ROM space, it does not get shifted around, thus it's safe from all timing issues like crossing pages.

xucaen said:

For instance, if I move #2 into VSYNC in the middle of drawing the playfield, does the electron beam return to the top of the screen? I just did a test and that's what it looks like is happening. And is it the same for VBLANK and RSYNC as well?

Precisely. The VBLANK register for example will switch the electron beam on and off immediately, wherever you want.

Greetings,
Manuel

#27 Haydn Jones OFFLINE  

Haydn Jones

    Moonsweeper

  • 287 posts
  • I love you... Atari
  • Location:cardiff, wales, uk, europe, earth, sol solar system, western spiral arm of the milky way

Posted Mon Jan 10, 2005 7:43 PM

Hi,
I'm new here, i have been enjoying the tutorials so far.

I have run in to a problem. I dont know if the code is wrong, or if it is the new compiler/vcs.h file.

I took the kernal from the previous tutorial and tride changeing the relavant block with the following....


 ; 192 scanlines of picture...



        ldx #0

        ldy #0

        REPEAT 192; scanlines



              inx

              stx COLUBK



              nop

              nop

              nop



              dey

              sty COLUBK



              sta WSYNC



        REPEND 


This showed up the same as befor an stellax and z26




               ; 192 scanlines of picture...



                ldx #0

                ldy #0

                REPEAT 192; scanlines



                    nop

                    nop

                    nop

                    nop

                    nop

                    nop

                    nop

                    nop

                    nop

                    nop

                     

                    inx

                    stx COLUBK



                    nop

                    nop

                    nop



                    dey

                    sty COLUBK



                    sta WSYNC



                REPEND 


This showed blank on both, but the example binary worked fine.
I am useing win 2k and the latest vcs.h macro.h and dasm.

PS. I have made a pdf (ziped) of the colour charts for anyone intrested as they are not available any more from the link.

Attached Files



#28 Cybergoth OFFLINE  

Cybergoth

    Quadrunner

  • 8,207 posts
  • This is Sparta!
  • Location:Bavaria

Posted Mon Jan 10, 2005 7:51 PM

Hi there!

Haydn Jones said:

This showed blank on both, but the example binary worked fine.

The code between REPEAT and REPEND gets copied into the binary 192 times. That is in the second case 192*21=4032 Bytes!

So you'll either need to read on and learn about loops or bankswitching. I'd suggest the first for the moment :)

Greetings,
Manuel

#29 supercat OFFLINE  

supercat

    Quadrunner

  • 6,367 posts

Posted Fri Jun 3, 2005 12:25 AM

Andrew Davie, on Tue May 27, 2003 9:29 AM, said:

     jsr _rts  ; 12 cycles, 3 bytes

View Post


This presupposes that the stack pointer is set somewhere in usable RAM. On most computers, this would be a reasonable assumption, but not on the 2600. Many programmers do weird tricky things like point the stack pointer at TIA registers. Using a "JSR KnownRTS" instruction in such cases will cause the processor to execute code in some 'random' spot.

BTW, if the IRQ vector points to an RTI, a BRK instruction followed by an arbitrary byte could be used for a slightly more compact time-waster in cases where the there are 3 bytes available at the stack pointer.

#30 Starman OFFLINE  

Starman

    Chopper Commander

  • 112 posts

Posted Tue Jun 21, 2005 12:11 PM

My turn :)

Ok, I understand the mechanics of the VCS and controlling the TV. I've been a programmer since '83 with a touch of hardware engineering. Here's what's bugging me.

I took the code example and tweaked it a bit because I wanted to know exactly what those sleep macros were doing. Here's my understanding so far:

sleep 15 = 15 nop instructions.

1 nop instruction = 2 cycles.

15 nops = 30 cycles = 90 clock counts (3 clock counts/machine cycle)

So based on that, I would expect the second "band" to start further from the left than my code shows. Looking at it blown up in Photoshop, there seems to be only one pixel between the left edge and the second band. That would mean the second band is starting on clock count 70, which is 20 cycles further to the left than I expected it to be.

Also, the first and second band, both defined by "sleep 10", seem to be different sizes. Band 1 is 78 pixels across, band 2 is 90.

I'm confused, and it's probably something real simple, too. If anyone can shed light on this, I'd appreciate it. Thanks.

Mike


           processor 6502

           include "vcs.h"

           include "macro.h"



           SEG

           ORG $F000



Reset

StartOfFrame



; Start of vertical blank processing



           lda #0

           sta VBLANK



           lda #2

           sta VSYNC

           

            ; 3 scanlines of VSYNCH signal...



               sta WSYNC

               sta WSYNC

               sta WSYNC



           lda #0

           sta VSYNC           



            ; 37 scanlines of vertical blank...


            ldx #0

VBLANKTOP   sta WSYNC

            inx

            cpx #37

            bne VBLANKTOP


   ldx #192  ; 192 scanlines of picture...

   ldy #0

   STY COLUBK

   STY VBLANK

drawpictureloop

   sta WSYNC

   iny

   sty COLUBK

   sleep 15

   stx COLUBK

   sleep 10

   sty COLUBK

   sleep 10

   dex

   stx COLUBK

   BNE drawpictureloop


   lda #%01000010

   sta VBLANK                    ; end of screen - enter blanking


; 30 scanlines of overscan...

            ldx #0

OVERSCAN    sta WSYNC

            inx

            cpx #30

            bne OVERSCAN


           jmp StartOfFrame





           ORG $FFFA



           .word Reset        ; NMI

           .word Reset        ; RESET

           .word Reset        ; IRQ



    END

Attached Thumbnails

  • colortest.jpg

Edited by Starman, Tue Jun 21, 2005 12:18 PM.


#31 Cybergoth OFFLINE  

Cybergoth

    Quadrunner

  • 8,207 posts
  • This is Sparta!
  • Location:Bavaria

Posted Tue Jun 21, 2005 2:28 PM

Hi there!

Starman, on Tue Jun 21, 2005 6:11 PM, said:

sleep 15 = 15 nop instructions.

View Post


That's where you got confused.

Sleep 15 = 15 cycles.

It does 6 NOP + 1 DOP.

Greetings,
Manuel

#32 supercat OFFLINE  

supercat

    Quadrunner

  • 6,367 posts

Posted Tue Jun 21, 2005 5:48 PM

Cybergoth, on Sun Jan 9, 2005 5:54 AM, said:

The layout I'd personally recommend though is starting the Kernel code right after the first ORG F000:

     ORG $F000

DoKernel
     Kernel     ; Kernel Code here
     JMP Overscan
...
     ORG FFFC
     .word CartridgeEntry

That way, with your kernel code being on top of the ROM space, it does not get shifted around, thus it's safe from all timing issues like crossing pages.

I put my sprite data at $F000, padded out to a multiple of 256 bytes, but arranging for the kernel to be page-aligned is definitely a good idea.

I personally prefer to JSR the kernel, though, at least for the programs I've written where one sometimes has to do calculations that don't really fit between display and VSync. I'm currently reworking one of my programs to try to increase the time available for calculation; I haven't figured out how best to handle the frame counting, but the basic concept is:
Kernel:
  bit INTIM
  bpl Kernel1
Kernel1:
  stx xsave
  inc fct
  lda fct
kwt2:
  bit INTIM
  bmi kwt2
  sta WSYNC
  lsr
  bcs  mainframe
  lda #$42
  sta VSYNC
  sta WSYNC
  sta WSYNC
  lda #[whatever]
  sta INTIM64  [forget the name]
  lda fct
  clc
  adc #1
  and #$7F
  sta fct
  sta WSYNC
  lda #$00
  sta VSYNC
  ldx xsave
  rts
mainframe:
 ; Main display kernel goes here (nine cycles after the last WSYNC)
  ...
  lda #[whatever]
  sta INTIM64
  ldx xsave
  rts

maincode:
 ; Periodically, in a time-consuming loop:
  bit INTIM
  bpl $+5
  jsr Kernel1  ; A and Y trashed; X preserved
 ; BPL target is here

This would allow user code to execute during both vertical blanking intervals. The values loaded into INTIM64 would have to be chosen so that the timer would expire about 150-200 cycles BEFORE the target horizontal blank. The user code would then have to ensure that it checked INTIM every 100 cycles or so [with a 3, 4, 5, or 6 cycle penalty each time it did so depending upon method]. It would be possible to save code space at the expense of time by making the kernel return if INTIM was not yet negative and calling it unconditionally. The extra 12 cycles overhead would seem unduly severe, however.

#33 Andrew Davie OFFLINE  

Andrew Davie

    Stargunner

  • 1,314 posts
  • Location:Tasmania

Posted Tue Jun 21, 2005 7:01 PM

supercat, on Wed Jun 22, 2005 9:48 AM, said:

This would allow user code to execute during both vertical blanking intervals.  The values loaded into INTIM64 would have to be chosen so that the timer would expire about 150-200 cycles BEFORE the target horizontal blank.  The user code would then have to ensure that it checked INTIM every 100 cycles or so [with a 3, 4, 5, or 6 cycle penalty each time it did so depending upon method].  It would be possible to save code space at the expense of time by making the kernel return if INTIM was not yet negative and calling it unconditionally.  The extra 12 cycles overhead would seem unduly severe, however.

View Post



'2600 Boulder Dash ® uses a slightly different technique, but along the same lines -- distributing complex calculations into the available calculating time in the 'vertical blank intervals'. In fact, these are the intervals where we are waiting for the timer to reach 0. Your method employs continuous checks. Boulder Dash ® uses a rough approximation (but always high) of the amount of time a particular bit of code will take (in TIM64T units) and pre-checks if there is enough time left on INTIM before proceeding with doing that bit of code.

Something like this...

    lda INTIM
    cmp #TIME_REQUIRED_FOR_THIS_SECTION
    bcc notEnoughTime
    jsr DoThisSection
notEnoughTime

'2600 Boulder Dash ® subdivides the game logic into many small steps, and approximates (always high) the time requirement for each of these steps. By doing this, much work can be done inside the intervals while waiting for INTIM to zero, without many checks of the INTIM value being required while you are doing that work. As stated, just the one check at the start of each step -- and that check specific to the amount of TIM64T 'ticks' required for that step.

As a reference point, to draw a 'character' in '2600 Boulder Dash ® takes about 12 TIM64T ticks maximum, and to process the slowest object (eg: boulder) is about 25 ticks.

One niceity about this system is that if a step ends early, that's fine -- we just have to guarantee that it is never late. If it ends early, we just proceed on to the next step, first checking there are enough ticks left for IT to do its stuff. If there aren't, we're coming to the end of the delay timer anyway, so we return.

Cheers
A

#34 supercat OFFLINE  

supercat

    Quadrunner

  • 6,367 posts

Posted Tue Jun 21, 2005 7:26 PM

Andrew Davie, on Tue Jun 21, 2005 8:01 PM, said:

'2600 Boulder Dash ® uses a slightly different technique, but along the same lines -- distributing complex calculations into the available calculating time in the 'vertical blank intervals'.  In fact, these are the intervals where we are waiting for the timer to reach 0.  Your method employs continuous checks.  Boulder Dash ® uses a rough approximation (but always high) of the amount of time a particular bit of code will take (in TIM64T units) and pre-checks if there is enough time left on INTIM before proceeding with doing that bit of code.

View Post


Interesting. I can see that could offer a performance benefit over doing constant checks. If the code is broken up into pieces of about 64 cycles each, that benefit would amount to about 8%. Possibly worthwhile for programming wizards trying to eke out every possible bit of speed in a processor-intensive game, but not so import for mere mortals merely trying to avoid losing vsync while recalculating information for the start of a new level, etc. (users won't care if it takes 5 frames or 10 frames to place all the obstacles in a new level, but losing vsync is ugly).

#35 Starman OFFLINE  

Starman

    Chopper Commander

  • 112 posts

Posted Wed Jun 22, 2005 6:11 AM

Thanks for the heads up on the sleep macro. I misread the macro. I got it working now.

Mike




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users