jace/src/main/java/jace/core/Video.java
Brendan Robert 0ccb63558f A lot of things have been deactivated to sever the link to the old Swing UI. Indicators, namely, have been commented out in many places. Ultimately the emulator is wholly unusable in this state.
The video rendering was re-written to use writableImages and is displaying (something) but keyboard input and configurations are broken so nothing much happens after the inital boot.  Basically the underlying part to make this show up in JavaFX is starting to take shape.
2015-02-03 00:55:25 -06:00

293 lines
9.7 KiB
Java

/*
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package jace.core;
import jace.state.Stateful;
import jace.Emulator;
import jace.config.ConfigurableField;
import jace.config.InvokableAction;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
/**
* Generic abstraction of a 560x192 video output device which renders 40 columns
* per scanline. This also triggers VBL and updates the physical screen.
* Subclasses are used to manage actual rendering via ScreenWriter
* implementations.
* Created on November 10, 2006, 4:29 PM
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
@Stateful
public abstract class Video extends Device {
@Stateful
WritableImage video;
WritableImage visible;
VideoWriter currentWriter;
private byte floatingBus = 0;
private int width = 560;
private int height = 192;
@Stateful
public int x = 0;
@Stateful
public int y = 0;
@Stateful
public int scannerAddress;
@Stateful
public int vPeriod = 0;
@Stateful
public int hPeriod = 0;
static final public int CYCLES_PER_LINE = 65;
static final public int TOTAL_LINES = 262;
static final public int APPLE_CYCLES_PER_LINE = 40;
static final public int APPLE_SCREEN_LINES = 192;
static final public int HBLANK = CYCLES_PER_LINE - APPLE_CYCLES_PER_LINE;
static final public int VBLANK = (TOTAL_LINES - APPLE_SCREEN_LINES) * CYCLES_PER_LINE;
static public int[] textOffset;
static public int[] hiresOffset;
static public int[] textRowLookup;
static public int[] hiresRowLookup;
private boolean screenDirty;
private boolean lineDirty;
private boolean isVblank = false;
static VideoWriter[][] writerCheck = new VideoWriter[40][192];
static {
textOffset = new int[192];
hiresOffset = new int[192];
textRowLookup = new int[0x0400];
hiresRowLookup = new int[0x02000];
for (int i = 0; i < 192; i++) {
textOffset[i] = calculateTextOffset(i >> 3);
hiresOffset[i] = calculateHiresOffset(i);
}
for (int i = 0; i < 0x0400; i++) {
textRowLookup[i] = identifyTextRow(i);
}
for (int i = 0; i < 0x2000; i++) {
hiresRowLookup[i] = identifyHiresRow(i);
}
}
private int forceRedrawRowCount = 0;
Thread updateThread;
/**
* Creates a new instance of Video
*/
public Video(Computer computer) {
super(computer);
suspend();
video = new WritableImage(560, 192);
visible = new WritableImage(560, 192);
vPeriod = 0;
hPeriod = 0;
forceRefresh();
}
public void setWidth(int w) {
width = w;
}
public int getWidth() {
return width;
}
public void setHeight(int h) {
height = h;
}
public int getHeight() {
return height;
}
public VideoWriter getCurrentWriter() {
return currentWriter;
}
public void setCurrentWriter(VideoWriter currentWriter) {
if (this.currentWriter != currentWriter || currentWriter.isMixed()) {
this.currentWriter = currentWriter;
forceRedrawRowCount = APPLE_SCREEN_LINES + 1;
}
}
@ConfigurableField(category = "video", name = "Min. Screen Refesh", defaultValue = "15", description = "Minimum number of miliseconds to wait before trying to redraw.")
public static int MIN_SCREEN_REFRESH = 15;
public void redraw() {
screenDirty = false;
javafx.application.Platform.runLater(() -> {
visible.getPixelWriter().setPixels(0, 0, 560, 192, video.getPixelReader(), 0, 0);
});
// screen.drawImage(video, 0, 0, width, height, null);
// if (Emulator.getFrame() != null) {
// Emulator.getFrame().repaintIndicators();
// }
}
public void vblankStart() {
if (screenDirty && isRunning()) {
redraw();
}
}
abstract public void vblankEnd();
abstract public void hblankStart(WritableImage screen, int y, boolean isDirty);
public void setScannerLocation(int loc) {
scannerAddress = loc;
}
@Override
public void tick() {
setFloatingBus(computer.getMemory().readRaw(scannerAddress + x));
if (hPeriod > 0) {
hPeriod--;
if (hPeriod == 0) {
x = -1;
setScannerLocation(currentWriter.getYOffset(y));
}
} else {
if (!isVblank) {
draw();
}
if (x >= APPLE_CYCLES_PER_LINE - 1) {
int yy = y + hblankOffsetY;
if (yy < 0) {
yy += APPLE_SCREEN_LINES;
}
if (yy >= APPLE_SCREEN_LINES) {
yy -= (TOTAL_LINES - APPLE_SCREEN_LINES);
}
setScannerLocation(currentWriter.getYOffset(yy) + hblankOffsetX + (yy < 64 ? 128 : 0));
x = -1;
if (!isVblank) {
if (lineDirty) {
screenDirty = true;
currentWriter.clearDirty(y);
}
hblankStart(video, y, lineDirty);
lineDirty = false;
forceRedrawRowCount--;
}
hPeriod = HBLANK;
y++;
if (y >= APPLE_SCREEN_LINES) {
if (!isVblank) {
y = APPLE_SCREEN_LINES - (TOTAL_LINES - APPLE_SCREEN_LINES);
isVblank = true;
vblankStart();
computer.getMotherboard().vblankStart();
} else {
y = 0;
isVblank = false;
vblankEnd();
computer.getMotherboard().vblankEnd();
}
}
}
}
x++;
}
abstract public void configureVideoMode();
protected static int byteDoubler(byte b) {
int num =
// Skip hi-bit because it's not used in display
// ((b&0x080)<<7) |
((b & 0x040) << 6)
| ((b & 0x020) << 5)
| ((b & 0x010) << 4)
| ((b & 0x08) << 3)
| ((b & 0x04) << 2)
| ((b & 0x02) << 1)
| (b & 0x01);
return num | (num << 1);
}
@ConfigurableField(name = "Waits per cycle", category = "Advanced", description = "Adjust the delay for the scanner")
public static int waitsPerCycle = 0;
@ConfigurableField(name = "Hblank X offset", category = "Advanced", description = "Adjust where the hblank period starts relative to the start of the line")
public static int hblankOffsetX = -29;
@ConfigurableField(name = "Hblank Y offset", category = "Advanced", description = "Adjust which line the HBLANK starts on (0=current, 1=next, etc)")
public static int hblankOffsetY = 1;
private void draw() {
if (lineDirty || forceRedrawRowCount > 0 || currentWriter.isRowDirty(y)) {
lineDirty = true;
currentWriter.displayByte(video, x, y, textOffset[y], hiresOffset[y]);
}
setWaitCycles(waitsPerCycle);
doPostDraw();
}
static public int calculateHiresOffset(int y) {
return calculateTextOffset(y >> 3) + ((y & 7) << 10);
}
static public int calculateTextOffset(int y) {
return ((y & 7) << 7) + 40 * (y >> 3);
}
static public int identifyTextRow(int y) {
//floor((x-1024)/128) + floor(((x-1024)%128)/40)*8
// Caller must check result is <= 23, if so then they are in a screenhole!
return (y >> 7) + (((y & 0x7f) / 40) << 3);
}
static public int identifyHiresRow(int y) {
int blockOffset = identifyTextRow(y & 0x03ff);
// Caller must check results is > 0, if not then they are in a screenhole!
if (blockOffset > 23) {
return -1;
}
return ((y >> 10) & 7) + (blockOffset << 3);
}
public abstract void doPostDraw();
public byte getFloatingBus() {
return floatingBus;
}
private void setFloatingBus(byte floatingBus) {
this.floatingBus = floatingBus;
}
@InvokableAction(name = "Refresh screen",
category = "display",
description = "Marks screen contents as changed, forcing full screen redraw",
alternatives = "redraw")
public final void forceRefresh() {
lineDirty = true;
screenDirty = true;
forceRedrawRowCount = APPLE_SCREEN_LINES + 1;
}
@Override
public String getShortName() {
return "vid";
}
public Image getFrameBuffer() {
return visible;
}
}