Palette color switching in HBlank

For anything related to VDP (plane, color, sprite, tiles)

Moderators: BigEvilCorporation, Mask of Destiny

Post Reply
mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Palette color switching in HBlank

Post by mitchConnor » Tue Jul 16, 2013 9:46 pm

Hi guys.

First of all, I'm new here. But I have read quite a lot of posts already and lots of other Genesis info. I'm currently starting to port an existing iPhone retro game to the Genesis. It's not very far yet, just some level scrolling code and a couple music prototypes using Echo and sgdk. Nothing to show yet.

What I'm currently trying to do is simply creating a background gradient by simply changing color 0 every 16 lines. However, the problem is that the color doesn't switch fast enough. The old color is still drawn for about half of the next line.

The way I implemented it is the naive approach of setting a HInt handler
that does nothing except setting the one color, like this:

Code: Select all

void handleHBlank()
{
	VDP_setPaletteColor(0, 0, gradientTestColor);
	gradientTestColor -= 0x100;
}
I have even added some optimizations (which I'm not sure are necessary):
1. Changed the VDP_setPaletteColor to macro to make sure it gets unrolled
2. bypassed the sgdk HInt handler and directly called my own.

What could I be doing wrong? I have read a couple threads that touched this topic, but I couldn't quite figure out what I'm supposed to do. Is it generally not possible to switch colors using this approach? Do I have to use DMA? It seemed overkill to me to use DMA to transmit just one value.

Your help is much appreciated.

tiagosr
Newbie
Posts: 3
Joined: Thu Jul 04, 2013 10:46 pm
Location: Rio de Janeiro, Brazil

Post by tiagosr » Wed Jul 17, 2013 12:42 am

Hi! I'm also new here, and am doing a game with raster effects on H-INT. It might be that your H-INT routine is taking too long to actually write something - as VDP_setPaletteColor(...) is a subroutine call, it might be pushing all registers to the stack before actually changing the palette.

I suggest you look at making the routine in assembly - in my case (as I'm making a racing game like Super Hang On) I ended up putting it in RAM and building it on frame start. You might not even need to build it each frame like me, but using assembly there won't hurt - 68k assembly is really nice. There's some variants of 68k instructions (like MOVE.w) which don't even need to touch the registers to send data, so saving and restoring registers is probably not necessary.

Hope that helps!

mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Post by mitchConnor » Wed Jul 17, 2013 1:40 pm

Thanks for your reply. I'm not really an assembler expert, but I got it working. I did it by looking at the code in MESS debugger and copying the needed fragments to sega.s.
So after this, when I press F7 (jump to next interrupt) in MESS, it's only these lines:

Code: Select all

move.l #$c0000000, $c00004.l
move.w $ff042e.l, $c00000.l
addi.w #$100, $ff042e.l // this color change is pretty random, just a test case
rte
BUT even with this absolutely minimal code, I can still see about 8 pixels of flickering in the following line (on the actual hardware).
Seriously, it can't get any simpler, can it?

PS: I didn't understand what you mean by putting it in RAM.

tiagosr
Newbie
Posts: 3
Joined: Thu Jul 04, 2013 10:46 pm
Location: Rio de Janeiro, Brazil

Post by tiagosr » Wed Jul 17, 2013 3:33 pm

One other thing I did (along with ensuring no other part of the code did write to the VDP during active lines) was setting up the CRAM address in the VDP so that the palette write is actually the first thing the routine does - this makes the write show up in time for the only access slot available before the VDP starts drawing stuff. Your code can do it like this:

Code: Select all

move.w $ff042e.l, $c00000.l // first write the colour to the vdp
subi.w #$100, $ff042e.l // then you can do other things, like the subtraction you mentioned the other post (notice you don't need to hard-code addresses like these, you can access every non-static global variable in C code from assembly right away)
move.l #$c0000000, $c00004.l // then the last op before exiting is going to set the vdp up for the next interrupt
rte
Then I also set up that address as the last step right before the screen starts rendering, in the V-INT code (right after having finished uploading everything else to the VDP, in time for the next frame).

Well, by putting the code in RAM I meant, especially in my case that there is also v-scroll updating and other stuff, I made it so that I can build the code in a chunk of RAM that is pointed to by the ROM header directly. This code could be something static that you just copy there, or you can build it up each frame like I'm doing - granted, my code is overkill for what you set up to do. Basically every line sets up stuff for the next, including writing the next jump destination in the first instruction - thank goodness the 68k instruction set is regular as it is to allow for that. I'll post it up later, as I'm not at home right now.

mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Post by mitchConnor » Wed Jul 17, 2013 4:38 pm

Thanks again.
Most of it works now. But I have an interesting issue left:
As long as the screen is static or I scroll only vertically, it's fine, but when I scroll horizontally, it flickers again, but only at the first color switch.
My only guess is that my vblank routine might take too long, but that is unlikely because then I would see other artifacts as well.
Do you have another idea?

(I have added the "setup" call at the end of my vblank routine, and I'm pretty sure that I don't do any other VDP stuff in-between)

PS: Also I'm wondering: In other threads people were talking about switching three colors during hblank. How the hell should that be possible? I can hardly switch one :(

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Wed Jul 17, 2013 9:51 pm

Actually you should not try to set the color of the current scanline in the HInt handler as you will miss it anyway (the HInt starts to late to let you modify the current scanline) so modify the next one instead. The simplest way to do that is to wait for the HBlank flag in the Hint callback then write to CRAM as soon you are in HBlank... if that still don't work, just read the H Counter and wait until you raise a specific value then do your stuff on CRAM :)

mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Post by mitchConnor » Wed Jul 17, 2013 10:27 pm

Hm, ok, just to make sure I understand it correctly: Actually I should "waste" a whole scanline by setting the color for the scanline that comes after the one which is after the end of the line at which the interrupt happens? :)

Or to put it differently: If I set the interrupt counter to 7, the interrupt will be triggered after the 8th drawn line for which I would have a very short amount of time to set values for the 9th line, but instead I wait for the hblank flag and set values for the 10th line?

tiagosr
Newbie
Posts: 3
Joined: Thu Jul 04, 2013 10:46 pm
Location: Rio de Janeiro, Brazil

Post by tiagosr » Thu Jul 18, 2013 1:42 am

Thanks, Stef, that actually makes more sense than trying to fit just one or two accesses, which was what I was getting. But this way if one is to do stuff each line (like change some colours and vscroll for plane A) I guess there's no use triggering a H-INT each line anymore - I should keep on spinning inside the interrupt for the first line until the screen is over... is there any other way to do this? Any way I can still do useful work during the active display with this scheme?

Sorry to ask these questions in your thread, mitchConnor, but I hope you'll want to know that too.

mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Post by mitchConnor » Thu Jul 18, 2013 1:55 am

Yes, I do :) I'm still a bit confused about this topic and still haven't got it running properly. (And I understand your issue. I was wondering the same)

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Thu Jul 18, 2013 8:10 am

