How to optimize hblank, vblank, loop ?

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

KanedaFr
Administrateur
Posts: 1139
Joined: Tue Aug 29, 2006 10:56 am
Contact:

How to optimize hblank, vblank, loop ?

Post by KanedaFr » Fri Mar 14, 2014 11:34 am

Hi,

Like you know, the basic template to follow is

Code: Select all

void hblank()
{
//code 1
}

void vblank()
{
//code 2
}

int main()
{
  while(1)
  {
      //code 3
      waitSync();
  }

}
After some years playing with it, I'm still unable to know for sure what should be done in each part ("code x") to avoid lag :oops:
All my demos/games work with (almost) no lag because I move code from //code 2 to //code 3 when it starts to lags (or invert if there is no more lag), it's more a try-and-see that a real code workflow optimization.
And I'd like to fix this lack of knowledge !

What I know :
//code 1 should be very small (what is small?)
//code 2 could be more advanced
//code 3 is a problem because a hint/vint could occurs if too heavy

So, is there someone able to explain, for newb like me (!), how it could be handled the best way ?
I know, in asm, you can count the ticks needed by each part and so optimize at most each one (just insane, but doable)
How could I know if my code would produce a lag, will be interrupted by a vint or simply will crash ?

I hope my question is clear enough ;)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Fri Mar 14, 2014 5:35 pm

The hblank should really only be assembly because it HAS to be short and fast. In the hblank, you might change two or three colors, for example, or change a horizontal scroll value. You're looking at maybe a dozen assembly instructions.

The vblank can be C without causing too much trouble. You would start the DMA of tiles or other vram data, then read the joy ports in the vblank code. You might also advance a clock variable so the game has a sense of how much time has passed.

The main loop is most of your game logic, as well as setting up any DMA operations (decompress the rom data into ram, then set a table with the values needed to store to the VDP; the vblank routine will actually do the DMA by storing the values in that table to the VDP). Many games can always complete all these tasks before the next vblank, so you wait for the vblank after doing them. If your game can sometimes take longer than a vblank period, you need to check the clock variable to see if a vblank has indeed already occured, and skip the wait (or do other things needed to catch up - this can be the most complex part of any game).

If your game logic can take longer than a vblank, you also need to have handshaking on the tables you use for DMA operations so that the vblank doesn't try to do a DMA operation that you are still setting up. Maybe something like have a circular buffer for the operation values, and have a variable that tells the vblank how many to send and don't increment that variable until after you put all the values into the buffer. Be sure to use "volatile" on any variables that the interrupts and main code use in common.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Fri Mar 14, 2014 5:49 pm

In my stuff, I do everything in the while loop. I don't do anything during hblank or vblank interrupts (other than what the SGDK does by default).

Code: Select all

int main() 
{ 
  while(1) 
  { 
        // do code logic
      waitSync(); 
     // do dma transfers, update vdp scrolling, etc.
  } 

} 

I'm not even sure if that's the correct way of doing it. I've massaged the code over and over to get to the point where I don't see tearing or glitches any more.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:

Post by r57shell » Fri Mar 14, 2014 6:54 pm

Did you hear about MVC method?
(you can skip MVC description)
MVC = Model, View, Controller
controller = logics of changes/interaction
model = data structures, types.
view = representation of data.
It's common way of building websites. That's why I noted that you can skip it.
controller knows everything about model, and control it.
model does nothing, it's just "interface" between controller and view.
view: it's how you represend model ("draw").

Nevermind.
Be simple. There is two parts of game:
1) frontend = what user see
2) backend = what happens inside.
In my vision of making game, you should never skip backend updates. It means, that your game must run at full speed at least without any rendering (representation = frontend).
parts of backend: reading input, updating position, and else.
but frontend: it's DMA, palettes updates, sprites table recalculation and anything else.
just show in VBLANK what you get at current time.

I know, it's hard to understand where must be code, but I think, this is enough to avoid lags at least in game mechanics! FPS would be less (sprites updates, planes upload) but you'll play as if there is no lag.

But! If there is NO lag at all, then you can use djcouchycouch strategy.
Image

db-electronics
Very interested
Posts: 89
Joined: Mon Feb 24, 2014 6:04 pm
Location: Kapuskasing, Ontario, Canada
Contact:

Post by db-electronics » Fri Mar 14, 2014 7:01 pm

