Where's the SP1 library headed?

ZX80, ZX 81, ZX Spectrum, TS2068 and other clones
Post Reply
thricenightly
Member
Posts: 28
Joined: Thu Jun 01, 2017 5:46 pm

Where's the SP1 library headed?

Post by thricenightly »

Over the last day or two I started looking at the SP1 library as a basis for ZX Spectrum games development. Progress has been extremely slow, and this is clearly going to take a lot of time. To be honest I was wondering if it's worth the effort.

Do I remember someone saying the SP1 library was going to be rewritten for the Next? If so, where does that leave the SP1 library as a platform for the 48K and 128K machines? Is it all about to be wiped and (with apologies to the late Douglas Adams) replaced with something even more bizarre and inexplicable?
Timmy
Well known member
Posts: 185
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Is it worth the effort? If your way of learning SP1 is to absorb everything before doing things (according to your other posts I think that's what you're doing), then NO.

sp1 has many many functions and I usually only need about 10 of them to make games. This is also the reason why I wrote that sp1 demo to show you what functions are really important and useful. These involve setting up the screen, putting tiles on screen, putting masked 16x16 sprites on screen, interrupts and keyboard input. It's also useful to know how the sprite format looks like, how you have to plan your memory layout, and how to plan your screen layout.

Of course, if your goal is to learn everything about sp1, then go ahead. My goal is always to make games, and for that I only need to know how to put sprites and tiles on screen. There is really no need for any aucillary functions because every extra function added will take away any precious memory left after including sp1.

The easiest way to start making games on the Spectrum using sp1 is to expand on the sp1 demo. It has everything you need to get you started.

---

As for your Next question, I assume you read it in this thread: https://www.worldofspectrum.org/forums/ ... elp-rqd/p1

Obviously, I'm not AA. But personally I don't think the Next routines fit in sp1, as sp1 is a single-screen based library and the Next has many new and exciting features that don't fit in there.

If sp1 is going to be changed in the future, you can still fall back on your current version, so I don't think it's ever going to be a problem. Just use it as-is, if you ask me.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

thricenightly wrote:Do I remember someone saying the SP1 library was going to be rewritten for the Next? If so, where does that leave the SP1 library as a platform for the 48K and 128K machines? Is it all about to be wiped and (with apologies to the late Douglas Adams) replaced with something even more bizarre and inexplicable?
SP1 (and its ancestor splib) is really a display algorithm. The idea behind how things work will not change but with every rewrite there's always a chance to reduce memory size or speed things up. And there's a chance to look at lifting some limitations.

For example, right now background tiles (or you can call them udgs) are always on the bottom in display order so the means to allow background to appear on top of sprites involves either (1) removing a particular character square from the engine so that sp1 doesn't draw there or (2) placing sprites or individual sprite characters on a particular square, which can have an intermediate display priority. But this last option can be made better by allowing background tiles to be assigned a display priority other than "at the back". So that might happen. Similarly there was a change from splib2 to SP1 where the memory allocation was modified to go through malloc and free instead of user defined functions. This made memory allocation invisible (and therefore easy) but the programmer lost some control as result. The idea was someone could override the library malloc and free to do his own thing if he wanted to. But this is actually harder because there could be a lot of sources doing malloc so a special routine would have to distinguish where requests come from to do anything special if SP1 is the one allocating. So instead it's very likely that the library will change to use special allocation functions again, as in splib2, but that will weak default to malloc and free. So you can use it without doing anything as it is now or you can provide specific allocation functions that work with SP1 only. There should also be a way to statically create sprites, and it is possible now but it requires detailed knowledge of the engine -- with the new macro capabilities in z88dk, static allocation of sprites should be easily doable. Another thing that is a problem is how SP1 keeps track of which character squares are dirty and need re-drawing. SP1 keeps track with a linked list (splib2 used a bit array). There are functions that can validate squares to make sure they aren't drawn even if they have been made dirty. This is done by setting a flag bit in the character square descriptor rather than removing the square from the dirty list because this is much faster. However it does mean you can't invalidate, then validate then invalidate a square again because the first invalidate will place the square in the list, the validate will mark it as not in the list and the last invalidate will corrupt the list. So there is a rule now that all validations should occur just before sp1_UpdateNow is called to avoid this situation. However, I don't think things have to be this way so it's likely a way will be found to lift this restriction.

Anyway, any changes are algorithmic. The interface functions "move sprite", "print background" etc will never change too much; mostly changes that occur will be invisible. Even if there are changes to the interface functions, like a change in parameters, what the functions do won't change. The ideas behind SP1 will not change; what you learn now will apply to what comes later.
Obviously, I'm not AA. But personally I don't think the Next routines fit in sp1, as sp1 is a single-screen based library and the Next has many new and exciting features that don't fit in there.
The Next brings a few more video modes like the timex hi colour and timex hi-res. These were supported in splib2 and will see revival in SP1. splib2 provided a common interface to all these display modes with differences in sprite graphics definitions.

