Sprite blitting question

Topics related to the API, programming discussions & questions, coding tips, bugs, etc. should go here.
Post Reply
User avatar
nicksen782
Posts: 714
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Sprite blitting question

Post by nicksen782 »

I'm working on an experiment for Video Mode 3 with no scrolling. I've removed the #ifdef for scrolling since I only intend to be non-scrolling in my current project. I was also hoping to aid to readability a bit. I have some questions.

The variables declared at the top, what are they? I wanted to put a comment to the side of each to indicate what their purpose is.

I'm trying to understand this in English. This is a critical part of the kernel and I want to make sure that I understand the modifications that I'm doing.

Goal is to get each ramtile assigned for blitting (which already works), get that new ramtile id and then modify them prior to the kernel actually blitting it. Think: Simple color palette changes.

Could someone with better knowledge explain this function better? I think it would also be good to reserve a ramtile (id 0) to work with and modify and then blit that tile to the screen. I would then need to copy that tile id to the next available ramtile id. I have a card game that I am working on that does something like this except the function is to rotate the tile. I'm doing that in user-land. The BlitSprite function runs immediately pre-vsync, right? How much time do I actually have? I'll have a 32x28 sized vram if that helps.

Code: Select all

void BlitSprite(u8 flags, u8 sprindex, u8 xpos, u8 ypos)
{
	u8  bx;     //
	u8  by;     //
	u8  dx;     //
	u8  dy;     //
	u8  bt;     //
	u8  x;      //
	u8  y;      //
	u8  tx;     //
	u8  ty;     //
	u8  wx;     //
	u8  wy;     //
	u16 ramPtr; //
	u8  ssx;    //
	u8  ssy;    //

	/* if sprite is off, then don't draw it */
	if ((flags & SPRITE_OFF) != 0U){ return; }

	/* get tile's screen section offsets */
	ssx = xpos;
	ssy = ypos;

	tx = 1U;
	ty = 1U;

	/* get the BG tiles that are overlapped by the sprite,
	** supporting wrapping (so sprites located just below zero X
	** or Y would clip on the left). In a scrolling config. only
	** TILE_WIDTH = 8 is really supported due to the "weird" VRAM
	** layout, VRAM_TILES_H is also fixed 32 this case. */

	bx = ssx / TILE_WIDTH;
	dx = ssx % TILE_WIDTH;
	if (dx != 0U){ tx++; }

	by = ((u8)((ssy + TILE_HEIGHT) & 0xFFU) / TILE_HEIGHT) - 1U;
	dy = ssy % TILE_HEIGHT;
	if (dy != 0U){ ty++; }

	/* Output sprite tiles */

	for (y = 0U; y < ty; y++){
		wy = by + y;
		if (wy < VRAM_TILES_V){
			for (x = 0U; x < tx; x++){
				wx = bx + x;
					if (wx < VRAM_TILES_H){
					ramPtr = (wy * VRAM_TILES_H) + wx;
					bt = vram[ramPtr];

					/* if no ram free ignore tile */
					if (
						//
						(
							(bt >= RAM_TILES_COUNT) 
							|
							(bt < user_ram_tiles_c)
						)
						&&
						//
						(free_tile_index < RAM_TILES_COUNT)
					){
						if (bt >= RAM_TILES_COUNT){
							/* tile is mapped to flash. Copy it to next free RAM tile. */
							CopyFlashTile(bt - RAM_TILES_COUNT, free_tile_index);
						}
						else if (bt < user_ram_tiles_c){
							/* tile is a user ram tile. Copy it to next free RAM tile. */
							CopyRamTile(bt, free_tile_index);
						}

						#if (RTLIST_ENABLE != 0)
						ram_tiles_restore[free_tile_index].addr = (&vram[ramPtr]);
						ram_tiles_restore[free_tile_index].tileIndex = bt;
						#endif

						vram[ramPtr] = free_tile_index;
						bt = free_tile_index;
						free_tile_index++;

					}

					
					if ( 
						// Is this a ram tile?
						(bt < RAM_TILES_COUNT) 
						&&
						// 
						(bt >= user_ram_tiles_c) 
					){
						BlitSpritePart(
							bt,
							((u16)(flags) << 8) + sprindex,
							((u16)(y)     << 8) + x       ,
							((u16)(dy)    << 8) + dx
						) ;
					}

				}

			} /* end for X */

		}

	} /* end for Y */

}
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Sprite blitting question

