Skip to content

Commit

Permalink
[#314] zx-spectrum48k: attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
vbmacher committed Jan 28, 2023
1 parent e6a8dc6 commit 7910faa
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@

/**
* https://worldofspectrum.org/faq/reference/48kreference.htm
* http://www.breakintoprogram.co.uk/hardware/computers/zx-spectrum/screen-memory-layout
* <p>
* Uncommitted Logic Array (ULA).
*
* <p>
* OUT:
* Bit 7 6 5 4 3 2 1 0
* Bit 7 6 5 4 3 2 1 0
* +-------------------------------+
* | | | | E | M | Border |
* +-------------------------------+
Expand All @@ -40,6 +44,20 @@
* 0xfdfe A, S, D, F, G 0xdffe P, O, I, U, Y
* 0xfbfe Q, W, E, R, T 0xbffe ENTER, L, K, J, H
* 0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B
* <p>
* The colour attribute data overlays the monochrome bitmap data and is arranged in a linear fashion from left to right,
* top to bottom.
* Each attribute byte colours an 8x8 character on the screen and is encoded as follows:
* <p>
* Bit 7 6 5 4 3 2 1 0
* +-------------------------------+
* | F | B | P2| P1| P0| I2| I1| I0|
* +-------------------------------+
* <p>
* - F sets the attribute FLASH mode
* - B sets the attribute BRIGHTNESS mode
* - P2 to P0 is the PAPER colour
* - I2 to I0 is the INK colour
*/
public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener {
public static final int SCREEN_WIDTH = 32; // in bytes; each byte represents 8 pixels in a row, reversed
Expand All @@ -51,6 +69,7 @@ public class ULA implements Context8080.CpuPortDevice, Keyboard.OnKeyListener {
private final Context8080 cpu;
private final byte[] keymap = new byte[8];
public final byte[][] videoMemory = new byte[SCREEN_WIDTH][SCREEN_HEIGHT];
public final byte[][] attributeMemory = new byte[SCREEN_WIDTH][SCREEN_HEIGHT / 8];
private final static int[] lineStartOffsets = computeLineStartOffsets();

private static int[] computeLineStartOffsets() {
Expand All @@ -73,11 +92,15 @@ public Context8080 getCpu() {
}

public void readScreen() {
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
for (int y = 0; y < SCREEN_HEIGHT; y++) {
videoMemory[x][y] = memory.read(0x4000 + lineStartOffsets[y] + x);
if (y < SCREEN_HEIGHT / 8) {
attributeMemory[x][y] = memory.read(0x5800 + lineStartOffsets[y] + x);
}
}
}

cpu.signalInterrupt(RST_7);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,29 @@ public class DisplayCanvas extends Canvas implements AutoCloseable {
private static final int Y_GAP = 48; // pixels

private static final Color FOREGROUND = new Color(255, 255, 255);
private static final Color BACKGROUND = Color.BLACK;
private static final Color BACKGROUND = new Color(0xAA, 0xAA, 0xAA);

private static final Color[] COLOR_MAP = new Color[] {
new Color(0,0 ,0), // black
new Color(0,0, 0xEE), // blue
new Color(0xEE, 0, 0), // red
new Color(0xEE, 0, 0xEE), // magenta
new Color(0, 0xEE, 0), // green
new Color(0, 0xEE, 0xEE), // cyan
new Color(0xEE, 0xEE, 0), // yellow
new Color(0xEE, 0xEE, 0xEE) // white
};

private static final Color[] BRIGHT_COLOR_MAP = new Color[] {
new Color(0,0 ,0), // black
new Color(0,0, 0xFF), // blue
new Color(0xFF, 0, 0), // red
new Color(0xFF, 0, 0xFF), // magenta
new Color(0, 0xFF, 0), // green
new Color(0, 0xFF, 0xFF), // cyan
new Color(0xFF, 0xFF, 0), // yellow
new Color(0xFF, 0xFF, 0xFF) // white
};

private final AtomicBoolean painting = new AtomicBoolean(false);
private volatile Dimension size = new Dimension(0, 0);
Expand Down Expand Up @@ -71,6 +93,10 @@ public void start() {
}
}

public void redrawNow() {
paintCycle.run();
}

@Override
public Dimension getPreferredSize() {
return this.size;
Expand Down Expand Up @@ -125,11 +151,18 @@ protected void paint() {
graphics.fillRect(0, 0, dimension.width, dimension.height);

graphics.setColor(FOREGROUND);
byte[][] memory = ula.videoMemory;
byte[][] videoMemory = ula.videoMemory;
byte[][] attrMemory = ula.attributeMemory;

int screenX = 0;
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
byte row = memory[x][y];
byte row = videoMemory[x][y];
int attr = attrMemory[x][y/8];
Color[] colorMap = ((attr & 0x40) == 0x40) ? BRIGHT_COLOR_MAP : COLOR_MAP;
graphics.setBackground(colorMap[(attr >>> 3) & 7]);
graphics.setColor(colorMap[attr & 7]);

for (int i = 0; i < 8; i++) {
boolean bit = ((row << i) & 0x80) == 0x80;
if (bit) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ private void initComponents() {
panelStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(panelStatusLayout.createSequentialGroup()
.addContainerGap()
.addComponent(lblStatusIcon, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)
.addComponent(btnRedraw, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
.addContainerGap(900, Short.MAX_VALUE))
);
panelStatusLayout.setVerticalGroup(
panelStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(lblStatusIcon, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(btnRedraw, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);

GroupLayout layout = new GroupLayout(getContentPane());
Expand All @@ -76,7 +76,9 @@ private void initComponents() {
);

pack();

btnRedraw.addActionListener(e -> canvas.redrawNow());
}

private final JLabel lblStatusIcon = new JLabel("No Status");
private final JButton btnRedraw = new JButton("Redraw screen");
}

0 comments on commit 7910faa

Please sign in to comment.