I remember reading somewhere, and I've searched for it for about an hour before posting but sadly I didn't bookmark it and can't seem to find it, about a trick that was used for console programming to visually determine how much "time" was left before the next VBLANK interrupt. I don't recall if it was for the Genesis specifically but there may be a way for you programming gurus to replicate this (I'm a hardware guy, go VHDL!).

Basically, the main loop would update the background colour at every HBLANK while the game code was executing, let's say, to red. At VBLANK, the background colour would be reset to some non-red value. Because there is a small part of the background colour that is visible at the screen's edge behind all other layers, this had the effect creating a red bar which grows from top to bottom with a length directly proportional to the time it takes for the main loop to complete. If the red bar completely fills the edge of the screen, it is a good indication that there are little to no cycles left in the main loop.

Makes sense? I wonder if this can be implemented on the Genesis?
What does db stand for? Well that's an excellent question...
http://www.db-electronics.ca

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

Post by Stef » Fri Mar 14, 2014 7:13 pm

Actually when you do everything in the main loop you have to take care about not enter in conflict with external device updates (VDP mainly) which can happen during VInt or HInt so when you access external devices in the main loop it is a good practice to protect access by disabling interrupts before and re enabling them after. SGDK now provides methods for that (SYS_disableInts() and SYS_enableInts()).

Also what r57shell about MVC model is interesting and generally you can be sure to have a good design by respecting this model. Model should be handled in main loop when View & Controller are probably handled in the VInt (actually that is not that simple).

MintyTheCat
Very interested
Posts: 484
Joined: Sat Mar 05, 2011 11:11 pm
Location: Berlin, Germany

Post by MintyTheCat » Fri Mar 14, 2014 9:11 pm

Chilly has some good points that needed to be made.

You are getting at Semaphores, Chilly :)

What I find remarkably odd is that most of you Guys appear to be writing in C. This is very strange for something like the Megadrive. I can honestly say that much of these oddities and strange behaviours become understandable the moment that you write pure Assembly.

What's missing also is that you Guys are not thinking about State-Machines and you need to seriously consider this.

Take a look at Sonic 1's HBlank and VBlank Routines and you'll see many sections that are executed depending on the state.

KanedaFr
Administrateur
Posts: 1139
Joined: Tue Aug 29, 2006 10:56 am
Contact:

Post by KanedaFr » Fri Mar 14, 2014 11:21 pm

Thanks everyone for all the tips !


@chilly willy: I like your idea of what should be on VBlank and loops.

@dj : I did this in my first demos... but it's clear not enought when you had lot of animated sprite and scrolling fields ;)

@r57shell : I use MVC a lot but nether thought to use it for Genny :oops:


@db-elec : i wonder if the trick is doable use hint to set color 0 of pal 0

@stef : I used disable/enable int before but I saw them more like a way to hide bad code, I mean you should not use them if your code is correctly done

@minty : C is easier to code and read, and historically, it was the only language with real samples (back on SGCC days). I'm already using State Machine but you still have to know where to code what.

MintyTheCat
Very interested
Posts: 484
Joined: Sat Mar 05, 2011 11:11 pm
Location: Berlin, Germany

Post by MintyTheCat » Sat Mar 15, 2014 12:37 am

KanedaFr wrote:Thanks everyone for all the tips !


@chilly willy: I like your idea of what should be on VBlank and loops.

@dj : I did this in my first demos... but it's clear not enought when you had lot of animated sprite and scrolling fields ;)

@r57shell : I use MVC a lot but nether thought to use it for Genny :oops:


@db-elec : i wonder if the trick is doable use hint to set color 0 of pal 0

@stef : I used disable/enable int before but I saw them more like a way to hide bad code, I mean you should not use them if your code is correctly done

@minty : C is easier to code and read, and historically, it was the only language with real samples (back on SGCC days). I'm already using State Machine but you still have to know where to code what.
Being easier to read has nothing to do with machine Performance, Kaneda. How can you say that it was the only example? All that you need to do is to disassemble any working Megadrive Game ROM and you have many, many Examples of 68K Code developed for the Megadrive. C will never reach the level of Performance that can be attained by writing Assembly by Hand. Plus C adds layers to the situation and you are not exactly sure what you have as the Compiler will be making decisions for you. In my Job I tend to write mainly C but I also have recourse to write pure Assembly too and it can make life much easier for many situations. Unless you have a symbolic Debugger you will also be forced to read Assembly and what's worse it is the Assembly that you now do not immediately recognise it as it was the Compiler who wrote it for you and not you. For simple Games though you can easily get away with writing C and that goes for tests and experiments but seriously consider switching to 68K Assembly for real Gamecode :D

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sat Mar 15, 2014 2:03 am

Stef wrote:Actually when you do everything in the main loop you have to take care about not enter in conflict with external device updates (VDP mainly) which can happen during VInt or HInt so when you access external devices in the main loop it is a good practice to protect access by disabling interrupts before and re enabling them after. SGDK now provides methods for that (SYS_disableInts() and SYS_enableInts()).