Post by Jubatian »

Huh, this was a bit more complex to figure out than I thought! Of course I did work on this part of the code, but mostly rather fixing it up so it worked the most meaningful way within Mode 3's logic, while I desing the equivalent part of my modes differently (I am not referring to assembly versus C here, rather the logic).
  • posx and posy are the screen coordinate based pixel positions where the sprite should go.
  • ssx and ssy are the VRAM surface based pixel coordinates. When scrolling, this takes in account that the VRAM behaves like on a NES in this mode.
  • tx and ty are the blit area tile counts, how many VRAM tiles the sprite covers. If it is aligned on an axis, the respective count is 1, otherwise 2.
  • bx and by are the VRAM tile coordinates of the tile over which the current part of the sprite tile blits.
  • wx and wy are the same, but adjusted for wrapping on the end of VRAM. These are used to fetch which tile has to be copied into the RAM tile.
  • bt is the VRAM tile index, the tile which is to be copied into the RAM tile before blitting the sprite part. No copying when it is already a sprite blitter RAM tile (so some other sprite part is already there).
  • dx and dy are the sub-tile pixel coordinates where the sprite part needs to end up on the background tile.
  • ramPtr is a temporary into which the VRAM address is calculated.
The scrolling variant was somewhat buggy earlier (like Artcfox reported) due to the complexities involved with NES-like VRAM behaviour, this is the most difficult aspect to follow. I settled on just going with the VIC II like approach for scrolling in my modes seeing this (you can only shift 0-7 pixels, then have to copy VRAM), and that it needs an extra cycle within the scanline logic.

If you don't need scrolling, I would suggest just removing those parts, following the SCROLLING define. What remains will be much easier to understand and tweak.
User avatar
nicksen782
Posts: 714
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Sprite blitting question

Post by nicksen782 »

Thank you for that. Now I know what those variables are for. Probably good to have them commented.

So, I've been trying out a paletting hack.

I have a separate array for source and replacement colors. And I also have functions for that but they do not run during anything time-sensitive and they just set some values.

So, I seem to be okay if I only have one sprite map. It recolors as expected. However, once I get two maps then tiles go missing or arranged wrong. Not all, but some. My 'hack' is that I copy the flash tile to a ram tile, change the colors and then call BlitSprite with the SPRITE_RAM flag and the ram tile id instead of the original flash tile id. This works, partially and for the first sprite map it works correctly, usually. Sometimes when I move the sprite it is like the sprite map "bleeds" and doesn't erase as it moves... and/or I'm missing tiles or some sprite maps have a tile from another sprite map that I've already displayed.

I'm sure my problem is a misunderstanding of how the sprite blitter works but then why does it actually work as expected (usually) with the first sprite map?

Here is my current version of ProcessSprites (below).

Code: Select all

