mirror of
https://github.com/irmen/ksim65.git
synced 2024-06-26 02:29:38 +00:00
fixed VIC memory banking, bitmap mode
This commit is contained in:
parent
31c50991c1
commit
8a2212e34f
|
@ -6,10 +6,7 @@ import razorvine.ksim65.components.UByte
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import java.awt.event.KeyListener
|
import java.awt.event.KeyListener
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.awt.image.DataBufferInt
|
|
||||||
import javax.swing.JFrame
|
import javax.swing.JFrame
|
||||||
import javax.swing.JPanel
|
|
||||||
import javax.swing.Timer
|
import javax.swing.Timer
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,167 +49,6 @@ object ScreenDefs {
|
||||||
val colorPalette = Palette()
|
val colorPalette = Palette()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BitmapScreenPanel(val chargenData: ByteArray, val ram: MemoryComponent) : JPanel() {
|
|
||||||
|
|
||||||
private val fullscreenImage: BufferedImage
|
|
||||||
private val fullscreenG2d: Graphics2D
|
|
||||||
private val normalCharacters = loadCharacters(false)
|
|
||||||
private val shiftedCharacters = loadCharacters(true)
|
|
||||||
|
|
||||||
init {
|
|
||||||
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
|
||||||
val gd = ge.defaultScreenDevice.defaultConfiguration
|
|
||||||
fullscreenImage = gd.createCompatibleImage(ScreenDefs.SCREEN_WIDTH + 2*ScreenDefs.BORDER_SIZE,
|
|
||||||
ScreenDefs.SCREEN_HEIGHT + 2*ScreenDefs.BORDER_SIZE, Transparency.OPAQUE)
|
|
||||||
fullscreenImage.accelerationPriority = 1.0f
|
|
||||||
fullscreenG2d = fullscreenImage.graphics as Graphics2D
|
|
||||||
|
|
||||||
val size = Dimension(
|
|
||||||
fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt(),
|
|
||||||
fullscreenImage.height*ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
|
||||||
)
|
|
||||||
minimumSize = size
|
|
||||||
maximumSize = size
|
|
||||||
preferredSize = size
|
|
||||||
isFocusable = true
|
|
||||||
isDoubleBuffered = false
|
|
||||||
requestFocusInWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadCharacters(shifted: Boolean): Array<BufferedImage> {
|
|
||||||
val chars = Array(256) { BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY).also { it.accelerationPriority=1.0f } }
|
|
||||||
val offset = if (shifted) 256 * 8 else 0
|
|
||||||
for (char in 0..255) {
|
|
||||||
for (line in 0..7) {
|
|
||||||
val charbyte = chargenData[offset + char * 8 + line].toInt()
|
|
||||||
for (x in 0..7) {
|
|
||||||
if (charbyte and (0b10000000 ushr x) != 0)
|
|
||||||
chars[char].setRGB(x, line, 0xffffff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chars
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun paint(graphics: Graphics) {
|
|
||||||
val windowG2d = graphics as Graphics2D
|
|
||||||
val vicSCROLY = ram[0xd011].toInt()
|
|
||||||
val vicVMCSB = ram[0xd018].toInt()
|
|
||||||
if(vicSCROLY and 0b10000 == 0) {
|
|
||||||
// screen blanked, only display border
|
|
||||||
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
|
|
||||||
fullscreenG2d.clearRect(
|
|
||||||
0, 0,
|
|
||||||
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT+ 2*ScreenDefs.BORDER_SIZE)
|
|
||||||
} else {
|
|
||||||
// draw the screen border
|
|
||||||
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
|
|
||||||
fullscreenG2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE)
|
|
||||||
fullscreenG2d.clearRect(
|
|
||||||
0, ScreenDefs.SCREEN_HEIGHT + ScreenDefs.BORDER_SIZE,
|
|
||||||
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE
|
|
||||||
)
|
|
||||||
fullscreenG2d.clearRect(0, ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT)
|
|
||||||
fullscreenG2d.clearRect(
|
|
||||||
ScreenDefs.SCREEN_WIDTH + ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
|
|
||||||
ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT
|
|
||||||
)
|
|
||||||
|
|
||||||
if(vicSCROLY and 0b100000 != 0) {
|
|
||||||
// bitmap mode 320x200
|
|
||||||
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd021]]
|
|
||||||
fullscreenG2d.clearRect(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
|
||||||
// TODO vic address offset in memory, so that medusa.prg will work
|
|
||||||
val bitmap = ram.getPages(if(vicVMCSB and 0b00001000 != 0) 32 else 0, 32)
|
|
||||||
val colorBytes = ram.getPages((vicVMCSB ushr 4) shl 2, 4)
|
|
||||||
val pixels: IntArray = (fullscreenImage.raster.dataBuffer as DataBufferInt).data
|
|
||||||
for(y in 0 until ScreenDefs.SCREEN_HEIGHT) {
|
|
||||||
for(x in 0 until ScreenDefs.SCREEN_WIDTH step 8) {
|
|
||||||
val colorbyte = ScreenDefs.SCREEN_WIDTH_CHARS*(y ushr 3) + (x ushr 3)
|
|
||||||
val bgColor = ScreenDefs.colorPalette[colorBytes[colorbyte].toInt() and 15].rgb
|
|
||||||
val fgColor = ScreenDefs.colorPalette[colorBytes[colorbyte].toInt() ushr 4].rgb
|
|
||||||
draw8Pixels(pixels, x, y, bitmap, fgColor, bgColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// normal character mode
|
|
||||||
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd021]]
|
|
||||||
fullscreenG2d.clearRect(ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT)
|
|
||||||
redrawCharacters()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale and draw the image to the window, and simulate a slight scanline effect
|
|
||||||
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
|
||||||
windowG2d.drawImage(
|
|
||||||
fullscreenImage, 0, 0, (fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
|
||||||
(fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
|
|
||||||
)
|
|
||||||
windowG2d.color = Color(0, 0, 0, 40)
|
|
||||||
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
|
||||||
val width = fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
|
||||||
val height = fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
|
||||||
for (y in 0 until height step ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()) {
|
|
||||||
windowG2d.drawLine(0, y, width, y)
|
|
||||||
}
|
|
||||||
Toolkit.getDefaultToolkit().sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun draw8Pixels(pixels: IntArray, xstart: Int, y: Int,
|
|
||||||
bitmap: Array<UByte>, fgColorRgb: Int, bgColorRgb: Int) {
|
|
||||||
val offset = ScreenDefs.BORDER_SIZE + ScreenDefs.BORDER_SIZE*fullscreenImage.width +
|
|
||||||
xstart + y * fullscreenImage.width
|
|
||||||
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS*(y and 248) + (y and 7) + (xstart and 504)].toInt()
|
|
||||||
pixels[offset + 0] = if(byte and 0b10000000 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 1] = if(byte and 0b01000000 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 2] = if(byte and 0b00100000 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 3] = if(byte and 0b00010000 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 4] = if(byte and 0b00001000 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 5] = if(byte and 0b00000100 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 6] = if(byte and 0b00000010 != 0) fgColorRgb else bgColorRgb
|
|
||||||
pixels[offset + 7] = if(byte and 0b00000001 != 0) fgColorRgb else bgColorRgb
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun redrawCharacters() {
|
|
||||||
val screen = 0x0400
|
|
||||||
val colors = 0xd800
|
|
||||||
val shifted = (ram[0xd018].toInt() and 0b00000010) != 0
|
|
||||||
for (y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) {
|
|
||||||
for (x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) {
|
|
||||||
val char = ram[screen + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
|
|
||||||
val color = ram[colors + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
|
|
||||||
drawColoredChar(x, y, char, color and 15, shifted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val coloredCharacters = mutableMapOf<Triple<Int, Int, Boolean>, BufferedImage>()
|
|
||||||
|
|
||||||
private fun drawColoredChar(x: Int, y: Int, char: Int, color: Int, shifted: Boolean) {
|
|
||||||
var cached = coloredCharacters[Triple(char, color, shifted)]
|
|
||||||
if (cached == null) {
|
|
||||||
cached = if (shifted) shiftedCharacters[char] else normalCharacters[char]
|
|
||||||
val colored = fullscreenG2d.deviceConfiguration.createCompatibleImage(8, 8, BufferedImage.BITMASK)
|
|
||||||
val sourceRaster = cached.raster
|
|
||||||
val coloredRaster = colored.raster
|
|
||||||
val pixelArray = IntArray(4)
|
|
||||||
val javaColor = ScreenDefs.colorPalette[color]
|
|
||||||
val coloredPixel = listOf(javaColor.red, javaColor.green, javaColor.blue, javaColor.alpha).toIntArray()
|
|
||||||
for (pixelY in 0..7) {
|
|
||||||
for (pixelX in 0..7) {
|
|
||||||
val source = sourceRaster.getPixel(pixelX, pixelY, pixelArray)
|
|
||||||
if (source[0] != 0) {
|
|
||||||
coloredRaster.setPixel(pixelX, pixelY, coloredPixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
coloredCharacters[Triple(char, color, shifted)] = colored
|
|
||||||
cached = colored
|
|
||||||
}
|
|
||||||
fullscreenG2d.drawImage(cached, x * 8 + ScreenDefs.BORDER_SIZE, y * 8 + ScreenDefs.BORDER_SIZE, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainC64Window(
|
class MainC64Window(
|
||||||
title: String,
|
title: String,
|
||||||
chargenData: ByteArray,
|
chargenData: ByteArray,
|
||||||
|
@ -220,14 +56,12 @@ class MainC64Window(
|
||||||
val cpu: Cpu6502,
|
val cpu: Cpu6502,
|
||||||
val keypressCia: Cia
|
val keypressCia: Cia
|
||||||
) : JFrame(title), KeyListener {
|
) : JFrame(title), KeyListener {
|
||||||
private val canvas = BitmapScreenPanel(chargenData, ram)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
defaultCloseOperation = EXIT_ON_CLOSE
|
defaultCloseOperation = EXIT_ON_CLOSE
|
||||||
isResizable = false
|
isResizable = false
|
||||||
isFocusable = true
|
isFocusable = true
|
||||||
|
|
||||||
add(canvas)
|
add(Screen(chargenData, ram))
|
||||||
addKeyListener(this)
|
addKeyListener(this)
|
||||||
pack()
|
pack()
|
||||||
setLocationRelativeTo(null)
|
setLocationRelativeTo(null)
|
||||||
|
|
252
src/main/kotlin/razorvine/c64emu/Screen.kt
Normal file
252
src/main/kotlin/razorvine/c64emu/Screen.kt
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package razorvine.c64emu
|
||||||
|
|
||||||
|
import razorvine.ksim65.components.Address
|
||||||
|
import razorvine.ksim65.components.MemoryComponent
|
||||||
|
import razorvine.ksim65.components.UByte
|
||||||
|
import java.awt.*
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
import java.awt.image.DataBufferInt
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rendering logic of the screen of the C64.
|
||||||
|
* It supports: Character mode,
|
||||||
|
* High res bitmap mode (320*200), Multicolor bitmap mode (160*200).
|
||||||
|
* TODO: sprites. Multicolor character mode. Extended background color mode.
|
||||||
|
*/
|
||||||
|
internal class Screen(private val chargenData: ByteArray, val ram: MemoryComponent) : JPanel() {
|
||||||
|
|
||||||
|
private val fullscreenImage: BufferedImage
|
||||||
|
private val fullscreenG2d: Graphics2D
|
||||||
|
private val normalCharacters = loadCharacters(false)
|
||||||
|
private val shiftedCharacters = loadCharacters(true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||||||
|
val gd = ge.defaultScreenDevice.defaultConfiguration
|
||||||
|
fullscreenImage = gd.createCompatibleImage(
|
||||||
|
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.SCREEN_HEIGHT + 2 * ScreenDefs.BORDER_SIZE,
|
||||||
|
Transparency.OPAQUE
|
||||||
|
)
|
||||||
|
fullscreenImage.accelerationPriority = 1.0f
|
||||||
|
fullscreenG2d = fullscreenImage.graphics as Graphics2D
|
||||||
|
|
||||||
|
val size = Dimension(
|
||||||
|
fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt(),
|
||||||
|
fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
||||||
|
)
|
||||||
|
minimumSize = size
|
||||||
|
maximumSize = size
|
||||||
|
preferredSize = size
|
||||||
|
isFocusable = true
|
||||||
|
isDoubleBuffered = false
|
||||||
|
requestFocusInWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCharacters(shifted: Boolean): Array<BufferedImage> {
|
||||||
|
val chars = Array(256) {
|
||||||
|
BufferedImage(8, 8, BufferedImage.TYPE_BYTE_BINARY)
|
||||||
|
.also { it.accelerationPriority = 1.0f }
|
||||||
|
}
|
||||||
|
val offset = if (shifted) 256 * 8 else 0
|
||||||
|
for (char in 0..255) {
|
||||||
|
for (line in 0..7) {
|
||||||
|
val charbyte = chargenData[offset + char * 8 + line].toInt()
|
||||||
|
for (x in 0..7) {
|
||||||
|
if (charbyte and (0b10000000 ushr x) != 0)
|
||||||
|
chars[char].setRGB(x, line, 0xffffff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chars
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paint(graphics: Graphics) {
|
||||||
|
val windowG2d = graphics as Graphics2D
|
||||||
|
val vicSCROLY = ram[0xd011].toInt()
|
||||||
|
if (vicSCROLY and 0b10000 == 0) {
|
||||||
|
// screen blanked, only display border
|
||||||
|
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
0, 0,
|
||||||
|
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.SCREEN_HEIGHT + 2 * ScreenDefs.BORDER_SIZE
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val vicSCROLX = ram[0xd016].toInt()
|
||||||
|
val vicVMCSB = ram[0xd018].toInt()
|
||||||
|
val multiColorMode = vicSCROLX and 0b00010000 != 0
|
||||||
|
val vicBank = when (ram[0xdd00].toInt() and 0b00000011) {
|
||||||
|
0b00 -> 0xc000
|
||||||
|
0b01 -> 0x8000
|
||||||
|
0b10 -> 0x4000
|
||||||
|
else -> 0x0000
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the screen border
|
||||||
|
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd020]]
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
0, 0,
|
||||||
|
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE
|
||||||
|
)
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
0, ScreenDefs.SCREEN_HEIGHT + ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.SCREEN_WIDTH + 2 * ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE
|
||||||
|
)
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
0, ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT
|
||||||
|
)
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
ScreenDefs.SCREEN_WIDTH + ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.BORDER_SIZE, ScreenDefs.SCREEN_HEIGHT
|
||||||
|
)
|
||||||
|
|
||||||
|
if (vicSCROLY and 0b00100000 != 0)
|
||||||
|
renderBitmapMode(vicBank, vicVMCSB, multiColorMode)
|
||||||
|
else {
|
||||||
|
fullscreenG2d.background = ScreenDefs.colorPalette[ram[0xd021]]
|
||||||
|
fullscreenG2d.clearRect(
|
||||||
|
ScreenDefs.BORDER_SIZE, ScreenDefs.BORDER_SIZE,
|
||||||
|
ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT
|
||||||
|
)
|
||||||
|
renderCharacterMode(vicBank, vicVMCSB, multiColorMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scale and draw the image to the window, and simulate a slight scanline effect
|
||||||
|
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
||||||
|
windowG2d.drawImage(
|
||||||
|
fullscreenImage, 0, 0, (fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(),
|
||||||
|
(fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING).toInt(), null
|
||||||
|
)
|
||||||
|
windowG2d.color = Color(0, 0, 0, 40)
|
||||||
|
windowG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
||||||
|
val width = fullscreenImage.width * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
||||||
|
val height = fullscreenImage.height * ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()
|
||||||
|
for (y in 0 until height step ScreenDefs.DISPLAY_PIXEL_SCALING.toInt()) {
|
||||||
|
windowG2d.drawLine(0, y, width, y)
|
||||||
|
}
|
||||||
|
Toolkit.getDefaultToolkit().sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderCharacterMode(vicBank: Address, vicVMCSB: Int, multiColorMode: Boolean) {
|
||||||
|
if (multiColorMode) {
|
||||||
|
TODO("multicolor character mode")
|
||||||
|
} else {
|
||||||
|
// normal character mode
|
||||||
|
val screenAddress = vicBank + (vicVMCSB ushr 4) shl 10
|
||||||
|
val charsetAddress = (vicVMCSB and 0b00001110) shl 10
|
||||||
|
if (charsetAddress == 0x1000 || charsetAddress == 0x1800) {
|
||||||
|
// use built-in character ROM
|
||||||
|
for (y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) {
|
||||||
|
for (x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) {
|
||||||
|
val char = ram[screenAddress + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt()
|
||||||
|
val color = ram[0xd800 + x + y * ScreenDefs.SCREEN_WIDTH_CHARS].toInt() // colors always at $d800
|
||||||
|
drawColoredChar(x, y, char, color, charsetAddress == 0x1800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* else {
|
||||||
|
// TODO: custom charsets from RAM. Currently the charset ROM is just loaded externally.
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderBitmapMode(vicBank: Address, vicVMCSB: Int, multiColorMode: Boolean) {
|
||||||
|
val bitmap = ram.getPages((vicBank ushr 8) + if (vicVMCSB and 0b00001000 != 0) 32 else 0, 32)
|
||||||
|
val colorBytes = ram.getPages((vicBank ushr 8) + ((vicVMCSB ushr 4) shl 2), 4)
|
||||||
|
val pixels: IntArray = (fullscreenImage.raster.dataBuffer as DataBufferInt).data
|
||||||
|
val screenColor = ScreenDefs.colorPalette[ram[0xd021]].rgb
|
||||||
|
if (multiColorMode) {
|
||||||
|
// multicolor bitmap mode 160x200
|
||||||
|
val fourColors = IntArray(4)
|
||||||
|
fourColors[0b00] = screenColor
|
||||||
|
for (y in 0 until ScreenDefs.SCREEN_HEIGHT) {
|
||||||
|
for (x in 0 until ScreenDefs.SCREEN_WIDTH/2 step 4) {
|
||||||
|
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS * (y ushr 3) + (x ushr 2)
|
||||||
|
fourColors[0b01] = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt() ushr 4].rgb
|
||||||
|
fourColors[0b10] = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt()].rgb
|
||||||
|
fourColors[0b11] = ScreenDefs.colorPalette[ram[0xd800 + colorIdx].toInt()].rgb
|
||||||
|
draw4bitmapPixelsMc(pixels, x, y, bitmap, fourColors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// bitmap mode 320x200
|
||||||
|
for (y in 0 until ScreenDefs.SCREEN_HEIGHT) {
|
||||||
|
for (x in 0 until ScreenDefs.SCREEN_WIDTH step 8) {
|
||||||
|
val colorIdx = ScreenDefs.SCREEN_WIDTH_CHARS * (y ushr 3) + (x ushr 3)
|
||||||
|
val bgColor = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt()].rgb
|
||||||
|
val fgColor = ScreenDefs.colorPalette[colorBytes[colorIdx].toInt() ushr 4].rgb
|
||||||
|
draw8bitmapPixels(pixels, x, y, bitmap, fgColor, bgColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun draw8bitmapPixels(
|
||||||
|
pixels: IntArray, xstart: Int, y: Int,
|
||||||
|
bitmap: Array<UByte>, fgColorRgb: Int, bgColorRgb: Int )
|
||||||
|
{
|
||||||
|
val offset = ScreenDefs.BORDER_SIZE + ScreenDefs.BORDER_SIZE * fullscreenImage.width +
|
||||||
|
xstart + y * fullscreenImage.width
|
||||||
|
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS * (y and 248) + (y and 7) + (xstart and 504)].toInt()
|
||||||
|
pixels[offset] = if (byte and 0b10000000 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 1] = if (byte and 0b01000000 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 2] = if (byte and 0b00100000 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 3] = if (byte and 0b00010000 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 4] = if (byte and 0b00001000 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 5] = if (byte and 0b00000100 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 6] = if (byte and 0b00000010 != 0) fgColorRgb else bgColorRgb
|
||||||
|
pixels[offset + 7] = if (byte and 0b00000001 != 0) fgColorRgb else bgColorRgb
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun draw4bitmapPixelsMc(pixels: IntArray, xstart: Int, y: Int,
|
||||||
|
bitmap: Array<UByte>, fourColors: IntArray) {
|
||||||
|
val realx = xstart * 2
|
||||||
|
val offset = ScreenDefs.BORDER_SIZE + ScreenDefs.BORDER_SIZE * fullscreenImage.width +
|
||||||
|
realx + y * fullscreenImage.width
|
||||||
|
val byte = bitmap[ScreenDefs.SCREEN_WIDTH_CHARS * (y and 248) + (y and 7) + (realx and 504)].toInt()
|
||||||
|
val colors = listOf(
|
||||||
|
byte and 0b11000000 ushr 6,
|
||||||
|
byte and 0b00110000 ushr 4,
|
||||||
|
byte and 0b00001100 ushr 2,
|
||||||
|
byte and 0b00000011
|
||||||
|
)
|
||||||
|
pixels[offset] = fourColors[colors[0]]
|
||||||
|
pixels[offset + 1] = fourColors[colors[0]]
|
||||||
|
pixels[offset + 2] = fourColors[colors[1]]
|
||||||
|
pixels[offset + 3] = fourColors[colors[1]]
|
||||||
|
pixels[offset + 4] = fourColors[colors[2]]
|
||||||
|
pixels[offset + 5] = fourColors[colors[2]]
|
||||||
|
pixels[offset + 6] = fourColors[colors[3]]
|
||||||
|
pixels[offset + 7] = fourColors[colors[3]]
|
||||||
|
}
|
||||||
|
|
||||||
|
private val coloredCharacters = mutableMapOf<Triple<Int, Int, Boolean>, BufferedImage>()
|
||||||
|
|
||||||
|
private fun drawColoredChar(x: Int, y: Int, char: Int, color: Int, shifted: Boolean) {
|
||||||
|
var cached = coloredCharacters[Triple(char, color, shifted)]
|
||||||
|
if (cached == null) {
|
||||||
|
cached = if (shifted) shiftedCharacters[char] else normalCharacters[char]
|
||||||
|
val colored = fullscreenG2d.deviceConfiguration.createCompatibleImage(8, 8, BufferedImage.BITMASK)
|
||||||
|
val sourceRaster = cached.raster
|
||||||
|
val coloredRaster = colored.raster
|
||||||
|
val pixelArray = IntArray(4)
|
||||||
|
val javaColor = ScreenDefs.colorPalette[color]
|
||||||
|
val coloredPixel = listOf(javaColor.red, javaColor.green, javaColor.blue, javaColor.alpha).toIntArray()
|
||||||
|
for (pixelY in 0..7) {
|
||||||
|
for (pixelX in 0..7) {
|
||||||
|
val source = sourceRaster.getPixel(pixelX, pixelY, pixelArray)
|
||||||
|
if (source[0] != 0) {
|
||||||
|
coloredRaster.setPixel(pixelX, pixelY, coloredPixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
coloredCharacters[Triple(char, color, shifted)] = colored
|
||||||
|
cached = colored
|
||||||
|
}
|
||||||
|
fullscreenG2d.drawImage(cached, x * 8 + ScreenDefs.BORDER_SIZE, y * 8 + ScreenDefs.BORDER_SIZE, null)
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,7 +65,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
hostDisplay.start(30)
|
hostDisplay.start(30)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
private fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||||
if (cpu.regA == 0) {
|
if (cpu.regA == 0) {
|
||||||
val fnlen = ram[0xb7] // file name length
|
val fnlen = ram[0xb7] // file name length
|
||||||
val fa = ram[0xba] // device number
|
val fa = ram[0xba] // device number
|
||||||
|
@ -85,7 +85,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
} else return Cpu6502.BreakpointResultAction(changePC = 0xf707) // 'device not present' (VERIFY command not supported)
|
} else return Cpu6502.BreakpointResultAction(changePC = 0xf707) // 'device not present' (VERIFY command not supported)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun breakpointKernelSave(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
private fun breakpointKernelSave(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||||
val fnlen = ram[0xb7] // file name length
|
val fnlen = ram[0xb7] // file name length
|
||||||
// val fa = ram[0xba] // device number
|
// val fa = ram[0xba] // device number
|
||||||
// val sa = ram[0xb9] // secondary address
|
// val sa = ram[0xb9] // secondary address
|
||||||
|
@ -107,16 +107,12 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
} else Cpu6502.BreakpointResultAction(changePC = 0xf710) // 'missing file name'
|
} else Cpu6502.BreakpointResultAction(changePC = 0xf710) // 'missing file name'
|
||||||
}
|
}
|
||||||
|
|
||||||
fun breakpointBRK(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
private fun breakpointBRK(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||||
throw Cpu6502.InstructionError("BRK instruction hit at $${hexW(pc)}")
|
throw Cpu6502.InstructionError("BRK instruction hit at $${hexW(pc)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchAndLoadFile(
|
private fun searchAndLoadFile(filename: String,
|
||||||
filename: String,
|
device: UByte, secondary: UByte, basicLoadAddress: Address): Address? {
|
||||||
device: UByte,
|
|
||||||
secondary: UByte,
|
|
||||||
basicLoadAddress: Address
|
|
||||||
): Address? {
|
|
||||||
when (filename) {
|
when (filename) {
|
||||||
"*" -> {
|
"*" -> {
|
||||||
// load the first file in the directory
|
// load the first file in the directory
|
||||||
|
@ -170,11 +166,9 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeDirListing(
|
private fun makeDirListing(dirname: String,
|
||||||
dirname: String,
|
files: Map<Pair<String, String>, Pair<File, Long>>, basicLoadAddress: Address): Array<UByte>
|
||||||
files: Map<Pair<String, String>, Pair<File, Long>>,
|
{
|
||||||
basicLoadAddress: Address
|
|
||||||
): Array<UByte> {
|
|
||||||
var address = basicLoadAddress
|
var address = basicLoadAddress
|
||||||
val listing = mutableListOf<UByte>()
|
val listing = mutableListOf<UByte>()
|
||||||
fun addLine(lineNumber: Int, line: String) {
|
fun addLine(lineNumber: Int, line: String) {
|
||||||
|
@ -193,8 +187,8 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
totalBlocks += blocksize
|
totalBlocks += blocksize
|
||||||
val filename = it.key.first.take(16)
|
val filename = it.key.first.take(16)
|
||||||
val padding1 = " ".substring(blocksize.toString().length)
|
val padding1 = " ".substring(blocksize.toString().length)
|
||||||
val padding2 = " ".substring(filename.length)
|
val padding2 = " ".substring(filename.length)
|
||||||
addLine(blocksize, "$padding1 \"$filename\" $padding2 ${it.key.second.take(3).padEnd(3)}")
|
addLine(blocksize, "$padding1 \"$filename\" $padding2${it.key.second.take(3).padEnd(3)}")
|
||||||
}
|
}
|
||||||
addLine(kotlin.math.max(0, 664 - totalBlocks), "BLOCKS FREE.")
|
addLine(kotlin.math.max(0, 664 - totalBlocks), "BLOCKS FREE.")
|
||||||
listing.add(0)
|
listing.add(0)
|
||||||
|
@ -249,19 +243,31 @@ class C64Machine(title: String) : IVirtualMachine {
|
||||||
}.start()
|
}.start()
|
||||||
|
|
||||||
val timer = java.util.Timer("cpu-cycle", true)
|
val timer = java.util.Timer("cpu-cycle", true)
|
||||||
timer.scheduleAtFixedRate(0, 1000L/VicII.framerate) {
|
timer.scheduleAtFixedRate(0, 1000L / VicII.framerate) {
|
||||||
if(!paused) {
|
if (!paused) {
|
||||||
// we synchronise cpu cycles to the vertical blank of the Vic chip
|
// we synchronise cpu cycles to the vertical blank of the Vic chip
|
||||||
// this should result in ~1 Mhz cpu speed
|
// this should result in ~1 Mhz cpu speed
|
||||||
try {
|
try {
|
||||||
while (vic.vsync) step()
|
while (vic.vsync) step()
|
||||||
while (!vic.vsync) step()
|
while (!vic.vsync) step()
|
||||||
} catch(rx: RuntimeException) {
|
} catch (rx: RuntimeException) {
|
||||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
JOptionPane.showMessageDialog(
|
||||||
|
hostDisplay,
|
||||||
|
"Run time error: $rx",
|
||||||
|
"Error during execution",
|
||||||
|
JOptionPane.ERROR_MESSAGE
|
||||||
|
)
|
||||||
this.cancel()
|
this.cancel()
|
||||||
} catch(ex: Error) {
|
throw rx
|
||||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
} catch (ex: Error) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
hostDisplay,
|
||||||
|
"Run time error: $ex",
|
||||||
|
"Error during execution",
|
||||||
|
JOptionPane.ERROR_MESSAGE
|
||||||
|
)
|
||||||
this.cancel()
|
this.cancel()
|
||||||
|
throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,6 @@ open class Cpu6502 : BusComponent() {
|
||||||
var tracing: ((state:String) -> Unit)? = null
|
var tracing: ((state:String) -> Unit)? = null
|
||||||
var totalCycles = 0L
|
var totalCycles = 0L
|
||||||
protected set
|
protected set
|
||||||
private var speedMeasureCycles = 0L
|
|
||||||
private var speedMeasureStart = System.nanoTime()
|
|
||||||
private var resetTime = System.nanoTime()
|
private var resetTime = System.nanoTime()
|
||||||
|
|
||||||
var breakpointForBRK: BreakpointHandler? = null
|
var breakpointForBRK: BreakpointHandler? = null
|
||||||
|
@ -166,14 +164,6 @@ open class Cpu6502 : BusComponent() {
|
||||||
totalCycles)
|
totalCycles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSpeedMeasureInterval() {
|
|
||||||
speedMeasureCycles = totalCycles
|
|
||||||
speedMeasureStart = System.nanoTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun measureAvgIntervalSpeedKhz() =
|
|
||||||
(totalCycles-speedMeasureCycles).toDouble() / (System.nanoTime() - speedMeasureStart) * 1_000_000
|
|
||||||
|
|
||||||
// has an interrupt been requested?
|
// has an interrupt been requested?
|
||||||
protected enum class Interrupt {
|
protected enum class Interrupt {
|
||||||
IRQ,
|
IRQ,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user