C API for Next hardware sprites

If you like transforming your statements into code, this is the place for you

Moderator: Programming Moderators

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

C API for Next hardware sprites

Post by Stefan123 » Sat Jun 24, 2017 7:59 pm

I have been playing around with the Next hardware sprites in C for a while. As part of this exercise I created a C API for using the Next hardware sprites as specified at http://www.specnext.com/sprites/. This API is a thin wrapper on top of the I/O port interface of the Next hardware sprites. I have also created a header file for the C definitions of the Next I/O port register system as specified at http://www.specnext.com/tbblue-io-port-system/.

Any feedback on the API or its implementation is much appreciated.

The source code has been compiled with the z88dk v1.99B C compiler and tested with the ZEsarUX v5.1 (2017-06-12 beta version) Next emulator.

Below are the files of the API and a simple example program demonstrating how to use it. If anyone is interested, I can send you the proper files.

zxnext_registers.h

Code: Select all

/*******************************************************************************
 * Stefan Bylund 2017
 *
 * C definitions of the I/O port register system of the Sinclair ZX Spectrum
 * Next as specified at http://www.specnext.com/tbblue-io-port-system/. These
 * registers are used for reading and writing information about the hardware and
 * settings.
 *
 * The letters (R), (W) and (R/W) indicates if a register is only readable, only
 * writable or both readable/writable.
 *
 * Some registers are accessible only during the initialization process (IPL
 * and/or config mode).
 ******************************************************************************/

#ifndef _ZXNEXT_REGISTERS_H
#define _ZXNEXT_REGISTERS_H

/*
 * Output port used to set the 8-bit registry number as listed below.
 */
#define REGISTER_NUMBER_PORT 0x243B

/*
 * Input/output port used to read/write the 8-bit registry value of the registry
 * number set.
 */
#define REGISTER_VALUE_PORT 0x253B

/*
 * (R) Machine ID (http://www.specnext.com/hardware-ids/):
 *  1 = Terasic Altera DE-1
 *  2 = Terasic Altera DE-2
 *  5 = FBLabs
 *  6 = VTrucco
 *  7 = WXEDA
 *  8 = Emulators
 *  9 = ZX-Uno
 * 10 = Sinclair ZX Spectrum Next
 * 11 = Multicore
 */
#define MACHINE_ID_REGISTER 0

/*
 * (R) Version:
 * bits 7-4 = major version
 * bits 3-0 = minor version
 */
#define VERSION_REGISTER 1

/*
 * (R/W) Reset:
 * bits 7-3 = Reserved, must be 0
 * bit 2 = (R) Power-on reset (PoR)
 * bit 1 = (R/W) Reading 1 indicates a hard reset. Writing 1 causes a hard reset.
 * bit 0 = (R/W) Reading 1 indicates a soft reset. Writing 1 causes a soft reset.
 */
#define RESET_REGISTER 2

/*
 * (W) Set machine type (only in IPL or config mode):
 * Writing this register disables the IPL (0x0000-0x3FFF are mapped to the RAM
 * instead of the internal ROM).
 * bits 7-5 = Reserved, must be 0
 * bits 4-3 = Timing:
 *   00/01 = ZX 48K
 *   10 = ZX 128K
 *   11 = ZX +2/+3e
 * bit 2 = Reserved, must be 0
 * bits 1-0 = Machine type (reset to 00 after a PoR or hard reset):
 *   00 = Config mode
 *   01 = ZX 48K
 *   10 = ZX 128K
 *   11 = ZX +2/+3e
 */
#define MACHINE_TYPE_REGISTER 3

/*
 * (W) Set page RAM (only in config mode, not IPL):
 * bits 7-5 = Reserved, must be 0
 * bits 4-0 = RAM page mapped in 0x0000-0x3FFF (32 pages of 16K = 512K). Reset to 0 after a PoR or hard reset.
 */
#define PAGE_RAM_REGISTER 4

/*
 * (R/W) Peripheral 1 settings:
 * bits 7-6 = joystick 1 mode (00 = Sinclair, 01 = Kempston, 10 = Cursor). Reset to 0 after a PoR or hard reset.
 * bits 5-4 = joystick 2 mode (00 = Sinclair, 01 = Kempston, 10 = Cursor). Reset to 0 after a PoR or hard reset.
 * bit 3 = Enable enhanced ULA (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 2 = 50/60 Hz mode (0 = 50 Hz, 1 = 60 Hz). Reset to 0 after a PoR or hard reset.
 * bit 1 = Enable scanlines (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 0 = Enable scandoubler (1 = enabled). Reset to 1 after a PoR or hard reset.
 */
#define PERIPHERAL_1_REGISTER 5

/*
 * (R/W) Peripheral 2 settings:
 * bit 7 = Enable turbo mode (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 6 = DAC chip mode (0 = I2S, 1 = JAP) (Only on VTrucco board). Reset to 0 after a PoR or hard reset.
 * bit 5 = Enable lightpen (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 4 = Enable DivMMC (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 3 = Enable Multiface (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 2 = PS/2 mode (0 = keyboard, 1 = mouse). Reset to 0 after a PoR or hard reset.
 * bits 1-0 = Audio chip mode (0- = disabled, 10 = YM, 11 = AY)
 */
