video delta encoding wip

This commit is contained in:
minjaesong
2025-04-15 21:38:33 +09:00
parent 9ce7e3dfd2
commit 4c4f24be37
11 changed files with 466 additions and 29 deletions

View File

@@ -1,9 +1,9 @@
if (exec_args[1] === undefined) {
println("Usage: compile myfile")
println("Usage: compile myfile.js")
println("The compiled file will be myfile.bin")
return 1
}
_G.shell.execute(`gzip -c ${exec_args[1]} | writeto ${exec_args[1]}.gz`)
_G.shell.execute(`enc ${exec_args[1]}.gz ${exec_args[1]}.bin`)
_G.shell.execute(`rm ${exec_args[1]}.gz`)
const filenameWithoutExt = exec_args[1].substringBeforeLast(".")
_G.shell.execute(`gzip -c ${exec_args[1]} | writeto ${filenameWithoutExt}.gz`)
_G.shell.execute(`enc ${filenameWithoutExt}.gz ${filenameWithoutExt}.bin`)
_G.shell.execute(`rm ${filenameWithoutExt}.gz`)

View File

@@ -3,7 +3,6 @@ if (exec_args[1] === undefined) {
println("The compiled file will be myfile.bin.js")
return 1
}
_G.shell.execute(`enc ${exec_args[1]} ${exec_args[1]}.gz`)
_G.shell.execute(`gzip -c -d ${exec_args[1]}.gz | writeto ${exec_args[1]}.js`)
_G.shell.execute(`rm ${exec_args[1]}.gz`)

View File

