A software sprite is a sprite which is positioned, clipped, drawn and erased by software functions.
A compound sprite is constructed from more than one sprite.
Each sprite has an (x,y) position which is relative to an origin position, a width and a height.
Each sprite is drawn in a defined order to make the final compound sprite.
For software sprites the order of drawing defines the priority of the image so that an image which is drawn last has a higher priority over an image which is drawn first. The pixels of the last image will be shown in preference to the images from a sprite which is drawn before it.
For a CPC+ hardware sprite the order is pre-defined by the hardware.
An example of a compound sprite is an animating character. The character is constructed from two images. An image for the body, and a an animating image for the legs.
A hardware sprite is a sprite which is positioned, drawn, erased and clipped by the hardware.
The CPC+ has 16 hardware sprites with these properties:
A compiled sprite is a special form of the software sprite.
The pixel and mask data for the sprite image is contained within the instructions to plot the sprite, the original sprite pixel data is not stored. Each sprite drawing function is dedicated to a single sprite image, each function can therefore only draw one sprite.
Drawing a compiled software sprite is much faster than drawing a standard software sprite.
The draw function doesn't use as much CPU time as a standard software sprite because we know in advance which pixels are opaque and which are transparent.
Therefore, we can create a dedicated plot function which doesn't need to perform any unnecessary functions:
In a standard software sprite plotting function, the masking is performed for every byte regardless of whether it is completely opaque or completely transparent, as a result some of the CPU time is being wasted by executing these unnecessary instructions. In contrast, the compiled sprite only executes the necessary instructions, therefore it is faster.
However, because we can only plot one sprite per function, we need one function for each sprite.
The instructions for the compiled sprite are generated from the sprite pixel data itself using another function. This reads the byte of sprite pixel data, and outputs the instructions required to draw the pixels in that byte. The output of the function is either the opcodes themselves, or the assembler mneumonics which can then be assembled using an assembler.
A compressed sprite is a special form of the software sprite. It can be drawn faster than a standard software sprite but slower than a compiled sprite.
But, in contrast to the compiled sprite, only one draw function is required for all compressed sprites.
The pixel data for the sprite is processed to identify which bytes of pixel data contain:
From this we generate an "instruction stream" which is stored in memory. The original sprite pixel data is not stored. The instruction stream is normally generated using a function which takes the original sprite pixel data as an input, and outputs the instruction stream.
Like the compiled sprite, the compressed sprite does not perform unnecessary instructions to plot the sprite. The "instruction stream" guides the plot function, so that unnecessary instructions are not required.
We can extend the instructions furthur to include a count parameter. This would define the number of continuous bytes which would be processed by the instruction. e.g. "Leave screen pixel data unchanged" with a count of 4, would indicate 4 bytes that would remain unchanged.
Using this method, the "instruction stream" would be smaller than the equivalent uncompressed sprite pixel data. This is ideal for large sprites which would otherwise consume a lot of memory.
Pre-shifted sprites are a special form of the software sprite. They are used to draw sprites at a pixel position.
Generally sprites are plotted byte-by-byte.
The image is duplicated, and each image represents the
Each image is "shifted"/offset by one pixel to the right compared to the previous image.
Image 0 is located at pixel offset 0, Image 1 is located at pixel offset 1... Image n is located at pixel offset n.
The number of images is defined by the number of pixels per byte:
In order to draw the sprite to a pixel position we now do the following.
The alternative is to use a custom plot routine which shifts the data before it is plotted to the screen
One of the disadvantages of the CPC+ hardware sprites is that you must copy the sprite pixels into the ASIC's sprite RAM. If the sprite is animating, then you must do this for every frame of the animation.
RLE hardware sprites is one method to speed up the update of the sprite pixel data.
This method assumes that a sprite is animating, and there is little difference between the current frame and the previous frame of the animation. Therefore it is not necessary to update the entire sprite each frame, we only need to write the pixels where the new frame is different from the previous frame.
The pixel data for each frame is stored in a special form, it is a sequence of instructions and data which is processed by the transfer function.
Generally a sprite image is drawn from top to bottom one line at a time. Each line is drawn from left to right.
The general method is:
The next section will describe the various plot methods.
Another document will describe the various methods to calculate a memory address and to manipulate it to calculate the next plot position (to the right, or the next line).
With this method, each byte of sprite pixel data is written to the screen. The pixels on the screen are completely replaced.
In Z80 assembly language:
. . . ;; DE = current memory address of sprite pixel data ;; HL = current screen memory address ld a,(de) ;; read byte of sprite pixel data ld (hl),a ;; write byte to screen . . .
This plot method is the fastest, however if this method is used for all of the sprite for both transparent and opaque pixels, the pixels on the screen will be erased where the sprite is drawn.
With this method, each byte of sprite pixel data is combined with each byte of screen pixel data using a bitwise XOR logical operation.
In Z80 assembly language:
. . . ;; DE = current memory address of sprite pixel data ;; HL = current screen memory address ld a,(de) ;; read byte of sprite pixel data xor (hl) ;; combine with a byte of screen pixel data ld (hl),a ;; write result byte to screen . . .
Truth table for the logical XOR operation:
The mask can either define:
If the mask is stored with the sprite pixel data, we can use this plot method for each byte:
. . . ;; HL = current screen memory address ;; DE = current memory address of sprite pixel and mask data ;; ;; data for sprite: ;; ;; sprite pixel data, mask, sprite pixel data, mask ... ld a,(de) ;; get byte of sprite pixel data ld c,a ;; store sprite pixel data temporarily in C ld a,(de) ;; get mask byte and (hl) ;; mask pixels on screen (remove pixels which will be replaced) or c ;; combine with sprite pixel data ld (hl),a ;; write result to screen . . .
However we can reduce the size of the memory used to store the sprite pixel and mask data.
A byte can have 256 possible values: 0 to 255 inclusive.
If the same pen, or combination of pens, is always used to define a transparent pixel within the image, then the mask bytes will always be the same, and there will always be exactly one mask value for each byte value: there will be 256 possible mask values.
We have now reduced the number of pens which can be used to define the sprite image:
e.g. If one pen is used to define a transparent pixel then:
But we no longer need to store a mask for each byte of the sprite pixel data, instead we only need to store a look-up table containing the masks corresponding to each byte value. As a result we can reduce the size of the memory required for the sprite and mask data.
We now use the value of the pixel data to look-up the corresponding mask.
Example source code to generate the mask look-up table.
Plotting each byte of sprite pixel data:
This now gives us the following advantages:
If we store the look-up table so that it starts on a 256 byte boundary, we can use the following plot function:
;; HL = current screen memory address ;; DE = current memory address of sprite pixel data ;; B = upper 8-bits (bits 15..8) of mask table memory address ;; ;; data for sprite: ;; ;; sprite pixel data, sprite pixel data ... ld a,(de) ;; get byte of sprite pixel data ld c,a ;; C = byte of sprite pixel data/look-up table value ;; BC = address (in look-up table) of mask corresponding to this sprite pixel data ld a,(bc) ;; lookup mask from table and (hl) ;; mask pixels on screen (remove pixels which will be replaced) or c ;; combine with sprite pixel data ld (hl),a ;; write result to screen
. . . ;; DE = pixel data ;; BC = screen address ;; H = upper 8-bits of mask table pop de ;; get two bytes of sprite pixel data. E is the first byte ;; D is the second byte ld l,e ;; L = first byte. HL = address of mask ld a,(bc) ;; get byte of screen pixel data and (hl) ;; mask pixels or e ;; combine with a byte of screen pixel data ld (bc),a ;; write result to screen inc bc ld l,d ld a,(bc) and (hl) or d ld (bc),a inc bc . . .