#define PERIPHERAL_2_REGISTER 6

/*
 * (R/W) Turbo mode:
 * bits 7-1 = Reserved, must be 0
 * bit 0 = Turbo (0 = 3.5 MHz, 1 = 7 MHz). Reset to 0 after a PoR or hard reset.
 */
#define TURBO_MODE_REGISTER 7

/*
 * (R/W) Peripheral 3 settings:
 * bits 7-6 = Reserved, must be 0
 * bit 5 = Stereo mode (0 = ABC, 1 = ACB). Reset to 0 after a PoR or hard reset.
 * bit 4 = Enable internal speaker (1 = enabled). Reset to 1 after a PoR or hard reset.
 * bit 3 = Enable Specdrum/Covox (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 2 = Enable Timex modes (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 1 = Enable TurboSound (1 = enabled). Reset to 0 after a PoR or hard reset.
 * bit 0 = NTSC/PAL for ZX-Uno board (0 = NTSC). Reset to 0 after a PoR or hard reset.
 */
#define PERIPHERAL_3_REGISTER 8

/*
 * (R/W) Anti-brick system (only on Sinclair ZX Spectrum Next board):
 * bit 7 = (W) If 1 start normal core
 * bits 6-2 = Reserved, must be 0
 * bit 1 = (R) Button DivMMC (1 = pressed)
 * bit 0 = (R) Button Multiface (1 = pressed)
 */
#define ANTI_BRICK_SYSTEM_REGISTER 10

/*
 * (R/W) Layer 2 RAM page:
 * bits 7-6 = Reserved, must be 0
 * bits 5-0 = SRAM page
 */
#define LAYER2_RAM_PAGE_REGISTER 19

/*
 * (R/W) Layer 2 transparency color:
 * bits 7-4 = Reserved, must be 0
 * bits 3-0 = ULA transparency color (IGRB). Reset to 0 after a reset.
 */
#define LAYER2_TRANSPARENCY_COLOR_REGISTER 20

/*
 * (R/W) Sprite system:
 * bits 7-2 = Reserved, must be 0
 * bit 1 = Sprites on border (1 = yes). Reset to 0 after a reset.
 * bit 0 = Sprites visible (1 = visible). Reset to 0 after a reset.
 */
#define SPRITE_SYSTEM_REGISTER 21

/*
 * (R/W) Layer 2 offset x:
 * bits 7-0 = X offset (0-255). Reset to 0 after a reset.
 */
#define LAYER2_OFFSET_X_REGISTER 22

/*
 * (R/W) Layer 2 offset y:
 * bits 7-0 = Y offset (0-191). Reset to 0 after a reset.
 */
#define LAYER2_OFFSET_Y_REGISTER 23

/*
 * (R) Raster video line (MSB):
 * bits 7-1 = Reserved, always 0
 * bit 0 = Raster line MSB. Reset to 0 after a reset.
 */
#define RASTER_VIDEO_LINE_MSB_REGISTER 30

/*
 * (R) Raster video line (LSB):
 * bits 7-0 = Raster line LSB (0-255). Reset to 0 after a reset.
 */
#define RASTER_VIDEO_LINE_LSB_REGISTER 31

/*
 * (R/W) Raster line interrupt control:
 * bit 7 = (R) INT flag, 1 = During INT (even if the CPU has interrupt disabled)
 * bits 6-3 = Reserved, must be 0
 * bit 2 = If 1 disables original ULA interrupt. Reset to 0 after a reset.
 * bit 1 = If 1 enables raster line interrupt. Reset to 0 after a reset.
 * bit 0 = Raster line interrupt value MSB. Reset to 0 after a reset.
 */
#define RASTER_LINE_INTERRUPT_CONTROL_REGISTER 34

/*
 * (R/W) Raster line interrupt value LSB:
 * bits 7-0 = Raster line interrupt value LSB (0-255). Reset to 0 after a reset.
 */
#define RASTER_LINE_INTERRUPT_VALUE_LSB_REGISTER 35

/*
 * (W) High address of keymap:
 * bits 7-1 = Reserved, must be 0
 * bit 0 = MSB address
 */
#define KEYMAP_HIGH_ADDRESS_REGISTER 40

/*
 * (W) Low address of keymap:
 * bits 7-0 = LSB address
 */
#define KEYMAP_LOW_ADDRESS_REGISTER 41

/*
 * (W) High data to keymap:
 * bits 7-1 = Reserved, must be 0
 * bit 0 = MSB data
 */
#define KEYMAP_HIGH_DATA_REGISTER 42

/*
 * (W) Low data to keymap (when writing this register, the address is automatically incremented):
 * bits 7-0 = LSB data
 */
#define KEYMAP_LOW_DATA_REGISTER 43

/*
 * (W) Debug LEDs (DE-1, DE-2 and Multicore boards only)
 */