@@ -8,7 +8,10 @@ if (exec_args[1] === undefined || exec_args[2] === undefined) {
let infilePath = _G.shell.resolvePathInput(exec_args[2]).full
let infile = files.open(infilePath)
let outfile = files.open(infilePath + ".out")
if (!infile.exists) throw Error("No such file: " + infilePath)
let outfile = files.open(infilePath.substringBeforeLast(".") + ".out")
let outMode = exec_args[1].toLowerCase()
let type = {

View File

@@ -7,11 +7,11 @@ let PATHFUN = (i) => `/ddol/${(''+i).padStart(5,'0')}.png`
const FBUF_SIZE = WIDTH * HEIGHT
let infile = sys.malloc(512000) // somewhat arbitrary
let imagearea = sys.malloc(FBUF_SIZE*3)
let decodearea = sys.malloc(FBUF_SIZE)
let ipfarea = sys.malloc(FBUF_SIZE)
let gzippedImage = sys.malloc(512000) // somewhat arbitrary
let infile = sys.malloc(512000) // allocate somewhat arbitrary amount of memory
let imagearea = sys.malloc(FBUF_SIZE*3) // allocate exact amount of memory
let decodearea = sys.malloc(FBUF_SIZE) // allocate exact amount of memory
let ipfarea = sys.malloc(FBUF_SIZE) // allocate exact amount of memory
let gzippedImage = sys.malloc(512000) // allocate somewhat arbitrary amount of memory
let outfilename = exec_args[1]
@@ -69,6 +69,7 @@ for (let f = 1; f <= TOTAL_FRAMES; f++) {
print(` ${gzlen} bytes\n`)
}
// free all the memory that has been allocated
sys.free(infile)
sys.free(imagearea)
sys.free(decodearea)

View File

@@ -0,0 +1,101 @@
// some manual config shits
let TOTAL_FRAMES = 3813
let FPS = 30
let WIDTH = 560
let HEIGHT = 448
let PATHFUN = (i) => `/ddol/${(''+i).padStart(5,'0')}.png`
if (WIDTH % 4 != 0 || HEIGHT % 4 != 0) {
printerrln(`Frame dimension is not multiple of 4 (${WIDTH}x${HEIGHT})`)
return 5
}
const FBUF_SIZE = WIDTH * HEIGHT
let infile = sys.malloc(512000) // somewhat arbitrary
let imagearea = sys.malloc(FBUF_SIZE*3)
let decodearea = sys.malloc(FBUF_SIZE)
let ipfarea1 = sys.malloc(FBUF_SIZE)
let ipfarea2 = sys.malloc(FBUF_SIZE)
let ipfDelta = sys.malloc(FBUF_SIZE)
let gzippedImage = sys.malloc(512000) // somewhat arbitrary
let outfilename = exec_args[1]
if (!outfilename) return 1
function appendToOutfile(bytes) {
filesystem.open("A", outfilename, "A")
filesystem.writeBytes("A", bytes)
}
function appendToOutfilePtr(ptr, len) {
filesystem.open("A", outfilename, "A")
dma.ramToCom(ptr, 0, len)
}
// write header to the file
let headerBytes = [
0x1F, 0x54, 0x53, 0x56, 0x4D, 0x4D, 0x4F, 0x56, // magic
WIDTH & 255, (WIDTH >> 8) & 255, // width
HEIGHT & 255, (HEIGHT >> 8) & 255, // height
FPS & 255, (FPS >> 8) & 255, // FPS
TOTAL_FRAMES & 255, (TOTAL_FRAMES >> 8) & 255, (TOTAL_FRAMES >> 16) & 255, (TOTAL_FRAMES >> 24) & 255, // frame count
0x04, 0x00, // type 4 frames (force no-alpha)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // reserved
]
filesystem.open("A", outfilename, "W")
filesystem.writeBytes("A", headerBytes)
let ipfAreaOld = ipfarea2
let ipfAreaNew = ipfarea1
for (let f = 1; f <= TOTAL_FRAMES; f++) {
let fname = PATHFUN(f)
filesystem.open("A", fname, "R")
let fileLen = filesystem.getFileLen("A")
dma.comToRam(0, 0, infile, fileLen)
let [_1, _2, channels, _3] = graphics.decodeImageTo(infile, fileLen, imagearea)
const val IPF_BLOCK_SIZE = (channels == 3) ? 12 : 20;
print(`Frame ${f}/${TOTAL_FRAMES} (Ch: ${channels}) ->`)
// graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1)
graphics.encodeIpf1(imagearea, ipfAreaNew, WIDTH, HEIGHT, channels, false, 0)
// get the difference map
let patchEncodedSize = graphics.encodeIpf1d(ipfAreaOld, ipfAreaNew, ipfDelta, WIDTH, HEIGHT, 0.90)
// decide whether or not the patch encoding should be used
let gzlen = gzip.compFromTo(
(patchEncodedSize) ? ipfDelta : ipfAreaNew,
patchEncodedSize || FBUF_SIZE,
gzippedImage
)
let frameSize = [
(gzlen >>> 0) & 255,
(gzlen >>> 8) & 255,
(gzlen >>> 16) & 255,
(gzlen >>> 24) & 255
]
appendToOutfile(frameSize)
appendToOutfilePtr(gzippedImage, gzlen)
print(` ${gzlen} bytes\n`)
// swap two pointers
let t = ipfAreaOld
ipfAreaOld = ipfAreaNew
ipfAreaNew = t
}
sys.free(infile)
sys.free(imagearea)
sys.free(decodearea)
sys.free(ipfarea1)
sys.free(ipfarea2)
sys.free(ipfDelta)
sys.free(gzippedImage)

View File

@@ -601,6 +601,36 @@ iPF2:
which packs into: [ 30 | 30 | FA | FA ] (because little endian)
iPF1-delta (for video encoding):
Delta encoded frames contain "insutructions" for delta-encoding the existing frame.
Or, a collection of [OPCODE | PAYLOAD] pairs
Opcode:
0x00 : Skip N blocks
payload: (varint) number of 4x4 blocks
0x10 : Patch
payload: (12 bytes) encoded delta block
0x20 : Repeat
payload: (varint) repeat last delta N times
0xF0 : End of delta stream
payload: none
Sample stream:
[SKIP 10] [PATCH A] [REPEAT 3] [SKIP 5] [PATCH B] [END]
Delta block format:
Each PATCH delta payload is still:
8 bytes of Luma (4-bit deltas for 16 pixels)
2 bytes of Co deltas (4× 4-bit deltas)
2 bytes of Cg deltas (4× 4-bit deltas)
Total: 12 bytes per PATCH.
These are always relative to the same-position block in the previous frame.
- Progressive Blocks
Ordered string of words (word size varies by the colour mode) are stored here.
If progressive mode is enabled, words are stored in the order that accomodates it.

View File

@@ -704,6 +704,113 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}}
}
/**
* @return non-zero if delta-encoded, 0 if delta encoding is worthless
*/
fun encodeIpf1d(
prevIPFptr: Int, // full iPF picture frame for t minus one
newIPFptr: Int, // full iPF picture frame for t equals zero
currentFrame: Int, // where to write delta-encoded payloads to. Not touched if delta-encoding is worthless
width: Int, height: Int,
inefficiencyThreshold: Double = 0.90
): Int {
val frameSize = width * height
val BLOCK_SIZE = 12
val totalBlocks = frameSize / BLOCK_SIZE
var skipCount = 0
var repeatCount = 0
val temp = ByteArray(frameSize * 2) // Overallocate
var tempPtr = 0
var lastDelta = ByteArray(BLOCK_SIZE)
fun readBlock(ptr: Int): ByteArray {
return ByteArray(BLOCK_SIZE) { i -> vm.peek(ptr.toLong() + i)!!.toByte() }
}
// MSB is a "continuation flag"; varint decoding terminates when it sees byte with no MSB set
fun writeVarInt(buf: ByteArray, start: Int, value: Int): Int {
var v = value
var i = 0
while (v >= 0x80) {
buf[start + i] = ((v and 0x7F) or 0x80).toByte()
v = v ushr 7
i++
}
buf[start + i] = v.toByte()
return i + 1
}
fun flushSkips() {
if (skipCount > 0) {
temp[tempPtr++] = 0x00
tempPtr += writeVarInt(temp, tempPtr, skipCount)
skipCount = 0
}
}
fun flushRepeats() {
if (repeatCount > 0) {
temp[tempPtr++] = 0x20
tempPtr += writeVarInt(temp, tempPtr, repeatCount)
repeatCount = 0
}
}
for (blockIndex in 0 until totalBlocks) {
val offset = blockIndex * BLOCK_SIZE
val prevBlock = readBlock(prevIPFptr + offset)
val currBlock = readBlock(newIPFptr + offset)
val diff = isSignificantlyDifferent(prevBlock, currBlock)
if (!diff) {
if (repeatCount > 0) flushRepeats()
skipCount++
} else if (lastDelta.contentEquals(currBlock)) {
flushSkips()
repeatCount++
} else {
flushSkips()
flushRepeats()
temp[tempPtr++] = 0x10
currBlock.copyInto(temp, tempPtr)
tempPtr += BLOCK_SIZE
lastDelta = currBlock
}
}
flushSkips()
flushRepeats()
temp[tempPtr++] = 0xF0.toByte()
if (tempPtr >= (frameSize * inefficiencyThreshold).toInt()) {
return 0 // delta is inefficient, do not write
}
// Write delta to memory
if (currentFrame >= 0) {
UnsafeHelper.memcpyRaw(temp, UnsafeHelper.getArrayOffset(temp), null, vm.usermem.ptr + currentFrame, tempPtr.toLong())
}
else {
for (i in 0 until tempPtr) {
vm.poke(currentFrame.toLong() + i, temp[i])
}
}
return tempPtr
}
private fun isSignificantlyDifferent(a: ByteArray, b: ByteArray, threshold: Int = 5): Boolean {
var total = 0
for (i in a.indices) {
total += kotlin.math.abs((a[i].toInt() and 0xFF) - (b[i].toInt() and 0xFF))
}
return total > threshold
}
fun encodeIpf2(srcPtr: Int, destPtr: Int, width: Int, height: Int, channels: Int, hasAlpha: Boolean, pattern: Int) {
var writeCount = 0L
@@ -868,7 +975,6 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
fun decodeIpf1(srcPtr: Int, destRG: Int, destBA: Int, width: Int, height: Int, hasAlpha: Boolean) {
val gpu = getFirstGPU()
val sign = if (destRG >= 0) 1 else -1
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
val sptr = srcPtr.toLong()
@@ -876,9 +982,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
val dptr2 = destBA.toLong()
var readCount = 0
fun readShort() =
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8).also {
gpu?.applyDelay()
}
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
for (blockY in 0 until ceil(height / 4f)) {
@@ -938,8 +1042,110 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}}
}
fun applyIpf1d(ipf1DeltaPtr: Int, destRG: Int, destBA: Int, width: Int, height: Int) {
val blocksPerRow = (width + 3) / 4
val totalBlocks = ((width + 3) / 4) * ((height + 3) / 4)
val sign = if (destRG >= 0) 1 else -1
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain")
var ptr = ipf1DeltaPtr.toLong()
var blockIndex = 0
fun readByte(): Int = (vm.peek(ptr++)!!.toInt() and 0xFF)
fun readShort(): Int {
val low = readByte()
val high = readByte()
return low or (high shl 8)
}
fun readVarInt(): Int {
var value = 0
var shift = 0
while (true) {
val byte = readByte()
value = value or ((byte and 0x7F) shl shift)
if ((byte and 0x80) == 0) break
shift += 7
}
return value
}
while (true) {
val opcode = readByte()
when (opcode) {
0x00 -> { // Skip blocks
val count = readVarInt()
blockIndex += count
}
0x10 -> { // Write literal patch
if (blockIndex >= totalBlocks) break
val co = readShort()
val cg = readShort()
val y1 = readShort()
val y2 = readShort()
val y3 = readShort()
val y4 = readShort()
val rg = IntArray(16)
val ba = IntArray(16)
var px = ycocgToRGB(co and 15, cg and 15, y1, 65535)
rg[0] = px[0]; ba[0] = px[1]
rg[1] = px[2]; ba[1] = px[3]
rg[4] = px[4]; ba[4] = px[5]
rg[5] = px[6]; ba[5] = px[7]
px = ycocgToRGB((co shr 4) and 15, (cg shr 4) and 15, y2, 65535)
rg[2] = px[0]; ba[2] = px[1]
rg[3] = px[2]; ba[3] = px[3]
rg[6] = px[4]; ba[6] = px[5]
rg[7] = px[6]; ba[7] = px[7]
px = ycocgToRGB((co shr 8) and 15, (cg shr 8) and 15, y3, 65535)
rg[8] = px[0]; ba[8] = px[1]
rg[9] = px[2]; ba[9] = px[3]
rg[12] = px[4]; ba[12] = px[5]
rg[13] = px[6]; ba[13] = px[7]
px = ycocgToRGB((co shr 12) and 15, (cg shr 12) and 15, y4, 65535)
rg[10] = px[0]; ba[10] = px[1]
rg[11] = px[2]; ba[11] = px[3]
rg[14] = px[4]; ba[14] = px[5]
rg[15] = px[6]; ba[15] = px[7]
val blockX = blockIndex % blocksPerRow
val blockY = blockIndex / blocksPerRow
for (py in 0..3) {
for (pxi in 0..3) {
val ox = blockX * 4 + pxi
val oy = blockY * 4 + py
if (ox < width && oy < height) {
val offset = oy * 560 + ox
val i = py * 4 + pxi
vm.poke((destRG + offset * sign).toLong(), rg[i].toByte())
vm.poke((destBA + offset * sign).toLong(), ba[i].toByte())
}
}
}
blockIndex++
}
0x20 -> { // Repeat last literal
val repeatCount = readVarInt()
// Just skip applying. We assume previous patch was already applied visually.
blockIndex += repeatCount
}
0xF0 -> return // End of stream
else -> error("Unknown delta opcode: ${opcode.toString(16)}")
}
}
}
fun decodeIpf2(srcPtr: Int, destRG: Int, destBA: Int, width: Int, height: Int, hasAlpha: Boolean) {
val gpu = getFirstGPU()
val sign = if (destRG >= 0) 1 else -1
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
val sptr = srcPtr.toLong()
@@ -947,14 +1153,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
val dptr2 = destBA.toLong()
var readCount = 0
fun readShort() =
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8).also {
gpu?.applyDelay()
}
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
fun readInt() =
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8) or vm.peek(sptr + readCount++)!!.toUint().shl(16) or vm.peek(sptr + readCount++)!!.toUint().shl(24).also {
gpu?.applyDelay()
gpu?.applyDelay()
}
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8) or vm.peek(sptr + readCount++)!!.toUint().shl(16) or vm.peek(sptr + readCount++)!!.toUint().shl(24)
for (blockY in 0 until ceil(height / 4f)) {

View File

@@ -346,6 +346,16 @@ String.prototype.tail = function() {
String.prototype.init = function() {
return this.substring(0, this.length - 1)
}
String.prototype.substringBeforeLast = function(delimiter) {
if (!this || this.length === 0) return ""
if (!delimiter || delimiter.length === 0) return ""
const lastIndex = this.lastIndexOf(delimiter)
if (lastIndex === -1) return this
return this.substring(0, lastIndex)
}
Array.prototype.shuffle = function() {
let counter = this.length;

View File

@@ -3,9 +3,7 @@ package net.torvald.tsvm
import net.torvald.UnsafeHelper
import net.torvald.UnsafePtr
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toHex
import net.torvald.tsvm.peripheral.IOSpace
import net.torvald.tsvm.peripheral.PeriBase
import net.torvald.tsvm.peripheral.VMProgramRom
import net.torvald.tsvm.peripheral.*
import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.Charset
@@ -275,6 +273,98 @@ class VM(
mallocSizes[blockStart] = allocBlocks
}
fun memcpy(from: Int, to: Int, len: Int) {
val from = from.toLong()
val to = to.toLong()
val len = len.toLong()
val fromVector = if (from >= 0) 1 else -1
val toVector = if (to >= 0) 1 else -1
val fromDev = getDev(from, len, false)
val toDev = getDev(to, len, true)
// println("from = $from, to = $to")
// println("fromDev = $fromDev, toDev = $toDev")
if (fromDev != null && toDev != null)
UnsafeHelper.memcpy(fromDev, toDev, len)
else if (fromDev == null && toDev != null) {
val buf = UnsafeHelper.allocate(len, this)
for (i in 0 until len) buf[i] = peek(from + i*fromVector)!!
UnsafeHelper.memcpy(buf.ptr, toDev, len)
buf.destroy()
}
else if (fromDev != null) {
for (i in 0 until len) poke(to + i*toVector, UnsafeHelper.unsafe.getByte(fromDev + i))
}
else {
for (i in 0 until len) poke(to + i*toVector, peek(from + i*fromVector)!!)
}
}
private fun relPtrInDev(from: Long, len: Long, start: Int, end: Int) =
(from in start..end && (from + len) in start..end)
private fun getDev(from: Long, len: Long, isDest: Boolean): Long? {
return if (from >= 0) usermem.ptr + from
// MMIO area
else if (from in -1048576..-1 && (from - len) in -1048577..-1) {
val fromIndex = (-from-1) / 131072
val dev = peripheralTable[fromIndex.toInt()].peripheral ?: return null
val fromRel = (-from-1) % 131072
if (fromRel + len > 131072) return null
return if (dev is IOSpace) {
if (relPtrInDev(fromRel, len, 1024, 2047)) dev.peripheralFast.ptr + fromRel - 1024
else if (relPtrInDev(fromRel, len, 4096, 8191)) (if (isDest) dev.blockTransferTx[0] else dev.blockTransferRx[0]).ptr + fromRel - 4096
else if (relPtrInDev(fromRel, len, 8192, 12287)) (if (isDest) dev.blockTransferTx[1] else dev.blockTransferRx[1]).ptr + fromRel - 8192
else if (relPtrInDev(fromRel, len, 12288, 16383)) (if (isDest) dev.blockTransferTx[2] else dev.blockTransferRx[2]).ptr + fromRel - 12288
else if (relPtrInDev(fromRel, len, 16384, 20479)) (if (isDest) dev.blockTransferTx[3] else dev.blockTransferRx[3]).ptr + fromRel - 16384
else null
}
else if (dev is AudioAdapter) {
if (relPtrInDev(fromRel, len, 64, 2367)) dev.mediaDecodedBin.ptr + fromRel - 64
else if (relPtrInDev(fromRel, len, 2368, 4096)) dev.mediaFrameBin.ptr + fromRel - 2368
else null
}
else if (dev is GraphicsAdapter) {
if (relPtrInDev(fromRel, len, 1024, 2047)) dev.scanlineOffsets.ptr + fromRel - 1024
else if (relPtrInDev(fromRel, len, 2048, 4095)) dev.mappedFontRom.ptr + fromRel - 2048
else if (relPtrInDev(fromRel, len, 65536, 131071)) dev.instArea.ptr + fromRel - 65536
else null
}
else null
}
// memory area
else {
val fromIndex = (-from-1) / 1048576
val dev = peripheralTable[fromIndex.toInt()].peripheral ?: return null
val fromRel = (-from-1) % 1048576
if (fromRel + len > 1048576) return null
return if (dev is AudioAdapter) {
if (relPtrInDev(fromRel, len, 0, 114687)) dev.sampleBin.ptr + fromRel - 0
else null
}
else if (dev is GraphicsAdapter) {
if (relPtrInDev(fromRel, len, 0, 250879)) dev.framebuffer.ptr + fromRel - 0
else if (relPtrInDev(fromRel, len, 250880, 251903)) dev.unusedArea.ptr + fromRel - 250880
else if (relPtrInDev(fromRel, len, 253950, 261631)) dev.textArea.ptr + fromRel - 253950
else if (relPtrInDev(fromRel, len, 262144, 513023)) dev.framebuffer2?.ptr?.plus(fromRel)?.minus(253950)
else null
}
else if (dev is RamBank) {
if (relPtrInDev(fromRel, len, 0, 524287))
dev.mem.ptr + 524288*dev.map0 + fromRel
else if (relPtrInDev(fromRel, len, 524288, 131071))
dev.mem.ptr + 524288*dev.map1 + fromRel - 524288
else
null
}
else null
}
}
internal data class VMNativePtr(val address: Int, val size: Int)
}

View File

@@ -42,7 +42,7 @@ data class AdapterConfig(
val paletteShader: String = DRAW_SHADER_FRAG,
val drawScale: Float = 1f,
val scaleFiltered: Boolean = false,
val baudRate: Double = 16_384_000.0,//57600.0,
val baudRate: Double = 115200.0,//16_384_000.0,//57600.0,
val bitsPerChar: Int = 10 // start bit + 8 data bits + stop bit
)
@@ -260,6 +260,7 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
protected var slpcnt = 0L
open fun applyDelay() {
applyDelay0()
}
protected fun applyDelay0() {

View File

@@ -56,7 +56,8 @@ public class AppLoader {
defaultPeripherals.add(new Pair(3, new PeripheralEntry2("net.torvald.tsvm.peripheral.AudioAdapter", vm)));
EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter2", diskPath, 560, 448, defaultPeripherals);
EmulInstance reference = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", diskPath, 560, 448, defaultPeripherals);
EmulInstance referenceRemote = new EmulInstance(vm, "net.torvald.tsvm.peripheral.RemoteGraphicsAdapter", diskPath, 560, 448, defaultPeripherals);
EmulInstance reference2 = new EmulInstance(vm, "net.torvald.tsvm.peripheral.ReferenceLikeLCD", diskPath, 560, 448, defaultPeripherals);
EmulInstance term = new EmulInstance(vm, "net.torvald.tsvm.peripheral.Term", diskPath, 720, 480);
EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CLCDDisplay", diskPath, 1080, 436);