But if the game logic completes before the vblank, and doesn't use any "external device updates" (like Sega CD?) then it should be okay? If it doesn't complete before vblank, then where do you put the game logic code?
MintyTheCat wrote: For simple Games though you can easily get away with writing C and that goes for tests and experiments but seriously consider switching to 68K Assembly for real Gamecode
I think you could write something pretty decent in C. Maybe not, say, Contra Hard Corps or Gunstar Heroes, but I'm pretty confident you could write something similar to Shinobi.

Gigasoft
Very interested
Posts: 95
Joined: Fri Jan 01, 2010 2:24 am

Post by Gigasoft » Sat Mar 15, 2014 8:14 am

The point is, if the main program accesses the VDP, and then the VBL or HBL handler interrupts it in the middle of it and also accesses the VDP, then something's going to go wrong. Therefore, if the main program accesses the VDP, it must ensure that it doesn't get interrupted. Usually, the game logic should not access the VDP, except in situations where you disable the screen to load a whole bunch of stuff at once.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:

Post by r57shell » Sat Mar 15, 2014 10:36 am

MintyTheCat wrote:What I find remarkably odd is that most of you Guys appear to be writing in C.
I write in pure asm :)
MintyTheCat wrote:What's missing also is that you Guys are not thinking about State-Machines and you need to seriously consider this.
I use it, but, there is much cleaner way to do this.
You don't need to store state, all you need is, to store pointer on next code chunk :)

Also, about interupting during VDP Access. Don't do any VDP update outside VBlank. How to achieve it? Store all info you need for VBlank, and update there. If in your case this is impossible, then stay a while and think: may be something wrong?)
Image

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

Post by Stef » Sat Mar 15, 2014 10:46 am

r57shell wrote: Also, about interupting during VDP Access. Don't do any VDP update outside VBlank. How to achieve it? Store all info you need for VBlank, and update there. If in your case this is impossible, then stay a while and think: may be something wrong?)
Of course that is the ideal case you want to obtain (again the famous MVC model) but in some case, it would make your code much more complex just to avoid a single VDP access in your main code so in these particular cases, i am definitely not against disabling interrupts for a short bit of time ;)

MintyTheCat> About machine state logic stuff, honestly there is tons of ways of handling that. Machine state is just one of them.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:

Post by r57shell » Sat Mar 15, 2014 12:34 pm

I don't know such particular cases.

little example:

Code: Select all

main loop:

while (counter > 0)
{
++player_sprite.frame;
player.pos_x += 1;
...
}

vblank:
dma_total = 0;
//build sprite table
... // setting pos_x, and incrementing dma_total to size of sprite_table.
//update sprites frames
foreach s in sprite
{
   if (s.frame != s.prev_frame)
   {
       if (sprite_tilescount(s) + dma_total < DMA_MAX)
       {
           sprite_dma(s);
           dma_total += sprite_tilescount(s);
       }
   }
   if (s.need_update_pal)
   {
       sprite_load_pal(s);
       s.need_update_pal = false;
   }
}
// updating planes
foreach l in dma_plane
{
    if (dma_total + l.size < DMA_MAX)
    {
       if (l.type == DMA_HORIZONTAL)
       {
          dma_horizontal_line(l.pos, l.size);
       }
       if (l.type == DMA_VERTICAL)
       {
          dma_vertical_line(l.pos, l.size);
       }
       dma_total += l.size;
       remove_from_list(dma_plane, l);
    }
}
...
it's example how can you update sprites and layers in VBlank. So, if lag appears, then some sprites loading will be skipped, and planes update will be delayed, but not skipped and not slowdown your game mechanics.
Image

KanedaFr
Administrateur
Posts: 1139
Joined: Tue Aug 29, 2006 10:56 am
Contact:

Post by KanedaFr » Sat Mar 15, 2014 3:20 pm

;)

A little off topic but, I'm not telling you (or anyone) to use C, I'm just able to produce something in C while I'm only able the read 68000 asm.
It's how I am and I can't take the time to become an asm guru.
I'm more like code all I can using SGDK and re-code sensitive part in asm (like the uncompress func for ex which won't be as much powerful in C I think).
And using C, I know I won't ever make something as great as treasure game but, he!, even using asm, I won't ever make something as great as a treasure game because it's homebrew, not pro game we re talking about!
It could be interesting, on another post, to see which part should be written in asm (HInt is the 1st one).
It makes me remember of a talk I had with Stef asking if he planned to port SGDK to asm. His answer was something like "I don't see the reason to do it, FOR NOW" (@stef, correct me if my memory bugs!)


this post isn't about C vs ASM, it's about what to do in main loop, vint and hint. The several answers are valids for asm and C I think.
So it's up to every one to use it like it want, no ?

Post Reply