#define DEBUG_LEDS_REGISTER 255

#endif
zxnext_sprite.h

Code: Select all

/*******************************************************************************
 * Stefan Bylund 2017
 *
 * C API for using the hardware sprites of the Sinclair ZX Spectrum Next as
 * specified at http://www.specnext.com/sprites/. This C API is a thin wrapper
 * on top of the I/O port interface of the hardware sprites.
 *
 * The Sinclair ZX Spectrum Next provides 64 hardware sprites numbered from 0 to
 * 63. Each sprite is 16 * 16 pixels where each pixel is an 8-bit index between
 * 0 and 255 into a 256-colour sprite palette. Each colour in the palette is an
 * 8-bit RRRGGGBB colour value. The palette colour pink 0xE3 (227) represents
 * the transparent colour. At reset, the palette is initialized with the colours
 * 0 to 255 using a one-to-one mapping between palette indexes and palette
 * colours, i.e. palette index 0 contains colour 0, palette index 1 contains
 * colour 1, ..., palette index 255 contains colour 255.
 *
 * Sprites can optionally be rendered on the border of the screen. The coordinate
 * system of the sprites therefore includes the border, which is 32 pixels, and
 * the total sprite resolution is thus 320 * 256 pixels. The standard screen
 * resolution is 256 * 192 pixels. This means that if sprites is not rendered on
 * the border, the sprite coordinates range from (32, 32) to (287, 223).
 *
 * For convenience, there is an extended version of the set_sprite_attributes()
 * function for setting the sprite position called set_sprite_attributes_ext(),
 * which accepts screen-based coordinates (256 * 192 pixels) and internally
 * converts them to border-based coordinates (320 * 256 pixels). This function
 * is convenient if you prefer to work in screen coordinates and don't want to
 * render the sprites on the border area.
 *
 * When using the sprites there is a differentiation between the actual sprites
 * and the sprite pattern (i.e. the sprite bitmap) used by the sprites. There
 * are 64 sprites and 64 sprite patterns. The sprite patterns are defined
 * independently of the sprites and are referenced by the sprites. This means
 * that multiple sprites can share the same sprite pattern.
 *
 * The sprite pattern is set for the currently selected sprite pattern slot
 * (0 - 63). The attributes of a sprite is set for the currently selected sprite
 * slot (0 - 63). The sprite attributes determine which sprite pattern the sprite
 * should use, the x and y position of the sprite, an optional sprite palette
 * offset, a bit-mask of flags for sprite mirroring and rotation, and whether or
 * not the sprite should be visible.
 *
 * If the optional sprite palette offset (0 - 15) is used when setting the
 * attributes of a sprite, it is added to the 4 most significant bits of each
 * 8-bit palette index in the sprite pattern of the sprite. In this way, the
 * 256-colour sprite palette is effectively divided into 16 sub-palettes
 * numbered from 0 to 15 where each sub-palette contains 16 colours indexed from
 * 0 to 15. The palette offset then controls which of the 16 sub-palettes should
 * be used. For example, if a pixel in a sprite pattern contains the palette
 * index 0x14, which denotes the colour at index 4 in sub-palette 1, and the
 * palette offset is 2, the actual palette index used for that pixel will be
 * 0x34, which denotes the colour at index 4 in sub-palette 3 (sub-palette 1 +
 * palette offset 2). When the palette offset is added to a sub-palette number,
 * the addition is actually done in modulo 16. For example, adding palette
 * offset 5 to sub-palette number 13 gives sub-palette number 2. If used, the
 * palette offset is an efficient way of displaying the same sprite pattern in
 * different colours.
 *
 * The priority between the sprites is determined by the sprite slot number.
 * Sprite 0 has the lowest priority and sprite 63 has the highest priority.
 * A sprite with a higher priority is drawn over a sprite with lower priority.
 * All sprites have higher priority than the standard screen and the layer 2
 * screen.
 *
 * There can be a maximum of 12 sprites per scanline. Whether or not this limit
 * has been reached for any scanline can be queried. If there are more than 12
 * sprites on a scanline, only the 12 sprites with the highest priority are
 * displayed.
 *
 * The sprite system provides collision detection of the sprites. A collision of
 * two or more sprites happen if a non-transparent pixel of the sprites are drawn
 * in the same position on the screen. The sprite system only informs whether a
 * sprite collision has occurred or not, which sprites has actually collided must
 * be determined in software.
 ******************************************************************************/

#ifndef _ZXNEXT_SPRITE_H
#define _ZXNEXT_SPRITE_H

#include <stdint.h>
#include <stdbool.h>

/* Max sprites per scanline limit reached. */
#define MAX_SPRITES_PER_SCANLINE_MASK 0x02

/* A collision between two or more sprites. */
#define SPRITE_COLLISION_MASK 0x01

/* Mirror the sprite x-wise. */
#define MIRROR_X_MASK 0x08

/* Mirror the sprite y-wise. */
#define MIRROR_Y_MASK 0x04

/* Rotate the sprite 90 degrees. */
#define ROTATE_MASK 0x02

