kopia lustrzana https://github.com/maccasoft/z80-tools
Implemented TMS9918 emulation
rodzic
68e8a95dd3
commit
aa575f1235
|
@ -131,6 +131,9 @@ public class Application {
|
|||
DebugTerminal debugTerminal;
|
||||
Emulator emulator;
|
||||
|
||||
TMS9918 tms9918;
|
||||
DebugTMS9918 debugTMS9918;
|
||||
|
||||
Z80 proc;
|
||||
MemIoOps memIoOps;
|
||||
SourceMap sourceMap;
|
||||
|
@ -359,6 +362,10 @@ public class Application {
|
|||
debugTerminal.dispose();
|
||||
debugTerminal = null;
|
||||
}
|
||||
if (debugTMS9918 != null) {
|
||||
debugTMS9918.dispose();
|
||||
debugTMS9918 = null;
|
||||
}
|
||||
try {
|
||||
preferences.removePropertyChangeListener(preferencesChangeListner);
|
||||
|
||||
|
@ -1111,6 +1118,36 @@ public class Application {
|
|||
|
||||
new MenuItem(menu, SWT.SEPARATOR);
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Open terminal window");
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleOpenDebugTerminal();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Open TMS9918 window");
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleOpenTMS9918Window();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new MenuItem(menu, SWT.SEPARATOR);
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Reset\tF5");
|
||||
item.setAccelerator(SWT.F5);
|
||||
|
@ -2398,6 +2435,12 @@ public class Application {
|
|||
return;
|
||||
}
|
||||
|
||||
tms9918 = new TMS9918();
|
||||
if (debugTMS9918 != null) {
|
||||
debugTMS9918.setTMS9918(tms9918);
|
||||
debugTMS9918.redraw();
|
||||
}
|
||||
|
||||
memIoOps = new MemIoOps(65536, 256) {
|
||||
|
||||
public final static int SIOA_D = 0x81;
|
||||
|
@ -2413,7 +2456,6 @@ public class Application {
|
|||
|
||||
@Override
|
||||
public int inPort(int port) {
|
||||
int result = super.inPort(port & 0xFF);
|
||||
switch (port & 0xFF) {
|
||||
case SIOA_C: {
|
||||
try {
|
||||
|
@ -2438,7 +2480,15 @@ public class Application {
|
|||
case SIOB_C:
|
||||
return 0x04; // Always return TX buffer empty
|
||||
}
|
||||
return result;
|
||||
|
||||
if ((port & 0xFF) == preferences.getTms9918Ram()) {
|
||||
return tms9918.inRam();
|
||||
}
|
||||
if ((port & 0xFF) == preferences.getTms9918Register()) {
|
||||
return tms9918.inReg();
|
||||
}
|
||||
|
||||
return super.inPort(port & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2456,6 +2506,12 @@ public class Application {
|
|||
out.write(value);
|
||||
break;
|
||||
}
|
||||
if ((port & 0xFF) == preferences.getTms9918Ram()) {
|
||||
tms9918.outRam(value);
|
||||
}
|
||||
if ((port & 0xFF) == preferences.getTms9918Register()) {
|
||||
tms9918.outReg(value);
|
||||
}
|
||||
super.outPort(port & 0xFF, value);
|
||||
}
|
||||
|
||||
|
@ -2647,6 +2703,8 @@ public class Application {
|
|||
}
|
||||
}
|
||||
|
||||
tms9918.reset();
|
||||
|
||||
memIoOps.reset();
|
||||
memory.clearUpdates();
|
||||
viewer.getControl().setFocus();
|
||||
|
@ -2774,6 +2832,11 @@ public class Application {
|
|||
memory.update();
|
||||
registers.updateRegisters(proc);
|
||||
|
||||
tms9918.redrawFrame();
|
||||
if (debugTMS9918 != null) {
|
||||
debugTMS9918.redraw();
|
||||
}
|
||||
|
||||
LineEntry lineEntry = sourceMap.getLineAtAddress(proc.getRegPC());
|
||||
if (lineEntry != null) {
|
||||
viewer.gotToLineColumn(lineEntry.lineNumber, 0);
|
||||
|
@ -2795,6 +2858,22 @@ public class Application {
|
|||
debugTerminal.setFocus();
|
||||
}
|
||||
|
||||
private void handleOpenTMS9918Window() {
|
||||
if (debugTMS9918 == null) {
|
||||
debugTMS9918 = new DebugTMS9918();
|
||||
debugTMS9918.setTMS9918(tms9918);
|
||||
debugTMS9918.open();
|
||||
debugTMS9918.getShell().addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
debugTMS9918 = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
debugTMS9918.setFocus();
|
||||
}
|
||||
|
||||
private void handleCompileAndCopyIntelHexToClipboard() throws Exception {
|
||||
CTabItem tabItem = tabFolder.getSelection();
|
||||
if (tabItem == null) {
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.DisposeEvent;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.FocusEvent;
|
||||
import org.eclipse.swt.events.FocusListener;
|
||||
import org.eclipse.swt.events.KeyAdapter;
|
||||
import org.eclipse.swt.events.KeyEvent;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.graphics.Font;
|
||||
import org.eclipse.swt.graphics.GC;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.ImageData;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Canvas;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import com.maccasoft.tools.internal.ImageRegistry;
|
||||
|
||||
public class DebugTMS9918 {
|
||||
|
||||
Display display;
|
||||
Shell shell;
|
||||
|
||||
Memory memory;
|
||||
|
||||
Canvas canvas;
|
||||
|
||||
Text[] reg;
|
||||
Label[] regPtr;
|
||||
|
||||
TMS9918 tms9918;
|
||||
ImageData imageData;
|
||||
Image image;
|
||||
|
||||
Font font;
|
||||
|
||||
final Runnable redrawRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (canvas == null || canvas.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
memory.getControl().redraw();
|
||||
|
||||
if (image != null) {
|
||||
image.dispose();
|
||||
}
|
||||
image = new Image(display, imageData);
|
||||
|
||||
GC gc = new GC(canvas);
|
||||
try {
|
||||
Rectangle canvasBounds = canvas.getBounds();
|
||||
gc.setAdvanced(false);
|
||||
gc.setAntialias(SWT.OFF);
|
||||
gc.setInterpolation(SWT.NONE);
|
||||
gc.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, canvasBounds.width, canvasBounds.height);
|
||||
} finally {
|
||||
gc.dispose();
|
||||
}
|
||||
|
||||
for (int i = 0; i < reg.length; i++) {
|
||||
reg[i].setText(String.format("%02X", tms9918.getReg(i)));
|
||||
if (i >= 2 && i <= 6) {
|
||||
regPtr[i].setText(String.format("%04X", tms9918.getRegAddr(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public DebugTMS9918() {
|
||||
|
||||
}
|
||||
|
||||
public void open() {
|
||||
shell = new Shell();
|
||||
shell.setText("Debug TMS9918");
|
||||
shell.setData(this);
|
||||
|
||||
display = shell.getDisplay();
|
||||
|
||||
Image[] images = new Image[] {
|
||||
ImageRegistry.getImageFromResources("app128.png"),
|
||||
ImageRegistry.getImageFromResources("app64.png"),
|
||||
ImageRegistry.getImageFromResources("app48.png"),
|
||||
ImageRegistry.getImageFromResources("app32.png"),
|
||||
ImageRegistry.getImageFromResources("app16.png"),
|
||||
};
|
||||
shell.setImages(images);
|
||||
|
||||
shell.setLayout(new GridLayout(1, false));
|
||||
|
||||
Control control = createContents(shell);
|
||||
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
|
||||
|
||||
Rectangle screen = display.getBounds();
|
||||
|
||||
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
|
||||
rect.x = (screen.width - rect.width) / 2;
|
||||
rect.y = (screen.height - rect.height) / 2;
|
||||
if (rect.y < 0) {
|
||||
rect.height += rect.y * 2;
|
||||
rect.y = 0;
|
||||
}
|
||||
shell.setBounds(rect);
|
||||
|
||||
shell.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
if (image != null) {
|
||||
image.dispose();
|
||||
}
|
||||
image = null;
|
||||
|
||||
if (font != null) {
|
||||
font.dispose();
|
||||
}
|
||||
font = null;
|
||||
}
|
||||
});
|
||||
|
||||
redrawRunnable.run();
|
||||
|
||||
shell.open();
|
||||
}
|
||||
|
||||
protected Control createContents(Composite parent) {
|
||||
Composite container = new Composite(parent, SWT.NONE);
|
||||
GridLayout layout = new GridLayout(3, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
container.setLayout(layout);
|
||||
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
createMemoryPanel(container);
|
||||
createCanvas(container);
|
||||
createRegistersGroup(container);
|
||||
|
||||
container.setTabList(new Control[] {
|
||||
canvas
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void createMemoryPanel(Composite parent) {
|
||||
memory = new Memory(parent);
|
||||
memory.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
|
||||
memory.setData(tms9918.getRam());
|
||||
}
|
||||
|
||||
void createCanvas(Composite parent) {
|
||||
canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
|
||||
canvas.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
|
||||
canvas.setBounds(canvas.computeTrim(0, 0, imageData.width * 2, imageData.height * 2));
|
||||
|
||||
canvas.addPaintListener(new PaintListener() {
|
||||
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
if (image == null || image.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
Rectangle canvasBounds = canvas.getBounds();
|
||||
e.gc.setAdvanced(false);
|
||||
e.gc.setAntialias(SWT.OFF);
|
||||
e.gc.setInterpolation(SWT.NONE);
|
||||
e.gc.drawImage(image, 0, 0, imageData.width, imageData.height, 0, 0, canvasBounds.width, canvasBounds.height);
|
||||
}
|
||||
});
|
||||
|
||||
Rectangle rect = canvas.getBounds();
|
||||
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.widthHint = rect.width;
|
||||
gridData.heightHint = rect.height;
|
||||
canvas.setLayoutData(gridData);
|
||||
}
|
||||
|
||||
void createRegistersGroup(Composite parent) {
|
||||
Composite container = new Composite(parent, SWT.BORDER);
|
||||
GridLayout layout = new GridLayout(3, false);
|
||||
container.setLayout(layout);
|
||||
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
|
||||
|
||||
if ("win32".equals(SWT.getPlatform())) {
|
||||
font = new Font(display, "Courier New", 9, SWT.NONE);
|
||||
}
|
||||
else {
|
||||
font = new Font(display, "mono", 9, SWT.NONE);
|
||||
}
|
||||
|
||||
reg = new Text[8];
|
||||
regPtr = new Label[reg.length];
|
||||
|
||||
for (int i = 0; i < reg.length; i++) {
|
||||
Label label = new Label(container, SWT.NONE);
|
||||
label.setText("R" + i);
|
||||
label.setFont(font);
|
||||
|
||||
reg[i] = new Text(container, SWT.BORDER);
|
||||
reg[i].setLayoutData(new GridData(20, SWT.DEFAULT));
|
||||
reg[i].setFont(font);
|
||||
reg[i].setData(i);
|
||||
reg[i].addFocusListener(new FocusListener() {
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
updateRegister((Text) e.widget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
((Text) e.widget).selectAll();
|
||||
}
|
||||
});
|
||||
reg[i].addKeyListener(new KeyAdapter() {
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.keyCode == SWT.CR) {
|
||||
updateRegister((Text) e.widget);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (i >= 2 && i <= 6) {
|
||||
regPtr[i] = new Label(container, SWT.NONE);
|
||||
regPtr[i].setLayoutData(new GridData(40, SWT.DEFAULT));
|
||||
regPtr[i].setFont(font);
|
||||
}
|
||||
else {
|
||||
new Label(container, SWT.NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateRegister(Text widget) {
|
||||
try {
|
||||
String text = widget.getText();
|
||||
int value = Integer.parseInt(text.toUpperCase(), 16);
|
||||
if (value != tms9918.getReg((Integer) widget.getData())) {
|
||||
tms9918.setReg((Integer) widget.getData(), value);
|
||||
tms9918.redrawFrame();
|
||||
display.asyncExec(redrawRunnable);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public void setFocus() {
|
||||
canvas.setFocus();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
shell.dispose();
|
||||
}
|
||||
|
||||
public Shell getShell() {
|
||||
return shell;
|
||||
}
|
||||
|
||||
public void setTMS9918(TMS9918 tms9918) {
|
||||
this.tms9918 = tms9918;
|
||||
this.imageData = tms9918.getImageData();
|
||||
if (this.memory != null) {
|
||||
this.memory.setData(tms9918.getRam());
|
||||
}
|
||||
}
|
||||
|
||||
public void redraw() {
|
||||
display.asyncExec(redrawRunnable);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,13 +14,13 @@ import java.io.IOException;
|
|||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
|
||||
import org.eclipse.jface.window.Window;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.DisposeEvent;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
|
@ -33,10 +33,10 @@ import org.eclipse.swt.widgets.Shell;
|
|||
|
||||
import com.maccasoft.tools.internal.ImageRegistry;
|
||||
|
||||
public class DebugTerminal extends Window {
|
||||
public class DebugTerminal {
|
||||
|
||||
Display display;
|
||||
Composite container;
|
||||
Shell shell;
|
||||
Terminal term;
|
||||
|
||||
Combo cursorKeys;
|
||||
|
@ -45,12 +45,15 @@ public class DebugTerminal extends Window {
|
|||
PipedInputStream is;
|
||||
|
||||
public DebugTerminal() {
|
||||
super((Shell) null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureShell(Shell newShell) {
|
||||
super.configureShell(newShell);
|
||||
public void open() {
|
||||
shell = new Shell();
|
||||
shell.setText("Debug Terminal");
|
||||
shell.setData(this);
|
||||
|
||||
display = shell.getDisplay();
|
||||
|
||||
Image[] images = new Image[] {
|
||||
ImageRegistry.getImageFromResources("app128.png"),
|
||||
|
@ -59,17 +62,33 @@ public class DebugTerminal extends Window {
|
|||
ImageRegistry.getImageFromResources("app32.png"),
|
||||
ImageRegistry.getImageFromResources("app16.png"),
|
||||
};
|
||||
newShell.setImages(images);
|
||||
newShell.setText("Debug Terminal");
|
||||
shell.setImages(images);
|
||||
|
||||
newShell.setData(this);
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
shell.setLayout(layout);
|
||||
|
||||
Control control = createContents(shell);
|
||||
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
|
||||
|
||||
Rectangle screen = display.getBounds();
|
||||
|
||||
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
|
||||
rect.x = (screen.width - rect.width) / 2;
|
||||
rect.y = (screen.height - rect.height) / 2;
|
||||
if (rect.y < 0) {
|
||||
rect.height += rect.y * 2;
|
||||
rect.y = 0;
|
||||
}
|
||||
shell.setBounds(rect);
|
||||
|
||||
shell.open();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
display = parent.getDisplay();
|
||||
|
||||
container = new Composite(parent, SWT.NONE);
|
||||
Composite container = new Composite(parent, SWT.NONE);
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
container.setLayout(layout);
|
||||
|
@ -165,7 +184,11 @@ public class DebugTerminal extends Window {
|
|||
}
|
||||
|
||||
public void dispose() {
|
||||
getShell().dispose();
|
||||
shell.dispose();
|
||||
}
|
||||
|
||||
public Shell getShell() {
|
||||
return shell;
|
||||
}
|
||||
|
||||
public void write(int b) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2018-19 Marco Maccaferri and others.
|
||||
* Copyright (c) 2018-20 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
|
@ -29,6 +29,7 @@ import org.eclipse.swt.events.DisposeListener;
|
|||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.ImageData;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
|
@ -59,6 +60,9 @@ public class Emulator {
|
|||
Composite container;
|
||||
Terminal term;
|
||||
|
||||
Shell tms9918Shell;
|
||||
TMS9918Terminal tms9918term;
|
||||
|
||||
Combo cursorKeys;
|
||||
|
||||
PipedOutputStream os;
|
||||
|
@ -72,52 +76,8 @@ public class Emulator {
|
|||
}
|
||||
|
||||
public void open() {
|
||||
display = Display.getDefault();
|
||||
preferences = Preferences.getInstance();
|
||||
|
||||
shell = new Shell(display);
|
||||
shell.setText(Application.APP_TITLE);
|
||||
shell.setData(this);
|
||||
|
||||
Image[] images = new Image[] {
|
||||
ImageRegistry.getImageFromResources("app128.png"),
|
||||
ImageRegistry.getImageFromResources("app64.png"),
|
||||
ImageRegistry.getImageFromResources("app48.png"),
|
||||
ImageRegistry.getImageFromResources("app32.png"),
|
||||
ImageRegistry.getImageFromResources("app16.png"),
|
||||
};
|
||||
shell.setImages(images);
|
||||
|
||||
Menu menu = new Menu(shell, SWT.BAR);
|
||||
createFileMenu(menu);
|
||||
createEditMenu(menu);
|
||||
createHelpMenu(menu);
|
||||
shell.setMenuBar(menu);
|
||||
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
shell.setLayout(layout);
|
||||
|
||||
Control control = createContents(shell);
|
||||
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
Rectangle screen = display.getClientArea();
|
||||
|
||||
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
|
||||
|
||||
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
|
||||
rect.x = (screen.width - rect.width) / 2;
|
||||
rect.y = (screen.height - rect.height) / 2;
|
||||
if (rect.y < 0) {
|
||||
rect.height += rect.y * 2;
|
||||
rect.y = 0;
|
||||
}
|
||||
|
||||
shell.setLocation(rect.x, rect.y);
|
||||
shell.setSize(rect.width, rect.height);
|
||||
|
||||
shell.open();
|
||||
|
||||
machine = new Machine() {
|
||||
|
||||
@Override
|
||||
|
@ -205,17 +165,112 @@ public class Emulator {
|
|||
super.outPort(port, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTMS9918VSync() {
|
||||
if (tms9918term != null) {
|
||||
tms9918term.redraw();
|
||||
}
|
||||
super.onTMS9918VSync();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
machine.tmsRam = preferences.getTms9918Ram();
|
||||
machine.tmsReg = preferences.getTms9918Register();
|
||||
|
||||
String s = preferences.getCompactFlashImage();
|
||||
if (s != null && !"".equals(s)) {
|
||||
machine.setCompactFlash(new File(s));
|
||||
}
|
||||
|
||||
createTerminalShell();
|
||||
|
||||
if (preferences.isOpenTMS9918Window()) {
|
||||
tms9918Shell = createTMS9918Shell();
|
||||
|
||||
Rectangle screen = display.getClientArea();
|
||||
|
||||
Rectangle rect = new Rectangle(0, 0, shell.getSize().x + 5 + tms9918Shell.getSize().x, shell.getSize().y);
|
||||
rect.x = (screen.width - rect.width) / 2;
|
||||
if (rect.x < 0) {
|
||||
rect.x = 0;
|
||||
}
|
||||
rect.y = (screen.height - rect.height) / 2;
|
||||
if (rect.y < 0) {
|
||||
rect.height += rect.y * 2;
|
||||
rect.y = 0;
|
||||
}
|
||||
|
||||
shell.setLocation(rect.x, rect.y);
|
||||
tms9918Shell.setLocation(rect.x + shell.getSize().x + 5, rect.y);
|
||||
|
||||
tms9918Shell.open();
|
||||
}
|
||||
|
||||
shell.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
if (tms9918Shell != null) {
|
||||
tms9918Shell.dispose();
|
||||
}
|
||||
tms9918Shell = null;
|
||||
}
|
||||
});
|
||||
shell.open();
|
||||
|
||||
machine.reset();
|
||||
machine.start();
|
||||
}
|
||||
|
||||
protected Shell createTerminalShell() {
|
||||
display = Display.getDefault();
|
||||
|
||||
shell = new Shell(display);
|
||||
shell.setText(Application.APP_TITLE);
|
||||
shell.setData(this);
|
||||
|
||||
Image[] images = new Image[] {
|
||||
ImageRegistry.getImageFromResources("app128.png"),
|
||||
ImageRegistry.getImageFromResources("app64.png"),
|
||||
ImageRegistry.getImageFromResources("app48.png"),
|
||||
ImageRegistry.getImageFromResources("app32.png"),
|
||||
ImageRegistry.getImageFromResources("app16.png"),
|
||||
};
|
||||
shell.setImages(images);
|
||||
|
||||
Menu menu = new Menu(shell, SWT.BAR);
|
||||
createFileMenu(menu);
|
||||
createEditMenu(menu);
|
||||
createWindowMenu(menu);
|
||||
createHelpMenu(menu);
|
||||
shell.setMenuBar(menu);
|
||||
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
shell.setLayout(layout);
|
||||
|
||||
Control control = createContents(shell);
|
||||
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
Rectangle screen = display.getClientArea();
|
||||
|
||||
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
|
||||
|
||||
Rectangle rect = shell.computeTrim(0, 0, size.x, size.y);
|
||||
rect.x = (screen.width - rect.width) / 2;
|
||||
rect.y = (screen.height - rect.height) / 2;
|
||||
if (rect.y < 0) {
|
||||
rect.height += rect.y * 2;
|
||||
rect.y = 0;
|
||||
}
|
||||
|
||||
shell.setLocation(rect.x, rect.y);
|
||||
shell.setSize(rect.width, rect.height);
|
||||
|
||||
return shell;
|
||||
}
|
||||
|
||||
void createFileMenu(Menu parent) {
|
||||
final Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
|
||||
|
||||
|
@ -273,6 +328,28 @@ public class Emulator {
|
|||
});
|
||||
}
|
||||
|
||||
void createWindowMenu(Menu parent) {
|
||||
Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
|
||||
|
||||
MenuItem item = new MenuItem(parent, SWT.CASCADE);
|
||||
item.setText("&Window");
|
||||
item.setMenu(menu);
|
||||
|
||||
item = new MenuItem(menu, SWT.PUSH);
|
||||
item.setText("Open TMS9918 Window");
|
||||
item.addListener(SWT.Selection, new Listener() {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Event e) {
|
||||
try {
|
||||
handleOpenTMS9918Window();
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void createHelpMenu(Menu parent) {
|
||||
Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN);
|
||||
|
||||
|
@ -588,6 +665,85 @@ public class Emulator {
|
|||
return shell;
|
||||
}
|
||||
|
||||
private void handleOpenTMS9918Window() {
|
||||
if (tms9918term != null) {
|
||||
tms9918term.setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
tms9918Shell = createTMS9918Shell();
|
||||
|
||||
Rectangle bounds = shell.getBounds();
|
||||
tms9918Shell.setLocation(bounds.x + bounds.width + 5, bounds.y);
|
||||
tms9918Shell.open();
|
||||
|
||||
shell.setFocus();
|
||||
}
|
||||
|
||||
protected Shell createTMS9918Shell() {
|
||||
final Shell shell = new Shell(display);
|
||||
shell.setText("TMS9918");
|
||||
|
||||
Image[] images = new Image[] {
|
||||
ImageRegistry.getImageFromResources("app128.png"),
|
||||
ImageRegistry.getImageFromResources("app64.png"),
|
||||
ImageRegistry.getImageFromResources("app48.png"),
|
||||
ImageRegistry.getImageFromResources("app32.png"),
|
||||
ImageRegistry.getImageFromResources("app16.png"),
|
||||
};
|
||||
shell.setImages(images);
|
||||
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
shell.setLayout(layout);
|
||||
|
||||
Control control = createTMS9918Contents(shell);
|
||||
control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
|
||||
Rectangle rect = shell.computeTrim(0, 0, size.x * 2, size.y * 2);
|
||||
|
||||
Rectangle screen = getShell().getBounds();
|
||||
|
||||
shell.setLocation(screen.x + screen.width + 10, screen.y);
|
||||
shell.setSize(rect.width, rect.height);
|
||||
|
||||
return shell;
|
||||
}
|
||||
|
||||
protected Control createTMS9918Contents(Composite parent) {
|
||||
Composite container = new Composite(parent, SWT.NONE);
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginWidth = layout.marginHeight = 0;
|
||||
container.setLayout(layout);
|
||||
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
tms9918term = new TMS9918Terminal(container) {
|
||||
|
||||
@Override
|
||||
protected ImageData getImageData() {
|
||||
return machine.tms9918.getImageData();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Rectangle rect = tms9918term.getBounds();
|
||||
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.widthHint = rect.width;
|
||||
gridData.heightHint = rect.height;
|
||||
tms9918term.setLayoutData(gridData);
|
||||
|
||||
container.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
tms9918term = null;
|
||||
}
|
||||
});
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
static {
|
||||
System.setProperty("SWT_GTK3", "0");
|
||||
}
|
||||
|
@ -603,6 +759,8 @@ public class Emulator {
|
|||
Emulator emulator = new Emulator();
|
||||
emulator.open();
|
||||
|
||||
emulator.getShell().setFocus();
|
||||
|
||||
while (display.getShells().length != 0) {
|
||||
if (!display.readAndDispatch()) {
|
||||
display.sleep();
|
||||
|
|
|
@ -54,16 +54,33 @@ public class Machine extends MemIoOps {
|
|||
|
||||
Z80 proc;
|
||||
Thread thread;
|
||||
double clockNs;
|
||||
long clockPeriodNs;
|
||||
long clockTimeNs;
|
||||
|
||||
int tmsRam;
|
||||
int tmsReg;
|
||||
TMS9918 tms9918;
|
||||
|
||||
public Machine() {
|
||||
rom = new byte[16384];
|
||||
ram = new byte[65536];
|
||||
|
||||
clockNs = 1000.0 / 7.3728;
|
||||
clockPeriodNs = (long) (1000.0 / 7.3728);
|
||||
clockTimeNs = 0;
|
||||
|
||||
proc = new Z80(this, null);
|
||||
|
||||
tms9918 = new TMS9918() {
|
||||
|
||||
@Override
|
||||
protected void onVSync() {
|
||||
Machine.this.onTMS9918VSync();
|
||||
}
|
||||
|
||||
};
|
||||
tmsRam = 0x40;
|
||||
tmsReg = 0x41;
|
||||
|
||||
thread = new Thread(new Runnable() {
|
||||
|
||||
@Override
|
||||
|
@ -84,7 +101,7 @@ public class Machine extends MemIoOps {
|
|||
}
|
||||
|
||||
public void setClock(double freq) {
|
||||
clockNs = 1000.0 / freq;
|
||||
clockPeriodNs = (long) (1000.0 / freq);
|
||||
}
|
||||
|
||||
public void setCompactFlash(File file) {
|
||||
|
@ -107,13 +124,17 @@ public class Machine extends MemIoOps {
|
|||
|
||||
while (!Thread.interrupted()) {
|
||||
synchronized (proc) {
|
||||
int runTstates = (int) ((System.nanoTime() - ns) / clockNs);
|
||||
int runTstates = (int) ((System.nanoTime() - ns) / clockPeriodNs);
|
||||
if (runTstates >= 4) {
|
||||
long prevTstates = tstates;
|
||||
long prevClockTime = clockTimeNs;
|
||||
while (tstates < (prevTstates + runTstates)) {
|
||||
proc.execute();
|
||||
}
|
||||
ns += (tstates - prevTstates) * clockNs;
|
||||
long elapsed = clockTimeNs - prevClockTime;
|
||||
tms9918.processFrame(elapsed);
|
||||
onElapsedTime(elapsed);
|
||||
ns += elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,11 +146,19 @@ public class Machine extends MemIoOps {
|
|||
}
|
||||
}
|
||||
|
||||
protected void onElapsedTime(long elapsedNs) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
synchronized (proc) {
|
||||
page = false;
|
||||
tstates = 0;
|
||||
clockTimeNs = 0;
|
||||
if (tms9918 != null) {
|
||||
tms9918.reset();
|
||||
}
|
||||
proc.reset();
|
||||
super.reset();
|
||||
}
|
||||
|
@ -138,6 +167,7 @@ public class Machine extends MemIoOps {
|
|||
@Override
|
||||
public int fetchOpcode(int address) {
|
||||
tstates += 4; // 3 clocks to fetch opcode from RAM and 1 execution clock
|
||||
clockTimeNs += clockPeriodNs * 4;
|
||||
if (!page && address < rom.length) {
|
||||
return rom[address & 0xFFFF] & 0xff;
|
||||
}
|
||||
|
@ -147,6 +177,14 @@ public class Machine extends MemIoOps {
|
|||
@Override
|
||||
public int inPort(int port) {
|
||||
tstates += 4; // 4 clocks for read byte from bus
|
||||
clockTimeNs += clockPeriodNs * 4;
|
||||
|
||||
if ((port & 0xFF) == tmsRam) {
|
||||
return tms9918.inRam();
|
||||
}
|
||||
if ((port & 0xFF) == tmsReg) {
|
||||
return tms9918.inReg();
|
||||
}
|
||||
|
||||
switch (port & 0xFF) {
|
||||
case CF_DATA:
|
||||
|
@ -168,12 +206,20 @@ public class Machine extends MemIoOps {
|
|||
return 0x04; // Always return TX buffer empty
|
||||
}
|
||||
|
||||
return 0;
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outPort(int port, int value) {
|
||||
tstates += 4; // 4 clocks for write byte to bus
|
||||
clockTimeNs += clockPeriodNs * 4;
|
||||
|
||||
if ((port & 0xFF) == tmsRam) {
|
||||
tms9918.outRam(value);
|
||||
}
|
||||
if ((port & 0xFF) == tmsReg) {
|
||||
tms9918.outReg(value);
|
||||
}
|
||||
|
||||
switch (port & 0xFF) {
|
||||
case CF_DATA:
|
||||
|
@ -222,6 +268,7 @@ public class Machine extends MemIoOps {
|
|||
@Override
|
||||
public int peek8(int address) {
|
||||
tstates += 3; // 3 clocks for read byte from RAM
|
||||
clockTimeNs += clockPeriodNs * 3;
|
||||
if (!page && address < rom.length) {
|
||||
return rom[address & 0xFFFF] & 0xff;
|
||||
}
|
||||
|
@ -231,6 +278,7 @@ public class Machine extends MemIoOps {
|
|||
@Override
|
||||
public void poke8(int address, int value) {
|
||||
tstates += 3; // 3 clocks for write byte to RAM
|
||||
clockTimeNs += clockPeriodNs * 3;
|
||||
if (page || address >= rom.length) {
|
||||
ram[address & 0xFFFF] = (byte) value;
|
||||
}
|
||||
|
@ -256,4 +304,8 @@ public class Machine extends MemIoOps {
|
|||
}
|
||||
}
|
||||
|
||||
protected void onTMS9918VSync() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -94,6 +94,9 @@ public class Preferences {
|
|||
String romImage2;
|
||||
int romAddress2;
|
||||
String compactFlashImage;
|
||||
boolean openTMS9918Window;
|
||||
int tms9918Ram;
|
||||
int tms9918Register;
|
||||
|
||||
int lastUploadType;
|
||||
String lastPath;
|
||||
|
@ -120,6 +123,9 @@ public class Preferences {
|
|||
downloadCommand = "A:DOWNLOAD {0}";
|
||||
xmodemCommand = "A:XMODEM {0} /R /X0 /Q";
|
||||
|
||||
tms9918Ram = 0x98;
|
||||
tms9918Register = 0x99;
|
||||
|
||||
lru = new ArrayList<String>();
|
||||
}
|
||||
|
||||
|
@ -414,6 +420,30 @@ public class Preferences {
|
|||
this.compactFlashImage = compactFlashImage;
|
||||
}
|
||||
|
||||
public boolean isOpenTMS9918Window() {
|
||||
return openTMS9918Window;
|
||||
}
|
||||
|
||||
public void setOpenTMS9918Window(boolean openTMS9918Window) {
|
||||
this.openTMS9918Window = openTMS9918Window;
|
||||
}
|
||||
|
||||
public int getTms9918Ram() {
|
||||
return tms9918Ram;
|
||||
}
|
||||
|
||||
public void setTms9918Ram(int tms9918Data) {
|
||||
this.tms9918Ram = tms9918Data;
|
||||
}
|
||||
|
||||
public int getTms9918Register() {
|
||||
return tms9918Register;
|
||||
}
|
||||
|
||||
public void setTms9918Register(int tms9918Control) {
|
||||
this.tms9918Register = tms9918Control;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
|
|
|
@ -85,6 +85,9 @@ public class PreferencesDialog extends Dialog {
|
|||
Text romImage2;
|
||||
Text romAddress2;
|
||||
Text compactFlashImage;
|
||||
Button openTMS9918Window;
|
||||
Text tms9918VRam;
|
||||
Text tms9918Register;
|
||||
|
||||
Preferences preferences;
|
||||
String defaultFont;
|
||||
|
@ -775,6 +778,23 @@ public class PreferencesDialog extends Dialog {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Label(composite, SWT.NONE);
|
||||
openTMS9918Window = new Button(composite, SWT.CHECK);
|
||||
openTMS9918Window.setText("Open TMS9918 Window");
|
||||
openTMS9918Window.setSelection(preferences.isOpenTMS9918Window());
|
||||
|
||||
label = new Label(composite, SWT.NONE);
|
||||
label.setText("TMS9918 VRam Port");
|
||||
tms9918VRam = new Text(composite, SWT.BORDER);
|
||||
tms9918VRam.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
|
||||
tms9918VRam.setText(String.format("%02X", preferences.getTms9918Ram()));
|
||||
|
||||
label = new Label(composite, SWT.NONE);
|
||||
label.setText("TMS9918 Register Port");
|
||||
tms9918Register = new Text(composite, SWT.BORDER);
|
||||
tms9918Register.setLayoutData(new GridData(convertWidthInCharsToPixels(3), SWT.DEFAULT));
|
||||
tms9918Register.setText(String.format("%02X", preferences.getTms9918Register()));
|
||||
}
|
||||
|
||||
void addSeparator(Composite parent) {
|
||||
|
@ -835,6 +855,9 @@ public class PreferencesDialog extends Dialog {
|
|||
preferences.setRomAddress2(Integer.valueOf(romAddress2.getText(), 16));
|
||||
preferences.setRomImage2(romImage2.getText());
|
||||
preferences.setCompactFlashImage(compactFlashImage.getText());
|
||||
preferences.setOpenTMS9918Window(openTMS9918Window.getSelection());
|
||||
preferences.setTms9918Ram(Integer.valueOf(tms9918VRam.getText(), 16));
|
||||
preferences.setTms9918Register(Integer.valueOf(tms9918Register.getText(), 16));
|
||||
|
||||
super.okPressed();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* Copyright (c) 2018-19 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.swt.graphics.ImageData;
|
||||
import org.eclipse.swt.graphics.PaletteData;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
|
||||
public class TMS9918 {
|
||||
|
||||
public static final int NTSC = 0;
|
||||
public static final int PAL = 1;
|
||||
|
||||
public static final int TMSMODE3 = 0b00000010;
|
||||
|
||||
public static final int TMSBLANK = 0b01000000;
|
||||
public static final int TMSMODE1 = 0b00010000;
|
||||
public static final int TMSMODE2 = 0b00001000;
|
||||
public static final int TMSSPRSIZE = 0b00000010;
|
||||
public static final int TMSSPRMAG = 0b00000001;
|
||||
|
||||
public static final int FRAME_WIDTH = 320;
|
||||
public static final int FRAME_HEIGHT = 240;
|
||||
public static final int FRAME_TOP = (FRAME_HEIGHT - 192) / 2;
|
||||
public static final int FRAME_LEFT = (FRAME_WIDTH - 256) / 2;
|
||||
|
||||
public static final int STATE_VSYNC = -1;
|
||||
|
||||
PaletteData paletteData;
|
||||
ImageData imageData;
|
||||
|
||||
byte[] reg;
|
||||
byte[] ram;
|
||||
byte status;
|
||||
|
||||
int ramPtr;
|
||||
int data;
|
||||
|
||||
int state;
|
||||
long currentTimeNs;
|
||||
long nextTimeNs;
|
||||
|
||||
int ROW_TIME_NS;
|
||||
int DISPLAY_ROWS;
|
||||
|
||||
public TMS9918() {
|
||||
ROW_TIME_NS = 63500;
|
||||
DISPLAY_ROWS = 262;
|
||||
|
||||
reg = new byte[8];
|
||||
ram = new byte[16384];
|
||||
ramPtr = 0;
|
||||
|
||||
data = -1;
|
||||
|
||||
state = STATE_VSYNC - 1;
|
||||
nextTimeNs = 0;
|
||||
|
||||
paletteData = new PaletteData(new RGB[] {
|
||||
new RGB(0x00, 0x00, 0x00), new RGB(0x00, 0x00, 0x00), new RGB(0x20, 0xC0, 0x20), new RGB(0x60, 0xE0, 0x60),
|
||||
new RGB(0x20, 0x20, 0xE0), new RGB(0x40, 0x60, 0xE0), new RGB(0xA0, 0x20, 0x20), new RGB(0x40, 0xC0, 0xE0),
|
||||
new RGB(0xE0, 0x20, 0x20), new RGB(0xE0, 0x60, 0x60), new RGB(0xC0, 0xC0, 0x20), new RGB(0xC0, 0xC0, 0x80),
|
||||
new RGB(0x20, 0x80, 0x20), new RGB(0xC0, 0x40, 0xA0), new RGB(0xA0, 0xA0, 0xA0), new RGB(0xE0, 0xE0, 0xE0)
|
||||
});
|
||||
|
||||
imageData = new ImageData(FRAME_WIDTH, FRAME_HEIGHT, 8, paletteData, 1, new byte[FRAME_WIDTH * FRAME_HEIGHT]);
|
||||
}
|
||||
|
||||
public void setTiming(int timing) {
|
||||
if (timing == PAL) {
|
||||
ROW_TIME_NS = 64000;
|
||||
DISPLAY_ROWS = 312;
|
||||
}
|
||||
else {
|
||||
ROW_TIME_NS = 63500;
|
||||
DISPLAY_ROWS = 262;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
Arrays.fill(reg, (byte) 0);
|
||||
}
|
||||
|
||||
public void outReg(int value) {
|
||||
if (data == -1) {
|
||||
data = value;
|
||||
}
|
||||
else {
|
||||
if ((value & 0x80) != 0) {
|
||||
writeReg(value & 0x07, data);
|
||||
}
|
||||
else {
|
||||
setRamPtr((value << 8) | data);
|
||||
}
|
||||
data = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void outRam(int value) {
|
||||
ram[ramPtr] = (byte) value;
|
||||
ramPtr = (ramPtr + 1) & 0x3FFF;
|
||||
}
|
||||
|
||||
protected void writeReg(int index, int value) {
|
||||
reg[index] = (byte) value;
|
||||
}
|
||||
|
||||
protected void setRamPtr(int value) {
|
||||
ramPtr = value & 0x3FFF;
|
||||
}
|
||||
|
||||
public int getRamPtr() {
|
||||
return ramPtr;
|
||||
}
|
||||
|
||||
public int inRam() {
|
||||
int result = ram[ramPtr] & 0xFF;
|
||||
ramPtr = (ramPtr + 1) & 0x3FFF;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int inReg() {
|
||||
int result = status;
|
||||
status &= ~0x80;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void processFrame(long elapsedTimeNs) {
|
||||
currentTimeNs += elapsedTimeNs;
|
||||
|
||||
while (currentTimeNs >= nextTimeNs) {
|
||||
state++;
|
||||
if (state >= FRAME_HEIGHT) {
|
||||
state = STATE_VSYNC;
|
||||
status |= 0x80;
|
||||
onVSync();
|
||||
}
|
||||
|
||||
if (state == STATE_VSYNC) {
|
||||
nextTimeNs += ROW_TIME_NS * (DISPLAY_ROWS - FRAME_HEIGHT);
|
||||
}
|
||||
else if (state >= 0 && state < FRAME_HEIGHT) {
|
||||
processRow(state);
|
||||
nextTimeNs += ROW_TIME_NS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onVSync() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void processRow(int row) {
|
||||
int register2 = (reg[2] << 10) & 0x3FFF;
|
||||
int register3 = (reg[3] << 6) & 0x3FFF;
|
||||
int register4 = (reg[4] << 11) & 0x3FFF;
|
||||
int register5 = (reg[5] << 7) & 0x3FFF;
|
||||
int register6 = (reg[6] << 11) & 0x3FFF;
|
||||
|
||||
byte backdrop = (byte) (reg[7] & 0x0F);
|
||||
|
||||
int index = row * FRAME_WIDTH;
|
||||
|
||||
if ((reg[1] & TMSBLANK) == 0) {
|
||||
Arrays.fill(imageData.data, index, index + FRAME_WIDTH, (byte) 0);
|
||||
return;
|
||||
}
|
||||
|
||||
Arrays.fill(imageData.data, index, index + FRAME_WIDTH, backdrop);
|
||||
|
||||
if (row >= FRAME_TOP && row < (FRAME_TOP + 192)) {
|
||||
index += FRAME_LEFT;
|
||||
row -= FRAME_TOP;
|
||||
|
||||
if ((reg[1] & TMSMODE1) != 0) {
|
||||
byte c0 = (byte) (reg[7] & 0x0F);
|
||||
byte c1 = (byte) ((reg[7] & 0xF0) >> 4);
|
||||
|
||||
register2 += (row >> 3) * 40;
|
||||
register4 += row & 0x07;
|
||||
|
||||
index += 8;
|
||||
for (int i = 0; i < 40; i++) {
|
||||
int tile = ram[register2++] & 0xFF;
|
||||
byte pattern = ram[register4 + (tile << 3)];
|
||||
|
||||
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((reg[1] & TMSMODE2) != 0) {
|
||||
register2 += (row >> 3) << 5;
|
||||
register4 += (row >> 2) & 0x07;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int tile = ram[register2++] & 0xFF;
|
||||
byte colors = ram[register4 + (tile << 3)];
|
||||
|
||||
byte c0 = (byte) (colors & 0x0F);
|
||||
if (c0 == 0) {
|
||||
c0 = backdrop;
|
||||
}
|
||||
byte c1 = (byte) ((colors & 0xF0) >> 4);
|
||||
if (c1 == 0) {
|
||||
c1 = backdrop;
|
||||
}
|
||||
|
||||
imageData.data[index++] = c1;
|
||||
imageData.data[index++] = c1;
|
||||
imageData.data[index++] = c1;
|
||||
imageData.data[index++] = c1;
|
||||
|
||||
imageData.data[index++] = c0;
|
||||
imageData.data[index++] = c0;
|
||||
imageData.data[index++] = c0;
|
||||
imageData.data[index++] = c0;
|
||||
}
|
||||
}
|
||||
else if ((reg[0] & TMSMODE3) != 0) {
|
||||
register2 += (row >> 3) * 32;
|
||||
|
||||
register3 &= ~0x1FFF;
|
||||
register3 += (row >> 6) << 11;
|
||||
register3 += row & 0x07;
|
||||
|
||||
register4 &= ~0x1FFF;
|
||||
register4 += (row >> 6) << 11;
|
||||
register4 += row & 0x07;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int tile = ram[register2++] & 0xFF;
|
||||
|
||||
byte pattern = ram[register4 + (tile << 3)];
|
||||
|
||||
byte colors = ram[register3 + (tile << 3)];
|
||||
byte c0 = (byte) (colors & 0x0F);
|
||||
if (c0 == 0) {
|
||||
c0 = backdrop;
|
||||
}
|
||||
byte c1 = (byte) ((colors & 0xF0) >> 4);
|
||||
if (c1 == 0) {
|
||||
c1 = backdrop;
|
||||
}
|
||||
|
||||
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x02) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x01) != 0 ? c1 : c0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
register2 += (row >> 3) * 32;
|
||||
register4 += row & 0x07;
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
int tile = ram[register2++] & 0xFF;
|
||||
|
||||
byte pattern = ram[register4 + (tile << 3)];
|
||||
|
||||
byte colors = ram[register3 + (tile >> 3)];
|
||||
byte c0 = (byte) (colors & 0x0F);
|
||||
if (c0 == 0) {
|
||||
c0 = backdrop;
|
||||
}
|
||||
byte c1 = (byte) ((colors & 0xF0) >> 4);
|
||||
if (c1 == 0) {
|
||||
c1 = backdrop;
|
||||
}
|
||||
|
||||
imageData.data[index++] = (pattern & 0x80) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x40) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x20) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x10) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x08) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x04) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x02) != 0 ? c1 : c0;
|
||||
imageData.data[index++] = (pattern & 0x01) != 0 ? c1 : c0;
|
||||
}
|
||||
}
|
||||
|
||||
int size = 8;
|
||||
if ((reg[1] & TMSSPRSIZE) != 0) {
|
||||
size <<= 1;
|
||||
}
|
||||
if ((reg[1] & TMSSPRMAG) != 0) {
|
||||
size <<= 1;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int sprite = 0; sprite < 32 && count < 4; sprite++, register5 += 4) {
|
||||
int y = ram[register5] & 0xFF;
|
||||
if (y == 0xD0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int x = ram[register5 + 1] & 0xFF;
|
||||
if ((ram[register5 + 3] & 0x80) != 0) {
|
||||
x -= 32;
|
||||
}
|
||||
int tile = ram[register5 + 2] & 0xFF;
|
||||
|
||||
byte c1 = (byte) (ram[register5 + 3] & 0x0F);
|
||||
if (c1 == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int offset = row - y;
|
||||
if (offset < 0 || offset >= size) {
|
||||
continue;
|
||||
}
|
||||
index = (row + FRAME_TOP) * FRAME_WIDTH + FRAME_LEFT + x;
|
||||
|
||||
if ((reg[1] & TMSSPRMAG) != 0) {
|
||||
offset >>= 1;
|
||||
}
|
||||
|
||||
int patternIndex = register6 + (tile << 3) + offset;
|
||||
|
||||
int pattern = ram[patternIndex] & 0xFF;
|
||||
if ((reg[1] & TMSSPRSIZE) != 0) {
|
||||
pattern = (pattern << 8) | (ram[patternIndex + 16] & 0xFF);
|
||||
for (int i = 0x8000; i != 0x0000; i >>= 1) {
|
||||
if (x >= 0 && x < 256) {
|
||||
if ((pattern & i) != 0) {
|
||||
imageData.data[index] = c1;
|
||||
}
|
||||
if ((reg[1] & TMSSPRMAG) != 0) {
|
||||
x++;
|
||||
index++;
|
||||
if (x >= 0 && x < 256) {
|
||||
if ((pattern & i) != 0) {
|
||||
imageData.data[index] = c1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0x80; i != 0x00; i >>= 1) {
|
||||
if (x >= 0 && x < 256) {
|
||||
if ((pattern & i) != 0) {
|
||||
imageData.data[index] = c1;
|
||||
}
|
||||
if ((reg[1] & TMSSPRMAG) != 0) {
|
||||
x++;
|
||||
index++;
|
||||
if (x >= 0 && x < 256) {
|
||||
if ((pattern & i) != 0) {
|
||||
imageData.data[index] = c1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void redrawFrame() {
|
||||
for (int row = 0; row < FRAME_HEIGHT; row++) {
|
||||
processRow(row);
|
||||
}
|
||||
status |= 0x80;
|
||||
}
|
||||
|
||||
public ImageData getImageData() {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status & 0xFF;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = (byte) status;
|
||||
}
|
||||
|
||||
public int getReg(int index) {
|
||||
return reg[index] & 0xFF;
|
||||
}
|
||||
|
||||
public void setReg(int index, int value) {
|
||||
reg[index] = (byte) value;
|
||||
}
|
||||
|
||||
public int getRegAddr(int index) {
|
||||
switch (index) {
|
||||
case 2:
|
||||
return (reg[2] << 10) & 0x3FFF;
|
||||
case 3:
|
||||
return (reg[3] << 6) & 0x3FFF;
|
||||
case 4:
|
||||
return (reg[4] << 11) & 0x3FFF;
|
||||
case 5:
|
||||
return (reg[5] << 7) & 0x3FFF;
|
||||
case 6:
|
||||
return (reg[6] << 11) & 0x3FFF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public byte[] getRegs() {
|
||||
return this.reg;
|
||||
}
|
||||
|
||||
public void setRegs(byte[] regs) {
|
||||
System.arraycopy(regs, 0, this.reg, 0, Math.min(regs.length, this.reg.length));
|
||||
}
|
||||
|
||||
public byte[] getRam() {
|
||||
return this.ram;
|
||||
}
|
||||
|
||||
public void setRam(byte[] ram) {
|
||||
System.arraycopy(ram, 0, this.ram, 0, Math.min(ram.length, this.ram.length));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright (c) 2018-19 Marco Maccaferri and others.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under
|
||||
* the terms of the Eclipse Public License v1.0 which accompanies this
|
||||
* distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
|
||||
package com.maccasoft.tools;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.DisposeEvent;
|
||||
import org.eclipse.swt.events.DisposeListener;
|
||||
import org.eclipse.swt.events.KeyListener;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.ImageData;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.widgets.Canvas;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
public abstract class TMS9918Terminal {
|
||||
|
||||
Display display;
|
||||
Canvas canvas;
|
||||
|
||||
Image image;
|
||||
|
||||
final Runnable redrawRunnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
pendingRedraw.set(false);
|
||||
if (canvas == null || canvas.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
if (image != null) {
|
||||
image.dispose();
|
||||
}
|
||||
image = new Image(display, getImageData());
|
||||
canvas.redraw();
|
||||
canvas.update();
|
||||
}
|
||||
};
|
||||
|
||||
final AtomicBoolean pendingRedraw = new AtomicBoolean();
|
||||
|
||||
public TMS9918Terminal(Composite parent) {
|
||||
display = parent.getDisplay();
|
||||
|
||||
canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
|
||||
canvas.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
|
||||
canvas.setBounds(canvas.computeTrim(0, 0, TMS9918.FRAME_WIDTH, TMS9918.FRAME_HEIGHT));
|
||||
|
||||
canvas.addDisposeListener(new DisposeListener() {
|
||||
|
||||
@Override
|
||||
public void widgetDisposed(DisposeEvent e) {
|
||||
if (image != null) {
|
||||
image.dispose();
|
||||
}
|
||||
image = null;
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addPaintListener(new PaintListener() {
|
||||
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
if (image == null || image.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
Rectangle bounds = canvas.getBounds();
|
||||
e.gc.setAdvanced(false);
|
||||
e.gc.setAntialias(SWT.OFF);
|
||||
e.gc.setInterpolation(SWT.NONE);
|
||||
e.gc.drawImage(image, 0, 0, TMS9918.FRAME_WIDTH, TMS9918.FRAME_HEIGHT, 0, 0, bounds.width, bounds.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract ImageData getImageData();
|
||||
|
||||
protected void redraw() {
|
||||
if (pendingRedraw.compareAndSet(false, true)) {
|
||||
display.asyncExec(redrawRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLayoutData(Object data) {
|
||||
canvas.setLayoutData(data);
|
||||
}
|
||||
|
||||
public Object getLayoutData() {
|
||||
return canvas.getLayoutData();
|
||||
}
|
||||
|
||||
public Rectangle getBounds() {
|
||||
return canvas.getBounds();
|
||||
}
|
||||
|
||||
public void setFocus() {
|
||||
canvas.setFocus();
|
||||
}
|
||||
|
||||
public void addKeyListener(KeyListener l) {
|
||||
canvas.addKeyListener(l);
|
||||
}
|
||||
|
||||
public void removeKeyListener(KeyListener l) {
|
||||
canvas.removeKeyListener(l);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
canvas.dispose();
|
||||
}
|
||||
|
||||
}
|
Ładowanie…
Reference in New Issue