void ProcessSprites(){
	u8 i;

	if (!sprites_on){ return; }

	user_ram_tiles_c = user_ram_tiles_c_tmp;
	free_tile_index = user_ram_tiles_c;

	extern const char bg_tiles_01[];
	extern const char sprites_01[];

	u8 ii      ;
	u8 find1   ;
	u8 find2   ;
	u8 find3   ;
	u8 replace1;
	u8 replace2;
	u8 replace3;

	u16 thisByte;
	u8 thisPixel;

	// u8 ramtile = RAM_TILES_COUNT-1;
	u16 ramtile = 0;
	
	for (i = 0U; i < MAX_SPRITES; i++){
		// if( sprites_colors[i].active == 1){
		if(sprites[i].flags!=SPRITE_OFF && sprites_colors[i].active == 1){

			// Copy the flash tile from the sprites bank. Then set the tile table back to the bg_tiles.
			SetTileTable( sprites_01 );
			// memset (&ram_tiles[ramtile], (u8) 0, (TILE_WIDTH*TILE_HEIGHT)); // Crashes.
			CopyFlashTile(sprites[i].tileIndex, ramtile);
			CopyRamTile(ramtile, ramtile+1);
			SetTileTable( bg_tiles_01 );

			// /*
			for(ii=0; ii<TILE_WIDTH*TILE_HEIGHT; ii+=1){
				// This should make the change lines easier to read (and the compiler should optimize them away as well.)
				thisByte = ((ramtile) * (TILE_WIDTH*TILE_HEIGHT)) + ii ;
				thisPixel= ram_tiles[thisByte];

				find1    = colors_arr[ sprites_colors[i].find1 ];
				find2    = colors_arr[ sprites_colors[i].find2 ];
				find3    = colors_arr[ sprites_colors[i].find3 ];
				replace1 = colors_arr[ sprites_colors[i].replace1 ];
				replace2 = colors_arr[ sprites_colors[i].replace2 ];
				replace3 = colors_arr[ sprites_colors[i].replace3 ];

				// There are three possible color replacements.
				if     ( thisPixel == find1 ){ thisPixel == replace1; }
				else if( thisPixel == find2 ){ thisPixel == replace2; }
				else if( thisPixel == find3 ){ thisPixel == replace3; }

				ram_tiles[ thisByte ] = thisPixel  ;
			}
			// */

			BlitSprite(
				sprites[i].flags | SPRITE_RAM ,
				// ramtile                       ,
				ramtile+1                       ,
				sprites[i].x                  ,
				sprites[i].y
			);

			// ramtile--;
			// ramtile++;

			// sprites_colors[i].active=0;
			memset (&ram_tiles[ramtile], (u8) 0, (TILE_WIDTH*TILE_HEIGHT));
		}
		else{
			BlitSprite(
				sprites[i].flags     ,
				sprites[i].tileIndex ,
				sprites[i].x         ,
				sprites[i].y
			);
		}
	}

	/* restore BG tiles */

	#if (SPRITES_VSYNC_PROCESS != 0)
	RestoreBackground();
	#endif

}
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Sprite blitting question

Post by Jubatian »

How ProcessSprites() is called in your setup?

If you left it in the original way, that it is executed from the VSync interrupt (SPRITES_AUTO_PROCESS is set, you are not calling it directly from the game loop), then messy things could happen if you are exceeding VBlank time, these components aren't exactly interrupt safe.

First of course I would say whether you properly allocated your user RAM tiles (but I guess you did, just be sure).