The Next's layer 2 is larger at 48k per screen with each pixel described by a single byte. At top cpu speed, the Next has a lower cpu speed to display file size ratio than the spectrum and its 6912 bytes did so doing sw sprites can still benefit from a differential algorithm like SP1. In SP1, the sprite draw code is independent of the engine itself so it's possible to drop in different sprite code for something like layer 2. There are special instructions that allow ldir-like copying that can skip source bytes that match a transparent colour so using this may change how sp1 organizes sprites (currently as an array of characters). So I'm not sure if that means something different for layer 2 or not. There is also an ldirscale instruction planned that would allow sw sprites to be scaled up and down in size by fractional amounts and that would change things as well. I can't say for sure if something like sp1 will come to layer 2 or if something a little different will be written specifically for layer 2. But for sure sp1 will be expanded to accommodate the timex video modes.

The Next has hw sprites but they come with limitations (12 per scanline, fixed 16x16 size, eg) and cannot replace sw sprites completely. The new lemmings game being developed does all the lemmings in sw on layer 2 for example. The Next also allows the ula, layer 2 and sprite layers to be mixed on display so it's possible to use fast sw sprites in ula modes mixed with hw sprites and layer 2 background. There are many ways to go in making a game on the Next.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

The idea behind SP1 is very simple. If you keep that in mind, it's much easier to understand what is going on.

The screen is divided into tiles (character squares on a spectrum). Each tile holds a background udg identified either by character code (<256) or memory address and a colour. This background udg is always drawn on the bottom.

There is an array of sp1_update describing what is in each character square:
https://github.com/z88dk/z88dk/blob/mas ... /sp1.h#L45

struct sp1_update { // "update structs" - 10 bytes - Every tile in the display area managed by SP1 is described by one of these

uint8_t nload; // +0 bit 7 = 1 for invalidated, bit 6 = 1 for removed, bits 5:0 = number of occluding sprites present + 1
uint8_t colour; // +1 background tile attribute
uint16_t tile; // +2 background 16-bit tile code (if MSB != 0 taken as address of graphic, else lookup in tile array)
struct sp1_cs *slist; // +4 BIG ENDIAN ; list of sprites occupying this tile (MSB = 0 if none) points at struct sp1_cs.attr_mask
struct sp1_update *ulist; // +6 BIG ENDIAN ; next update struct in list of update structs queued for draw (MSB = 0 if none)
uint8_t *screen; // +8 address in display file where this tile is drawn

};

There you will see slots for colour and tile. So you have an array of 32x24 struct sp1_update, each corresponding to one character square. Using the sp1 functions to print background or draw sprites accesses this array of struct sp1_update by position x=0-31, y=0-23. The screen address each struct sp1_update is drawn to is held in the "screen" member. At sp1 initialization, these are set so the 32x24 array of struct sp1_update draw to corresponding positions on screen but these can be changed so that the logical coordinate space 0-31,0-23 which locate a sp1_update can be drawn to a different shape on screen and you can see that in some of the sp1 demos.

The other members have to do with which sprites occupy the sp1_update. The ulist member is for the linked list of dirty sp1_updates.


