mirror of
https://github.com/sethm/symon.git
synced 2025-04-12 23:37:07 +00:00
First pass at Video window
This commit is contained in:
parent
a5af522c5c
commit
e7e3c77e3f
19
pom.xml
19
pom.xml
@ -130,7 +130,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>cobertura-maven-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<check>
|
||||
<haltOnFailure>false</haltOnFailure>
|
||||
@ -167,6 +167,21 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<configuration>
|
||||
<mainClass>com.loomcom.symon.Simulator</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@ -175,7 +190,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>cobertura-maven-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>2.6</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
@ -161,10 +161,8 @@ public class Cpu implements InstructionTable {
|
||||
irAddressMode = (state.ir >> 2) & 0x07;
|
||||
irOpMode = state.ir & 0x03;
|
||||
|
||||
// Increment PC
|
||||
incrementPC();
|
||||
|
||||
// Clear the illegal opcode trap.
|
||||
clearOpTrap();
|
||||
|
||||
// Decode the instruction and operands
|
||||
@ -175,7 +173,6 @@ public class Cpu implements InstructionTable {
|
||||
incrementPC();
|
||||
}
|
||||
|
||||
// Increment step counter
|
||||
state.stepCounter++;
|
||||
|
||||
// Get the data from the effective address (if any)
|
||||
@ -412,18 +409,17 @@ public class Cpu implements InstructionTable {
|
||||
}
|
||||
|
||||
state.pc = address(bus.read(lo), bus.read(hi));
|
||||
/* TODO: For accuracy, allow a flag to enable broken behavior
|
||||
* of early 6502s:
|
||||
*
|
||||
* "An original 6502 has does not correctly fetch the target
|
||||
* address if the indirect vector falls on a page boundary
|
||||
* (e.g. $xxFF where xx is and value from $00 to $FF). In this
|
||||
* case fetches the LSB from $xxFF as expected but takes the MSB
|
||||
* from $xx00. This is fixed in some later chips like the 65SC02
|
||||
* so for compatibility always ensure the indirect vector is not
|
||||
* at the end of the page."
|
||||
* (http://www.obelisk.demon.co.uk/6502/reference.html#JMP)
|
||||
*/
|
||||
/* TODO: For accuracy, allow a flag to enable broken behavior of early 6502s:
|
||||
*
|
||||
* "An original 6502 has does not correctly fetch the target
|
||||
* address if the indirect vector falls on a page boundary
|
||||
* (e.g. $xxFF where xx is and value from $00 to $FF). In this
|
||||
* case fetches the LSB from $xxFF as expected but takes the MSB
|
||||
* from $xx00. This is fixed in some later chips like the 65SC02
|
||||
* so for compatibility always ensure the indirect vector is not
|
||||
* at the end of the page."
|
||||
* (http://www.obelisk.demon.co.uk/6502/reference.html#JMP)
|
||||
*/
|
||||
break;
|
||||
|
||||
|
||||
@ -724,6 +720,7 @@ public class Cpu implements InstructionTable {
|
||||
break;
|
||||
|
||||
/** Unimplemented Instructions ****************************************/
|
||||
// TODO: Create a flag to enable highly-accurate emulation of unimplemented instructions.
|
||||
default:
|
||||
setOpTrap();
|
||||
break;
|
||||
|
@ -24,6 +24,7 @@
|
||||
package com.loomcom.symon;
|
||||
|
||||
import com.loomcom.symon.devices.Acia;
|
||||
import com.loomcom.symon.devices.Crtc;
|
||||
import com.loomcom.symon.devices.Memory;
|
||||
import com.loomcom.symon.devices.Via;
|
||||
import com.loomcom.symon.exceptions.FifoUnderrunException;
|
||||
@ -37,11 +38,7 @@ import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.*;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -69,6 +66,10 @@ public class Simulator {
|
||||
// ACIA at $8800-$8803
|
||||
private static final int ACIA_BASE = 0x8800;
|
||||
|
||||
// CRTC at $9000-$9001
|
||||
private static final int CRTC_BASE = 0x9000;
|
||||
private static final int VIDEO_RAM_BASE = 0x7000;
|
||||
|
||||
// 16KB ROM at $C000-$FFFF
|
||||
private static final int ROM_BASE = 0xC000;
|
||||
private static final int ROM_SIZE = 0x4000;
|
||||
@ -96,12 +97,18 @@ public class Simulator {
|
||||
private final Cpu cpu;
|
||||
private final Acia acia;
|
||||
private final Via via;
|
||||
private final Crtc crtc;
|
||||
private final Memory ram;
|
||||
private Memory rom;
|
||||
|
||||
// Number of CPU steps between CRT repaints.
|
||||
// TODO: Dynamically refresh the value at runtime based on performance figures to reach ~ 30fps.
|
||||
private long stepsBetweenCrtcRefreshes = 2500;
|
||||
|
||||
// A counter to keep track of the number of UI updates that have been
|
||||
// requested
|
||||
private int stepsSinceLastUpdate = 0;
|
||||
private int stepsSinceLastCrtcRefresh = 0;
|
||||
|
||||
// The number of steps to run per click of the "Step" button
|
||||
private int stepsPerClick = 1;
|
||||
@ -123,10 +130,7 @@ public class Simulator {
|
||||
*/
|
||||
private MemoryWindow memoryWindow;
|
||||
|
||||
/**
|
||||
* The Zero Page Window shows the contents of page 0.
|
||||
*/
|
||||
private JFrame zeroPageWindow;
|
||||
private VideoWindow videoWindow;
|
||||
|
||||
private SimulatorMenu menuBar;
|
||||
|
||||
@ -153,6 +157,7 @@ public class Simulator {
|
||||
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
|
||||
this.cpu = new Cpu();
|
||||
this.ram = new Memory(MEMORY_BASE, MEMORY_SIZE, false);
|
||||
this.crtc = new Crtc(CRTC_BASE, ram, VIDEO_RAM_BASE);
|
||||
|
||||
bus.addCpu(cpu);
|
||||
bus.addDevice(ram);
|
||||
@ -259,6 +264,9 @@ public class Simulator {
|
||||
// Prepare the memory window
|
||||
memoryWindow = new MemoryWindow(bus);
|
||||
|
||||
// Prepare the video window
|
||||
videoWindow = new VideoWindow(crtc.getCrtPanel());
|
||||
|
||||
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
// The Menu. This comes last, because it relies on other components having
|
||||
@ -266,10 +274,10 @@ public class Simulator {
|
||||
menuBar = new SimulatorMenu();
|
||||
mainWindow.setJMenuBar(menuBar);
|
||||
|
||||
console.requestFocus();
|
||||
|
||||
mainWindow.pack();
|
||||
mainWindow.setVisible(true);
|
||||
|
||||
console.requestFocus();
|
||||
}
|
||||
|
||||
private void handleStart() {
|
||||
@ -365,6 +373,11 @@ public class Simulator {
|
||||
logger.severe("Console type-ahead buffer underrun!");
|
||||
}
|
||||
|
||||
if (stepsSinceLastCrtcRefresh++ > stepsBetweenCrtcRefreshes) {
|
||||
videoWindow.refreshDisplay();
|
||||
stepsSinceLastCrtcRefresh = 0;
|
||||
}
|
||||
|
||||
// This is a very expensive update, and we're doing it without
|
||||
// a delay, so we don't want to overwhelm the Swing event processing thread
|
||||
// with requests. Limit the number of ui updates that can be performed.
|
||||
@ -671,6 +684,23 @@ public class Simulator {
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleVideoWindowAction extends AbstractAction {
|
||||
public ToggleVideoWindowAction() {
|
||||
super("Video Window", null);
|
||||
putValue(SHORT_DESCRIPTION, "Show or Hide the Video Window");
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
synchronized (videoWindow) {
|
||||
if (videoWindow.isVisible()) {
|
||||
videoWindow.setVisible(false);
|
||||
} else {
|
||||
videoWindow.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SimulatorMenu extends JMenuBar {
|
||||
// Menu Items
|
||||
private JMenuItem loadProgramItem;
|
||||
@ -758,6 +788,15 @@ public class Simulator {
|
||||
});
|
||||
viewMenu.add(showMemoryTable);
|
||||
|
||||
final JCheckBoxMenuItem showVideoWindow = new JCheckBoxMenuItem(new ToggleVideoWindowAction());
|
||||
videoWindow.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
showVideoWindow.setSelected(false);
|
||||
}
|
||||
});
|
||||
viewMenu.add(showVideoWindow);
|
||||
|
||||
add(viewMenu);
|
||||
}
|
||||
|
||||
|
101
src/main/java/com/loomcom/symon/devices/Crtc.java
Normal file
101
src/main/java/com/loomcom/symon/devices/Crtc.java
Normal file
@ -0,0 +1,101 @@
|
||||
package com.loomcom.symon.devices;
|
||||
|
||||
import com.loomcom.symon.exceptions.MemoryAccessException;
|
||||
import com.loomcom.symon.exceptions.MemoryRangeException;
|
||||
import com.loomcom.symon.ui.CrtPanel;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Simulation of a 6545 CRTC and virtual CRT output.
|
||||
*/
|
||||
public class Crtc extends Device {
|
||||
|
||||
public static final int CHAR_WIDTH = 8;
|
||||
public static final int CHAR_HEIGHT = 8;
|
||||
public static final int SCAN_LINES = 9;
|
||||
public static final int COLUMNS = 40;
|
||||
public static final int ROWS = 25;
|
||||
public static final int SCALE = 2;
|
||||
|
||||
public static final int REGISTER_SELECT = 0;
|
||||
public static final int REGISTER_WRITE = 1;
|
||||
|
||||
public static String CHAR_ROM_RESOURCE = "/pet.rom";
|
||||
|
||||
private CrtPanel crtPanel;
|
||||
private int currentRegister = 0;
|
||||
|
||||
public Crtc(int deviceAddress, Memory memory, int videoRamStartAddress) throws MemoryRangeException, IOException {
|
||||
super(deviceAddress, 2, "CRTC");
|
||||
this.crtPanel = new CrtPanel(loadCharRom(CHAR_ROM_RESOURCE), memory.getDmaAccess(), COLUMNS, ROWS,
|
||||
CHAR_WIDTH, CHAR_HEIGHT,
|
||||
SCALE, SCALE, videoRamStartAddress);
|
||||
}
|
||||
|
||||
private byte[] loadCharRom(String resource) throws IOException {
|
||||
BufferedInputStream bis = null;
|
||||
try {
|
||||
bis = new BufferedInputStream(this.getClass().getResourceAsStream(resource));
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
while (bis.available() > 0) {
|
||||
bos.write(bis.read());
|
||||
}
|
||||
bos.flush();
|
||||
bos.close();
|
||||
return bos.toByteArray();
|
||||
} finally {
|
||||
if (bis != null) {
|
||||
bis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CrtPanel getCrtPanel() {
|
||||
return crtPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int address, int data) throws MemoryAccessException {
|
||||
switch (address) {
|
||||
case REGISTER_SELECT:
|
||||
setCurrentRegister(data);
|
||||
case REGISTER_WRITE:
|
||||
writeRegisterValue(data);
|
||||
default:
|
||||
throw new MemoryAccessException("No such address.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(int address) throws MemoryAccessException {
|
||||
switch (address) {
|
||||
case REGISTER_SELECT:
|
||||
return status();
|
||||
case REGISTER_WRITE:
|
||||
return 0;
|
||||
default:
|
||||
throw new MemoryAccessException("No such address.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private int status() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void setCurrentRegister(int registerNumber) {
|
||||
this.currentRegister = registerNumber;
|
||||
}
|
||||
|
||||
private void writeRegisterValue(int data) {
|
||||
|
||||
}
|
||||
}
|
@ -109,4 +109,7 @@ public class Memory extends Device {
|
||||
return "Memory: " + getMemoryRange().toString();
|
||||
}
|
||||
|
||||
public int[] getDmaAccess() {
|
||||
return mem;
|
||||
}
|
||||
}
|
275
src/main/java/com/loomcom/symon/ui/CrtPanel.java
Normal file
275
src/main/java/com/loomcom/symon/ui/CrtPanel.java
Normal file
@ -0,0 +1,275 @@
|
||||
package com.loomcom.symon.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Simulates a CRT Display backed by a 6545 CRTC.
|
||||
*/
|
||||
public class CrtPanel extends JPanel {
|
||||
|
||||
// Character width and height are hardware-implementation specific
|
||||
// and cannot be modified at runtime.
|
||||
private final int charWidth;
|
||||
private final int charHeight;
|
||||
private final int scaleX, scaleY;
|
||||
private final boolean shouldScale;
|
||||
|
||||
private Dimension dimensions;
|
||||
private BufferedImage image;
|
||||
|
||||
private int[] charRom;
|
||||
private int[] videoRam;
|
||||
|
||||
/* Fields corresponding to internal registers in the MOS/Rockwell 6545 */
|
||||
|
||||
// R1 - Horizontal Displayed
|
||||
private final int horizontalDisplayed;
|
||||
// R6 - Vertical Displayed
|
||||
private final int verticalDisplayed;
|
||||
// R9 - Scan Lines: Number of scan lines per character, including spacing.
|
||||
private int scanLinesPerRow = 9;
|
||||
// R10 - Cursor Start
|
||||
private int cursorStartLine;
|
||||
// R11 - Cursor End
|
||||
private int cursorStopLine;
|
||||
private boolean cursorEnabled;
|
||||
private int cursorBlinkDelay;
|
||||
private boolean cursorBlinkEnabled;
|
||||
// R12, R13 - Display Start Address: The starting address in the video RAM of the displayed page.
|
||||
private int startAddress;
|
||||
// R14, R15 - Cursor Position
|
||||
private int cursorPosition;
|
||||
|
||||
// The size, in bytes, of a displayed page of characters.
|
||||
private int pageSize;
|
||||
|
||||
private ScheduledExecutorService scheduler;
|
||||
private ScheduledFuture<?> cursorBlinker;
|
||||
|
||||
private class CursorBlinker implements Runnable {
|
||||
public void run() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
if (cursorBlinkEnabled) {
|
||||
cursorEnabled = !cursorEnabled;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public CrtPanel(byte[] charRom, int[] videoRam,
|
||||
int horizontalDisplayed, int verticalDisplayed,
|
||||
int charWidth, int charHeight,
|
||||
int scaleX, int scaleY,
|
||||
int startAddress) {
|
||||
super();
|
||||
|
||||
this.charRom = convertCharRom(charRom, charWidth);
|
||||
this.videoRam = videoRam;
|
||||
this.horizontalDisplayed = horizontalDisplayed;
|
||||
this.verticalDisplayed = verticalDisplayed;
|
||||
this.pageSize = horizontalDisplayed * verticalDisplayed;
|
||||
this.scaleX = scaleX;
|
||||
this.scaleY = scaleY;
|
||||
this.startAddress = startAddress;
|
||||
this.charWidth = charWidth;
|
||||
this.charHeight = charHeight;
|
||||
this.scanLinesPerRow = charHeight + 1;
|
||||
this.cursorStartLine = 0;
|
||||
this.cursorStopLine = charHeight - 1;
|
||||
this.cursorBlinkEnabled = true;
|
||||
this.cursorBlinkDelay = 500; // ms
|
||||
|
||||
this.shouldScale = (this.scaleX > 1 || this.scaleY > 1);
|
||||
|
||||
buildImage();
|
||||
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(), cursorBlinkDelay, cursorBlinkDelay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
for (int i = 0; i < pageSize; i++) {
|
||||
int address = startAddress + i;
|
||||
int originX = (i % horizontalDisplayed) * charWidth;
|
||||
int originY = (i / horizontalDisplayed) * scanLinesPerRow;
|
||||
image.getRaster().setPixels(originX, originY, charWidth, scanLinesPerRow, getGlyph(i, videoRam[address]));
|
||||
}
|
||||
Graphics2D g2d = (Graphics2D)g;
|
||||
if (shouldScale) {
|
||||
g2d.scale(scaleX, scaleY);
|
||||
}
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
}
|
||||
|
||||
public void setStartAddress(int address) {
|
||||
startAddress = address;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public int getStartAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of pixels (including extra scanlines, if any) corresponding to the
|
||||
* Character ROM plus cursor overlay (if any). The cursor overlay simulates an XOR
|
||||
* of the Character Rom output and the 6545 Cursor output.
|
||||
*
|
||||
* @param position The position within the character field, from 0 to (horizontalDisplayed * verticalDisplayed)
|
||||
* @param chr The character value within the ROM to display.
|
||||
* @return
|
||||
*/
|
||||
private int[] getGlyph(int position, int chr) {
|
||||
int romOffset = (chr & 0xff) * (charHeight * charWidth);
|
||||
int[] glyph = new int[charWidth * scanLinesPerRow];
|
||||
|
||||
// Populate the character
|
||||
for (int i = 0; i < (charWidth * Math.min(charHeight, scanLinesPerRow)); i++) {
|
||||
glyph[i] = charRom[romOffset + i];
|
||||
}
|
||||
|
||||
// Overlay the cursor
|
||||
if (cursorEnabled && cursorPosition == position) {
|
||||
int cursorStart = Math.min(glyph.length, cursorStartLine * charWidth);
|
||||
int cursorStop = Math.min(glyph.length, (cursorStopLine + 1) * charWidth);
|
||||
|
||||
for (int i = cursorStart; i < cursorStop; i++) {
|
||||
glyph[i] ^= 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return glyph;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return (int) dimensions.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return (int) dimensions.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
public int getCursorStartLine() {
|
||||
return cursorStartLine;
|
||||
}
|
||||
|
||||
public void setCursorStartLine(int cursorStartLine) {
|
||||
this.cursorStartLine = cursorStartLine;
|
||||
}
|
||||
|
||||
public int getCursorStopLine() {
|
||||
return cursorStopLine;
|
||||
}
|
||||
|
||||
public void setCursorStopLine(int cursorStopLine) {
|
||||
this.cursorStopLine = cursorStopLine;
|
||||
}
|
||||
|
||||
public int getCursorBlinkDelay() {
|
||||
return cursorBlinkDelay;
|
||||
}
|
||||
|
||||
public void setCursorBlinkDelay(int cursorBlinkDelay) {
|
||||
this.cursorBlinkDelay = cursorBlinkDelay;
|
||||
}
|
||||
|
||||
public int getCursorPosition() {
|
||||
return cursorPosition;
|
||||
}
|
||||
|
||||
public void setCursorPosition(int cursorPosition) {
|
||||
this.cursorPosition = cursorPosition;
|
||||
}
|
||||
|
||||
public boolean isCursorEnabled() {
|
||||
return cursorEnabled;
|
||||
}
|
||||
|
||||
public void setCursorEnabled(boolean cursorEnabled) {
|
||||
this.cursorEnabled = cursorEnabled;
|
||||
}
|
||||
|
||||
public boolean isCursorBlinkEnabled() {
|
||||
return cursorBlinkEnabled;
|
||||
}
|
||||
|
||||
public void setCursorBlinkEnabled(boolean cursorBlinkEnabled) {
|
||||
if (cursorBlinkEnabled && cursorBlinker == null) {
|
||||
cursorBlinker = scheduler.scheduleAtFixedRate(new CursorBlinker(),
|
||||
cursorBlinkDelay,
|
||||
cursorBlinkDelay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
} else if (!cursorBlinkEnabled && cursorBlinker != null) {
|
||||
cursorBlinker.cancel(true);
|
||||
cursorBlinker = null;
|
||||
}
|
||||
|
||||
this.cursorBlinkEnabled = cursorBlinkEnabled;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setScanLinesPerRow(int scanLinesPerRow) {
|
||||
this.scanLinesPerRow = scanLinesPerRow;
|
||||
buildImage();
|
||||
}
|
||||
|
||||
public int getScanLinesPerRow() {
|
||||
return scanLinesPerRow;
|
||||
}
|
||||
|
||||
private void buildImage() {
|
||||
int rasterWidth = charWidth * horizontalDisplayed;
|
||||
int rasterHeight = scanLinesPerRow * verticalDisplayed;
|
||||
|
||||
this.image = new BufferedImage(rasterWidth, rasterHeight, BufferedImage.TYPE_BYTE_BINARY);
|
||||
this.dimensions = new Dimension(rasterWidth * scaleX, rasterHeight * scaleY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a raw binary Character ROM image into an array of pixel data usable
|
||||
* by the Raster underlying the display's BufferedImage.
|
||||
*
|
||||
* @param rawBytes
|
||||
* @param charWidth
|
||||
* @return
|
||||
*/
|
||||
private int[] convertCharRom(byte[] rawBytes, int charWidth) {
|
||||
int[] converted = new int[rawBytes.length * charWidth];
|
||||
|
||||
int romIndex = 0;
|
||||
for (int i = 0; i < converted.length;) {
|
||||
byte charRow = rawBytes[romIndex++];
|
||||
|
||||
for (int j = 7; j >= 0; j--) {
|
||||
converted[i++] = ((charRow & (1 << j)) == 0) ? 0 : 0xff;
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
}
|
39
src/main/java/com/loomcom/symon/ui/VideoWindow.java
Normal file
39
src/main/java/com/loomcom/symon/ui/VideoWindow.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.loomcom.symon.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class VideoWindow extends JFrame {
|
||||
|
||||
private CrtPanel crtPanel;
|
||||
|
||||
public VideoWindow(CrtPanel crtPanel) {
|
||||
this.crtPanel = crtPanel;
|
||||
createUi();
|
||||
}
|
||||
|
||||
public void createUi() {
|
||||
setTitle("Composite Video");
|
||||
|
||||
int borderWidth = (int) (crtPanel.getWidth() * 0.08);
|
||||
int borderHeight = (int) (crtPanel.getHeight() * 0.08);
|
||||
|
||||
JPanel containerPane = new JPanel();
|
||||
containerPane.setBorder(BorderFactory.createEmptyBorder(borderHeight, borderWidth, borderHeight, borderWidth));
|
||||
containerPane.setLayout(new BorderLayout());
|
||||
containerPane.setBackground(Color.black);
|
||||
containerPane.add(crtPanel, BorderLayout.CENTER);
|
||||
|
||||
getContentPane().add(containerPane);
|
||||
setResizable(false);
|
||||
pack();
|
||||
}
|
||||
|
||||
public void refreshDisplay() {
|
||||
// TODO: Verify whether this is necessary. Does `repaint()' do anything if the window is not visible?
|
||||
if (isVisible()) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
BIN
src/main/resources/cga8.rom
Normal file
BIN
src/main/resources/cga8.rom
Normal file
Binary file not shown.
BIN
src/main/resources/pet.rom
Normal file
BIN
src/main/resources/pet.rom
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user