/*
 * Set the sprite system properties. Specify if the sprites should be visible
 * and if they should be rendered on the border of the screen.
 */
void set_sprite_system(bool sprites_visible, bool sprites_on_border);

/*
 * Returns the state of the sprite system as a bit-mask informing if the maximum
 * number of sprites per scanline limit has been reached (MAX_SPRITES_PER_SCANLINE_MASK)
 * and if two or more sprites has collided (SPRITE_COLLISION_MASK).
 */
uint8_t get_sprite_system_state(void);

/*
 * Set the sprite colour palette. The palette should contain 256 8-bit RRRGGGBB
 * colours.
 */
void set_sprite_palette(void *sprite_palette_data);

/*
 * Set the sprite slot used for the set_sprite_pattern() or set_sprite_attributes()
 * function. The sprite slot is a number between 0 and 63.
 */
void set_sprite_slot(uint8_t sprite_slot);

/*
 * Set the sprite pattern data for the selected sprite pattern slot. The sprite
 * pattern data should be 16 * 16 pixels where each pixel is an 8-bit index
 * between 0 and 255 into the 256-colour sprite palette. The pixels are laid out
 * left to right and top to bottom.
 *
 * After each call to set_sprite_pattern(), the current sprite pattern slot is
 * automatically incremented.
 */
void set_sprite_pattern(void *sprite_pattern_data);

/*
 * Set the sprite attributes for the selected sprite slot. The given coordinates
 * are border-based (320 * 256 pixels).
 *
 * The sprite attributes determine which sprite pattern the sprite should use,
 * the x and y position of the sprite, an optional sprite palette offset
 * (0 - 15, set to 0 if not used), a bit-mask of sprite flags (MIRROR_X_MASK,
 * MIRROR_Y_MASK, ROTATE_MASK) or 0 if not set, and whether or not the sprite
 * should be visible.
 *
 * After each call to set_sprite_attributes(), the current sprite slot is
 * automatically incremented.
 */
void set_sprite_attributes(uint8_t sprite_pattern_slot,
                           uint16_t x,
                           uint8_t y,
                           uint8_t palette_offset,
                           uint8_t sprite_flags,
                           bool visible);

/*
 * Set the sprite attributes for the selected sprite slot. The given coordinates
 * are screen-based (256 * 192 pixels) and internally converted to border-based
 * coordinates (320 * 256 pixels). This function is convenient if you prefer to
 * work in screen coordinates and don't want to render the sprites on the border
 * area.
 *
 * The sprite attributes determine which sprite pattern the sprite should use,
 * the x and y position of the sprite, an optional sprite palette offset
 * (0 - 15, set to 0 if not used), a bit-mask of sprite flags (MIRROR_X_MASK,
 * MIRROR_Y_MASK, ROTATE_MASK) or 0 if not set, and whether or not the sprite
 * should be visible.
 *
 * After each call to set_sprite_attributes_ext(), the current sprite slot is
 * automatically incremented.
 */
void set_sprite_attributes_ext(uint8_t sprite_pattern_slot,
                               uint8_t x,
                               uint8_t y,
                               uint8_t palette_offset,
                               uint8_t sprite_flags,
                               bool visible);

#endif
zxnext_sprite.c

Code: Select all

/*******************************************************************************
 * Stefan Bylund 2017
 *
 * C API for using the hardware sprites of the Sinclair ZX Spectrum Next.
 * See zxnext_sprite.h for more information.
 ******************************************************************************/

#include <z80.h>
#include "zxnext_registers.h"
#include "zxnext_sprite.h"

#define SPRITE_SLOT_PORT       0x303B
#define SPRITE_PALETTE_PORT    0x53
#define SPRITE_PATTERN_PORT    0x55
#define SPRITE_ATTRIBUTES_PORT 0x57

#define SPRITES_VISIBLE_MASK   0x01
#define SPRITES_ON_BORDER_MASK 0x02

#define SPRITE_SLOT_MASK     0x3F
#define X_LSB_MASK           0x00FF
#define X_MSB_MASK           0x0100
#define X_MSB_SHIFT          8
#define PALETTE_OFFSET_SHIFT 4
#define SPRITE_VISIBLE_MASK  0x80

void set_sprite_system(bool sprites_visible, bool sprites_on_border)
{
    uint8_t value = 0;

    if (sprites_visible)
    {
        value = SPRITES_VISIBLE_MASK;
    }

    if (sprites_on_border)
    {
        value = value | SPRITES_ON_BORDER_MASK;
    }

    z80_outp(REGISTER_NUMBER_PORT, SPRITE_SYSTEM_REGISTER);
    z80_outp(REGISTER_VALUE_PORT, value);
}

uint8_t get_sprite_system_state(void)
{
    return z80_inp(SPRITE_SLOT_PORT);
}

void set_sprite_palette(void *sprite_palette_data)
{
    int i;
    uint8_t *sprite_palette = (uint8_t *) sprite_palette_data;

    for (i = 0; i < 256; i++)
    {
        z80_outp(SPRITE_PALETTE_PORT, sprite_palette[i]);
    }
}

