sp1 sprite flipping question

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
Timmy
Well known member
Posts: 393
Joined: Sat Mar 10, 2012 4:18 pm

sp1 sprite flipping question

Post by Timmy »

Sorry if this is not the right forum for it, but I still have some sp1 questions, and I thought I'd ask here while the WoS forums is improving...

When we first talked about sp1, there were talks about adding code on top of sp1 to do other things, like water reflections and stuff.

But one thing I really would like to see is sprite flipping, like what they have on the NES, where you can specify a sprite to be drawn mirrored horizontally and/or vertically. This would save lots of sprite memory for something simple. (I do realise there would be a need for a left/right swapping table, but that's about 256 bytes.)

The simple alternative I can think of (if I don't want to mess with sp1) is to swap the sprite data before calling the sprite routines, but that would mean all sprites of that instance will be flipped all at once. So I'm wondering how this can be done.

I do realise you might not have time doing this, in that case I don't mind writing it myself, but I'd like a few pointers in that case. :)
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Timmy wrote:When we first talked about sp1, there were talks about adding code on top of sp1 to do other things, like water reflections and stuff.
It's very likely I will end up doing a revisit from scratch rather than modifying anything but the to-do list is very long atm.
The simple alternative I can think of (if I don't want to mess with sp1) is to swap the sprite data before calling the sprite routines, but that would mean all sprites of that instance will be flipped all at once. So I'm wondering how this can be done.
If you're willing to have multiple sprite images in memory: one regular, one mirrored horizontally, one mirrored vertically, one mirrored vert & horiz, then you can choose among those individual images for each sprite by pointing the sprite's frame address at the base of one of those. You can do that by writing into the sprite structure directly:

Code: Select all

struct sp1_ss *s;
....
s->frame = man_hmirror;
The image will change at the next sprite move or if you can't wait for that, you can invalidate the sprite with "sp1_Invalidate(s);"

The same effect can be had in the sprite move functions by specifying a non-zero frame address.

You might be able to reduce memory requirements by generating those images from the unmodified one prior to each level, assuming you only need a portion of the sprites for a level.

But you're talking about modifying a single image to reverse it which would affect all sprites using that image so I suppose the extra memory required for multiple images is a problem.
But one thing I really would like to see is sprite flipping, like what they have on the NES, where you can specify a sprite to be drawn mirrored horizontally and/or vertically. This would save lots of sprite memory for something simple. (I do realise there would be a need for a left/right swapping table, but that's about 256 bytes.)
I'll give you a little background first before I make one possible suggestion.

The sprite image is stored in columns and the columns are usually one after the other in memory. So an image that is 2x2 chars in size is stored in memory as 3x3 without the last (blank column) to give the software shifter space to pixel shift the image. I assume here you want pixel precision horizontally and vertically (if you only want character positioning you can do away with the extra blank space).

Code: Select all

Image:

AC
BD

Sprite Image in Memory:

          defb at least seven blank pixel rows ahead of the first column, possibly tail end of previous sprite image

          ;; FIRST COLUMN

imageA:   defb eight pixel rows making up the character   ;; offset 0
imageB:   defb eight pixel rows making up the character   ;; offset 8 or 16 if masks present
          defb eight blank pixel rows                        ;; offset 16 or 32 if masks present

          ;; SECOND COLUMN

imageC:   defb eight pixel rows making up the character   ;; offset 24 or 48 if masks present
imageD:   defb eight pixel rows making up the character   ;; offset 32 or 64 if masks present
          defb eight blank pixel rows                        ;; offset 40 or 80 if masks present
The sprite holding this 2x2 image is actually 3x3 in size and each character in that sprite is described by a struct_sp1_cs and that struct holds offsets that point into the image. There is a cs_struct that corresponds to the char imageA and its stored graphic offset is 0. There is a cs_struct that corresponds to imageB and its stored graphic offset is 8, etc. The function sp1_IterateSprChar will iterate over the cs_structs to allow you to make changes to these offsets.

A horizontal flip involves two things: you need to reverse the columns making up the sprite which can be done by changing the graphic offsets and secondly you need to mirror the graphic when it is drawn.

To do part 1, I think I would store offsets in a few arrays and simply copy them into the cs_structs using sp1_IterateSprChar when an image state is changed.

For our 3x3 ACBD image the normal orientation array of offsets might look like:

Code: Select all

// raw image
//
// A C -
// B D -
// - - -

unsigned int image_raw_3x3[] = { 0,0,  24,0,  0,24,  8,0,  32,8,  0,32,  16,0,  40,16,  0,40,   };  // AC-,BD-,---

// image mirrored horizontally
//
// C A -
// D B -
// - - -

unsigned int image_hmirror_3x3[] = { 24,0,  0,24,  0,0,  32,0,  8,32,  0,8,  40,0,  16,40,  0,16 };  // CA-,DB-,---
These arrays have a pair of offsets for each character and occur in row-major order because sp1_IterateSprChar iterates the sprite characters in row-major order.

image_raw_3x3[] is storing the offsets for the unmodified image as shown above the definition. We store the offset pairs in row major order as shown in the comment after the definition ("AC-,BD-,---"). Each pair consists of the graphic offset and the graphic offset of the char to the left of the square.

The first row stored is the offsets for image "A C -". "A" has graphic offset 0 and there is no char to the left so its offset is also 0. "C" has graphic offset 24 and A is to the left with an offset of 0. The blank column has no associated graphic so its offset is set to 0 and C is to the left with an offset of 24. The same applies to the rest of the offsets.

For each character in the 3x3 sprite we are effectively specifying what offset to use for the character's graphic and for the character to the left of current square in row-major order.

To make the change call sp1_IterateSprChar:

Code: Select all

int *offset_array;

void change_offsets(unsigned int count, struct sp1_cs *c)
{
   c->def = offset_array[0];     // change the char's graphic offset
   c->l_def = offset_array[1];  // change the char's left graphic offset
   offset_array += 2;              // move to next entry for next call
}

...

main()
{
...
   // horizontally mirror sprite

   offset_array = image_hmirror_3x3;
   sp1_IterateSprChar(s, change_offsets);
...
}
After executing that code, the 3x3 image should be drawn in a mirrored order. The array is good for any 3x3 sprite image (without masks) as the offsets will be the same.

That was step 1. Step 2 is you have to draw each character with mirrored pixels. For that you will have to write your own sprite draw functions.

The library's sprite draw functions can be located in sp1/zx/sprites/draw. To pick a simpler example, imagine your sprites are "OR" sprites. The leftmost column will be using draw function "SP1_DRAW_OR1_LB" (left border), the center column will use "SP1_DRAW_OR1" and the rightmost column will use "SP1_DRAW_OR1_RB" (right border). The difference between these is the LB version assumes there is no graphic to the left of the column and the RB version assume it is the rightmost blank column so doesn't draw anything except what shifts in from the left.

You can take a look at the SP1_DRAW_OR1 function to see a general shifted draw of an OR sprite character:
http://z88dk.cvs.sourceforge.net/viewvc ... iew=markup

The entrypoint is line 30 with register set-up noted prior.

The first thing it does is check if there is no horizontal rotation and if there isn't it uses the faster & simpler "SP1_DRAW_OR1NR" (no rotate) function.

Then it adds the sprite's frame pointer to the offsets in ix and hl to form the image address for the character.

Then it proceeds to draw the character. The drawing is done into an 8-byte buffer at a fixed location in memory (SP1V_PIXELBUFFER). The OR sprite logically ORs the image into that buffer after rotating it. The rotation rotates the image in from the left and ors with the image rotate right using the horizontal shift table.

You would have to write a new draw function that after constructing the sprite image, mirrors the byte and then ORs it into the buffer.

That's the last step to get mirrored bytes.

When you iterate the sprite to change the offsets you'd then also change the draw function to this mirrored version (c->draw = ...) which I skipped in the above example.



That's probably quite a bit to chew on :)
Timmy
Well known member
Posts: 393
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Thanks for your answer. It took me a while to look at and understand stuff like SP1DrawUpdateStruct and SP1_DRAW_OR1.

Basically you're telling me the drawing part is pretty tightly organised, and implementing things like flipping (even the vertical ones) is going to be a challenge, because of the many assembly parts that are coded tightly together.

Well, that's fine with me, so how about this:

Instead of rewriting any of those drawing methods, we check during a square update, whether the sprite that should be drawn on should be drawn flipped. If a flipped sprite should be drawn, we will not use the sprite data that is located in memory. Instead we make a copy of that sprite first, and flip it in some designated sprite working area. After flipping it, we tell the drawing methods to use the sprite working area instead of the original sprite. This sprite working area doesn't need to be large, for a 2x2 sprite, a 3x8 data area is sufficient (or if you need more, 96 bytes).

How about this? (Yes, I do realise that this is slower than putting pre-mirrored sprite data in memory.)
Last edited by Timmy on Wed May 27, 2015 10:03 pm, edited 1 time in total.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Timmy wrote:Basically you're telling me the drawing part is pretty tightly organised, and implementing things like flipping (even the vertical ones) is going to be a challenge, because of the many assembly parts that are coded tightly together.
The engine is intended to incorporate user-defined draw functions so that is not outside what should be considered normal. The act of reversing the sprite columns involves a write of 3 words to each sprite character (54 bytes for a 3x3 sprite) which should be quite quick. The mirrored draw function can be very similar to the supplied draw functions; the only difference is after a sprite byte has been rotated you would look up the mirror in a table before writing to the buffer. So it's not in the realm of outer space to do things this way.

When a draw of a specific sprite char is initiated, the code in the struct_sp1_ss at offset 8 is executed:

Code: Select all

   uint8_t              res0;           // +8  "LD A,n" opcode
   uint8_t              e_hrot;         // +9  effective horizontal rotation = MSB of rotation table to use
   uint8_t              res1;           // +10 "LD BC,nn" opcode
   uint16_t               e_offset;       // +11 effective offset to add to graphic pointers, equals result of vertical rotation + frame addr
   uint8_t              res2;           // +13 "EX DE,HL" opcode
   uint8_t              res3;           // +14 "JP (HL)" opcode
This should be considered fixed for all the sprite chars in the sprite. The A register is loaded with the MSB of the rotation table to use and BC is the graphic offset. After this the code in the struct_sp1_cs at offset 10 is executed:

Code: Select all

   uint8_t              res0;           // +10 typically "LD HL,nn" opcode
   uint8_t             *def;            // +11 graphic definition pointer
   uint8_t              res1;           // +13 typically "LD IX,nn" opcode
   uint8_t              res2;           // +14
   uint8_t             *l_def;          // +15 graphic definition pointer for sprite character to left of this one
   uint8_t              res3;           // +17 typically "CALL nn" opcode
   void              *draw;           // +18 & draw function for this sprite char
There is some latitude in this code regarding which registers are loaded with the graphics data. In this char, HL will be the graphic offset, IX the graphic offset for the char to the left, and the draw function is called. Some sprite draw functions set up registers differently and each has a short header that is copied into each sprite char that loads registers appropriately.

That's how the register set-up is done for the draw function.

Instead of rewriting any of those drawing methods, we check during a square update, whether the sprite that should be drawn on should be drawn flipped. If a flipped sprite should be drawn, we will not use the sprite data that is located in memory. Instead we make a copy of that sprite first, and flip it in some designated sprite working area. After flipping it, we tell the drawing methods to use the sprite working area instead of the original sprite. This sprite working area doesn't need to be large, for a 2x2 sprite, a 3x8 data area is sufficient (or if you need more, 96 bytes).
So this is a temporary buffer created while the sprite is being drawn? If you are making a permanent buffer you're using up the memory for two sprite images so you may as well just make the two sprite images and point the sprite's frame at the mirrored version in memory.

If it's temporary, the problem with this is the entire sprite is not drawn at once. Only individual characters are and there is no guarantee about what order the sprite's characters are drawn in nor whether all the sprite characters will be drawn. So after drawing one sprite character, another sprite's character might be drawn. If that one needs mirroring too, it will also need a temporary buffer to reverse its image and if it's not the same buffer as the first sprite's your memory requirements keep increasing, pointing again at just allocating memory for an entire image in the first place.

The other thing is the mirroring of the entire sprite at runtime is slow if it has to be done frequently.
Timmy
Well known member
Posts: 393
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Yes, I've read this and thought a bit for now. Wanted to answer earlier but I wanted to update my Games List on WoS too...

Anyway, basically you're saying that sprite caching can better be done outside the sprite routine, as that would be faster and can also be done without timing constraints. Well, you're probably right that the sprite drawing routine should be as fast as possible, and flipping a 2x2 sprite outside the drawing routine is way faster.

Yeah, I might actually do that. Thank you very much for your insights. :)
Last edited by Timmy on Fri May 29, 2015 9:07 pm, edited 1 time in total.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Timmy wrote:Anyway, basically you're saying that sprite caching can better be done outside the sprite routine, as that would be faster and can also be done without timing constraints.
Yes if you want to go the caching route I think doing it outside sp1 is the way to go.
Yeah, I might actually do that. Thank you very much for your insights. :)
Anytime, discussion also helps to figure out where the weaknesses are and if things can be done in a more suitable way in future iterations.
fraespre
Member
Posts: 56
Joined: Mon Aug 19, 2019 8:08 pm

Post by fraespre »

Hello,
I've registered in this forum to ask about this topic: the horizontal flipping in the sprite graphics.

I want to suggest in this topic that it would be very interesant the sp1 library cuold append this functionality. This would save a lot of memory wasted in flipped sprites.

Under my point of view, it could enabled a new flag in the sprite's structure. The flipped columns cuold be managed by the library (I think this wouldn't consume more cpu). And the chars drawing routines could be add a little part to do the graphic flip before the main processing. It only do it when the flag is enabled.