To reply you both, yeah basically you have to waste one scanline to update the next one... Then now if you want to change value for each scanline you have indeed to avoid the hint callback and directly do it in your main loop: you wait for scanline 0 (with V Counter, be careful te the rollback) then you spent all the active period for the raster effect. Still you have the whole vblank period to achieve the rest of the game logic (you can use the vint handler for that for easy synchronisation)...
I don't see any easy way of doinf full scanline raster effect while having time left in active period to do others stuff... you can try use pure assembly then but still that won't be an easy task ias you will have to "interleave" your raster effect and the other code.

mitchConnor
Newbie
Posts: 8
Joined: Tue Jul 16, 2013 9:25 pm
Location: Darmstadt, Germany

Post by mitchConnor » Thu Jul 18, 2013 2:08 pm

Okay, I finally got it working now :) I hope future generations will also benefit from this thread ;)

Btw, here my "final" code:

Code: Select all

void handleHBlank()
{
	asm("move.l #0xc0000000, 0xc00004.l");
	while((*((volatile u16*)GFX_CTRL_PORT) & 4) == 0);
	asm("move.w gradientTestColor2, 0xc00000.l");
	gradientTestColor2 -= 0x100;
}
Sorry, this is still a mess (I was copying a lot from sega.s to main.c and back, so it's C/asm mix). But the idea is:
1. Prepare everything (in this case only setting up the color switch call)
2. Wait until HBlank flag becomes true
3. And now immediately finish off the color switching call by writing the color to the address
4. At last, calculate the color for the next blank (this could theoretically be done at step 1 I guess). In future I will just have a LUT prepared.

Toddo
Interested
Posts: 12
Joined: Thu Sep 12, 2013 5:08 am
Location: Australia

Post by Toddo » Thu Jan 02, 2014 10:24 pm

Correct me if i'm wrong but, wouldn't it be easier to do a pseudo gradient of 8 (as to only use half of the bg 2 palette) or so colours that had been drawn 16 pixels high and dithered like so:

https://drive.google.com/file/d/0B7Inuo ... sp=sharing

The palette is swapped mid image at Y*0F

Palette swaps also are kind of glitchy on real hardware, hence why Sonic games have flickering sprites over the top of water, as to hide dots (CRAM errors maybe?) that appear at the top of the palette swap.[/img]
-Chill dude, the 68k can handle it.

Mask of Destiny
Very interested
Posts: 615
Joined: Thu Nov 30, 2006 6:30 am

Post by Mask of Destiny » Thu Jan 02, 2014 11:32 pm

Toddo wrote: Palette swaps also are kind of glitchy on real hardware, hence why Sonic games have flickering sprites over the top of water, as to hide dots (CRAM errors maybe?) that appear at the top of the palette swap.[/img]
CRAM is single port so any updates that occur during the active display will result in the color being written to be displayed rather than the intended color. If you can keep all your writes in hblank you won't have a problem; however, most of the VDP external access slots are during the active display. You can free up some VDP bandwidth during hblank by disabling the display, but this will cost you some of your sprite rendering.

masteries
Very interested
Posts: 53
Joined: Thu Jul 30, 2020 3:33 pm

Re:

Post by masteries » Tue Nov 02, 2021 1:27 pm

mitchConnor wrote:
Thu Jul 18, 2013 2:08 pm
Okay, I finally got it working now :) I hope future generations will also benefit from this thread ;)