void set_sprite_slot(uint8_t sprite_slot)
{
    z80_outp(SPRITE_SLOT_PORT, sprite_slot & SPRITE_SLOT_MASK);
}

void set_sprite_pattern(void *sprite_pattern_data)
{
    int i;
    uint8_t *sprite_pattern = (uint8_t *) sprite_pattern_data;

    for (i = 0; i < 256; i++)
    {
        z80_outp(SPRITE_PATTERN_PORT, sprite_pattern[i]);
    }
}

void set_sprite_attributes(uint8_t sprite_pattern_slot,
                           uint16_t x,
                           uint8_t y,
                           uint8_t palette_offset,
                           uint8_t sprite_flags,
                           bool visible)
{
    uint8_t x_lsb = (uint8_t) (x & X_LSB_MASK);
    uint8_t x_msb = (uint8_t) ((x & X_MSB_MASK) >> X_MSB_SHIFT);
    uint8_t third_byte = (palette_offset << PALETTE_OFFSET_SHIFT) | x_msb | sprite_flags;
    uint8_t fourth_byte = sprite_pattern_slot & SPRITE_SLOT_MASK;

    if (visible)
    {
        fourth_byte = fourth_byte | SPRITE_VISIBLE_MASK;
    }

    z80_outp(SPRITE_ATTRIBUTES_PORT, x_lsb);
    z80_outp(SPRITE_ATTRIBUTES_PORT, y);
    z80_outp(SPRITE_ATTRIBUTES_PORT, third_byte);
    z80_outp(SPRITE_ATTRIBUTES_PORT, fourth_byte);
}

void set_sprite_attributes_ext(uint8_t sprite_pattern_slot,
                               uint8_t x,
                               uint8_t y,
                               uint8_t palette_offset,
                               uint8_t sprite_flags,
                               bool visible)
{
    uint16_t x_ext = ((uint16_t) x) + 32;
    uint8_t x_lsb = (uint8_t) (x_ext & X_LSB_MASK);
    uint8_t x_msb = (uint8_t) ((x_ext & X_MSB_MASK) >> X_MSB_SHIFT);
    uint8_t y_ext = y + 32;
    uint8_t third_byte = (palette_offset << PALETTE_OFFSET_SHIFT) | x_msb | sprite_flags;
    uint8_t fourth_byte = sprite_pattern_slot & SPRITE_SLOT_MASK;

    if (visible)
    {
        fourth_byte = fourth_byte | SPRITE_VISIBLE_MASK;
    }

    z80_outp(SPRITE_ATTRIBUTES_PORT, x_lsb);
    z80_outp(SPRITE_ATTRIBUTES_PORT, y_ext);
    z80_outp(SPRITE_ATTRIBUTES_PORT, third_byte);
    z80_outp(SPRITE_ATTRIBUTES_PORT, fourth_byte);
}
zxnext_sprite_demo.c

Code: Select all

/*******************************************************************************
 * Stefan Bylund 2017
 *
 * A simple sprite demo program for Sinclair ZX Spectrum Next.
 *
 * Compile with sccz80:
 * zcc +zx -vn -O3 -startup=1 -clib=new zxnext_sprite_demo.c zxnext_sprite.c \
 *    -o zxnext_sprite_demo -create-app
 * or sdcc:
 * zcc +zx -vn -SO3 -startup=1 -clib=sdcc_iy --max-allocs-per-node200000 \
 *    zxnext_sprite_demo.c zxnext_sprite.c -o zxnext_sprite_demo -create-app
 ******************************************************************************/

#include <arch/zx.h>
#include <input.h>
#include <z80.h>
#include <stdint.h>
#include <stdbool.h>

#include "zxnext_registers.h"
#include "zxnext_sprite.h"

#pragma output CRT_ORG_CODE = 32768
#pragma output CLIB_MALLOC_HEAP_SIZE = 0
#pragma output CLIB_STDIO_HEAP_SIZE = 0
#pragma output CLIB_FOPEN_MAX = -1

/*******************************************************************************
 * Function Prototypes
 ******************************************************************************/

static void init_hardware(void);

static void create_background(void);

static void create_sprites(void);

static void update_sprites(void);

static void clear_background(void);

/*******************************************************************************
 * Type Definitions
 ******************************************************************************/

typedef struct sprite_info {
    int x;  // X coordinate in pixels
    int y;  // Y coordinate in pixels
    int dx; // Horizontal displacement in pixels
    int dy; // Vertical displacement in pixels
} sprite_info_t;

/*******************************************************************************
 * Variables
 ******************************************************************************/

