mirror of
https://github.com/sicklittlemonkey/AppleIIGo.git
synced 2024-09-27 08:54:51 +00:00
1120 lines
39 KiB
Java
1120 lines
39 KiB
Java
|
|
||
|
/**
|
||
|
* AppleIIGo
|
||
|
* Display processing
|
||
|
* (C) 2006 by Marc S. Ressl (ressl@lonetree.com)
|
||
|
* Released under the GPL
|
||
|
*/
|
||
|
|
||
|
import java.io.*;
|
||
|
import java.applet.*;
|
||
|
import java.awt.*;
|
||
|
import java.awt.image.*;
|
||
|
|
||
|
import javax.imageio.ImageIO;
|
||
|
|
||
|
/**
|
||
|
* AppleDisplay class<p>
|
||
|
* Refreshes the display
|
||
|
*/
|
||
|
public class AppleDisplay implements Runnable {
|
||
|
// Instances of other classes
|
||
|
private Applet applet;
|
||
|
private EmAppleII apple;
|
||
|
|
||
|
// Configuration variables
|
||
|
public static final int COLORMODE_GREEN = 0;
|
||
|
public static final int COLORMODE_COLOR = 1;
|
||
|
|
||
|
private int colorMode;
|
||
|
private boolean isGlare;
|
||
|
private boolean isStatMode;
|
||
|
|
||
|
// Refresh
|
||
|
private int refreshRate;
|
||
|
private long refreshInterval;
|
||
|
private long refreshDelayCumulative;
|
||
|
private long refreshDelayPerSecond;
|
||
|
private long refreshCycle;
|
||
|
|
||
|
private boolean isPrecalcRequested = true;
|
||
|
private boolean isRefreshRequested = true;
|
||
|
|
||
|
// Graphics interface variables
|
||
|
private boolean[] graphicsDirty = new boolean[0x6000 >> 7];
|
||
|
private int graphicsMode;
|
||
|
|
||
|
// Display
|
||
|
private static final int DISPLAY_CHAR_SIZE_X = 7;
|
||
|
private static final int DISPLAY_CHAR_SIZE_Y = 8;
|
||
|
private static final int DISPLAY_CHAR_COUNT_X = 80;
|
||
|
private static final int DISPLAY_CHAR_COUNT_Y = 24;
|
||
|
private static final int DISPLAY_SIZE_X = DISPLAY_CHAR_COUNT_X * DISPLAY_CHAR_SIZE_X;
|
||
|
private static final int DISPLAY_SIZE_Y = DISPLAY_CHAR_COUNT_Y * DISPLAY_CHAR_SIZE_Y;
|
||
|
|
||
|
// Display composition
|
||
|
private BufferedImage displayImage;
|
||
|
private int[] displayImageBuffer;
|
||
|
private BufferedImage displayImagePaused;
|
||
|
private BufferedImage displayImageGlare;
|
||
|
|
||
|
// Display scale
|
||
|
private float displayScale;
|
||
|
private int displayScaledSizeX;
|
||
|
private int displayScaledSizeY;
|
||
|
|
||
|
// Display palette
|
||
|
private int[] displayPalette;
|
||
|
private static final int[] displayPaletteGreen = {
|
||
|
0x000000, 0x0e470e, 0x041204, 0x166e16,
|
||
|
0x0f4a0f, 0x115411, 0x0c3b0c, 0x1f9e1f,
|
||
|
0x125c12, 0x1b8a1b, 0x22ab22, 0x24b524,
|
||
|
0x1A871a, 0x2de32d, 0x25bd25, 0x32ff32
|
||
|
};
|
||
|
private static final int[] displayPaletteColor = {
|
||
|
0x000000, 0xdd0033, 0x000099, 0xdd22dd,
|
||
|
0x007722, 0x555555, 0x2222ff, 0x66aaff,
|
||
|
0x885500, 0xff6600, 0xaaaaaa, 0xff9988,
|
||
|
0x11dd00, 0xffff00, 0x44ff99, 0xffffff
|
||
|
};
|
||
|
|
||
|
// Character stuff
|
||
|
private final int CHARSET_CHAR_SIZE_X = 8;
|
||
|
private final int CHARSET_CHAR_SIZE_Y = 8;
|
||
|
private final int CHARSET_SIZE_X = 0x100 * CHARSET_CHAR_SIZE_X;
|
||
|
private final int CHARSET_SIZE_Y = CHARSET_CHAR_SIZE_Y;
|
||
|
|
||
|
private static final int CHARMAP_NORMAL = 0;
|
||
|
private static final int CHARMAP_FLASH = 1;
|
||
|
private static final int CHARMAP_ALT = 2;
|
||
|
|
||
|
private int[] charSet = new int[CHARSET_SIZE_X * CHARSET_CHAR_SIZE_Y];
|
||
|
private int[] charMap;
|
||
|
private int[][] charMaps = new int[3][0x100];
|
||
|
private long charMapFlashCycle = 0;
|
||
|
private boolean isCharMapFlash = false;
|
||
|
|
||
|
private int[][] charMapLookup = {
|
||
|
{0xc0,0xa0,0x40,0x20,0x40,0x20,0x40,0x60},
|
||
|
{0xc0,0xa0,0xc0,0xa0,0x40,0x20,0x40,0x60},
|
||
|
{0xc0,0xa0,0x00,0xe0,0x40,0x20,0x40,0x60},
|
||
|
};
|
||
|
|
||
|
private static final int[] textLineAddress = {
|
||
|
0x0000,0x0080,0x0100,0x0180,0x0200,0x0280,0x0300,0x0380,
|
||
|
0x0028,0x00a8,0x0128,0x01a8,0x0228,0x02a8,0x0328,0x03a8,
|
||
|
0x0050,0x00d0,0x0150,0x01d0,0x0250,0x02d0,0x0350,0x03d0,
|
||
|
};
|
||
|
|
||
|
// Hires stuff
|
||
|
private int hiresEvenOddToWord[] = new int[0x200];
|
||
|
private int hiresWord[] = new int[8];
|
||
|
private int hiresWordNext[] = new int[8];
|
||
|
private int hiresLookup[] = new int[0x100]; // Bits: [NNccccPP] - Next, current, Previous bits
|
||
|
private static final int hiresLookupColor[] = {
|
||
|
// Bits: [PPNNcccc] - Previous, Next, current bits => 4 pixel @ 4 bit color output
|
||
|
// Color-bleeding algorithm
|
||
|
0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 00cccc00
|
||
|
0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 01cccc00
|
||
|
0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 10cccc00
|
||
|
0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 11cccc00
|
||
|
0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 00cccc01
|
||
|
0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 01cccc01
|
||
|
0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 10cccc01
|
||
|
0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 11cccc01
|
||
|
0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 00cccc10
|
||
|
0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 01cccc10
|
||
|
0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 10cccc10
|
||
|
0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 11cccc10
|
||
|
0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 00cccc11
|
||
|
0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 01cccc11
|
||
|
0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 10cccc11
|
||
|
0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 11cccc11
|
||
|
|
||
|
// First table
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc00
|
||
|
0x0000,0x1111,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc00
|
||
|
0x0000,0x0001,0x2222,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc00
|
||
|
0x0000,0x0001,0x0020,0x3333,0x0400,0x0505,0x0660,0x0777,0xf000,0x9009,0xa0a0,0xb0bb,0xff00,0xdd0d,0xfff0,0xffff, // 11cccc00
|
||
|
0x0000,0x0001,0x0020,0x0033,0x4444,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc01
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x5555,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc01
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x6666,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc01
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x7777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc01
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0fff,0x8888,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc10
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9999,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc10
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xaaaa,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc10
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xbbbb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc10
|
||
|
0x0000,0x000f,0x0020,0x00ff,0x0400,0x0505,0x0660,0x0fff,0x8000,0x9009,0xa0a0,0xb0bb,0xcccc,0xdd0d,0xeee0,0xffff, // 00cccc11
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdddd,0xeee0,0xffff, // 01cccc11
|
||
|
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeeee,0xffff, // 10cccc11
|
||
|
0x0000,0x000f,0x00f0,0x00ff,0x0f00,0x0f0f,0x0ff0,0x0fff,0xf000,0xf00f,0xf0f0,0xf0ff,0xff00,0xff0f,0xfff0,0xffff, // 11cccc11
|
||
|
|
||
|
};
|
||
|
private int[] doubleHiresPalette;
|
||
|
private static final int[] doubleHiresPaletteColor = {
|
||
|
0x000000, 0x000099, 0x007722, 0x2222ff,
|
||
|
0x885500, 0xaaaaaa, 0x11dd00, 0x44ff99,
|
||
|
0xdd0033, 0xdd22dd, 0x555555, 0x66aaff,
|
||
|
0xff6600, 0xff9988, 0xffff00, 0xffffff,
|
||
|
};
|
||
|
|
||
|
|
||
|
// Thread stuff
|
||
|
private boolean isPaused = true;
|
||
|
private Thread thread;
|
||
|
private String threadError = null;
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* AppleDisplay class constructor
|
||
|
*
|
||
|
* @param apple The EmAppleII instance
|
||
|
*/
|
||
|
public AppleDisplay(Applet applet, EmAppleII apple) {
|
||
|
this.applet = applet;
|
||
|
this.apple = apple;
|
||
|
|
||
|
// Create display image
|
||
|
displayImage = new BufferedImage(
|
||
|
DISPLAY_SIZE_X,
|
||
|
DISPLAY_SIZE_Y,
|
||
|
BufferedImage.TYPE_INT_RGB);
|
||
|
displayImageBuffer = ((DataBufferInt) displayImage.getRaster().getDataBuffer()).getData();
|
||
|
|
||
|
// Load glare and pause images
|
||
|
try {
|
||
|
displayImageGlare = ImageIO.read(this.getClass().getResourceAsStream(
|
||
|
"/Resources/Glare.png"));
|
||
|
displayImagePaused = ImageIO.read(this.getClass().getResourceAsStream(
|
||
|
"/Resources/Paused.png"));
|
||
|
} catch (IOException e) {
|
||
|
threadError = e.toString();
|
||
|
}
|
||
|
|
||
|
// Character maps
|
||
|
precalcCharMaps();
|
||
|
|
||
|
// Hires
|
||
|
precalcHiresEvenEddToWord();
|
||
|
|
||
|
// Set parameters
|
||
|
setScale(1.0f);
|
||
|
setRefreshRate(10);
|
||
|
setColorMode(COLORMODE_GREEN);
|
||
|
setGlare(false);
|
||
|
setStatMode(false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set scale
|
||
|
*/
|
||
|
public void setScale(float value) {
|
||
|
if (value <= 0.0f)
|
||
|
return;
|
||
|
|
||
|
displayScale = value;
|
||
|
isPrecalcRequested = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get scale
|
||
|
*/
|
||
|
public float getScale() {
|
||
|
return displayScale;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set refresh rate
|
||
|
*
|
||
|
* @param value Display refresh rate in mHz
|
||
|
*/
|
||
|
public void setRefreshRate(int value) {
|
||
|
if (value <= 0.0f)
|
||
|
return;
|
||
|
|
||
|
this.refreshRate = value;
|
||
|
refreshInterval = (int) (1000.0 / value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get refresh rate
|
||
|
*/
|
||
|
public int getRefreshRate() {
|
||
|
return refreshRate;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set color mode
|
||
|
*/
|
||
|
public void setColorMode(int value) {
|
||
|
colorMode = value;
|
||
|
isPrecalcRequested = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get color mode
|
||
|
*/
|
||
|
public int getColorMode() {
|
||
|
return colorMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set paused
|
||
|
*/
|
||
|
public void setPaused(boolean value) {
|
||
|
if (isPaused == value)
|
||
|
return;
|
||
|
|
||
|
isPaused = value;
|
||
|
if (isPaused) {
|
||
|
try {
|
||
|
thread.join(1000);
|
||
|
} catch (InterruptedException e) {
|
||
|
}
|
||
|
applet.repaint();
|
||
|
} else {
|
||
|
isRefreshRequested = true;
|
||
|
thread = new Thread(this);
|
||
|
thread.start();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get glare
|
||
|
*/
|
||
|
public boolean getPaused() {
|
||
|
return isPaused;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set glare
|
||
|
*/
|
||
|
public void setGlare(boolean value) {
|
||
|
isGlare = value;
|
||
|
isRefreshRequested = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get glare
|
||
|
*/
|
||
|
public boolean getGlare() {
|
||
|
return isGlare;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set stat mode
|
||
|
*/
|
||
|
public void setStatMode(boolean value) {
|
||
|
isStatMode = value;
|
||
|
isRefreshRequested = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get stat mode
|
||
|
*/
|
||
|
public boolean getStatMode() {
|
||
|
return isStatMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Step instruction in debug mode
|
||
|
*/
|
||
|
public String getStatInfo() {
|
||
|
String statInfo = "";
|
||
|
long refreshRateCurrent;
|
||
|
|
||
|
// Calculate effective CPU speed
|
||
|
if (refreshDelayPerSecond > 1000)
|
||
|
refreshRateCurrent = refreshRate * 1000 / refreshDelayPerSecond;
|
||
|
else
|
||
|
refreshRateCurrent = refreshRate;
|
||
|
|
||
|
// Return FPS
|
||
|
statInfo += " FPS=" + refreshRateCurrent + " [" + refreshDelayPerSecond + " ms/s]\n";
|
||
|
statInfo += " GM=" + graphicsMode + "\n";
|
||
|
if (threadError != null)
|
||
|
statInfo = statInfo.concat(threadError + "\n");
|
||
|
|
||
|
return statInfo;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Paint function
|
||
|
*
|
||
|
* @param g Graphics object
|
||
|
*/
|
||
|
public void paint(Graphics g) {
|
||
|
if (displayImage != null)
|
||
|
g.drawImage(displayImage,
|
||
|
0, 0, displayScaledSizeX, displayScaledSizeY,
|
||
|
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
|
||
|
applet);
|
||
|
|
||
|
if (isStatMode) {
|
||
|
g.setColor(Color.black);
|
||
|
g.fillRect(0,0,256,128);
|
||
|
drawStatInfo(g);
|
||
|
}
|
||
|
if ((displayImagePaused != null) && isPaused)
|
||
|
g.drawImage(displayImagePaused,
|
||
|
0, 0, displayScaledSizeX, displayScaledSizeY,
|
||
|
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
|
||
|
applet);
|
||
|
|
||
|
if (isGlare && (displayImageGlare != null))
|
||
|
g.drawImage(displayImageGlare,
|
||
|
0, 0, displayScaledSizeX, displayScaledSizeY,
|
||
|
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
|
||
|
applet);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Display refresh thread
|
||
|
*/
|
||
|
public void run() {
|
||
|
try {
|
||
|
while (!isPaused) {
|
||
|
long refreshStart = System.currentTimeMillis();
|
||
|
long refreshDelay;
|
||
|
|
||
|
refreshDisplay();
|
||
|
|
||
|
refreshDelay = System.currentTimeMillis() - refreshStart;
|
||
|
|
||
|
refreshDelayCumulative += refreshDelay;
|
||
|
refreshCycle++;
|
||
|
if (refreshCycle >= refreshRate) {
|
||
|
refreshDelayPerSecond = refreshDelayCumulative;
|
||
|
refreshDelayCumulative = refreshCycle = 0;
|
||
|
}
|
||
|
|
||
|
if (refreshDelay < refreshInterval)
|
||
|
Thread.sleep(refreshInterval - refreshDelay);
|
||
|
}
|
||
|
} catch (InterruptedException e) {
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Paint stat info
|
||
|
*/
|
||
|
private void drawStatInfo(Graphics g) {
|
||
|
final int fontSize = 16;
|
||
|
|
||
|
g.setColor(Color.WHITE);
|
||
|
|
||
|
String statInfo = apple.getStatInfo() + "\n" + getStatInfo();
|
||
|
String[] lines = statInfo.split("\n");
|
||
|
|
||
|
int drawPosY = fontSize;
|
||
|
for(int lineNo = 0; lineNo < lines.length; lineNo++) {
|
||
|
String line = lines[lineNo];
|
||
|
g.drawString(line, 0, drawPosY);
|
||
|
drawPosY += fontSize;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Display refresh
|
||
|
*/
|
||
|
private void refreshDisplay() {
|
||
|
boolean isCharsetUpdateRequested = false;
|
||
|
boolean isSetDirtyRequested = false;
|
||
|
boolean isSetHiresDirtyRequested = false;
|
||
|
boolean isRenderRequested = false;
|
||
|
|
||
|
// Precalculation
|
||
|
if (isPrecalcRequested) {
|
||
|
isPrecalcRequested = false;
|
||
|
precalcDisplay();
|
||
|
graphicsMode = -1;
|
||
|
}
|
||
|
|
||
|
// Repaint if graphics mode changes
|
||
|
if (graphicsMode != apple.graphicsMode) {
|
||
|
graphicsMode = apple.graphicsMode;
|
||
|
isCharsetUpdateRequested = true;
|
||
|
isSetHiresDirtyRequested = true;
|
||
|
}
|
||
|
|
||
|
// Periodic refresh
|
||
|
if (charMapFlashCycle <= 0) {
|
||
|
charMapFlashCycle = (int) (refreshRate / 4 - 1);
|
||
|
isCharMapFlash = !isCharMapFlash;
|
||
|
isCharsetUpdateRequested = true;
|
||
|
} else
|
||
|
charMapFlashCycle--;
|
||
|
|
||
|
// Some internal variables
|
||
|
boolean isSomeText = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) != 0);
|
||
|
boolean isSomeLores = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == 0);
|
||
|
boolean isSomeHires = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == EmAppleII.GR_HIRES);
|
||
|
|
||
|
boolean isMixedMode = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) == EmAppleII.GR_MIXMODE);
|
||
|
boolean isPage2 = ((graphicsMode & (EmAppleII.GR_80STORE | EmAppleII.GR_PAGE2)) == EmAppleII.GR_PAGE2);
|
||
|
boolean isAltChar = ((graphicsMode & EmAppleII.GR_ALTCHAR) != 0);
|
||
|
boolean isDoubleTextMode = ((graphicsMode & EmAppleII.GR_80CHAR) == EmAppleII.GR_80CHAR);
|
||
|
boolean isDoubleGraphicsMode = ((graphicsMode & (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES)) == (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES));
|
||
|
|
||
|
int baseAddressText = isPage2 ? apple.MEM_MAIN_RAM2 : apple.MEM_MAIN_TEXT;
|
||
|
int baseAddressHires = isPage2 ? apple.MEM_MAIN_RAM3 : apple.MEM_MAIN_HIRES;
|
||
|
|
||
|
// Set char map
|
||
|
if (isCharsetUpdateRequested) {
|
||
|
if (isAltChar)
|
||
|
setCharMap(CHARMAP_ALT);
|
||
|
else if (isCharMapFlash)
|
||
|
setCharMap(CHARMAP_FLASH);
|
||
|
else
|
||
|
setCharMap(CHARMAP_NORMAL);
|
||
|
|
||
|
isSetDirtyRequested = true;
|
||
|
isRefreshRequested = true;
|
||
|
}
|
||
|
|
||
|
// Refresh dirty buffers?
|
||
|
if (isSetDirtyRequested) {
|
||
|
if (isSomeText || isSomeLores)
|
||
|
setTextBufferDirty(baseAddressText);
|
||
|
|
||
|
if (isSetHiresDirtyRequested && isSomeHires)
|
||
|
setHiresBufferDirty(baseAddressHires);
|
||
|
|
||
|
isRenderRequested = true;
|
||
|
} else {
|
||
|
if ((isSomeText || isSomeLores) && isTextBufferDirty(baseAddressText))
|
||
|
isRenderRequested = true;
|
||
|
|
||
|
if (isSomeHires && isHiresBufferDirty(baseAddressHires))
|
||
|
isRenderRequested = true;
|
||
|
}
|
||
|
|
||
|
// Draw
|
||
|
if (isRenderRequested) {
|
||
|
if (isSomeText) {
|
||
|
if (isDoubleTextMode)
|
||
|
renderDoubleText(baseAddressText, isMixedMode);
|
||
|
else
|
||
|
renderText(baseAddressText, isMixedMode);
|
||
|
}
|
||
|
|
||
|
if (isSomeHires) {
|
||
|
if (isDoubleGraphicsMode)
|
||
|
renderDoubleHires(baseAddressHires, isMixedMode);
|
||
|
else
|
||
|
renderHires(baseAddressHires, isMixedMode);
|
||
|
} else if (isSomeLores) {
|
||
|
if (isDoubleGraphicsMode)
|
||
|
renderDoubleLores(baseAddressText, isMixedMode);
|
||
|
else
|
||
|
renderLores(baseAddressText, isMixedMode);
|
||
|
}
|
||
|
|
||
|
isRefreshRequested = true;
|
||
|
}
|
||
|
|
||
|
if (isRefreshRequested) {
|
||
|
isRefreshRequested = false;
|
||
|
applet.repaint();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set text buffer dirty
|
||
|
*/
|
||
|
private void setTextBufferDirty(int baseAddress) {
|
||
|
// Update dirty
|
||
|
int addressStart = baseAddress >> 7;
|
||
|
int addressEnd = addressStart + 8;
|
||
|
for (int address = addressStart; address < addressEnd; address++) {
|
||
|
graphicsDirty[address] = true;
|
||
|
apple.graphicsDirty[address] = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set hires buffer dirty
|
||
|
*/
|
||
|
private void setHiresBufferDirty(int baseAddress) {
|
||
|
// Update dirty
|
||
|
int addressStart = baseAddress >> 7;
|
||
|
int addressEnd = addressStart + 8;
|
||
|
for (int address = addressStart; address < addressEnd; address++) {
|
||
|
graphicsDirty[address] = true;
|
||
|
apple.graphicsDirty[address + (0x0000 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0400 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0800 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0c00 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1000 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1400 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1800 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1c00 >> 7)] = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is text buffer dirty?
|
||
|
*/
|
||
|
private boolean isTextBufferDirty(int baseAddress) {
|
||
|
boolean isDirty = false;
|
||
|
|
||
|
// Update dirty
|
||
|
int addressStart = baseAddress >> 7;
|
||
|
int addressEnd = addressStart + 8;
|
||
|
for (int address = addressStart; address < addressEnd; address++) {
|
||
|
graphicsDirty[address] = apple.graphicsDirty[address];
|
||
|
apple.graphicsDirty[address] = false;
|
||
|
if (graphicsDirty[address])
|
||
|
isDirty = true;
|
||
|
}
|
||
|
|
||
|
return isDirty;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is hires buffer dirty?
|
||
|
*/
|
||
|
private boolean isHiresBufferDirty(int baseAddress) {
|
||
|
boolean isDirty = false;
|
||
|
|
||
|
// Update dirty
|
||
|
int addressStart = baseAddress >> 7;
|
||
|
int addressEnd = addressStart + 8;
|
||
|
for (int address = addressStart; address < addressEnd; address++) {
|
||
|
graphicsDirty[address] =
|
||
|
apple.graphicsDirty[address + (0x0000 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x0400 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x0800 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x0c00 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x1000 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x1400 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x1800 >> 7)] ||
|
||
|
apple.graphicsDirty[address + (0x1c00 >> 7)];
|
||
|
apple.graphicsDirty[address + (0x0000 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0400 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0800 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x0c00 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1000 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1400 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1800 >> 7)] = false;
|
||
|
apple.graphicsDirty[address + (0x1c00 >> 7)] = false;
|
||
|
if (graphicsDirty[address])
|
||
|
isDirty = true;
|
||
|
}
|
||
|
|
||
|
return isDirty;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Display precalculation
|
||
|
*/
|
||
|
private void precalcDisplay() {
|
||
|
// Display scaled size
|
||
|
displayScaledSizeX = (int) (DISPLAY_SIZE_X * displayScale / 2);
|
||
|
displayScaledSizeY = (int) (DISPLAY_SIZE_Y * displayScale);
|
||
|
|
||
|
// Prepare display palette
|
||
|
setDisplayPalette();
|
||
|
|
||
|
// Prepare character set
|
||
|
loadCharSet();
|
||
|
|
||
|
// Prepare hires graphics
|
||
|
precalcHiresLookup();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Precalculate charSet
|
||
|
*/
|
||
|
private void loadCharSet() {
|
||
|
final int CHARSET_SOURCE_CHAR_COUNT = 128;
|
||
|
final int CHARSET_SOURCE_SIZE_X = CHARSET_SOURCE_CHAR_COUNT * CHARSET_CHAR_SIZE_X;
|
||
|
final int CHARSET_SOURCE_SIZE_Y = CHARSET_CHAR_SIZE_Y;
|
||
|
|
||
|
int charSetOffset = 0;
|
||
|
|
||
|
// Get RGB image
|
||
|
try {
|
||
|
BufferedImage charSetSource = ImageIO.read(this.getClass().getResourceAsStream(
|
||
|
"/Resources/Character Set.png"));
|
||
|
|
||
|
charSetSource.getRGB(
|
||
|
0, 0,
|
||
|
CHARSET_SOURCE_SIZE_X, CHARSET_SOURCE_SIZE_Y,
|
||
|
charSet,
|
||
|
0, CHARSET_SIZE_X);
|
||
|
} catch (IOException e) {
|
||
|
threadError = e.toString();
|
||
|
}
|
||
|
|
||
|
// Duplicate and invert
|
||
|
for(int charSetPosY = 0; charSetPosY < CHARSET_SIZE_Y; charSetPosY++) {
|
||
|
for(int charSetPosX = 0; charSetPosX < CHARSET_SOURCE_SIZE_X; charSetPosX++) {
|
||
|
charSet[charSetOffset + CHARSET_SOURCE_SIZE_X + charSetPosX] =
|
||
|
charSet[charSetOffset + charSetPosX] ^ 0xffffff;
|
||
|
}
|
||
|
charSetOffset += CHARSET_SIZE_X;
|
||
|
}
|
||
|
|
||
|
// Colorize
|
||
|
for(charSetOffset = 0; charSetOffset < (CHARSET_SIZE_X * CHARSET_SIZE_Y); charSetOffset++)
|
||
|
charSet[charSetOffset] &= (displayPalette[0xf] | 0xff000000);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Precalculate char map
|
||
|
*/
|
||
|
private void precalcCharMaps() {
|
||
|
for(int index = 0; index < 3; index++) {
|
||
|
for(int character = 0; character < 0x100; character++)
|
||
|
charMaps[index][character] = charMapLookup[index][character >> 5] + (character & 0x1f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set char map
|
||
|
*/
|
||
|
private void setCharMap(int value) {
|
||
|
charMap = charMaps[value];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set lores palette
|
||
|
*/
|
||
|
private void setDisplayPalette() {
|
||
|
displayPalette = (colorMode == COLORMODE_COLOR) ? displayPaletteColor : displayPaletteGreen;
|
||
|
doubleHiresPalette = (colorMode == COLORMODE_COLOR) ? doubleHiresPaletteColor : displayPaletteGreen;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Precalculate hires even odd to word
|
||
|
*/
|
||
|
private void precalcHiresEvenEddToWord() {
|
||
|
for(int value = 0; value < 0x200; value++) {
|
||
|
hiresEvenOddToWord[value] =
|
||
|
((value & 0x01) << 0) |
|
||
|
((value & 0x01) << 1) |
|
||
|
((value & 0x02) << 1) |
|
||
|
((value & 0x02) << 2) |
|
||
|
((value & 0x04) << 2) |
|
||
|
((value & 0x04) << 3) |
|
||
|
((value & 0x08) << 3) |
|
||
|
((value & 0x08) << 4) |
|
||
|
((value & 0x10) << 4) |
|
||
|
((value & 0x10) << 5) |
|
||
|
((value & 0x20) << 5) |
|
||
|
((value & 0x20) << 6) |
|
||
|
((value & 0x40) << 6) |
|
||
|
((value & 0x40) << 7);
|
||
|
|
||
|
if ((value & 0x80) != 0) {
|
||
|
hiresEvenOddToWord[value] <<= 1;
|
||
|
hiresEvenOddToWord[value] |= ((value & 0x100) >> 8);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Precalculate hires
|
||
|
*/
|
||
|
private void precalcHiresLookup() {
|
||
|
if (colorMode == COLORMODE_COLOR) {
|
||
|
for(int value = 0; value < 0x100; value++)
|
||
|
hiresLookup[value] = hiresLookupColor[((value << 6) & 0xff) | (value >> 2)];
|
||
|
} else {
|
||
|
for(int value = 0; value < 0x100; value++)
|
||
|
hiresLookup[value] =
|
||
|
(((value & 0x04) != 0) ? 0x000f : 0) |
|
||
|
(((value & 0x08) != 0) ? 0x00f0 : 0) |
|
||
|
(((value & 0x10) != 0) ? 0x0f00 : 0) |
|
||
|
(((value & 0x20) != 0) ? 0xf000 : 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Render text canvas
|
||
|
*/
|
||
|
private final void renderTextScanLine(int destOffset, int sourceOffset) {
|
||
|
displayImageBuffer[destOffset + 0] = displayImageBuffer[destOffset + 1] = charSet[sourceOffset];
|
||
|
displayImageBuffer[destOffset + 2] = displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 1];
|
||
|
displayImageBuffer[destOffset + 4] = displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 2];
|
||
|
displayImageBuffer[destOffset + 6] = displayImageBuffer[destOffset + 7] = charSet[sourceOffset + 3];
|
||
|
displayImageBuffer[destOffset + 8] = displayImageBuffer[destOffset + 9] = charSet[sourceOffset + 4];
|
||
|
displayImageBuffer[destOffset + 10] = displayImageBuffer[destOffset + 11] = charSet[sourceOffset + 5];
|
||
|
displayImageBuffer[destOffset + 12] = displayImageBuffer[destOffset + 13] = charSet[sourceOffset + 6];
|
||
|
}
|
||
|
private final void renderTextCharacter(int destOffset, int sourceOffset) {
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderTextScanLine(destOffset, sourceOffset);
|
||
|
}
|
||
|
private void renderText(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYStart = isMixedMode ? 20 : 0;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
for (address = addressStart; address < addressEnd; address++) {
|
||
|
renderTextCharacter(displayOffset, charMap[apple.mem[address] & 0xff] << 3);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X * 2;
|
||
|
}
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render double text canvas
|
||
|
*/
|
||
|
private final void renderDoubleTextScanLine(int destOffset, int sourceOffset) {
|
||
|
displayImageBuffer[destOffset + 0] = charSet[sourceOffset];
|
||
|
displayImageBuffer[destOffset + 1] = charSet[sourceOffset + 1];
|
||
|
displayImageBuffer[destOffset + 2] = charSet[sourceOffset + 2];
|
||
|
displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 3];
|
||
|
displayImageBuffer[destOffset + 4] = charSet[sourceOffset + 4];
|
||
|
displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 5];
|
||
|
displayImageBuffer[destOffset + 6] = charSet[sourceOffset + 6];
|
||
|
}
|
||
|
private final void renderDoubleTextCharacter(int destOffset, int sourceOffset) {
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
|
||
|
renderDoubleTextScanLine(destOffset, sourceOffset);
|
||
|
}
|
||
|
private void renderDoubleText(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYStart = isMixedMode ? 20 : 0;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
for (address = addressStart; address < addressEnd; address++) {
|
||
|
renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x10000] & 0xff] << 3);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x00000] & 0xff] << 3);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
}
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render lores canvas
|
||
|
*/
|
||
|
private final void renderLoresScanLine(int destOffset, int color) {
|
||
|
displayImageBuffer[destOffset + 0] = color;
|
||
|
displayImageBuffer[destOffset + 1] = color;
|
||
|
displayImageBuffer[destOffset + 2] = color;
|
||
|
displayImageBuffer[destOffset + 3] = color;
|
||
|
displayImageBuffer[destOffset + 4] = color;
|
||
|
displayImageBuffer[destOffset + 5] = color;
|
||
|
displayImageBuffer[destOffset + 6] = color;
|
||
|
}
|
||
|
private final void renderLoresBlock(int destOffset, int colorTop, int colorBottom) {
|
||
|
renderLoresScanLine(destOffset, colorTop);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorTop);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorTop);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorTop);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorBottom);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorBottom);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorBottom);
|
||
|
destOffset += DISPLAY_SIZE_X;
|
||
|
renderLoresScanLine(destOffset, colorBottom);
|
||
|
}
|
||
|
private void renderLores(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = 0;
|
||
|
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
for (address = addressStart; address < addressEnd; address++) {
|
||
|
renderLoresBlock(displayOffset,
|
||
|
displayPalette[apple.mem[address] & 0xf],
|
||
|
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
renderLoresBlock(displayOffset,
|
||
|
displayPalette[apple.mem[address] & 0xf],
|
||
|
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
}
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render double lores canvas
|
||
|
*/
|
||
|
private void renderDoubleLores(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = 0;
|
||
|
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
for (address = addressStart; address < addressEnd; address++) {
|
||
|
renderLoresBlock(displayOffset,
|
||
|
displayPalette[apple.mem[address + 0x10000] & 0xf],
|
||
|
displayPalette[(apple.mem[address + 0x10000] & 0xf0) >> 4]);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
renderLoresBlock(displayOffset,
|
||
|
displayPalette[apple.mem[address] & 0xf],
|
||
|
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X;
|
||
|
}
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render hires canvas
|
||
|
*/
|
||
|
private final void renderHiresWord(int destOffset, int hiresNibble) {
|
||
|
displayImageBuffer[destOffset + 0] = displayPalette[(hiresNibble >> 0) & 0xf];
|
||
|
displayImageBuffer[destOffset + 1] = displayPalette[(hiresNibble >> 4) & 0xf];
|
||
|
displayImageBuffer[destOffset + 2] = displayPalette[(hiresNibble >> 8) & 0xf];
|
||
|
displayImageBuffer[destOffset + 3] = displayPalette[(hiresNibble >> 12) & 0xf];
|
||
|
}
|
||
|
private final void renderHiresScanLine(int destOffset, int hiresWord) {
|
||
|
renderHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]);
|
||
|
renderHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]);
|
||
|
renderHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]);
|
||
|
renderHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]);
|
||
|
renderHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]);
|
||
|
renderHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]);
|
||
|
renderHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]);
|
||
|
}
|
||
|
private final void renderHiresBlock(int destOffset) {
|
||
|
renderHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderHiresScanLine(destOffset, hiresWord[7]);
|
||
|
}
|
||
|
private final void resetHiresWords() {
|
||
|
hiresWord[0] = 0;
|
||
|
hiresWord[1] = 0;
|
||
|
hiresWord[2] = 0;
|
||
|
hiresWord[3] = 0;
|
||
|
hiresWord[4] = 0;
|
||
|
hiresWord[5] = 0;
|
||
|
hiresWord[6] = 0;
|
||
|
hiresWord[7] = 0;
|
||
|
}
|
||
|
private final void bufferHiresWords() {
|
||
|
hiresWord[0] = hiresWordNext[0];
|
||
|
hiresWord[1] = hiresWordNext[1];
|
||
|
hiresWord[2] = hiresWordNext[2];
|
||
|
hiresWord[3] = hiresWordNext[3];
|
||
|
hiresWord[4] = hiresWordNext[4];
|
||
|
hiresWord[5] = hiresWordNext[5];
|
||
|
hiresWord[6] = hiresWordNext[6];
|
||
|
hiresWord[7] = hiresWordNext[7];
|
||
|
}
|
||
|
private final void calcNextHiresWord(int hiresWordIndex, int byteEven, int byteOdd) {
|
||
|
hiresWordNext[hiresWordIndex] = hiresWord[hiresWordIndex] >> 28;
|
||
|
hiresWordNext[hiresWordIndex] |=
|
||
|
hiresEvenOddToWord[(byteEven & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x2) << 7)] << 2;
|
||
|
hiresWordNext[hiresWordIndex] |=
|
||
|
hiresEvenOddToWord[(byteOdd & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x8000) >> 7)] << 16;
|
||
|
hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28);
|
||
|
}
|
||
|
private final void calcNextHiresWords(int address) {
|
||
|
calcNextHiresWord(0, apple.mem[address + 0x00000], apple.mem[address + 0x00001]);
|
||
|
calcNextHiresWord(1, apple.mem[address + 0x00400], apple.mem[address + 0x00401]);
|
||
|
calcNextHiresWord(2, apple.mem[address + 0x00800], apple.mem[address + 0x00801]);
|
||
|
calcNextHiresWord(3, apple.mem[address + 0x00c00], apple.mem[address + 0x00c01]);
|
||
|
calcNextHiresWord(4, apple.mem[address + 0x01000], apple.mem[address + 0x01001]);
|
||
|
calcNextHiresWord(5, apple.mem[address + 0x01400], apple.mem[address + 0x01401]);
|
||
|
calcNextHiresWord(6, apple.mem[address + 0x01800], apple.mem[address + 0x01801]);
|
||
|
calcNextHiresWord(7, apple.mem[address + 0x01c00], apple.mem[address + 0x01c01]);
|
||
|
}
|
||
|
private void renderHires(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = 0;
|
||
|
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
resetHiresWords();
|
||
|
calcNextHiresWords(addressStart);
|
||
|
for (address = (addressStart + 2); address < addressEnd; address += 2) {
|
||
|
bufferHiresWords();
|
||
|
calcNextHiresWords(address);
|
||
|
renderHiresBlock(displayOffset);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
|
||
|
}
|
||
|
bufferHiresWords();
|
||
|
renderHiresBlock(displayOffset);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
|
||
|
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render double hires canvas
|
||
|
*/
|
||
|
private final void renderDoubleHiresWord(int destOffset, int hiresNibble) {
|
||
|
displayImageBuffer[destOffset + 0] = doubleHiresPalette[(hiresNibble >> 0) & 0xf];
|
||
|
displayImageBuffer[destOffset + 1] = doubleHiresPalette[(hiresNibble >> 4) & 0xf];
|
||
|
displayImageBuffer[destOffset + 2] = doubleHiresPalette[(hiresNibble >> 8) & 0xf];
|
||
|
displayImageBuffer[destOffset + 3] = doubleHiresPalette[(hiresNibble >> 12) & 0xf];
|
||
|
}
|
||
|
private final void renderDoubleHiresScanLine(int destOffset, int hiresWord) {
|
||
|
renderDoubleHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]);
|
||
|
renderDoubleHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]);
|
||
|
}
|
||
|
private final void renderDoubleHiresBlock(int destOffset) {
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X;
|
||
|
renderDoubleHiresScanLine(destOffset, hiresWord[7]);
|
||
|
}
|
||
|
private final void calcNextDoubleHiresWord(int hiresWordIndex, int byte1, int byte2, int byte3, int byte4) {
|
||
|
hiresWordNext[hiresWordIndex] = (
|
||
|
((byte1 & 0x7f) << 2) | ((byte2 & 0x7f) << 9) |
|
||
|
((byte3 & 0x7f) << 16) | ((byte4 & 0x7f) << 23) |
|
||
|
(hiresWord[hiresWordIndex] >> 28));
|
||
|
hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28);
|
||
|
}
|
||
|
private final void calcNextDoubleHiresWords(int address) {
|
||
|
calcNextDoubleHiresWord(0,
|
||
|
apple.mem[address + 0x10000], apple.mem[address + 0x00000],
|
||
|
apple.mem[address + 0x10001], apple.mem[address + 0x00001]);
|
||
|
calcNextDoubleHiresWord(1,
|
||
|
apple.mem[address + 0x10400], apple.mem[address + 0x00400],
|
||
|
apple.mem[address + 0x10401], apple.mem[address + 0x00401]);
|
||
|
calcNextDoubleHiresWord(2,
|
||
|
apple.mem[address + 0x10800], apple.mem[address + 0x00800],
|
||
|
apple.mem[address + 0x10801], apple.mem[address + 0x00801]);
|
||
|
calcNextDoubleHiresWord(3,
|
||
|
apple.mem[address + 0x10c00], apple.mem[address + 0x00c00],
|
||
|
apple.mem[address + 0x10c01], apple.mem[address + 0x00c01]);
|
||
|
calcNextDoubleHiresWord(4,
|
||
|
apple.mem[address + 0x11000], apple.mem[address + 0x01000],
|
||
|
apple.mem[address + 0x11001], apple.mem[address + 0x01001]);
|
||
|
calcNextDoubleHiresWord(5,
|
||
|
apple.mem[address + 0x11400], apple.mem[address + 0x01400],
|
||
|
apple.mem[address + 0x11401], apple.mem[address + 0x01401]);
|
||
|
calcNextDoubleHiresWord(6,
|
||
|
apple.mem[address + 0x11800], apple.mem[address + 0x01800],
|
||
|
apple.mem[address + 0x11801], apple.mem[address + 0x01801]);
|
||
|
calcNextDoubleHiresWord(7,
|
||
|
apple.mem[address + 0x11c00], apple.mem[address + 0x01c00],
|
||
|
apple.mem[address + 0x11c01], apple.mem[address + 0x01c01]);
|
||
|
}
|
||
|
private void renderDoubleHires(int baseAddress, boolean isMixedMode) {
|
||
|
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
|
||
|
int displayOffset, sourceOffset;
|
||
|
int address, addressEnd, addressStart;
|
||
|
|
||
|
displayOffset = 0;
|
||
|
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
|
||
|
addressStart = baseAddress + textLineAddress[screenCharY];
|
||
|
|
||
|
if (graphicsDirty[addressStart >> 7]) {
|
||
|
addressEnd = addressStart + 40;
|
||
|
|
||
|
resetHiresWords();
|
||
|
calcNextDoubleHiresWords(addressStart);
|
||
|
for (address = (addressStart + 2); address < addressEnd; address += 2) {
|
||
|
bufferHiresWords();
|
||
|
calcNextDoubleHiresWords(address);
|
||
|
renderDoubleHiresBlock(displayOffset);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
|
||
|
}
|
||
|
bufferHiresWords();
|
||
|
renderDoubleHiresBlock(displayOffset);
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
|
||
|
|
||
|
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
|
||
|
} else
|
||
|
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
|
||
|
}
|
||
|
}
|
||
|
}
|