mirror of
https://github.com/irmen/ksim65.git
synced 2024-06-01 06:41:34 +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.event.KeyEvent
|
||||
import java.awt.event.KeyListener
|
||||
import java.awt.image.BufferedImage
|
||||
import java.awt.image.DataBufferInt
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.Timer
|
||||
|
||||
|
||||
|
@ -52,167 +49,6 @@ object ScreenDefs {
|
|||
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(
|
||||
title: String,
|
||||
chargenData: ByteArray,
|
||||
|
@ -220,14 +56,12 @@ class MainC64Window(
|
|||
val cpu: Cpu6502,
|
||||
val keypressCia: Cia
|
||||
) : JFrame(title), KeyListener {
|
||||
private val canvas = BitmapScreenPanel(chargenData, ram)
|
||||
|
||||
init {
|
||||
defaultCloseOperation = EXIT_ON_CLOSE
|
||||
isResizable = false
|
||||
isFocusable = true
|
||||
|
||||
add(canvas)
|
||||
add(Screen(chargenData, ram))
|
||||
addKeyListener(this)
|
||||
pack()
|
||||
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)
|
||||
}
|
||||
|
||||
fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
private fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
if (cpu.regA == 0) {
|
||||
val fnlen = ram[0xb7] // file name length
|
||||
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)
|
||||
}
|
||||
|
||||
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 fa = ram[0xba] // device number
|
||||
// val sa = ram[0xb9] // secondary address
|
||||
|
@ -107,16 +107,12 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
} 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)}")
|
||||
}
|
||||
|
||||
private fun searchAndLoadFile(
|
||||
filename: String,
|
||||
device: UByte,
|
||||
secondary: UByte,
|
||||
basicLoadAddress: Address
|
||||
): Address? {
|
||||
private fun searchAndLoadFile(filename: String,
|
||||
device: UByte, secondary: UByte, basicLoadAddress: Address): Address? {
|
||||
when (filename) {
|
||||
"*" -> {
|
||||
// load the first file in the directory
|
||||
|
@ -170,11 +166,9 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
}
|
||||
}
|
||||
|
||||
private fun makeDirListing(
|
||||
dirname: String,
|
||||
files: Map<Pair<String, String>, Pair<File, Long>>,
|
||||
basicLoadAddress: Address
|
||||
): Array<UByte> {
|
||||
private fun makeDirListing(dirname: String,
|
||||
files: Map<Pair<String, String>, Pair<File, Long>>, basicLoadAddress: Address): Array<UByte>
|
||||
{
|
||||
var address = basicLoadAddress
|
||||
val listing = mutableListOf<UByte>()
|
||||
fun addLine(lineNumber: Int, line: String) {
|
||||
|
@ -193,8 +187,8 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
totalBlocks += blocksize
|
||||
val filename = it.key.first.take(16)
|
||||
val padding1 = " ".substring(blocksize.toString().length)
|
||||
val padding2 = " ".substring(filename.length)
|
||||
addLine(blocksize, "$padding1 \"$filename\" $padding2 ${it.key.second.take(3).padEnd(3)}")
|
||||
val padding2 = " ".substring(filename.length)
|
||||
addLine(blocksize, "$padding1 \"$filename\" $padding2${it.key.second.take(3).padEnd(3)}")
|
||||
}
|
||||
addLine(kotlin.math.max(0, 664 - totalBlocks), "BLOCKS FREE.")
|
||||
listing.add(0)
|
||||
|
@ -249,19 +243,31 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
}.start()
|
||||
|
||||
val timer = java.util.Timer("cpu-cycle", true)
|
||||
timer.scheduleAtFixedRate(0, 1000L/VicII.framerate) {
|
||||
if(!paused) {
|
||||
timer.scheduleAtFixedRate(0, 1000L / VicII.framerate) {
|
||||
if (!paused) {
|
||||
// we synchronise cpu cycles to the vertical blank of the Vic chip
|
||||
// this should result in ~1 Mhz cpu speed
|
||||
try {
|
||||
while (vic.vsync) step()
|
||||
while (!vic.vsync) step()
|
||||
} catch(rx: RuntimeException) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
} catch (rx: RuntimeException) {
|
||||
JOptionPane.showMessageDialog(
|
||||
hostDisplay,
|
||||
"Run time error: $rx",
|
||||
"Error during execution",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
this.cancel()
|
||||
} catch(ex: Error) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
throw rx
|
||||
} catch (ex: Error) {
|
||||
JOptionPane.showMessageDialog(
|
||||
hostDisplay,
|
||||
"Run time error: $ex",
|
||||
"Error during execution",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
this.cancel()
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ open class Cpu6502 : BusComponent() {
|
|||
var tracing: ((state:String) -> Unit)? = null
|
||||
var totalCycles = 0L
|
||||
protected set
|
||||
private var speedMeasureCycles = 0L
|
||||
private var speedMeasureStart = System.nanoTime()
|
||||
private var resetTime = System.nanoTime()
|
||||
|
||||
var breakpointForBRK: BreakpointHandler? = null
|
||||
|
@ -166,14 +164,6 @@ open class Cpu6502 : BusComponent() {
|
|||
totalCycles)
|
||||
}
|
||||
|
||||
fun startSpeedMeasureInterval() {
|
||||
speedMeasureCycles = totalCycles
|
||||
speedMeasureStart = System.nanoTime()
|
||||
}
|
||||
|
||||
fun measureAvgIntervalSpeedKhz() =
|
||||
(totalCycles-speedMeasureCycles).toDouble() / (System.nanoTime() - speedMeasureStart) * 1_000_000
|
||||
|
||||
// has an interrupt been requested?
|
||||
protected enum class Interrupt {
|
||||
IRQ,
|
||||
|
|
Loading…
Reference in New Issue
Block a user