static const uint8_t sprite_pattern[] =
{
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0xFF, 0xFB, 0xF5, 0xF5, 0xFB, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0xFF, 0xFB, 0xF5, 0xA8, 0xA8, 0xFB, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0xFF, 0xFF, 0xFB, 0xA8, 0x44, 0xA8, 0xFB, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0x04, 0x04, 0x04, 0xFF, 0xFB, 0xA8, 0x44, 0xA8, 0xFB, 0xFF, 0x04, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3,
    0xE3, 0xE3, 0xE3, 0x04, 0xFF, 0xFB, 0xA8, 0x44, 0x44, 0xFB, 0xFF, 0x04, 0xE3, 0x04, 0xE3, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0xFF, 0xFB, 0x44, 0x44, 0x44, 0xFB, 0xFF, 0x04, 0x4D, 0x04, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0xFF, 0xFB, 0x44, 0x44, 0x44, 0x44, 0xFA, 0x4D, 0x04, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0xFF, 0xFB, 0x44, 0xFF, 0xF5, 0x44, 0x04, 0xE3, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0xFF, 0x44, 0xF5, 0xA8, 0x04, 0xE3, 0xE3, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0xFA, 0x44, 0x04, 0xA8, 0x04, 0xE3, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0x4D, 0x4D, 0x04, 0xE3, 0x04, 0xF5, 0x04, 0xE3,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0x04, 0xE3, 0xE3, 0xE3, 0x04, 0xFA, 0x04,
    0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0x04, 0x04
};

static sprite_info_t sprite = {120, 88, 1, 1};

/*******************************************************************************
 * Functions
 ******************************************************************************/

static void init_hardware(void)
{
    // Put Z80 in 7 MHz turbo mode.
    z80_outp(REGISTER_NUMBER_PORT, TURBO_MODE_REGISTER);
    z80_outp(REGISTER_VALUE_PORT, 1);
}

static void create_background(void)
{
    zx_border(INK_YELLOW);
    zx_cls(BRIGHT | INK_BLACK | PAPER_WHITE);
}

static void create_sprites(void)
{
    set_sprite_slot(0);
    set_sprite_pattern(sprite_pattern);

    set_sprite_slot(0);
    set_sprite_attributes_ext(0, sprite.x, sprite.y, 0, 0, true);
}

static void update_sprites(void)
{
    // Calculate next position of sprite.
    sprite.x += sprite.dx;
    sprite.y += sprite.dy;

    // If sprite is outside the screen then clip its position and change direction.
    if (sprite.x <= 0)
    {
        sprite.x = 0;
        sprite.dx = -sprite.dx;
    }
    else if (sprite.x >= 239)
    {
        sprite.x = 239;
        sprite.dx = -sprite.dx;
    }
    if (sprite.y <= 0)
    {
        sprite.y = 0;
        sprite.dy = -sprite.dy;
    }
    else if (sprite.y >= 175)
    {
        sprite.y = 175;
        sprite.dy = -sprite.dy;
    }

    // Update sprite position.
    set_sprite_slot(0);
    set_sprite_attributes_ext(0, sprite.x, sprite.y, 0, 0, true);
}

static void clear_background(void)
{
    zx_border(INK_WHITE);
    zx_cls(INK_BLACK | PAPER_WHITE);
}

int main(void)
{
    init_hardware();
    create_background();
    create_sprites();
    set_sprite_system(true, false);

    while (true)
    {
        if (in_inkey() != 0)
        {
            break;
        }

        // Wait a short while between each movement of the sprite.
        z80_delay_ms(20);

        update_sprites();
    }

    set_sprite_system(false, false);
    clear_background();
    return 0;
}

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: C API for Next hardware sprites

Post by Alcoholics Anonymous » Sun Jun 25, 2017 2:11 am

Stefan looks nice. Just a couple of notes:

* Make use of special function registers to define io ports. Using them will enable the compilers to inline io instructions. The z80_outp() functions are turned into function calls into the library which comes with extra overhead. sccz80 got this feature after v1.99B but it will work with sccz80 in the current version.

* There is a z80_otir() function in z80.h that implements the otir instruction. That could probably replace that loop if len=0 (0 will mean 256 bytes). I think using sprite_palette_data in the loop will lead to best code, otherwise you're depending on the compilers to figure out they don't need to make an extra variable. zsdcc might figure that out but I don't think sccz80 will. (Edit: otir may not be usable - we're still waiting to hear from the Next team whether ports 0x55 and 0x57 are 8-bit decoded or 16-bit decoded; the emulators are currently assuming 16-bit decode so that otir does not work. Edit Edit: the Next team has confirmed these are 8-bit ports so otir is safe to use - it shouldn't be long until the emulators are updated to reflect this).

Code: Select all

void set_sprite_palette(void *sprite_palette_data)
{
    int i;
    uint8_t *sprite_palette = (uint8_t *) sprite_palette_data;

    for (i = 0; i < 256; i++)
    {
        z80_outp(SPRITE_PALETTE_PORT, sprite_palette[i]);
    }
}
* Although this code is clean and easy to understand, better code will probably result if you put those expressions directly where they are needed in the z80_outp() calls. Again, it's down to whether the compilers can figure out they don't need local vars and with so many there I'm not sure that will happen. You can see what code is being generated by adding "--list --c-code-in-asm" to a compile line that produces a binary. Or you could translate to assembler as in "zcc +zx -vn -a -clib=sdcc_iy -SO3 --max-allocs-per-node200000 --c-code-in-asm foo.c"