Btw, here my "final" code:

Code: Select all

void handleHBlank()
{
	asm("move.l #0xc0000000, 0xc00004.l");
	while((*((volatile u16*)GFX_CTRL_PORT) & 4) == 0);
	asm("move.w gradientTestColor2, 0xc00000.l");
	gradientTestColor2 -= 0x100;
}
Sorry, this is still a mess (I was copying a lot from sega.s to main.c and back, so it's C/asm mix). But the idea is:
1. Prepare everything (in this case only setting up the color switch call)
2. Wait until HBlank flag becomes true
3. And now immediately finish off the color switching call by writing the color to the address
4. At last, calculate the color for the next blank (this could theoretically be done at step 1 I guess). In future I will just have a LUT prepared.
hmmm...

This code is not working for me; it changes colour 1 instead colour 0; and it produces strange things when horizontal scroll is running.

Due to I am interested in create sky gradients, via change colour index 0; I tested this piece of code, but with no success.

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Re: Palette color switching in HBlank

Post by Stef » Tue Nov 02, 2021 2:23 pm

Declare your H-Int function like this:

Code: Select all

void hint()
{
  PAL_setColor(0, gradientColor);
  gradientColor-= 0x100;
}

Then prepare your H-Int gradient effect using this from your main code:

Code: Select all

        // each 4 lines
        VDP_setHIntCounter(4);
        // enable H-Int
        VDP_setHInterrupt(TRUE);
        // set H-Int callback method
        SYS_setHIntCallback(hint);
        
        ...
        
        while(TRUE)
        {
          ..
          SYS_doVBlankProcess();
          // init base h-int color during vblank
          gradientColor = 0xE82;
        }

Post Reply