mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-09 14:44:05 +09:00
video delta encoding wip
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
if (exec_args[1] === undefined) {
|
if (exec_args[1] === undefined) {
|
||||||
println("Usage: compile myfile")
|
println("Usage: compile myfile.js")
|
||||||
println("The compiled file will be myfile.bin")
|
println("The compiled file will be myfile.bin")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
const filenameWithoutExt = exec_args[1].substringBeforeLast(".")
|
||||||
_G.shell.execute(`gzip -c ${exec_args[1]} | writeto ${exec_args[1]}.gz`)
|
_G.shell.execute(`gzip -c ${exec_args[1]} | writeto ${filenameWithoutExt}.gz`)
|
||||||
_G.shell.execute(`enc ${exec_args[1]}.gz ${exec_args[1]}.bin`)
|
_G.shell.execute(`enc ${filenameWithoutExt}.gz ${filenameWithoutExt}.bin`)
|
||||||
_G.shell.execute(`rm ${exec_args[1]}.gz`)
|
_G.shell.execute(`rm ${filenameWithoutExt}.gz`)
|
||||||
@@ -3,7 +3,6 @@ if (exec_args[1] === undefined) {
|
|||||||
println("The compiled file will be myfile.bin.js")
|
println("The compiled file will be myfile.bin.js")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
_G.shell.execute(`enc ${exec_args[1]} ${exec_args[1]}.gz`)
|
_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(`gzip -c -d ${exec_args[1]}.gz | writeto ${exec_args[1]}.js`)
|
||||||
_G.shell.execute(`rm ${exec_args[1]}.gz`)
|
_G.shell.execute(`rm ${exec_args[1]}.gz`)
|
||||||
@@ -8,7 +8,10 @@ if (exec_args[1] === undefined || exec_args[2] === undefined) {
|
|||||||
|
|
||||||
let infilePath = _G.shell.resolvePathInput(exec_args[2]).full
|
let infilePath = _G.shell.resolvePathInput(exec_args[2]).full
|
||||||
let infile = files.open(infilePath)
|
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 outMode = exec_args[1].toLowerCase()
|
||||||
|
|
||||||
let type = {
|
let type = {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ let PATHFUN = (i) => `/ddol/${(''+i).padStart(5,'0')}.png`
|
|||||||
|
|
||||||
|
|
||||||
const FBUF_SIZE = WIDTH * HEIGHT
|
const FBUF_SIZE = WIDTH * HEIGHT
|
||||||
let infile = sys.malloc(512000) // somewhat arbitrary
|
let infile = sys.malloc(512000) // allocate somewhat arbitrary amount of memory
|
||||||
let imagearea = sys.malloc(FBUF_SIZE*3)
|
let imagearea = sys.malloc(FBUF_SIZE*3) // allocate exact amount of memory
|
||||||
let decodearea = sys.malloc(FBUF_SIZE)
|
let decodearea = sys.malloc(FBUF_SIZE) // allocate exact amount of memory
|
||||||
let ipfarea = sys.malloc(FBUF_SIZE)
|
let ipfarea = sys.malloc(FBUF_SIZE) // allocate exact amount of memory
|
||||||
let gzippedImage = sys.malloc(512000) // somewhat arbitrary
|
let gzippedImage = sys.malloc(512000) // allocate somewhat arbitrary amount of memory
|
||||||
|
|
||||||
let outfilename = exec_args[1]
|
let outfilename = exec_args[1]
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ for (let f = 1; f <= TOTAL_FRAMES; f++) {
|
|||||||
print(` ${gzlen} bytes\n`)
|
print(` ${gzlen} bytes\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// free all the memory that has been allocated
|
||||||
sys.free(infile)
|
sys.free(infile)
|
||||||
sys.free(imagearea)
|
sys.free(imagearea)
|
||||||
sys.free(decodearea)
|
sys.free(decodearea)
|
||||||
|
|||||||
101
assets/disk0/tvdos/moviedev/encodemovipf_delta.js
Normal file
101
assets/disk0/tvdos/moviedev/encodemovipf_delta.js
Normal 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)
|
||||||
@@ -601,6 +601,36 @@ iPF2:
|
|||||||
|
|
||||||
which packs into: [ 30 | 30 | FA | FA ] (because little endian)
|
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
|
- Progressive Blocks
|
||||||
Ordered string of words (word size varies by the colour mode) are stored here.
|
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.
|
If progressive mode is enabled, words are stored in the order that accomodates it.
|
||||||
|
|||||||
@@ -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) {
|
fun encodeIpf2(srcPtr: Int, destPtr: Int, width: Int, height: Int, channels: Int, hasAlpha: Boolean, pattern: Int) {
|
||||||
var writeCount = 0L
|
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) {
|
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
|
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)")
|
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
|
||||||
val sptr = srcPtr.toLong()
|
val sptr = srcPtr.toLong()
|
||||||
@@ -876,9 +982,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
val dptr2 = destBA.toLong()
|
val dptr2 = destBA.toLong()
|
||||||
var readCount = 0
|
var readCount = 0
|
||||||
fun readShort() =
|
fun readShort() =
|
||||||
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8).also {
|
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
|
||||||
gpu?.applyDelay()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (blockY in 0 until ceil(height / 4f)) {
|
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) {
|
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
|
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)")
|
if (destRG * destBA < 0) throw IllegalArgumentException("Both destination memories must be on the same domain (both being Usermem or HWmem)")
|
||||||
val sptr = srcPtr.toLong()
|
val sptr = srcPtr.toLong()
|
||||||
@@ -947,14 +1153,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
val dptr2 = destBA.toLong()
|
val dptr2 = destBA.toLong()
|
||||||
var readCount = 0
|
var readCount = 0
|
||||||
fun readShort() =
|
fun readShort() =
|
||||||
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8).also {
|
vm.peek(sptr + readCount++)!!.toUint() or vm.peek(sptr + readCount++)!!.toUint().shl(8)
|
||||||
gpu?.applyDelay()
|
|
||||||
}
|
|
||||||
fun readInt() =
|
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 {
|
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)
|
||||||
gpu?.applyDelay()
|
|
||||||
gpu?.applyDelay()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (blockY in 0 until ceil(height / 4f)) {
|
for (blockY in 0 until ceil(height / 4f)) {
|
||||||
|
|||||||
@@ -346,6 +346,16 @@ String.prototype.tail = function() {
|
|||||||
String.prototype.init = function() {
|
String.prototype.init = function() {
|
||||||
return this.substring(0, this.length - 1)
|
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() {
|
Array.prototype.shuffle = function() {
|
||||||
let counter = this.length;
|
let counter = this.length;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package net.torvald.tsvm
|
|||||||
import net.torvald.UnsafeHelper
|
import net.torvald.UnsafeHelper
|
||||||
import net.torvald.UnsafePtr
|
import net.torvald.UnsafePtr
|
||||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toHex
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toHex
|
||||||
import net.torvald.tsvm.peripheral.IOSpace
|
import net.torvald.tsvm.peripheral.*
|
||||||
import net.torvald.tsvm.peripheral.PeriBase
|
|
||||||
import net.torvald.tsvm.peripheral.VMProgramRom
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
@@ -275,6 +273,98 @@ class VM(
|
|||||||
mallocSizes[blockStart] = allocBlocks
|
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)
|
internal data class VMNativePtr(val address: Int, val size: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ data class AdapterConfig(
|
|||||||
val paletteShader: String = DRAW_SHADER_FRAG,
|
val paletteShader: String = DRAW_SHADER_FRAG,
|
||||||
val drawScale: Float = 1f,
|
val drawScale: Float = 1f,
|
||||||
val scaleFiltered: Boolean = false,
|
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
|
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
|
protected var slpcnt = 0L
|
||||||
|
|
||||||
open fun applyDelay() {
|
open fun applyDelay() {
|
||||||
|
applyDelay0()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun applyDelay0() {
|
protected fun applyDelay0() {
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ public class AppLoader {
|
|||||||
defaultPeripherals.add(new Pair(3, new PeripheralEntry2("net.torvald.tsvm.peripheral.AudioAdapter", vm)));
|
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 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 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);
|
EmulInstance portable = new EmulInstance(vm, "net.torvald.tsvm.peripheral.CLCDDisplay", diskPath, 1080, 436);
|
||||||
|
|||||||
Reference in New Issue
Block a user