Code: Select all

void set_sprite_attributes(uint8_t sprite_pattern_slot,
                           uint16_t x,
                           uint8_t y,
                           uint8_t palette_offset,
                           uint8_t sprite_flags,
                           bool visible)
{
    uint8_t x_lsb = (uint8_t) (x & X_LSB_MASK);
    uint8_t x_msb = (uint8_t) ((x & X_MSB_MASK) >> X_MSB_SHIFT);
    uint8_t third_byte = (palette_offset << PALETTE_OFFSET_SHIFT) | x_msb | sprite_flags;
    uint8_t fourth_byte = sprite_pattern_slot & SPRITE_SLOT_MASK;

    if (visible)
    {
        fourth_byte = fourth_byte | SPRITE_VISIBLE_MASK;
    }

    z80_outp(SPRITE_ATTRIBUTES_PORT, x_lsb);
    z80_outp(SPRITE_ATTRIBUTES_PORT, y);
    z80_outp(SPRITE_ATTRIBUTES_PORT, third_byte);
    z80_outp(SPRITE_ATTRIBUTES_PORT, fourth_byte);
}
* Try as much as possible to get rid of any signed numbers (unsigned int instead of int, unsigned char instead of char, etc) and try to use as small a type as possible. zsdcc does an especially good job with unsigned char types.

The last step is to make a library. If people use your functions as they are now, all those functions will be attached to their output binaries even if they don't use all the functions. But if those functions are packaged into a library, the linker will only pull stuff out that the program uses.

Unfortunately the linker's basic unit is the file so what you'd need to do is separate all those functions into their own files. You'd have a "set_sprite_system.c", "get_sprite_system_state.c", etc. Then make a list file that lists all the functions that should be in your library. Something like:

nextsprlib.lst:

Code: Select all

set_sprite_system.c
get_sprite_system_state.c
...
Then make your library:

zcc +zx -vn -x -clib=sdcc_iy -SO3 --max-allocs-per-node200000 @nextsprlib.lst -o nextsprlib

To build a program linking against your library:

zcc +zx -vn -clib=sdcc_iy -SO3 --max-allocs-per-node200000 zxnext_sprite_demo.c -o demo -lnextsprlib -create-app

Now that demo only pulls out functions it is actually using. Note that a library is compiler (ie clib=???) specific. The way we get around this is to put the three libraries you build (clib=new, clib=sdcc_ix, clib=sdcc_iy) into their respective directories in z88dk/libsrc/_DEVELOPMENT/lib . Once there, the linker will automatically look for the right version of the library and the library won't have to be in your local directory when you compile.

Another nice thing about this organization is you an rewrite any of your library functions in asm. For example, if you decide you are going to rewrite "set_sprite_system.c" in asm, you'd make a new file "set_sprite_system.asm" and then change your list file:

nextsprlib.lst:

Code: Select all

set_sprite_system.asm
get_sprite_system_state.c
...
Rebuild your library and bob's your uncle.

There are other details like what sections you should place your code and data into. Sections will control where in memory things go - in a different memory bank, etc. but the defaults are fine for a 48k program. And a convention for allowing asm programs to call your routines if you write any in asm (otherwise they can call your c using c calling convention). We usually separate an asm entry point (prefixing its name with asm_...) from a c entry point that jumps into the asm after collecting parameters. Because fastcall linkage passes params in registers, the c entry for fastcall functions can be the same as the asm entry.
Last edited by Alcoholics Anonymous on Sun Jun 25, 2017 5:18 pm, edited 5 times in total.

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

Re: C API for Next hardware sprites

Post by Stefan123 » Sun Jun 25, 2017 10:51 am

Alvin, thank you for your valuable feedback and suggestions. Your comments here and on the z88dk forum are always of top class :)

I will update the code as soon as I manage to get some free time.

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

Re: C API for Next hardware sprites

Post by Stefan123 » Sun Jul 02, 2017 7:34 pm

I have updated my C hardware sprite API with Allen's suggested changes and uploaded a first version on GitHub:
https://github.com/stefanbylund/zxnext_sprite

I have also uploaded a simple example program on GitHub demonstrating how to use the API:
https://github.com/stefanbylund/zxnext_sprite_demo

Any feedback on the API or its implementation is welcome.

I plan to add C APIs for layer 2 and raster interrupts when the specs are published and implemented by ZEsarUX.

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

Re: C API for Next hardware sprites

Post by Stefan123 » Fri Jul 07, 2017 6:00 pm

I read that ZEsarUX (latest version built from source code) now treats the 0x53 (sprite palette port), 0x55 (sprite pattern port) and 0x57 (sprite attributes port) ports as 8-bit ports so we should now be able to define them as 8-bit SFR ports in z88dk as below (i.e. the __banked attribute is removed from the declarations):

__sfr __at 0x53 IO_SPRITE_PALETTE_PORT;
__sfr __at 0x55 IO_SPRITE_PATTERN_PORT;
__sfr __at 0x57 IO_SPRITE_ATTRIBUTES_PORT;