The developer could use the current process with the flip flag disabled. Or he could chooses save memory, enabling the flag in exchange of a few cycles of cpu.

Here I found a cute process to do an efficient horizontal flipping.
http://www.retroprogramming.com/2014/01 ... ersal.html

What do you think about ?
Timmy
Well known member
Posts: 393
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

fraespre wrote:I've registered in this forum to ask about this topic: the horizontal flipping in the sprite graphics.

[...]

Here I found a cute process to do an efficient horizontal flipping.
http://www.retroprogramming.com/2014/01 ... ersal.html

What do you think about ?
Hi!

I see Alvin haven't really answered this question, so I could say something about it.

The process you are suggesting is very cute, exactly as you say it. ;)

For an engine like sp1, that intentionally uses a lot of memory to get maximum speed, it's probably not very useful. Especially when a function like 'reversing a byte' is very critical during a sprite routine and needs to be called many many times (naturally this function needs to be called for every single byte in a sprite).

If using such routine means I can show 3 less sprites within one frame (and I wouldn't be surprised if this will happen), then it's really not worth it.
User avatar
dom
Well known member
Posts: 2091
Joined: Sun Jul 15, 2007 10:01 pm

Post by dom »

That algorithm is used a fair bit in z88dk as it happens: quite a few of the Japanese machines have "reversed" displays, with bit 0 on the left and bit 7 on the right of the display. So when printing fonts/udgs the byte has to be reversed:

https://github.com/z88dk/z88dk/blob/mas ... e.asm#L118
https://github.com/z88dk/z88dk/blob/mas ... E1.asm#L53
https://github.com/z88dk/z88dk/blob/mas ... e.asm#L151
https://github.com/z88dk/z88dk/blob/mas ... le.asm#L71
https://github.com/z88dk/z88dk/blob/mas ... e.asm#L173

are the ones I can find at the moment.
fraespre
Member
Posts: 56
Joined: Mon Aug 19, 2019 8:08 pm

Post by fraespre »

Timmy wrote:If using such routine means I can show 3 less sprites within one frame (and I wouldn't be surprised if this will happen), then it's really not worth it.
Could be it don't worth for you. But there are other games/scenarios , which the memory saving is critical. And to have n sprites concurrently in the screen is not very important.

From my view of point, the sp1 is awesome. But I would like to be able use it in other games, where the memory consumption is more necessary than the maximum speed.

I don't suggest modify the current sp1 behabiour, only to have an alternative through configuration or the setup of the engine.
fraespre
Member
Posts: 56
Joined: Mon Aug 19, 2019 8:08 pm

Post by fraespre »

I've implemented a little function using c to generate new frames with the flipping image (columns re-struct plus byte flipping). It works very good.
It's quite similar that Timmy commented here:
Timmy wrote:Instead of rewriting any of those drawing methods, we check during a square update, whether the sprite that should be drawn on should be drawn flipped. If a flipped sprite should be drawn, we will not use the sprite data that is located in memory. Instead we make a copy of that sprite first, and flip it in some designated sprite working area. After flipping it, we tell the drawing methods to use the sprite working area instead of the original sprite.
But the problem is the same, the memory waste is too big. Though this copy only was generated by level.
For me, it will be necessary a runtime method to avoid the memory duplication.
I've reviewed the initial Alvin answer regarding with:
Alvin wrote:The engine is intended to incorporate user-defined draw functions so that is not outside what should be considered normal ...
But my assemble knowledge are very low.
For me the ideal solution would be to implement the flipping in a c function using the asm ... endasm and call this function from the user-defined draw handler in sp1 described by Alvin.

Someone can help me ... :-D
Post Reply