Mode3 blitter boost

Topics related to the API, programming discussions & questions, coding tips, bugs, etc. should go here.
User avatar
Jubatian
Posts: 1561
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Mode3 blitter boost

Post by Jubatian »

Preliminary support for higher resolution and some extra features were added:

https://github.com/Jubatian/uzebox/tree/mode3_modding

Currently only for the scrolling Mode 3, I will merge in the non-scrolling variant in a further update. It should be capable to compile with any of the games using Mode 3 (without customizations). I tested it with the Super Mario Demo and Iros, both work correctly (although it is a bit funny to see the map being drawn on the right when I set it to have 31 horizontal tiles instead of the default 28).

The high resolution option can be turned on by setting RESOLUTION_EXT to nonzero. Modifying SCREEN_TILES_H is now accepted with the scrolling Mode 3, and works as expected (will center any width correctly).

EDIT: Non-scrolling Mode 3 is also completed. I also enabled 32 tiles width for the scrolling variant so you might have it for a vertical scroller. Iros, and games in the master repo seem to work all fine with this new Mode 3. The normal scrolling Mode 3 is also capable to have 30 tiles width now (previously it was 28, I didn't even know until taking the dive into the code).
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Mode3 blitter boost

Post by Artcfox »

So I just tried using these new kernel options:

Code: Select all

-DSPRITES_VSYNC_PROCESS=0 -DRESOLUTION_EXT=1
and modifying the calls to WaitVsync() calls appropriately, but it doesn't seem to work. I get sprites drawn all over the place.

Here are the changes I made:

Code: Select all

diff --git a/bugz.c b/bugz.c
index f629a27..2ee89ce 100644
--- a/bugz.c
+++ b/bugz.c
@@ -1111,7 +1111,12 @@ static GAME_FLAGS doTitleScreen(ENTITY* const monster, uint8_t* highScore)
       }
 #endif // (PLAYERS == 2)
 
-      WaitVsync(32);
+      for (uint8_t vs = 0; vs < 32; ++vs) {
+        ProcessSprites();
+        WaitVsync(1);
+        RestoreBackground();
+      }
+      
       switch (selection) {
       case 0:
         return GFLAG_1P;
@@ -1122,7 +1127,9 @@ static GAME_FLAGS doTitleScreen(ENTITY* const monster, uint8_t* highScore)
       }
     }
 
+    ProcessSprites();
     WaitVsync(1);
+    RestoreBackground();
   }
 }
 