It should also be possible to use the z80_otir() function in z80.h when setting the sprite palette or pattern as below:

z80_otir(my_sprite_pattern, 0x55, 0);

However, when testing this it didn't work for me. When using the sccz80 compiler my program crashes and when using sdcc the sprites are not diplayed. I use the z88dk snapshot from 2017-06-25.

I don't know if I'm doing something wrong or if ZEsarUX still fails to treat these ports as 8-bit ports or if z88dk generates bad code in this case.

When compiling with sccz80, I get the following assembly code when using z80_otir() for setting the sprite pattern:

Code: Select all

107   0000              ;# 12 "src/set_sprite_pattern.c"
108   0000              ;void set_sprite_pattern(void *sprite_pattern)
109   0000              ;{
110   0000                  SECTION code_compiler
111   0000
112   0000
113   0000              ._set_sprite_pattern
114   0000                  LINE    13
115   0000              ;# 26
116   0000              ;z80_otir_callee(sprite_pattern,0x55,0);
117   0000                  LINE    26
118   0000              ;sprite_pattern;
119   0000  C1              pop bc
120   0001  E1              pop hl
121   0002  E5              push    hl
122   0003  C5              push    bc
123   0004  E5              push    hl
124   0005              ;0x55;
125   0005  21 55 00        ld  hl,85 % 256 ;const
126   0008  E5              push    hl
127   0009              ;0;
128   0009  21 00 00        ld  hl,0 % 256  ;const
129   000C  E5              push    hl
130   000D  CD 00 00        call    z80_otir_callee
131   0010              ;}
132   0010  C9              ret
133   0011
When compiling with sdcc, I get the following assembly code when using z80_otir() for setting the sprite pattern:

Code: Select all

265   0000              ;src/set_sprite_pattern.c:12: void set_sprite_pattern(void *sprite_pattern)
266   0000              ;   ---------------------------------
267   0000              ; Function set_sprite_pattern
268   0000              ; ---------------------------------
269   0000              _set_sprite_pattern:
270   0000  DD E5           push    ix
271   0002  DD 21 00 00     ld  ix,0
272   0006  DD 39           add ix,sp
273   0008              ;src/set_sprite_pattern.c:26: z80_otir(sprite_pattern, SPRITE_PATTERN_PORT, 0);
274   0008  21 55 00        ld  hl,0x0055
275   000B  E5              push    hl
276   000C  DD 6E 04        ld  l,(ix+4)
277   000F  DD 66 05        ld  h,(ix+5)
278   0012  E5              push    hl
279   0013  CD 00 00        call    _z80_otir_callee
280   0016  DD E1           pop ix
281   0018  C9              ret
282   0019                  SECTION IGNORE
283   0000
However, when using the 8-bit declaration of port 0x57 for setting the sprite attributes it does actually work.

Alcoholics Anonymous
Posts: 513
Joined: Mon May 29, 2017 7:00 pm

Re: C API for Next hardware sprites

Post by Alcoholics Anonymous » Fri Jul 07, 2017 7:16 pm

Did you build ZEsarUX yourself? I'm still waiting for the binaries to be updated at Sourceforge.

The generated code looks correct and so does the library code (I'll try to see if we can get that inlined). Neither compile crashes on a regular Spectrum emulator which simply ignores unknown i/o. So the crashes have something to do with ZEsarUX, perhaps a small chance a 16-bit port is being tickled during the otir or else the otir is not emulated properly.

I can't say for sure until I have an updated ZEsarUX to work with.

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

Re: C API for Next hardware sprites

Post by Stefan123 » Fri Jul 07, 2017 9:55 pm

I got a bit eager and built ZEsarUX myself according to the instructions at https://sourceforge.net/p/zesarux/code/ ... ALLWINDOWS

Good to hear that the generated code looks okay. I did suspect that the problem was with ZEsarUX but I wanted a second opinion.

Yes, inlining z80_otir() would be great :-)

Thanks for trying out the code. I'll create a minimal standalone example that shows the problem and talk to chernandezba about it.

fbelavenuto
Posts: 11
Joined: Tue May 30, 2017 1:23 am

Re: C API for Next hardware sprites

Post by fbelavenuto » Mon Jul 10, 2017 3:55 pm

Hi Guys,

We need to make a change, port 0x55 has been changed and should now be used at 0x5B instead.

Thanks.

Stefan123
Posts: 102
Joined: Mon Jun 05, 2017 9:38 pm

Re: C API for Next hardware sprites

Post by Stefan123 » Mon Jul 10, 2017 5:42 pm

Great, I assume port 0x5B can then be used with the otir instruction :-)

fbelavenuto
Posts: 11
Joined: Tue May 30, 2017 1:23 am

Re: C API for Next hardware sprites

Post by fbelavenuto » Mon Jul 10, 2017 5:48 pm

Yep!
Stefan123 wrote:
Mon Jul 10, 2017 5:42 pm
Great, I assume port 0x5B can then be used with the otir instruction :-)

Post Reply