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.
sp1 sprite flipping question
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.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.
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: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.
Code: Select all
struct sp1_ss *s;
....
s->frame = man_hmirror;
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.
I'll give you a little background first before I make one possible suggestion.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 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
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-,---
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);
...
}
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
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.)
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.
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.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.
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
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
That's how the register set-up is done for the draw function.
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.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).
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.
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.
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.
Yes if you want to go the caching route I think doing it outside sp1 is the way to go.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.
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.Yeah, I might actually do that. Thank you very much for your insights.
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 ?
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 ?
Hi!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 ?
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.
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.
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.
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.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.
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.
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:
For me, it will be necessary a runtime method to avoid the memory duplication.
I've reviewed the initial Alvin answer regarding with:
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
It's quite similar that Timmy commented here:
But the problem is the same, the memory waste is too big. Though this copy only was generated by level.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.
For me, it will be necessary a runtime method to avoid the memory duplication.
I've reviewed the initial Alvin answer regarding with:
But my assemble knowledge are very low.Alvin wrote:The engine is intended to incorporate user-defined draw functions so that is not outside what should be considered normal ...
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