Screen device

This is the user manual for the screen device of the Bedrock computer system.

This document is aimed at people who are learning about or writing programs for the Bedrock system. For people who are implementing the system from scratch, the screen device specification will provide more relevant information.

Overview

The screen device provides access to a resizable 16 colour screen with two layers.

Concepts

The screen has a palette of 16 freely changeable colours, selected from a 12-bit RGB colour space. Every pixel on the screen is drawn using one of these 16 colours, and changing a colour in the palette with the palette port will update all pixels on the screen that use that colour.

The screen has two overlapping layers, called the foreground layer and the background layer. Either layer can be drawn to by writing to the draw port. The foreground layer is drawn in front of the background layer, with colour zero on the foreground layer being treated as transparent.

The screen cursor is a two-dimensional point that marks a position for the next draw operation. It can be set by writing to the horizontal coordinate and vertical coordinate ports, or shifted up to 63 pixels by writing to the move port. Each coordinate value is signed, where the values 0000 to 7FFF represent 0 to +32767, and the values FFFF to 8000 represent -1 to -32768. The pixel in the top-left corner of the screen has coordinates zero-zero.

Sprites are small 8 by 8 pixel images, drawn with either two or four colours. A two-colour sprite is stored as eight bytes, with each byte representing a row and each bit of the byte representing a pixel. A four-colour sprite is stored as sixteen bytes and is treated as two overlapping two-colour sprites, with each pixel of the sprite being represented by two bits. See the loading a sprite example for a diagram.

Sprites are drawn using a set of four selected colours.

Ports

Port Name Description Read Write
50 Horizontal coordinate Screen cursor position.
51 grouped grouped
52 Vertical coordinate Screen cursor position.
53 grouped grouped
54 Screen width Screen width.
55 grouped grouped
56 Screen height Screen height.
57 grouped grouped
58 Palette Change the colour palette.
59 grouped grouped
5A Selection Select the sprite colours.
5B grouped grouped
5C Sprite Write sprite data to a buffer.
5D aliased aliased
5E Draw Draw to the screen.
5F Move Move the screen cursor.

Horizontal coordinate

Writing a double to this port will set the horizontal coordinate of the screen cursor, and reading a double will return the horizontal coordinate. The value is measured in pixels and increases rightwards. The pixel in the top-left corner of the program window has a coordinate of zero.

See the setting the screen cursor example.

Vertical coordinate

Writing a double to this port will set the vertical coordinate of the screen cursor, and reading a double will return the vertical coordinate. The value is measured in pixels and increases downwards. The pixel in the top-left corner of the program window has a coordinate of zero.

See the setting the screen cursor example.

Screen width

Reading a double from this port will return the width of the screen in pixels. Writing a double to this port will set and lock the screen width, preventing it from being changed by the user.

Screen height

Reading a double from this port will return the height of the screen in pixels. Writing a double to this port will set and lock the screen height, preventing it from being changed by the user.

Palette

Writing a double to this port will set one of the 16 palette colours. The first digit is the palette colour index, and the other three digits are the red, green, and blue values of the colour.

See the setting the palette example.

Bits Description
F000 Index
0F00 Red
00F0 Green
000F Blue

Selection

Writing a double to this port will select the four palette colours that will be used to draw sprites to the screen. Each digit of the double determines the palette index of each of the four selected colours:

Bits Description
F000 Selected colour 0
0F00 Selected colour 1
00F0 Selected colour 2
000F Selected colour 3

Sprite

Writing a byte to this port will push that byte into the sprite buffer, which will change the sprite that will be drawn with the draw port. A two-colour sprite uses the eight most recently pushed bytes, and a four-colour sprite uses the sixteen most recent.

The next port is a port alias, allowing two bytes to be pushed to the buffer with a single instruction.

See the loading a sprite example.

Draw

Writing a byte to this port will perform a draw operation on the screen. The first digit of the byte determines which draw operation will be performed, and the second digit modifies that operation. The following table shows the operations that will be selected for each value of the first digit:

Value Operation Sprite
0_ Draw a pixel to the background layer.
1_ Draw a two-colour sprite to the background layer.
2_ Fill the background layer with a colour.
3_ Draw a four-colour sprite to the background layer.
4_ Draw a solid line to the background layer.
5_ Draw a textured line to the background layer.
6_ Draw a solid rectangle to the background layer.
7_ Draw a textured rectangle to the background layer.
8_ Draw a pixel to the foreground layer.
9_ Draw a two-colour sprite to the foreground layer.
A_ Fill the foreground layer with a colour.
B_ Draw a four-colour sprite to the foreground layer.
C_ Draw a solid line to the foreground layer.
D_ Draw a textured line to the foreground layer.
E_ Draw a solid rectangle to the foreground layer.
F_ Draw a textured rectangle to the foreground layer.

If the Sprite column is unchecked, the second digit of the byte will be the palette colour index to use for the operation. If the Sprite column is checked, the operation will use a sprite, and each bit of the second digit will represent a transformation to be applied to that sprite in the following order:

Bit Name Description
_1 Flip X Flip sprite left-to-right.
_2 Flip Y Flip sprite top-to-bottom.
_4 Flip diagonal Flip sprite bottom-left to top-right.
_8 Transparent Don’t draw sprite pixels that use selected colour 0.

Each combination of the bottom three bits will flip or rotate the sprite in a different direction:

Bit X Y D Description
_0 Rotate 0°.
_6 Rotate 90° clockwise.
_3 Rotate 180° clockwise.
_5 Rotate 270° clockwise.
_1 Flip horizontal, rotate 0° anti-clockwise.
_4 Flip horizontal, rotate 90° anti-clockwise.
_2 Flip horizontal, rotate 180° anti-clockwise.
_7 Flip horizontal, rotate 270° anti-clockwise.

The line and rectangle operations draw shapes between two points. The first point is the current position of the screen cursor, and the second point is the position of the screen cursor during the previous draw operation. The textured variants use the current two-colour sprite as the texture.

See the transforming a sprite example.

Move

Writing a byte to this port will shift the screen cursor by up to 63 pixels in a given direction. The upper two bits of the byte determine the direction, and the lower six bits determine the distance.

See the drawing and moving example.

Bits Direction
00 Right
40 Down
80 Left
C0 Up

Wake behaviour

This device will wake the system from sleep when the window is resized.

Examples

Setting the screen cursor

This example sets the screen cursor to the position of the mouse cursor each frame, and then draws a line between the old and new positions:

*:0133 STD*:58     ( set background colour    )
*:1ffc STD*:58     ( set foreground colour    )

@start
  LDD*:40 STD*:50  ( load horizontal position )
  LDD*:42 STD*:52  ( load vertical position   )
  :41 STD:5E       ( draw line to the screen  )
  *:0800 STD*:00   ( wait for an input event  )
  JMP:start

Setting the palette

Each colour in the screen palette can be selected from a 12-bit RGB colour space. To convert a hex colour code like #A2D7A3 to a value that can be written to the palette port, first discard every second digit to get the value ADA, and then prefix the palette colour index to the front. To write the colour to palette index 0, you would use 0ADA:

*:0ADA STD*:58  ( set background colour )

To convert a colour value the other way, you would duplicate every digit. The Bedrock colour value ADA would convert back to the hex colour code #AADDAA. Be aware that some precision will be lost, because we’ve converted from a 24-bit colour space to a 12-bit colour space and then back to 24-bit.

Loading a sprite

A two-colour sprite can be loaded by writing the eight bytes of the sprite to the sprite port. Each byte represents a row of the sprite, and each bit of the byte represents a pixel. Each pixel will be drawn as selected colour 0 if the bit is unset, or selected colour 1 if the bit is set. If you write out each byte in binary, you can see the image that will be drawn:

:0F ( binary 00001111 ) STD:5C
:0F ( binary 00001111 ) STD:5C
:0F ( binary 00001111 ) STD:5C
:0F ( binary 00001111 ) STD:5C
:F0 ( binary 11110000 ) STD:5C
:F0 ( binary 11110000 ) STD:5C
:F0 ( binary 11110000 ) STD:5C
:F0 ( binary 11110000 ) STD:5C

*:0133 STD*:58  ( set background colour  )
*:1ffc STD*:58  ( set foreground colour  )
*:0123 STD*:5A  ( select sprite colours  )
:10 STD:5E      ( draw two-colour sprite )

A four-colour sprite can be loaded by writing the sixteen bytes of the sprite to the sprite port. The sixteen bytes are treated as two overlapping two-colour sprites, so that each pixel will have two bits instead of one. A pixel will be drawn as selected colour 0 if neither bit is set, selected colour 1 if only the second bit is set, selected colour 2 if only the first bit is set, and selected colour 3 if both bits are set:

:00 ( binary 00000000 ) STD:5C
:00 ( binary 00000000 ) STD:5C
:00 ( binary 00000000 ) STD:5C
:00 ( binary 00000000 ) STD:5C
:FF ( binary 11111111 ) STD:5C
:FF ( binary 11111111 ) STD:5C
:FF ( binary 11111111 ) STD:5C
:FF ( binary 11111111 ) STD:5C

:0F ( binary 00001111 ) STD:5C  ( overlapped is 00001111 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 00001111 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 00001111 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 00001111 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 22223333 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 22223333 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 22223333 )
:0F ( binary 00001111 ) STD:5C  ( overlapped is 22223333 )

*:0133 STD*:58  ( set background colour   )
*:1ffc STD*:58  ( set palette colour 1    )
*:2f0f STD*:58  ( set palette colour 2    )
*:30ff STD*:58  ( set palette colour 3    )
*:0123 STD*:5A  ( select sprite colours   )
:30 STD:5E      ( draw four-colour sprite )

Reusing a sprite

Sprites can be reused by storing them in memory with an attached label, and then calling a function to load all eight or sixteen bytes of the sprite:

*:0133 STD*:58  ( set background colour )
*:1ffc STD*:58  ( set foreground colour )
*:0123 STD*:5A  ( select sprite colours )

*:sprite-a JMS:load-1-bit-sprite *:[10 08] STD*:5E
*:sprite-b JMS:load-1-bit-sprite *:[10 08] STD*:5E
*:sprite-c JMS:load-1-bit-sprite *:[10 08] STD*:5E
HLT

@sprite-a  78 CC CC CC FC CC CC 00
@sprite-b  F8 CC CC F8 CC CC F8 00
@sprite-c  78 CC C0 C0 C0 CC 78 00

@load-2-bit-sprite  ( sprite* -- )
  *:0010 JMP:-load-sprite
@load-1-bit-sprite  ( sprite* -- )
  *:0008 ( ...fall through... )
@-load-sprite  ( sprite* len* -- )
  OVR* ADD* SWP*              ( end* i* )
  &loop                       ( end* i* )
    DUP* LDA* STD*:5C         ( end* i* )
    ADD*:0002 NQK* JCN:~loop  ( end* i* )
  POP* POP* JMPr              (         )

Transforming a sprite

This example draws an arrow sprite using each of the eight flip and rotate transformations:

*:0133 STD*:58  ( set background colour )
*:1ffc STD*:58  ( set foreground colour )
*:0123 STD*:5A  ( select sprite colours )
*:sprite JMS:load-1-bit-sprite

%DRAW  :09 STD*:5E ;
:10 DRAW  :16 DRAW  :13 DRAW  :15 DRAW
:11 DRAW  :14 DRAW  :12 DRAW  :17 DRAW
HLT

@sprite  080C 7EFF FECC C8C0

@load-1-bit-sprite  ( sprite* -- )
  DUP* ADD*:0008 SWP*         ( end* i* )
  &loop                       ( end* i* )
    DUP* LDA* STD*:5C         ( end* i* )
    ADD*:0002 NQK* JCN:~loop  ( end* i* )
  POP* POP* JMPr              (         )

Drawing a line

This example will draw a line using the draw port by moving the screen cursor to the start of the line, drawing a pixel, moving the screen cursor to the end of the line, and then performing the line-drawing operation:

*:0133 STD*:58     ( set background colour )
*:1ffc STD*:58     ( set foreground colour )

*:0010 STD*:50     ( set screen cursor     )
*:0010 STD*:52     ( set screen cursor     )
:00 STD:5E         ( draw a pixel          )
*:0050 STD*:50     ( set screen cursor     )
*:0030 STD*:52     ( set screen cursor     )
:41 STD:5E         ( draw a line           )

Drawing and moving

The move port follows directly after the draw port, so we can perform a draw operation and move the screen cursor with a single instruction:

*:0133 STD*:58     ( set background colour )
*:1ffc STD*:58     ( set foreground colour )
*:1111 STD*:5A     ( select sprite colours )

*:[10 09] STD*:5E  ( draw and move cursor  )
*:[10 09] STD*:5E  ( draw and move cursor  )
*:[10 09] STD*:5E  ( draw and move cursor  )
*:[10 09] STD*:5E  ( draw and move cursor  )