@@ -1230,7 +1237,9 @@ int main()
     // Main game loop
     for (;;) {
       /* static uint8_t localFrameCounter; */
+      ProcessSprites();
       WaitVsync(1);
+      RestoreBackground();
 /* __asm__ __volatile__ ("wdr"); */
 
       /* uint8_t* ramTile = GetUserRamTile(0); */
diff --git a/default/Makefile b/default/Makefile
index c474407..328f2a8 100644
--- a/default/Makefile
+++ b/default/Makefile
@@ -25,7 +25,7 @@ MIX_PATH_ESC := $(subst $(SPACE),$(SPACE_ESC),$(MIX_PATH))
 ## Kernel settings
 KERNEL_DIR = ../../../kernel
 KERNEL_OPTIONS  = -DVIDEO_MODE=3 -DINTRO_LOGO=0 -DSCROLLING=0 -DSOUND_MIXER=1 -DSOUND_CHANNEL_5_ENABLE=0
-KERNEL_OPTIONS += -DMAX_SPRITES=12 -DRAM_TILES_COUNT=36 -DSCREEN_TILES_V=28
+KERNEL_OPTIONS += -DMAX_SPRITES=12 -DRAM_TILES_COUNT=36 -DSCREEN_TILES_V=28 -DSPRITES_VSYNC_PROCESS=0 -DRESOLUTION_EXT=1
 KERNEL_OPTIONS += -DMIXER_WAVES=\"$(MIX_PATH_ESC)\"
 
 ## Options common to compile, link and assembly rules
Attached is the .uze file that has the sprites drawn everywhere exept for the ones on the title screen.

If I'm understanding this mod correctly, this would mean that I could devote all of the user time for caclulations, and it won't get interrupted early by the a vsync call to process sprites, allowing my calculations to change the sprite positions and ensuring they take effect the next displayed frame?
Attachments
bugz.uze
sprite everywhere except on title screen
(57.66 KiB) Downloaded 297 times
User avatar
Jubatian
Posts: 1561
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Mode3 blitter boost

Post by Jubatian »

There is one bug in your game somewhere which makes it happening.

It seems like incorrect tile indices are being restored to the initial location of the bugs of the level (what you see are not "bugs all over", rather blocks of RAM tiles incorrectly being locked on those positions where you see the artifacts, which show other areas where sprites render).

What I think is happening in your code is that you draw the initial level map over an "unclean" state somehow. I could fix it by adding ProcessSprites() & RestoreBackground() before the line "levelOffset = LoadLevel(...)" (it is at 1175 in my copy), but for now I don't know what is going on exactly (I will figure it out later).
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Mode3 blitter boost

Post by Artcfox »

If I get rid of all WaitVsync(1); calls except for just the one in the main loop then it works correctly during the levels. I know that loading a level takes a long time, so it might miss a vsync.
User avatar
Jubatian
Posts: 1561
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Mode3 blitter boost

Post by Jubatian »

I got it.

What you see is that just before LoadLevel, you disable your sprites by moving them away, but since you don't call ProcessSprites() after it, the RAM tile restore list is still set up according to their original locations. Then you start a long process (LoadLevel) possibly interrupted by a video frame, which video frame will still "think" the sprites are on their original locations (then even the RAM tiles still have their correct contents) by the restore list. So it updates the VRAM with those sprites, and then it doesn't restore it (as this isn't something which is supposed to be happening; you are supposed to be calling RestoreBackround manually after every such video frame in which you wanted to work with the VRAM). So you get corrupted level data.

If you call ProcessSprites on the sprites moved off-screen, it will work correctly since the RAM tile restore list will be empty (all sprites are off-screen), so the interrupting video frame won't damage the level data you are building.

So what you are doing there is something which in this mode you shouldn't really be doing (working with VRAM interrupted by a video frame), but it is safe as long as you have all sprites off during it. With the normal Mode 3 this works since it auto-restores the VRAM after each video frame.

(Maybe I will remove the automatic set-up of RAM tiles from the video frame code when SPRITES_VSYNC_PROCESS is off as it isn't really necessary anyway, and as this proves, having it removed will likely produce less headache - you will only not see sprites when you forget to call ProcessSprites() - than leaving it there)
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Mode3 blitter boost

Post by Artcfox »

Awesome analysis! That is why many of the sprites overlapped as well, because they were all set to the same offscreen coordinate. My vote might be to keep it like it is, because this is so jarring and now documented in the forum that people can easily search on it when it happens and know what is going on. Maybe add a comment in the docs for this flag/video mode about it?
User avatar
Jubatian
Posts: 1561
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Mode3 blitter boost

Post by Jubatian »

I rather fixed it, now it is committed everywhere. So now when you turn SPRITES_VSYNC_PROCESS off, the video frame driver will no longer try to spit RAM tile indices into your VRAM. So if you forget ProcessSprites, you will see no sprites, but that's easier to figure out as a logical consequence than a broken VRAM when your calculations get interrupted by the video frame.

Bugz of course now work without adding ProcessSprites there, too.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Mode3 blitter boost

Post by Artcfox »

Awesome! I just verified it on my end. The only changes I needed to make were:

Code: Select all

diff --git a/bugz.c b/bugz.c
index f629a27..5178ae2 100644
--- a/bugz.c
+++ b/bugz.c
@@ -1111,7 +1111,10 @@ static GAME_FLAGS doTitleScreen(ENTITY* const monster, uint8_t* highScore)
       }
 #endif // (PLAYERS == 2)
 
+      ProcessSprites();
       WaitVsync(32);
+      RestoreBackground();
+      
       switch (selection) {
       case 0:
         return GFLAG_1P;
@@ -1122,7 +1125,9 @@ static GAME_FLAGS doTitleScreen(ENTITY* const monster, uint8_t* highScore)
       }
     }
 
+    ProcessSprites();
     WaitVsync(1);
+    RestoreBackground();
   }
 }
 