On top of the background you have sprites which are also divided into character squares. When you move a sprite, this involves removing the sprite's characters from the sp1_update they currently occupy and inserting the sprite's characters into the new sp1_updates they occupy. The coordinate space for sprites is large (2048 pixels IIRC?) so that sprites can appear partly on screen around the edges of the display area. An entire sprite is defined by a struct sp1_ss ( https://github.com/z88dk/z88dk/blob/mas ... /sp1.h#L56 ) There you will find the first four members defining a rectangle of characters that the sprite occupies. In order to display a sprite at pixel resolution on screen, the software has to rotate the sprite image to the right by 0-7 pixels (the hrot member) and downward by 0-7 pixels (the vrot member). Rotating horizontally is done by table look up at draw time. Vertical pixel rotation is done by changing the sprite image pointer back by vrot bytes. So a sprite is expected to have a blank square on top of its graphics image so that the image - vrot bytes will have blank pixels drawn at the top of the char square to make the image look like it shifted down vrot pixels. Because the image is shifted, sometimes this means the image occupies more character squares horizontally or vertically so members "xthresh" and "ythresh" give thresholds to indicate minimum shifts before the images spills outside its small rectangle. By default it's one pixel shift but it can be less depending on how much whitespace surrounds the image.

How the sprite's image is mixed into the screen is determined by the draw functions chosen when the sprite is created. There are many possibilities - masked, xor, or, load. If it's load, which simply overwrites the background, then there is no need to draw anything underneath it. When the engine draws a particular square, it draws the background tile to an 8-byte buffer, then individual sprite characters on top in priority order. But a load sprites wipes out what's underneath so it's better to mark that in the sp1_update so that the engine can skip stuff underneath that sprite; that's what the "nload" member is for in the sp1_update struct (number of occluding sprites...).



Anyway, the programming model is simple: background tiles you can print at or print string to. Sprites on top you can move. A means to remove individual characters from the display so that background can appear on top. The rest is optimizations and details to get at some finer points.
thricenightly
Member
Posts: 28
Joined: Thu Jun 01, 2017 5:46 pm

Post by thricenightly »

Timmy, the problem with working from demos is that the programmer is constrained by what the demos do and how they do it. For example, I wanted to use an 8x8 pixel sprite, as opposed to a 16x16 pixel one. So I picked up one of the demos, changed the sprite graphic to what I thought should work, tweaked the size arguments to what appeared to be the relevant functions, and presto!, it didn't work. Splats and squiggles on screen. At that point I set about trying to understand what was going on so I could see what I had got wrong. I read the splib1 tutorial again, searched the net, looked at more examples, then started reading the asm source code. 8 hours passed. Eventually I got my 8x8 pixel sprite on screen, but I didn't (and still don't) properly know how I did it. Working from examples is all very well, but in practise, as soon as you want to do something different, you need to know how the thing works.

Besides, as you correctly surmise, I do actually want to understand the library. Learn to use it, explore its potential. If I just wanted to plonk sprites on screen to create a run of the mill game I'd use AGD. I want to see what else is possible.

Regarding the Next and what it means for the future of SP1, what I picked up from the other comments was that SP1 might evolve to support Next features, but the 48K variant will likely remain broadly the same, especially from the programming API point of view. So effort put in with the current version probably won't be lost as Next support is developed. But I think a "getting started from scratch" guide like my Z88DK one would have to be the starting point. Shame I go back to work in a couple of days. :(
Timmy
Well known member
Posts: 185
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Hi!

I understand your idea of learning to use it, and to make something special out of it. And sp1 is really an old, mature library now; I started using it 5+ years ago.

Since then a lot of this have been explored. And I tried making 8x8 sprites too back in the day. And after I did that I realised it's not worth using 8x8 sprites due to its huge memory footprint, as well as the fact it cannot be reused as a 16x16 sprite. (Also a tip, don't free any sprites but instead change the place where it points to. I believe it is better memory usage than free-ing and allocating new sprites.)

And this is why you don't see many games with sp1 that use 8x8 sprites, because it's not worth it. Many people have explored those ways already and ended up on (at least) 16x16. With 16x16 sprites, you can still repurpose them as 8x8 but only have pixels in the upper left corner, too.

Like the usage of IM2 that doesn't really do anything, most of them are best practises for sp1. Or the choice to not use most of the other functions. I also used some function to update parts of the screen, that's probably the only other stuff i use for sp1.

As for the question how I would make a game with 16x16 sprites and 8x8 sprites, I did precisely that with Future Looter. It's z88dk but not sp1. Has a lot of stuff on screen at once and only XOR mode, using preshifted sprites. Even without sp1, it was a small game because there really wasn't much room left for levels after that.

And as for how I make 16x16 "weird" format sprites, I use my own tools. :)

TLDR: I think all sp1 users already figured out that 8x8 sprites were not worth doing, and therefore we don't really talk about that any more. Some of the best practises for sp1 are: use masked 16x16 sprites (i don't use larger sprites because i can't draw not because of other reasons), updating the background using a prepared big array to update it using 1 function call, and an IM2 routine that does mostly nothing. The demo therefore, covers most of these functionalities. For everything else, you are probably better off writing your own routines.

Happy New Year!
Timmy
Well known member
Posts: 185
Joined: Sat Mar 10, 2012 4:18 pm

Post by Timmy »

Before I forget, the difference between an AGD game and a z88dk game is, that z88dk games can be much more versatile than AGD games.

I've made a text adventure with it, a VVVVVV-like platformer (HS2), a shooter like game (Future Looter), a nirvana demo, a AY tester, a roguelike, just to name a few. I wouldn't use AGD for most of them. I'd use AGD for other kinds of games.

Each tool has its own strength and best practises. sp1 is the best documented, fastest, masked sprite engine for the Spectrum. AGD is a great tool to make arcade games on the Spectrum, squeezing almost every bit of the humble machine, using a simple programming language.

Choose the right tool for the right job. :)
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

No one has really gone past the top level of functions in sp1. There is a second layer underneath that is intended to make animated backgrounds easy that is based on iterating lists of sp1_updates. Customized sprite draw functions can also be written; part of the intention of that was to allow parts of screens to act like mirrors or reflections (the draw function can copy & process what's on screen someplace else).

About smaller sprites - it's a performance thing. If you have a 2x2 char image that can be placed at pixel precision, it can occupy anywhere from 4 characters to 9 depending on the current pixel placement (take a 2x2 image and move it right one pixel and down one pixel - now it occupies a 3x3 char area). A 1x1 char image with pixel precision can occupy from 1 character to 4. So they can draw much faster than the larger sprite. Sometimes there is whitespace around the image so it doesn't occupy more characters until the shift is larger - that is the purpose of the xthresh and yhtresh sprite members mentioned earlier. They can prevent unnecessary drawing of extra blank characters until the image actually extends into more character cells.
Post Reply