Jump to content
IGNORED

Session 11: Colourful Colors


Recommended Posts

Hi there!

 

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.

 

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

Link to comment
Share on other sites

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.

colour_chart.zip

Link to comment
Share on other sites

Hi there!

 

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

Link to comment
Share on other sites

  • 4 months later...
    jsr _rts  ; 12 cycles, 3 bytes

306384[/snapback]

 

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.

Link to comment
Share on other sites

  • 3 weeks later...

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

post-3033-1119377453_thumb.jpg

Edited by Starman
Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

878537[/snapback]

 

 

'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

Link to comment
Share on other sites

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

878587[/snapback]

 

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

Link to comment
Share on other sites

  • 14 years later...

So what was the solve for the "Origin Reverse-indexed" issue with this chapter's code? Because I have it copied verbatim, and I'm getting the same

error. I see people changing the code and saying "do it this way instead", which is cool, but I'm first curious to know why code that should compile, doesn't.

 

 

Link to comment
Share on other sites

3 hours ago, SavedByZero said:

So what was the solve for the "Origin Reverse-indexed" issue with this chapter's code? Because I have it copied verbatim, and I'm getting the same

error. I see people changing the code and saying "do it this way instead", which is cool, but I'm first curious to know why code that should compile, doesn't.

 

 

Please post your code/files here and we'll have a look-see.

And, just being a bit pedantic... dasm is an assembler, not a compiler :)

 

Link to comment
Share on other sites

21 hours ago, Andrew Davie said:

 

Please post your code/files here and we'll have a look-see.

And, just being a bit pedantic... dasm is an assembler, not a compiler :)

 

Can't shake the habits of my day job I suppose. 

 

I got it to work, but I'll be darned if I know how. I literally cut out the words at the end to get it to assemble, watched it show me a black screen, messed with it to make it more like the stuff I've seen here, ran into some assembly errors, put the words back, then ran it again and it worked, and I'd swear it's identical to what I started with except for the patterns of the draw routine as I messed with them to see what I could change.  I do notice that the assembler is super sensitive to tabs and spacing for some things. 

 

Edited by SavedByZero
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...