Thursday, February 28, 2019

Fixing Sprite Rendering Problem

After all the fiddling around with video modes a while back, a bug with sprite handling has crept in, where gaps would appear between sprites that were placed side-by-side, e.g., as in the big planet display on Gyrrus:



Or between the halves of the 16-colour sprites in Daniel's Beach Demo test program for the MEGA65:



At first I thought it was pixel edge timing funniness, but it turns out after a little inspection that the left column of pixels in a sprite are only one pixel wide instead of the correct two pixels wide (when at 320x200), as I was able to confirm by setting the first few pixels in the left and right column to check for clues.  So, the question is what is causing this problem?

To figure out the likely cause, I first checked to make sure that the problem happens whether or not sprites are horizontally expanded, and indeed, it does. Also, it is still exactly one physical pixel that is missing, i.e., the gap does not get wider if the sprites are horizontally expanded.

This suggests something about the logic that triggers when on a raster line a sprite should begin being drawn is to blame.  Quite possibly it triggers the logic to advance the pixels one cycle before it actually begins outputting sprite data.

There is some evidence in support of this, in that if the sprites are set to un-expanded H640 mode, i.e., where each sprite pixel is only one physical pixel wide, then the left pixels simply don't appear.


So I started looking at the logic for the sprite pixel advancing, and found that the sprite left edge could be triggered on a cycle when it is not a visible pixel edge.  As a result, an update happens to the sprite X pixel position on this partially wide pixel.  The fix turned out to be quite simple:

@@ -299,11 +300,19 @@ begin  -- behavioural
 --      else
 --        report "x_in = " & integer'image(x_in) & ", != sprite_x = " & integer'image(to_integer(sprite_x));
 --      end if;
+
+      -- Make sure we don't start a sprite edge except on an output pixel edge,
+      -- to stop the left pixel column being trimmed by one clock tick
+      if (x_in_sprite_soon= '1') and (pixel_strobe = '1')  then
+        x_in_sprite_soon <= '0';
+        x_in_sprite <= '1';
+      end if;
+
       if (x_in = to_integer(sprite_x))
         and (x_in /= x_last)
         and (sprite_enable='1')
         and ((y_top='1') or (sprite_drawing = '1')) then
-        x_in_sprite <= '1';
+        x_in_sprite_soon <= '1';         x_expand_toggle <= '0';
         report "SPRITE: drawing row " & integer'image(y_offset)
           & " of sprite " & integer'image(sprite_number)


Basically I have added an extra signal that notes when the sprite X starting position is reached, but then delays it until the next physical pixel edge.  I like it when things are simple to fix.  The result is that the problem is corrected, as the following screen-shots show:


Now if I only I can fix the remaining bitplane problems so quickly.  But that will have to wait for another post.