diff --git a/assets/bios/pipboot.js b/assets/bios/pipboot.js new file mode 100644 index 0000000..4765340 --- /dev/null +++ b/assets/bios/pipboot.js @@ -0,0 +1,7 @@ +println("Hello, Personal Information Processor!") + +while (1) { + for (let i = 0; i <= 160*140; i++) { + sys.poke(-1048576 - i, Math.round(Math.random()*15)); + } +} \ No newline at end of file diff --git a/assets/bios/pipcode.bas b/assets/bios/pipcode.bas new file mode 100644 index 0000000..30e8c16 --- /dev/null +++ b/assets/bios/pipcode.bas @@ -0,0 +1 @@ +10 PRINT "HAI!" \ No newline at end of file diff --git a/src/net/torvald/tsvm/AppLoader.java b/src/net/torvald/tsvm/AppLoader.java index 874d260..bd48378 100644 --- a/src/net/torvald/tsvm/AppLoader.java +++ b/src/net/torvald/tsvm/AppLoader.java @@ -4,6 +4,8 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import kotlin.Pair; +import kotlin.collections.CollectionsKt; import net.torvald.tsvm.peripheral.*; public class AppLoader { @@ -33,16 +35,26 @@ public class AppLoader { // VM vm = new VM(128 << 10, new TheRealWorld(), new VMProgramRom[]{BasicBios.INSTANCE, WPBios.INSTANCE}); VM vm = new VM(2048 << 10, new TheRealWorld(), new VMProgramRom[]{TsvmBios.INSTANCE}); + VM pipvm = new VM(4096, new TheRealWorld(), new VMProgramRom[]{PipBios.INSTANCE, PipROM.INSTANCE}); + // uncomment to target the TerranBASIC runner // VM vm = new VM(64 << 10, new TheRealWorld(), new VMProgramRom[]{TBASRelBios.INSTANCE}); EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448); - EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", "assets/disk0", 560, 447); + EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", "assets/disk0", 560, 448); EmulInstance term = new EmulInstance(vm, "net.torvald.tsvm.peripheral.Term", "assets/disk0", 720, 480); EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CharacterLCDdisplay", "assets/disk0", 628, 302); EmulInstance wp = new EmulInstance(vm, "net.torvald.tsvm.peripheral.WpTerm", "assets/wpdisk", 810, 360); - new Lwjgl3Application(new VMGUI(reference, WIDTH, HEIGHT), appConfig); + EmulInstance pip = new EmulInstance(pipvm, null, "assets/disk0", 640, 480, CollectionsKt.listOf(new Pair(1, new PeripheralEntry2( + 32768L, + 1, + 0, + "net.torvald.tsvm.peripheral.ExtDisp", + pipvm, 160, 140 + )))); + + new Lwjgl3Application(new VMGUI(pip, WIDTH, HEIGHT), appConfig); } public static ShaderProgram loadShaderFromFile(String vert, String frag) { diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index 50ec95f..da6187c 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -59,7 +59,6 @@ class VM( fun init() { peripheralTable[0] = PeripheralEntry( - "io", IOSpace(this), HW_RESERVE_SIZE, MMIO_SIZE.toInt() - 256, @@ -196,12 +195,21 @@ class VM( internal data class VMNativePtr(val address: Int, val size: Int) } -data class PeripheralEntry( - val type: String = "null", +class PeripheralEntry( val peripheral: PeriBase? = null, val memsize: Long = 0, val mmioSize: Int = 0, - val interruptCount: Int = 0 // max: 4 + val interruptCount: Int = 0, // max: 4 +) { + val type = peripheral?.typestring +} + +class PeripheralEntry2( + val memsize: Long = 0, + val mmioSize: Int = 0, + val interruptCount: Int = 0, // max: 4 + val peripheralClassname: String, + vararg val args: Any ) fun Int.kB() = this * 1024L diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index 5603db5..5eab0b8 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -11,14 +11,28 @@ import java.io.File fun ByteArray.startsWith(other: ByteArray) = this.sliceArray(other.indices).contentEquals(other) - -data class EmulInstance( +class EmulInstance( val vm: VM, - val display: String, + val display: String?, val diskPath: String = "assets/disk0", val drawWidth: Int, - val drawHeight: Int -) + val drawHeight: Int, +) { + + var extraPeripherals: List> = listOf(); private set + + constructor( + vm: VM, + display: String?, + diskPath: String = "assets/disk0", + drawWidth: Int, + drawHeight: Int, + extraPeripherals: List> + ) : this(vm, display, diskPath, drawWidth, drawHeight) { + this.extraPeripherals = extraPeripherals + } + +} class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHeight: Int) : ApplicationAdapter() { @@ -27,7 +41,7 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe lateinit var batch: SpriteBatch lateinit var camera: OrthographicCamera - lateinit var gpu: GraphicsAdapter + var gpu: GraphicsAdapter? = null lateinit var vmRunner: VMRunner lateinit var coroutineJob: Job lateinit var memvwr: Memvwr @@ -57,27 +71,48 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe } private fun init() { - val loadedClass = Class.forName(loaderInfo.display) - val loadedClassConstructor = loadedClass.getConstructor(vm::class.java) - val loadedClassInstance = loadedClassConstructor.newInstance(vm) - gpu = (loadedClassInstance as GraphicsAdapter) + if (loaderInfo.display != null) { + val loadedClass = Class.forName(loaderInfo.display) + val loadedClassConstructor = loadedClass.getConstructor(vm::class.java) + val loadedClassInstance = loadedClassConstructor.newInstance(vm) + gpu = (loadedClassInstance as GraphicsAdapter) - vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath))) + vm.getIO().blockTransferPorts[0].attachDevice(TestDiskDrive(vm, 0, File(loaderInfo.diskPath))) - vm.peripheralTable[1] = PeripheralEntry( - VM.PERITYPE_GPU_AND_TERM, - gpu, - GraphicsAdapter.VRAM_SIZE, - 16, - 0 - ) + vm.peripheralTable[1] = PeripheralEntry( + gpu, + GraphicsAdapter.VRAM_SIZE, + 16, + 0 + ) + + vm.getPrintStream = { gpu!!.getPrintStream() } + vm.getErrorStream = { gpu!!.getErrorStream() } + vm.getInputStream = { gpu!!.getInputStream() } + } + else { + vm.getPrintStream = { System.out } + vm.getErrorStream = { System.err } + vm.getInputStream = { System.`in` } + } + + loaderInfo.extraPeripherals.forEach { (port, peri) -> + val typeargs = peri.args.map { it.javaClass }.toTypedArray() + + val loadedClass = Class.forName(peri.peripheralClassname) + val loadedClassConstructor = loadedClass.getConstructor(*typeargs) + val loadedClassInstance = loadedClassConstructor.newInstance(*peri.args) + + vm.peripheralTable[port] = PeripheralEntry( + loadedClassInstance as PeriBase, + peri.memsize, + peri.mmioSize, + peri.interruptCount + ) + } Gdx.input.inputProcessor = vm.getIO() - vm.getPrintStream = { gpu.getPrintStream() } - vm.getErrorStream = { gpu.getErrorStream() } - vm.getInputStream = { gpu.getInputStream() } - if (usememvwr) memvwr = Memvwr(vm) @@ -138,11 +173,21 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe fun poke(addr: Long, value: Byte) = vm.poke(addr, value) + private val defaultGuiBackgroundColour = Color(0x444444ff) + private fun renderGame(delta: Float) { - val clearCol = gpu.getBackgroundColour() + val clearCol = gpu?.getBackgroundColour() ?: defaultGuiBackgroundColour Gdx.gl.glClearColor(clearCol.r, clearCol.g, clearCol.b, clearCol.a) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) - gpu.render(delta, batch, (viewportWidth - loaderInfo.drawWidth).div(2).toFloat(), (viewportHeight - loaderInfo.drawHeight).div(2).toFloat()) + gpu?.render(delta, batch, (viewportWidth - loaderInfo.drawWidth).div(2).toFloat(), (viewportHeight - loaderInfo.drawHeight).div(2).toFloat()) + + vm.findPeribyType("oled")?.let { + val disp = it.peripheral as ExtDisp + + disp.render(batch, + (viewportWidth - loaderInfo.drawWidth).div(2).toFloat() + (gpu?.config?.width ?: 0), + (viewportHeight - loaderInfo.drawHeight).div(2).toFloat()) + } } private fun setCameraPosition(newX: Float, newY: Float) { diff --git a/src/net/torvald/tsvm/peripheral/ExtDisp.kt b/src/net/torvald/tsvm/peripheral/ExtDisp.kt new file mode 100644 index 0000000..995193f --- /dev/null +++ b/src/net/torvald/tsvm/peripheral/ExtDisp.kt @@ -0,0 +1,151 @@ +package net.torvald.tsvm.peripheral + +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 com.badlogic.gdx.math.Matrix4 +import net.torvald.tsvm.AppLoader +import net.torvald.tsvm.VM + +/** + * External Display that is always visible through its own UI ingame. + * + * Created by minjaesong on 2021-12-01. + */ +class ExtDisp(val vm: VM, val width: Int, val height: Int) : PeriBase { + + constructor(vm: VM, w: java.lang.Integer, h: java.lang.Integer) : this( + vm, w.toInt(), h.toInt() + ) + + override val typestring = "oled" + + override fun getVM(): VM { + return vm + } + + internal val framebuffer = Pixmap(width, height, Pixmap.Format.Alpha) + private val outFBObatch = SpriteBatch() + + protected val drawShader = AppLoader.loadShaderInline(GraphicsAdapter.DRAW_SHADER_VERT, OLED_PAL_SHADER) + + init { + // no orthographic camera, must be "raw" Matrix4 + val m = Matrix4() + m.setToOrtho2D(0f, 0f, width.toFloat(), height.toFloat()) + outFBObatch.projectionMatrix = m + + framebuffer.blending = Pixmap.Blending.None + framebuffer.setColor(0) + framebuffer.fill() + } + + private lateinit var tex: Texture + + open fun render(uiBatch: SpriteBatch, xoff: Float, yoff: Float) { + framebuffer.pixels.position(0) + + tex = Texture(framebuffer) + + uiBatch.inUse { + uiBatch.color = Color.WHITE + uiBatch.shader = drawShader + uiBatch.draw(tex, xoff, yoff) + } + + tex.dispose() + } + + /** + * Get the next power of two of the given number. + * + * E.g. for an input 100, this returns 128. + * Returns 1 for all numbers <= 1. + * + * @param number The number to obtain the POT for. + * @return The next power of two. + */ + private fun nextPowerOfTwo(number: Int): Int { + var number = number + number-- + number = number or (number shr 1) + number = number or (number shr 2) + number = number or (number shr 4) + number = number or (number shr 8) + number = number or (number shr 16) + number++ + number += if (number == 0) 1 else 0 + return number + } + + override fun peek(addr: Long): Byte? { + val adi = addr.toInt() + return when (addr) { + in 0 until width * height -> { + framebuffer.pixels.get(adi) + } + in 0 until nextPowerOfTwo(width * height) -> { null } + else -> peek(addr % nextPowerOfTwo(width * height)) + } + } + + override fun poke(addr: Long, byte: Byte) { + val adi = addr.toInt() + val bi = byte.toInt().and(255) + when (addr) { + in 0 until width * height -> { + framebuffer.pixels.put(adi, byte) + } + in 0 until nextPowerOfTwo(width * height) -> { /* do nothing */ } + else -> poke(addr % nextPowerOfTwo(width * height), byte) + } + } + + override fun mmio_read(addr: Long): Byte? { + TODO("Not yet implemented") + } + + override fun mmio_write(addr: Long, byte: Byte) { + TODO("Not yet implemented") + } + + override fun dispose() { + framebuffer.dispose() + try { tex.dispose() } catch (e: Throwable) {} + } + + + companion object { + val OLED_PAL_SHADER = """ +#version 130 + +varying vec4 v_color; +varying vec2 v_texCoords; +uniform sampler2D u_texture; +vec4 pal[16] = vec4[]( +vec4(0.0,0.0,0.0,1.0), +vec4(0.0,0.1765,0.6667,1.0), +vec4(0.0,0.6667,0.0,1.0), +vec4(0.0,0.7255,0.6667,1.0), +vec4(0.6667,0.0,0.0,1.0), +vec4(0.6667,0.1765,0.6667,1.0), +vec4(0.6667,0.6667,0.0,1.0), +vec4(0.6667,0.6667,0.6667,1.0), + +vec4(0.0,0.0,0.0,1.0), +vec4(0.0,0.2667,1.0,1.0), +vec4(0.0,1.0,0.0,1.0), +vec4(0.0,1.0,1.0,1.0), +vec4(1.0,0.0,0.0,1.0), +vec4(1.0,0.2667,1.0,1.0), +vec4(1.0,1.0,0.0,1.0), +vec4(1.0,1.0,1.0,1.0) +); + +void main(void) { + gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).a * 255.0) % 16]; +} + """ + } +} \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 626ddf4..0fe293a 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -49,6 +49,8 @@ class ReferenceLikeLCD(vm: VM) : GraphicsAdapter(vm, GraphicsAdapter.DEFAULT_CON open class GraphicsAdapter(val vm: VM, val config: AdapterConfig, val sgr: SuperGraphicsAddonConfig = SuperGraphicsAddonConfig()) : GlassTty(config.textRows, config.textCols), PeriBase { + override val typestring = VM.PERITYPE_GPU_AND_TERM + override fun getVM(): VM { return vm } diff --git a/src/net/torvald/tsvm/peripheral/IOSpace.kt b/src/net/torvald/tsvm/peripheral/IOSpace.kt index 9fbcb9d..6bc142c 100644 --- a/src/net/torvald/tsvm/peripheral/IOSpace.kt +++ b/src/net/torvald/tsvm/peripheral/IOSpace.kt @@ -11,6 +11,8 @@ import kotlin.experimental.and class IOSpace(val vm: VM) : PeriBase, InputProcessor { + override val typestring = "io" + override fun getVM(): VM { return vm } diff --git a/src/net/torvald/tsvm/peripheral/PeriBase.kt b/src/net/torvald/tsvm/peripheral/PeriBase.kt index 5cb030b..2efb023 100644 --- a/src/net/torvald/tsvm/peripheral/PeriBase.kt +++ b/src/net/torvald/tsvm/peripheral/PeriBase.kt @@ -20,4 +20,6 @@ interface PeriBase { fun dispose() fun getVM(): VM + + val typestring: String } \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/TTY.kt b/src/net/torvald/tsvm/peripheral/TTY.kt index 036fc9a..b8fdca9 100644 --- a/src/net/torvald/tsvm/peripheral/TTY.kt +++ b/src/net/torvald/tsvm/peripheral/TTY.kt @@ -8,6 +8,8 @@ import java.io.OutputStream class TTY(val vm: VM) : GlassTty(TEXT_ROWS, TEXT_COLS), PeriBase { + override val typestring = VM.PERITYPE_GPU_AND_TERM + companion object { const val TEXT_ROWS = 25 const val TEXT_COLS = 80 diff --git a/src/net/torvald/tsvm/peripheral/VMProgramRom.kt b/src/net/torvald/tsvm/peripheral/VMProgramRom.kt index 0ebee81..556020f 100644 --- a/src/net/torvald/tsvm/peripheral/VMProgramRom.kt +++ b/src/net/torvald/tsvm/peripheral/VMProgramRom.kt @@ -33,4 +33,7 @@ object TandemBios : VMProgramRom("./assets/bios/tandemport.js") object TsvmBios : VMProgramRom("./assets/bios/tsvmbios.js") object BasicRom : VMProgramRom("./assets/bios/basic.bin") object TBASRelBios : VMProgramRom("./assets/bios/tbasdist.js") -object WPBios : VMProgramRom("./assets/bios/wp.js") \ No newline at end of file +object WPBios : VMProgramRom("./assets/bios/wp.js") + +object PipBios : VMProgramRom("./assets/bios/pipboot.js") +object PipROM : VMProgramRom("./assets/bios/pipcode.bas") \ No newline at end of file diff --git a/src/net/torvald/tsvm/vdc/V2kRunTest.kt b/src/net/torvald/tsvm/vdc/V2kRunTest.kt index 3e9e39f..572c101 100644 --- a/src/net/torvald/tsvm/vdc/V2kRunTest.kt +++ b/src/net/torvald/tsvm/vdc/V2kRunTest.kt @@ -32,7 +32,6 @@ class V2kRunTest : ApplicationAdapter() { gpu = GraphicsAdapter(vm, GraphicsAdapter.DEFAULT_CONFIG_COLOR_CRT) vm.peripheralTable[1] = PeripheralEntry( - VM.PERITYPE_GPU_AND_TERM, gpu, GraphicsAdapter.VRAM_SIZE, 16,