@@ -1230,7 +1235,9 @@ int main()
     // Main game loop
     for (;;) {
       /* static uint8_t localFrameCounter; */
+      ProcessSprites();
       WaitVsync(1);
+      RestoreBackground();
 /* __asm__ __volatile__ ("wdr"); */
 
       /* uint8_t* ramTile = GetUserRamTile(0); */
diff --git a/default/Makefile b/default/Makefile
index c474407..328f2a8 100644
--- a/default/Makefile
+++ b/default/Makefile
@@ -25,7 +25,7 @@ MIX_PATH_ESC := $(subst $(SPACE),$(SPACE_ESC),$(MIX_PATH))
 ## Kernel settings
 KERNEL_DIR = ../../../kernel
 KERNEL_OPTIONS  = -DVIDEO_MODE=3 -DINTRO_LOGO=0 -DSCROLLING=0 -DSOUND_MIXER=1 -DSOUND_CHANNEL_5_ENABLE=0
-KERNEL_OPTIONS += -DMAX_SPRITES=12 -DRAM_TILES_COUNT=36 -DSCREEN_TILES_V=28
+KERNEL_OPTIONS += -DMAX_SPRITES=12 -DRAM_TILES_COUNT=36 -DSCREEN_TILES_V=28 -DSPRITES_VSYNC_PROCESS=0 -DRESOLUTION_EXT=1
 KERNEL_OPTIONS += -DMIXER_WAVES=\"$(MIX_PATH_ESC)\"
 
 ## Options common to compile, link and assembly rules
A long time ago in a thread that I can't recall it was mentioned something about the kernel interrupting you to do sprite processing, and if your user code moves any sprites after that interruption, the positions/attributes woudn't take effect until the next frame. Am I correct in believing that one of the benfits of separating the ProcessSprites() and RestoreBackground() is that this is no longer the case, and you can use the entire user portion of clock cycles for calculations and as long as you reposition/change the attributes of the sprites before you call ProcessSprites() they'll always get the new values?

Are there any negatives to this, regarding doing processing that would cause you to miss a Vsync, or would it just bounce between 60fps and 30fps and then back to 60fps if you missed one Vsync?
User avatar
Jubatian
Posts: 1561
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Mode3 blitter boost

Post by Jubatian »

Yes, one of the benefit is that: The sprites are placed on-screen when you call ProcessSprites(). So you have much better control on how you are using your VBlank time.

If you miss a VSync, then if you use WaitVsync(1); as everyone should, yes, the frame rate will drop to 30FPS. The interrupted ProcessSprites() will cause sprite flicker (as those sprite parts which weren't rendered when the frame interrupts won't display). However if you are okay with 30FPS, you can also create 30FPS code with this like Flight of a Dragon does:

Code: Select all

while(1){
  WaitVsync(1);
  RestoreBackground();
  /* Update VRAM, scrolling etc. */
  ProcessSprites();
  WaitVsync(1);
  /* Process inputs, calculate game logic and sprite locations */
}
The Process inputs... part is so between two WaitVsync(1) calls between which no graphics rendering takes place. So you have a render frame and a logic frame, each having a full VBlank to do its job.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: Mode3 blitter boost

Post by Artcfox »

Perfect! Thank you for spelling this out so clearly. I think that the information in this thread will be a huge help for anyone looking to take advantage of the new mode 3 extensions.
Post Reply