diff --git a/FontROM7x14.kra b/FontROM7x14.kra new file mode 100644 index 0000000..30495fe Binary files /dev/null and b/FontROM7x14.kra differ diff --git a/FontROM7x14.png b/FontROM7x14.png new file mode 100644 index 0000000..e4874a3 Binary files /dev/null and b/FontROM7x14.png differ diff --git a/smpte_bars.png b/smpte_bars.png new file mode 100644 index 0000000..2343f1a Binary files /dev/null and b/smpte_bars.png differ diff --git a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt new file mode 100644 index 0000000..fe46a9c --- /dev/null +++ b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -0,0 +1,28 @@ +package net.torvald.tsvm + +import net.torvald.tsvm.peripheral.GraphicsAdapter + +class GraphicsJSR223Delegate(val vm: VM) { + + private fun getFirstGPU(): GraphicsAdapter? { + return vm.peripheralTable[vm.findPeribyType("gpu") ?: return null].peripheral as? GraphicsAdapter + } + + fun resetPalette() { + getFirstGPU()?.poke(250883L, 1) + } + + /** + * @param index which palette number to modify, 0-255 + * @param r g - b - a - RGBA value, 0-15 + */ + fun setPalette(index: Int, r: Int, g: Int, b: Int, a: Int = 16) { + getFirstGPU()?.let { + it.paletteOfFloats[index * 4] = (r and 15) / 15f + it.paletteOfFloats[index * 4 + 1] = (g and 15) / 15f + it.paletteOfFloats[index * 4 + 2] = (b and 15) / 15f + it.paletteOfFloats[index * 4 + 3] = (a and 15) / 15f + } + } + +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index 72bfc49..e3e105f 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -115,7 +115,7 @@ class VM( } } - fun poke(addr: Long, value: Byte) { + internal fun poke(addr: Long, value: Byte) { val (memspace, offset) = translateAddr(addr) if (memspace == null) Firmware.errorIllegalAccess(addr) @@ -125,7 +125,7 @@ class VM( (memspace as PeriBase).poke(offset, value) } - fun peek(addr:Long): Byte? { + internal fun peek(addr:Long): Byte? { val (memspace, offset) = translateAddr(addr) return if (memspace == null) null diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index d188e81..d1c795c 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -70,21 +70,12 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() private var latch = true private fun updateGame(delta: Float) { - // black screening workaround - if (latch) { - latch = false - //paintTestPalette() - val peripheralSlot = vm.findPeribyType(VM.PERITYPE_GRAPHICS)!! - val hwoff = VM.HW_RESERVE_SIZE * peripheralSlot - for (i in 250880 until 250972) { - vm.poke(-(i + 1) - hwoff, 0) - } - } } fun poke(addr: Long, value: Byte) = vm.poke(addr, value) private fun paintTestPalette() { + } private val gpuTestPaletteKt = """ @@ -101,30 +92,25 @@ fun inthash(x: Int): Int { var rng = (Math.floor(Math.random() * 2147483647) + 1).toInt() -bindings.forEach { - println(it) -} - -println(zzz) - while (true) { val tstart: Long = System.nanoTime() for (y1 in 0..359) { for (x1 in 0 until w) { val palnum = 20 * (y1 / 30) + (x1 / 28) - vm.poke(-(y1 * w + x1 + 1L) - hwoff, inthash(palnum + rng).toByte()) + vm.poke(-(y1 * w + x1 + 1) - hwoff, inthash(palnum + rng)) } } for (y2 in 360 until h) { for (x2 in 0 until w) { val palnum = 240 + x2 / 35 - vm.poke(-(y2 * w + x2 + 1L) - hwoff, palnum.toByte()) + vm.poke(-(y2 * w + x2 + 1) - hwoff, palnum) } } - for (k in 0..2239) { - vm.poke(-(254912L + k + 1) - hwoff, -2) // white - vm.poke(-(254912L + 2240 + k + 1) - hwoff, -1) // transparent - vm.poke(-(254912L + 2240 * 2 + k + 1) - hwoff, Math.round(Math.random() * 255).toByte()) + + for (k in 0 until 2560) { + vm.poke(-(253952 + k + 1) - hwoff, -2) // white + vm.poke(-(253952 + 2560 + k + 1) - hwoff, -1) // transparent + vm.poke(-(253952 + 2560 * 2 + k + 1) - hwoff, Math.round(Math.random() * 255).toInt()) } rng = inthash(rng) @@ -133,6 +119,42 @@ while (true) { } """.trimIndent() + private val gpuTestPaletteKt2 = """ +val w = 560 +val h = 448 +val hwoff = 1048576 + +fun inthash(x: Int): Int { + var x = (x.shr(16) xor x) * 0x45d9f3b + x = (x.shr(16) xor x) * 0x45d9f3b + x = (x.shr(16) xor x) + return x +} + +var rng = ((Math.random() * 2147483647) + 1).toInt() + +while (true) { + for (y1 in 0..359) { + for (x1 in 0 until w) { + val palnum = 20 * (y1 / 30) + (x1 / 28) + vm.poke(-(y1 * w + x1 + 1) - hwoff, palnum)//inthash(palnum + rng)) + } + } + for (y2 in 360 until h) { + for (x2 in 0 until w) { + val palnum = 240 + x2 / 35 + vm.poke(-(y2 * w + x2 + 1) - hwoff, palnum) + } + } + + for (k in 0 until 255) { + graphics.setPalette(k, (Math.random() * 15).toInt(), (Math.random() * 15).toInt(), (Math.random() * 15).toInt()) + } + + println("arst") +} + """.trimIndent() + private val gpuTestPalette = """ local vm = require("rawmem") local bit = require("bit32") @@ -150,7 +172,7 @@ end local rng = math.floor(math.random() * 2147483647) while true do - local tstart = vm.nanotime() + local tstart = vm.nanoTime() for y = 0, 359 do for x = 0, w - 1 do @@ -167,14 +189,14 @@ while true do end for k = 0, 2239 do - vm.poke(-(254912 + k + 1) - hwoff, 254) - vm.poke(-(254912 + 2240 + k + 1) - hwoff, 255) - vm.poke(-(254912 + 2240*2 + k + 1) - hwoff, math.floor(math.random() * 255.0)) + vm.poke(-(253952 + k + 1) - hwoff, 254) + vm.poke(-(253952 + 2560 + k + 1) - hwoff, 255) + vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, math.floor(math.random() * 255.0)) end rng = inthash(rng) - local tend = vm.nanotime() + local tend = vm.nanoTime() print("Apparent FPS: "..tostring(1000000000.0 / (tend - tstart))) end @@ -186,7 +208,7 @@ var h = 448 var hwoff = 1048576 print(typeof print) //function -print(typeof poke.invoke) //function +print(typeof vm.poke) //function function inthash(x) { x = ((x >> 16) ^ x) * 0x45d9f3b @@ -199,31 +221,31 @@ var rng = Math.floor(Math.random() * 2147483647) + 1 while (true) { - var tstart = nanotime.invoke() + var tstart = vm.nanoTime() for (var y = 0; y < 360; y++) { for (var x = 0; x < w; x++) { var palnum = 20 * Math.floor(y / 30) + Math.floor(x / 28) - poke.invoke(-(y * w + x + 1) - hwoff, inthash(palnum + rng)) + vm.poke(-(y * w + x + 1) - hwoff, inthash(palnum + rng)) } } for (var y = 360; y < h; y++) { for (var x = 0; x < w; x++) { var palnum = 240 + Math.floor(x / 35) - poke.invoke(-(y * w + x + 1) - hwoff, palnum) + vm.poke(-(y * w + x + 1) - hwoff, palnum) } } - for (var k = 0; k < 2240; k++) { - poke.invoke(-(254912 + k + 1) - hwoff, -2) // white - poke.invoke(-(254912 + 2240 + k + 1) - hwoff, -1) // transparent - poke.invoke(-(254912 + 2240*2 + k + 1) - hwoff, Math.round(Math.random() * 255)) + for (var k = 0; k < 2560; k++) { + vm.poke(-(253952 + k + 1) - hwoff, -2) // white + vm.poke(-(253952 + 2560 + k + 1) - hwoff, -1) // transparent + vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255)) } rng = inthash(rng) - var tend = nanotime.invoke() + var tend = vm.nanoTime() print("Apparent FPS: " + (1000000000 / (tend - tstart))) } @@ -250,7 +272,7 @@ int rng = Math.floor(Math.random() * 2147483647) + 1; while (true) { - long tstart = nanotime.invoke(); + long tstart = nanoTime.invoke(); for (int y1 = 0; y1 < 360; y1++) { for (int x1 = 0; x1 < w; x1++) { @@ -266,15 +288,15 @@ while (true) { } } - for (int k = 0; k < 2240; k++) { - poke.invoke(-(254912 + k + 1) - hwoff, -2); // white - poke.invoke(-(254912 + 2240 + k + 1) - hwoff, -1); // transparent - poke.invoke(-(254912 + 2240*2 + k + 1) - hwoff, Math.round(Math.random() * 255)); + for (int k = 0; k < 2560; k++) { + poke.invoke(-(253952 + k + 1) - hwoff, -2); // white + poke.invoke(-(253952 + 2560 + k + 1) - hwoff, -1); // transparent + poke.invoke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255)); } rng = inthash(rng); - long tend = nanotime.invoke(); + long tend = nanoTime.invoke(); System.out.println("Apparent FPS: " + (1000000000.0 / (tend - tstart))); } @@ -301,7 +323,7 @@ rng = random.randint(1, 2147483647) while True: - tstart = nanotime.invoke() + tstart = nanoTime.invoke() for y1 in range(0, 360): for x1 in range(0, w): @@ -313,14 +335,14 @@ while True: palnum = 240 + int(x2 / 35) poke.invoke(-(y2 * w + x2 + 1) - hwoff, palnum) - for k in range(0, 2240): - poke.invoke(-(254912 + k + 1) - hwoff, -2) - poke.invoke(-(254912 + 2240 + k + 1) - hwoff, -1) - poke.invoke(-(254912 + 2240*2 + k + 1) - hwoff, random.randint(0, 255)) + for k in range(0, 2560): + poke.invoke(-(253952 + k + 1) - hwoff, -2) + poke.invoke(-(253952 + 2560 + k + 1) - hwoff, -1) + poke.invoke(-(253952 + 2560*2 + k + 1) - hwoff, random.randint(0, 255)) rng = inthash(rng) - tend = nanotime.invoke() + tend = nanoTime.invoke() print("Apparent FPS: " + str(1000000000.0 / (tend - tstart))) diff --git a/src/net/torvald/tsvm/VMJSR223Delegate.kt b/src/net/torvald/tsvm/VMJSR223Delegate.kt new file mode 100644 index 0000000..fddcf90 --- /dev/null +++ b/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -0,0 +1,22 @@ +package net.torvald.tsvm + +import net.torvald.tsvm.peripheral.GraphicsAdapter + +/** + * Pass the instance of the class to the ScriptEngine's binding, preferably under the namespace of "vm" + */ +class VMJSR223Delegate(val vm: VM) { + + fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) + fun peek(addr: Int) = vm.peek(addr.toLong()) + fun nanoTime() = System.nanoTime() + fun dmagload(from: Int, to: Int, length: Int) { + val periid = vm.findPeribyType("gpu") + if (periid == null) + throw IllegalStateException("GPU not found") + else { + (vm.peripheralTable[periid].peripheral as GraphicsAdapter).bulkLoad(vm, from.toLong(), to.toLong(), length.toLong()) + } + } + +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/VMKotlinAdapter.kt b/src/net/torvald/tsvm/VMKotlinAdapter.kt deleted file mode 100644 index 76e206c..0000000 --- a/src/net/torvald/tsvm/VMKotlinAdapter.kt +++ /dev/null @@ -1,10 +0,0 @@ -package net.torvald.tsvm - -class VMKotlinAdapter(val vm: VM) { - - /*fun getClassLoader(): ClassLoader { - val cl = object : ClassLoader() { - - } - }*/ -} \ No newline at end of file diff --git a/src/net/torvald/tsvm/VMRunnerFactory.kt b/src/net/torvald/tsvm/VMRunnerFactory.kt index 3415c64..ee5bffb 100644 --- a/src/net/torvald/tsvm/VMRunnerFactory.kt +++ b/src/net/torvald/tsvm/VMRunnerFactory.kt @@ -52,10 +52,10 @@ object VMRunnerFactory { private val bind = context.getBindings(ScriptContext.ENGINE_SCOPE) init { - bind.put("zzz", 42) - bind.put("vm", vm) // TODO use delegator class to access peripheral (do not expose VM itself) - bind.put("poke", { a: Long, b: Byte -> vm.poke(a, b) }) // kts: lambda does not work... - bind.put("nanotime", { System.nanoTime() }) + bind.put("vm", VMJSR223Delegate(vm)) // TODO use delegator class to access peripheral (do not expose VM itself) + bind.put("graphics", GraphicsJSR223Delegate(vm)) + //bind.put("poke", { a: Long, b: Byte -> vm.poke(a, b) }) // kts: lambda does not work... + //bind.put("nanotime", { System.nanoTime() }) } override fun executeCommand(command: String) { diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 7654e03..0e700a0 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -1,15 +1,14 @@ package net.torvald.tsvm.peripheral -import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import net.torvald.UnsafeHelper -import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.tsvm.AppLoader import net.torvald.tsvm.VM import net.torvald.tsvm.kB +import sun.nio.ch.DirectBuffer import kotlin.experimental.and class GraphicsAdapter : PeriBase { @@ -21,7 +20,7 @@ class GraphicsAdapter : PeriBase { val channel = it % 4 rgba.shr((3 - channel) * 8).and(255) / 255f } - private val chrrom0 = Texture("./EGA8x14.png") + private val chrrom0 = Texture("./FontROM7x14.png") private val faketex: Texture private val spriteAndTextArea = UnsafeHelper.allocate(10660L) @@ -34,11 +33,11 @@ class GraphicsAdapter : PeriBase { private var graphicsUseSprites = false private var lastUsedColour = (-1).toByte() private var currentChrRom = 0 - private var chrWidth = 8f + private var chrWidth = 7f private var chrHeight = 14f - private var ttyFore = 254 - private var ttyBack = 255 + private var ttyFore: Int = 254 // cannot be Byte + private var ttyBack: Int = 255 // cannot be Byte private val textForePixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) private val textBackPixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) @@ -48,7 +47,13 @@ class GraphicsAdapter : PeriBase { private var textBackTex = Texture(textBackPixmap) private var textTex = Texture(textPixmap) - private fun getTtyCursorPos() = spriteAndTextArea.getShort(3938L) % TEXT_COLS to spriteAndTextArea.getShort(3938L) / TEXT_COLS + private val memTextCursorPosOffset = 2978L + private val memTextForeOffset = 2980L + private val memTextBackOffset = 2980L + 2560 + private val memTextOffset = 2980L + 2560 + 2560 + + private fun getTtyCursorPos() = spriteAndTextArea.getShort(memTextCursorPosOffset) % TEXT_COLS to spriteAndTextArea.getShort(3938L) / TEXT_COLS + private fun toTtyTextOffset(x: Int, y: Int) = y * TEXT_COLS + x init { framebuffer.blending = Pixmap.Blending.None @@ -62,7 +67,10 @@ class GraphicsAdapter : PeriBase { faketex = Texture(pm) pm.dispose() - spriteAndTextArea.fillWith(0) + // initialise with NONZERO value; value zero corresponds with opaque black, and it will paint the whole screen black + // when in text mode, and that's undesired behaviour + // -1 is preferred because it points to the colour CLEAR, and it's constant. + spriteAndTextArea.fillWith(-1) } override fun peek(addr: Long): Byte? { @@ -88,6 +96,10 @@ class GraphicsAdapter : PeriBase { lastUsedColour = byte framebuffer.drawPixel(adi % WIDTH, adi / WIDTH, bi.shl(24)) } + 250883L -> { + unusedArea[adi - 250880] = byte + runCommand(byte) + } in 250880 until 250972 -> unusedArea[adi - 250880] = byte in 250972 until 261632 -> spriteAndTextArea[addr - 250972] = byte in 261632 until 262144 -> pokePalette(adi - 261632, byte) @@ -132,6 +144,64 @@ class GraphicsAdapter : PeriBase { TODO("Not yet implemented") } + private fun runCommand(opcode: Byte) { + val arg1 = unusedArea[4].toInt().and(255) + val arg2 = unusedArea[5].toInt().and(255) + + when (opcode.toInt()) { + 1 -> { + for (it in 0 until 1024) { + val rgba = DEFAULT_PALETTE[it / 4] + val channel = it % 4 + rgba.shr((3 - channel) * 8).and(255) / 255f + } + } + 2 -> { + framebuffer.setColor( + paletteOfFloats[arg1 * 4], + paletteOfFloats[arg1 * 4 + 1], + paletteOfFloats[arg1 * 4 + 2], + paletteOfFloats[arg1 * 4 + 3] + ) + framebuffer.fill() + } + } + } + + /** + * @param from memory address (pointer) on the VM's user memory. Because of how the VM is coded, only the user space is eligible for move. + * @param to memory "offset" in Graphics Adapter's memory space, starts from zero. + * @param length how many bytes should be moved + */ + fun bulkLoad(vm: VM, from: Long, to: Long, length: Long) { + UnsafeHelper.unsafe.copyMemory(null, vm.usermem.ptr + from, (framebuffer.pixels as DirectBuffer).address(), to, length) + } + + private fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte = ttyFore.toByte(), backColour: Byte = ttyBack.toByte()) { + val textOff = toTtyTextOffset(x, y) + spriteAndTextArea[memTextForeOffset + textOff] = foreColour + spriteAndTextArea[memTextBackOffset + textOff] = backColour + spriteAndTextArea[memTextOffset + textOff] = text + } + + private fun advanceCursor() { + spriteAndTextArea.setShort( + memTextCursorPosOffset, + ((spriteAndTextArea.getShort(memTextCursorPosOffset) + 1) % (TEXT_COLS * TEXT_ROWS)).toShort() + ) + } + + // how TTY should work with all those ASCII control characters + fun print(char: Byte) { + val (cx, cy) = getTtyCursorPos() + when (char) { + in 0x20..0x7E, in 0x80..0xFF -> { + putChar(cx, cy, char) + advanceCursor() + } + } + } + override fun dispose() { framebuffer.dispose() rendertex.dispose() @@ -178,7 +248,6 @@ class GraphicsAdapter : PeriBase { // draw framebuffer batch.draw(rendertex, x, y) - // draw texts or sprites batch.color = Color.WHITE @@ -190,9 +259,9 @@ class GraphicsAdapter : PeriBase { for (y in 0 until TEXT_ROWS) { for (x in 0 until TEXT_COLS) { val addr = y.toLong() * TEXT_COLS + x - val char = spriteAndTextArea[3940 + 2240 + 2240 + addr].toInt().and(255) - val back = spriteAndTextArea[3940 + 2240 + addr].toInt().and(255) - val fore = spriteAndTextArea[3940 + addr].toInt().and(255) + val char = spriteAndTextArea[memTextOffset + addr].toInt().and(255) + val back = spriteAndTextArea[memTextBackOffset + addr].toInt().and(255) + val fore = spriteAndTextArea[memTextForeOffset + addr].toInt().and(255) textPixmap.setColor(Color(0f, 0f, char / 255f, 1f)) textPixmap.drawPixel(x, y) @@ -232,7 +301,7 @@ class GraphicsAdapter : PeriBase { batch.shader = null - if (textCursorIsOn) { + /*if (textCursorIsOn) { batch.color = Color( paletteOfFloats[4 * ttyFore], paletteOfFloats[4 * ttyFore + 1], @@ -241,7 +310,7 @@ class GraphicsAdapter : PeriBase { ) val (cursorx, cursory) = getTtyCursorPos() batch.draw(faketex, cursorx * chrWidth, (TEXT_ROWS - cursory - 1) * chrHeight, chrWidth, chrHeight) - } + }*/ } else { // draw sprites @@ -293,7 +362,7 @@ class GraphicsAdapter : PeriBase { companion object { const val WIDTH = 560 const val HEIGHT = 448 - const val TEXT_COLS = 70 + const val TEXT_COLS = 80 const val TEXT_ROWS = 32 val VRAM_SIZE = 256.kB() diff --git a/terranmon.txt b/terranmon.txt index 85f32e9..3c57cfa 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -44,7 +44,16 @@ Endianness: little From the start of the memory space: 250880 bytes Framebuffer -92 bytes +3 bytes + *reserved for future use* +1 byte + command (writing to this memory address changes the status) + 1: reset palette to default + 2: fill framebuffer with given colour (arg1) +2 bytes + argument for "command" (arg1: Byte, arg2: Byte) + write to this address FIRST and then write to "command" to execute the command +86 bytes *Unused* IF graphics_mode THEN (41 sprites : 260 bytes each -> 10660 bytes) @@ -56,15 +65,15 @@ IF graphics_mode THEN 256 bytes 16x16 texture for the sprite ELSE - 3938 bytes + 2978 bytes *Unused* 2 bytes Cursor position in: (y*32 + x) - 2240 bytes + 2560 bytes Text foreground colours - 2240 bytes + 2560 bytes Text background colours - 2240 bytes + 2560 bytes Text buffer of 70x32 (8x14 character size, and yes: actual character data is on the bottom) FI 512 bytes