CRTC refactoring
This commit is contained in:
1 parent
e157217f50
commit
eeb246ebc2
3 files changed
+101
-32
No files matched your search
@@ -65,6 +65,11 @@ public class Crtc extends Device {
|
|||||||
|
|
||||||
private int currentRegister = 0;
|
private int currentRegister = 0;
|
||||||
|
|
||||||
|
// Status bits
|
||||||
|
private boolean rowColumnAddressing = false;
|
||||||
|
private boolean displayEnableSkew = false;
|
||||||
|
private boolean cursorSkew = false;
|
||||||
|
|
||||||
private Memory memory;
|
private Memory memory;
|
||||||
|
|
||||||
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
public Crtc(int deviceAddress, Memory memory) throws MemoryRangeException, IOException {
|
||||||
@@ -118,8 +123,9 @@ public class Crtc extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getDmaAccess() {
|
public int getCharAtAddress(int address) throws MemoryAccessException {
|
||||||
return memory.getDmaAccess();
|
// TODO: Row/Column addressing
|
||||||
|
return memory.read(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHorizontalDisplayed() {
|
public int getHorizontalDisplayed() {
|
||||||
@@ -162,6 +168,18 @@ public class Crtc extends Device {
|
|||||||
return pageSize;
|
return pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getRowColumnAddressing() {
|
||||||
|
return rowColumnAddressing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getDisplayEnableSkew() {
|
||||||
|
return displayEnableSkew;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getCursorSkew() {
|
||||||
|
return cursorSkew;
|
||||||
|
}
|
||||||
|
|
||||||
private void setCurrentRegister(int registerNumber) {
|
private void setCurrentRegister(int registerNumber) {
|
||||||
this.currentRegister = registerNumber;
|
this.currentRegister = registerNumber;
|
||||||
}
|
}
|
||||||
@@ -180,7 +198,9 @@ public class Crtc extends Device {
|
|||||||
pageSize = horizontalDisplayed * verticalDisplayed;
|
pageSize = horizontalDisplayed * verticalDisplayed;
|
||||||
break;
|
break;
|
||||||
case MODE_CONTROL:
|
case MODE_CONTROL:
|
||||||
// TODO: Implement multiple addressing modes and cursor skew.
|
rowColumnAddressing = (data & 0x04) != 0;
|
||||||
|
displayEnableSkew = (data & 0x10) != 0;
|
||||||
|
cursorSkew = (data & 0x20) != 0;
|
||||||
break;
|
break;
|
||||||
case SCAN_LINE:
|
case SCAN_LINE:
|
||||||
scanLinesPerRow = data;
|
scanLinesPerRow = data;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ package com.loomcom.symon.ui;
|
|||||||
|
|
||||||
import com.loomcom.symon.devices.Crtc;
|
import com.loomcom.symon.devices.Crtc;
|
||||||
import com.loomcom.symon.devices.DeviceChangeListener;
|
import com.loomcom.symon.devices.DeviceChangeListener;
|
||||||
|
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -36,8 +37,11 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.lang.System.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
|
* VideoWindow represents a graphics framebuffer backed by a 6545 CRTC.
|
||||||
* Each time the window's VideoPanel is repainted, the video memory is
|
* Each time the window's VideoPanel is repainted, the video memory is
|
||||||
@@ -65,7 +69,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
|
|
||||||
private BufferedImage image;
|
private BufferedImage image;
|
||||||
private int[] charRom;
|
private int[] charRom;
|
||||||
private int[] videoRam;
|
|
||||||
|
|
||||||
private int horizontalDisplayed;
|
private int horizontalDisplayed;
|
||||||
private int verticalDisplayed;
|
private int verticalDisplayed;
|
||||||
@@ -85,17 +88,21 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
private class VideoPanel extends JPanel {
|
private class VideoPanel extends JPanel {
|
||||||
@Override
|
@Override
|
||||||
public void paintComponent(Graphics g) {
|
public void paintComponent(Graphics g) {
|
||||||
for (int i = 0; i < crtc.getPageSize(); i++) {
|
try {
|
||||||
int address = crtc.getStartAddress() + i;
|
for (int i = 0; i < crtc.getPageSize(); i++) {
|
||||||
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
int address = crtc.getStartAddress() + i;
|
||||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
int originX = (i % horizontalDisplayed) * CHAR_WIDTH;
|
||||||
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||||
|
image.getRaster().setPixels(originX, originY, CHAR_WIDTH, scanLinesPerRow, getGlyph(address));
|
||||||
|
}
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
if (shouldScale) {
|
||||||
|
g2d.scale(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
g2d.drawImage(image, 0, 0, null);
|
||||||
|
} catch (MemoryAccessException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Memory Access Exception, can't paint video window! " + ex.getMessage());
|
||||||
}
|
}
|
||||||
Graphics2D g2d = (Graphics2D)g;
|
|
||||||
if (shouldScale) {
|
|
||||||
g2d.scale(scaleX, scaleY);
|
|
||||||
}
|
|
||||||
g2d.drawImage(image, 0, 0, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,7 +139,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
this.scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
this.crtc = crtc;
|
this.crtc = crtc;
|
||||||
this.charRom = loadCharRom("/pet.rom");
|
this.charRom = loadCharRom("/pet.rom");
|
||||||
this.videoRam = crtc.getDmaAccess();
|
|
||||||
this.scaleX = scaleX;
|
this.scaleX = scaleX;
|
||||||
this.scaleY = scaleY;
|
this.scaleY = scaleY;
|
||||||
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
this.shouldScale = (scaleX > 1 || scaleY > 1);
|
||||||
@@ -158,13 +164,6 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshDisplay() {
|
|
||||||
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
|
||||||
if (isVisible()) {
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the CRTC on state change.
|
* Called by the CRTC on state change.
|
||||||
*/
|
*/
|
||||||
@@ -239,15 +238,13 @@ public class VideoWindow extends JFrame implements DeviceChangeListener {
|
|||||||
* @param address The address of the character being requested.
|
* @param address The address of the character being requested.
|
||||||
* @return An array of integers representing the pixel data.
|
* @return An array of integers representing the pixel data.
|
||||||
*/
|
*/
|
||||||
private int[] getGlyph(int address) {
|
private int[] getGlyph(int address) throws MemoryAccessException {
|
||||||
int chr = videoRam[address];
|
int chr = crtc.getCharAtAddress(address);
|
||||||
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
int romOffset = (chr & 0xff) * (CHAR_HEIGHT * CHAR_WIDTH);
|
||||||
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
int[] glyph = new int[CHAR_WIDTH * scanLinesPerRow];
|
||||||
|
|
||||||
// Populate the character
|
// Populate the character
|
||||||
for (int i = 0; i < (CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow)); i++) {
|
arraycopy(charRom, romOffset, glyph, 0, CHAR_WIDTH * Math.min(CHAR_HEIGHT, scanLinesPerRow));
|
||||||
glyph[i] = charRom[romOffset + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlay the cursor
|
// Overlay the cursor
|
||||||
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
|
if (!hideCursor && crtc.isCursorEnabled() && crtc.getCursorPosition() == address) {
|
||||||
|
|||||||
@@ -19,19 +19,17 @@ import static org.mockito.Mockito.*;
|
|||||||
public class CrtcTest {
|
public class CrtcTest {
|
||||||
|
|
||||||
Crtc crtc;
|
Crtc crtc;
|
||||||
|
Memory memory;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
DeviceChangeListener changeListener;
|
DeviceChangeListener changeListener;
|
||||||
@Mock
|
|
||||||
Memory memory;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void createDevices() throws Exception {
|
public void createDevices() throws Exception {
|
||||||
|
memory = new Memory(0, 0x7fff);
|
||||||
|
|
||||||
crtc = new Crtc(0x9000, memory);
|
crtc = new Crtc(0x9000, memory);
|
||||||
crtc.registerListener(changeListener);
|
crtc.registerListener(changeListener);
|
||||||
|
|
||||||
when(memory.startAddress()).thenReturn(0);
|
|
||||||
when(memory.endAddress()).thenReturn(0x7fff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -340,4 +338,58 @@ public class CrtcTest {
|
|||||||
crtc.write(1, 0x80); // Can't position cursor
|
crtc.write(1, 0x80); // Can't position cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetRowColumnAddressing() throws Exception {
|
||||||
|
assertEquals(false, crtc.getRowColumnAddressing());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x04);
|
||||||
|
assertEquals(true, crtc.getRowColumnAddressing());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetDisplayEnableSkew() throws Exception {
|
||||||
|
assertEquals(false, crtc.getDisplayEnableSkew());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x10);
|
||||||
|
assertEquals(true, crtc.getDisplayEnableSkew());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSetCursorSkew() throws Exception {
|
||||||
|
assertEquals(false, crtc.getCursorSkew());
|
||||||
|
crtc.write(0, 8); // Select mode control register
|
||||||
|
crtc.write(1, 0x20);
|
||||||
|
assertEquals(true, crtc.getCursorSkew());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDoStraightBinaryAddressing() throws Exception {
|
||||||
|
crtc.write(0, 8);
|
||||||
|
crtc.write(1, 0); // Select straight binary addressing
|
||||||
|
|
||||||
|
// Fill the memory with a repeating pattern
|
||||||
|
int videoMemoryBase = 0x7000;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2048; i++) {
|
||||||
|
memory.write(videoMemoryBase + i, j);
|
||||||
|
if (j == 255) {
|
||||||
|
j = 0;
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify that straight-binary addressing of the CRTC works
|
||||||
|
j = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2048; i++) {
|
||||||
|
assertEquals(j, crtc.getCharAtAddress(videoMemoryBase + i));
|
||||||
|
if (j == 255) {
|
||||||
|
j = 0;
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user