mirror of https://github.com/badvision/jace.git
300 lines
9.7 KiB
Java
300 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 java.awt.Graphics;
|
|
import java.awt.image.BufferedImage;
|
|
|
|
/**
|
|
* 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
|
|
BufferedImage video;
|
|
VideoWriter currentWriter;
|
|
Graphics screen;
|
|
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() {
|
|
suspend();
|
|
video = new BufferedImage(560, 192, BufferedImage.TYPE_INT_RGB);
|
|
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 void setScreen(Graphics g) {
|
|
screen = g;
|
|
|
|
}
|
|
|
|
public Graphics getScreen() {
|
|
return screen;
|
|
}
|
|
|
|
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() {
|
|
if (screen == null || video == null) {
|
|
return;
|
|
}
|
|
screenDirty = false;
|
|
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(BufferedImage screen, int y, boolean isDirty);
|
|
|
|
public void setScannerLocation(int loc) {
|
|
scannerAddress = loc;
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
setFloatingBus(Computer.getComputer().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();
|
|
Motherboard.vblankStart();
|
|
} else {
|
|
y = 0;
|
|
isVblank = false;
|
|
vblankEnd();
|
|
Motherboard.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 BufferedImage getFrameBuffer() {
|
|
return video;
|
|
}
|
|
}
|