Then I would say try to set up your game with SPRITES_AUTO_PROCESS cleared, it will at the very least eliminate interrupt related hazards from the possible failure scenarios. I could imagine that what you are attempting to do might be exceeding VBlank time (although I don't know how the actual setup looks like now, but for sure, it is very easy to go overboard even without any tweaking if you are using a 224 lines tall screen, and about 35 or more RAM tiles worth of sprite blitting).
User avatar
nicksen782
Posts: 714
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Sprite blitting question

Post by nicksen782 »

I'm calling it in the normal way. I've only modified the function and added a few more to handle an array of structs that hold the colors.

I've tried reserving ram tiles and also not doing so. I've also tried to work with the last ram tile too. I get the same sort of issue each time. It also appears to be very "touchy." I suspected that I was doing too much in a limited amount of time. (I don't know exactly how much time either.)

My hack was to work with one ram tile only by copying the flash tile into it, then modify the ram tile and blit it as a SPRITE_RAM. Yes, it works but not consistently.

I know that I can copy a flash tile into a ram tile and then use it as a source which is then blit and would then require another ram tile. I was trying to avoid paying the source cost. The blitter, when running normally does something like this. That was my whole idea really.

Clearly, I can do this on a couple of ramtiles but it just isn't stable.

I really need some way of doing paletting with sprites. Mode 52 looks interesting but appears (after some short review) to be rather foreign to me.

My goal here is a clone of the game "Kung-Fu Heroes". No scrolling, sprites are tall (usually 2x4). I've modified some to fit within that. Probably going to be pretty hard without paletting on the sprites. I may be able to shrink the graphics a bit but not by much.

Question, how much time do I have pre-vsync? ( SetUserPreVsyncCallback ( &pre_VsyncCallBack ); ) What if I determined how many ramtiles I needed for sprite blits and then just did the color changes there and changed the tile indexes used and the sprite flags to use those ram tiles? And then, I could adjust ProcessSprites to change the reserved ramtile count in some way so that the sourced ramtiles can be used for the blit.

Does this sound too crazy? Sounds good to me... but I thought my other idea was good too! Ha ha!
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Sprite blitting question

Post by Jubatian »

The problem is that I think it should normally just work, that is, if you reserve user RAM tiles, prepare the colour-replaced sprite into the user RAM tile, then forwarded that to the sprite blitter, the outcome should be as expected.

However I am saying this assuming sequential execution. I am really not sure on how things migth end up with interrupt threw into play, and part of the code doing stuff with sprites running in main, part in interrupt (back when I was fixing stuff in Mode 3, I paid attention of course to make it as safe with its original mechanism as safe it could be, but I couldn't look into how things would behave or whether they would break when starting tampering with stuff there).

That's why I am suggesting first moving to a sequential approach (SPRITES_AUTO_PROCESS set zero) if you would like to do this, it is way easier to debug, behaviour would be a lot more consistent.

There are about ~11000 clocks on a 224 lines tall display before VSync. Then Vsync happens, in which ProcessSprites() takes place, after which you are back into the mainline. Or ProcessSprites() could get interrupted by the Video Frame if it is too big. Then when the Video Frame ends, it would still be ProcessSprites running, ending up shifting around mainline slots, making execution rather chaotic, triggering all sorts of hazards which could be lurking in the code.

The colour replacement if you are doing any meaningful number of sprites is costly, Mode 3 when using ~35 RAM tiles worth of sprites is already CPU bounded. The colour replacement code with those conditionals could be a very significant hit on CPU (even done in assembly), it feels to me that if you had any singificant number of sprites, this just wouldn't really work out in the end (or only on a narrow screen).

I would suggest Mode 74 here which has high performance colour replacer (Flight of a Dragon uses it), however that mode honestly is a huge monstroity to tame (I am actually thinking on recreating it with a clean, useful interface, and maybe do a few smaller game ideas, such as a neat vertical shooter).

Or probably even Mode 72 if the concept fits. There sprites are "colour-replaced" by nature, and are "automatic" (as it is a "hardware sprite" mode).

In Mode 52 I think the odd part you see is that you have to define a row selector table, a 32 tile rows tall surface to start with, of which you could grab segments for physical display. I would say look into the examples, the "junk" one just setting up some crap might be more useful than the Lisunov Li-2 demo as it does nothing much more than setting up a display.
User avatar
nicksen782
Posts: 714
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Sprite blitting question

Post by nicksen782 »

So, I tried my idea. Similar results although I did figure out a way to remove the need for a large array of structs for replacement colors on each sprite. So, ram saved. But, still, timing appears to be the issue.

If I was to turn off sprite auto processing what would I really need to do in order to achieve this? I would be guessing that I could set a flag after I'm done doing all the the ram tile changes and THEN do the sprite blitting followed by the restore background. Would I be correct?

Does the restore background just clear out the ramtiles from vram and replace them with what was stored in the restore buffer?

The ramtile cost is still very high anyway. But, clearly, you CAN do paletting in mode 3 with ram tiles. Is there maybe a better way instead? I will need to look into mode 72 and 52.
User avatar
Jubatian
Posts: 1560
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: Sprite blitting question

Post by Jubatian »

nicksen782 wrote: Tue May 28, 2019 9:03 pmIf I was to turn off sprite auto processing what would I really need to do in order to achieve this? I would be guessing that I could set a flag after I'm done doing all the the ram tile changes and THEN do the sprite blitting followed by the restore background. Would I be correct?
There is some documentation included with these definitons, although it might be a bit too concise (here they are in the code). Sorry, I mistook it above, it is not SPRITES_AUTO_PROCESS which you need to disable, only SPRITES_VSYNC_PROCESS. The routines you need to call and their order should be there, hopefully it is all right. You don't really need the flag as it would then be all mainline, you sequence it.

If I remember right, Joyrider might have a sprite recoloring implementation for Mode 3. It can be done, that game (if I really remember it right) proves that it is doable in a practical manner. My gut feeling is that copying (for the replacing) and then copying again (for the actual blit) is not exactly the way to get there, using too much CPU to be really practical.
User avatar
nicksen782
Posts: 714
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Sprite blitting question

Post by nicksen782 »

I'll try clearing SPRITES_VSYNC_PROCESS. I'll let you know what happens. Thanks Jubation.

jhoward posted about color palletes in the past:
http://uzebox.org/forums/viewtopic.php? ... 520#p18520
Post Reply