mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
vm update
This commit is contained in:
@@ -12,6 +12,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
- TerranBASIC integration
|
- TerranBASIC integration
|
||||||
- Multiple platform build system
|
- Multiple platform build system
|
||||||
|
|
||||||
|
## Documentations
|
||||||
|
|
||||||
|
Documentation for TSVM and TVDOS are available on `./doc/*.tex` as machine-readable format.
|
||||||
|
|
||||||
|
Documentatino for TSVM architecture is available on `terranmon.txt`
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Core Components
|
### Core Components
|
||||||
|
|||||||
@@ -368,6 +368,8 @@ let header = {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fps: 0,
|
fps: 0,
|
||||||
|
fps_num: 0, // Fractional FPS numerator (from XFPS or derived from fps)
|
||||||
|
fps_den: 1, // Fractional FPS denominator (from XFPS, default 1)
|
||||||
totalFrames: 0,
|
totalFrames: 0,
|
||||||
waveletFilter: 0, // TAV-specific: wavelet filter type
|
waveletFilter: 0, // TAV-specific: wavelet filter type
|
||||||
decompLevels: 0, // TAV-specific: decomposition levels
|
decompLevels: 0, // TAV-specific: decomposition levels
|
||||||
@@ -382,6 +384,22 @@ let header = {
|
|||||||
fileRole: 0
|
fileRole: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to parse XFPS string ("num/den" format) and update header
|
||||||
|
function parseXFPS(xfpsStr) {
|
||||||
|
let parts = xfpsStr.split("/")
|
||||||
|
if (parts.length === 2) {
|
||||||
|
let num = parseInt(parts[0], 10)
|
||||||
|
let den = parseInt(parts[1], 10)
|
||||||
|
if (!isNaN(num) && !isNaN(den) && den > 0) {
|
||||||
|
header.fps_num = num
|
||||||
|
header.fps_den = den
|
||||||
|
header.fps = num / den
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Read and validate header
|
// Read and validate header
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
header.magic[i] = seqread.readOneByte()
|
header.magic[i] = seqread.readOneByte()
|
||||||
@@ -409,6 +427,9 @@ header.version = seqread.readOneByte()
|
|||||||
header.width = seqread.readShort()
|
header.width = seqread.readShort()
|
||||||
header.height = seqread.readShort()
|
header.height = seqread.readShort()
|
||||||
header.fps = seqread.readOneByte()
|
header.fps = seqread.readOneByte()
|
||||||
|
// Set default fractional fps (will be overridden by XFPS if present)
|
||||||
|
header.fps_num = header.fps
|
||||||
|
header.fps_den = 1
|
||||||
header.totalFrames = seqread.readInt()
|
header.totalFrames = seqread.readInt()
|
||||||
header.waveletFilter = seqread.readOneByte()
|
header.waveletFilter = seqread.readOneByte()
|
||||||
header.decompLevels = seqread.readOneByte()
|
header.decompLevels = seqread.readOneByte()
|
||||||
@@ -461,7 +482,7 @@ const isLossless = (header.videoFlags & 0x04) !== 0
|
|||||||
|
|
||||||
console.log(`TAV Decoder`)
|
console.log(`TAV Decoder`)
|
||||||
console.log(`Resolution: ${header.width}x${header.height}`)
|
console.log(`Resolution: ${header.width}x${header.height}`)
|
||||||
console.log(`FPS: ${header.fps}`)
|
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
|
||||||
console.log(`Total frames: ${header.totalFrames}`)
|
console.log(`Total frames: ${header.totalFrames}`)
|
||||||
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
||||||
console.log(`Decomposition levels: ${header.decompLevels}`)
|
console.log(`Decomposition levels: ${header.decompLevels}`)
|
||||||
@@ -492,20 +513,32 @@ if (isTapFile) {
|
|||||||
// Skip non-video packets until we find the image data
|
// Skip non-video packets until we find the image data
|
||||||
while (packetType !== TAV_PACKET_IFRAME) {
|
while (packetType !== TAV_PACKET_IFRAME) {
|
||||||
if (packetType === TAV_PACKET_EXTENDED_HDR) {
|
if (packetType === TAV_PACKET_EXTENDED_HDR) {
|
||||||
// Skip extended header - parse key-value pairs properly
|
// Parse extended header - look for XFPS
|
||||||
let numPairs = seqread.readShort()
|
let numPairs = seqread.readShort()
|
||||||
for (let i = 0; i < numPairs; i++) {
|
for (let i = 0; i < numPairs; i++) {
|
||||||
// Skip key (4 bytes)
|
// Read key (4 bytes)
|
||||||
let keyBytes = seqread.readBytes(4)
|
let keyBytes = seqread.readBytes(4)
|
||||||
|
let key = ""
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
key += String.fromCharCode(sys.peek(keyBytes + j))
|
||||||
|
}
|
||||||
sys.free(keyBytes)
|
sys.free(keyBytes)
|
||||||
|
|
||||||
// Read value type and skip value
|
// Read value type and value
|
||||||
let valueType = seqread.readOneByte()
|
let valueType = seqread.readOneByte()
|
||||||
if (valueType === 0x04) { // Uint64 - 8 bytes
|
if (valueType === 0x04) { // Uint64 - 8 bytes
|
||||||
seqread.skip(8)
|
seqread.skip(8)
|
||||||
} else if (valueType === 0x10) { // Bytes - length-prefixed
|
} else if (valueType === 0x10) { // Bytes - length-prefixed
|
||||||
let length = seqread.readShort()
|
let length = seqread.readShort()
|
||||||
let dataBytes = seqread.readBytes(length)
|
let dataBytes = seqread.readBytes(length)
|
||||||
|
// Check for XFPS key
|
||||||
|
if (key === "XFPS") {
|
||||||
|
let xfpsStr = ""
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
xfpsStr += String.fromCharCode(sys.peek(dataBytes + j))
|
||||||
|
}
|
||||||
|
parseXFPS(xfpsStr)
|
||||||
|
}
|
||||||
sys.free(dataBytes)
|
sys.free(dataBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1291,7 +1324,7 @@ try {
|
|||||||
|
|
||||||
console.log(`\nStarting file ${currentFileIndex}:`)
|
console.log(`\nStarting file ${currentFileIndex}:`)
|
||||||
console.log(`Resolution: ${header.width}x${header.height}`)
|
console.log(`Resolution: ${header.width}x${header.height}`)
|
||||||
console.log(`FPS: ${header.fps}`)
|
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
|
||||||
console.log(`Total frames: ${header.totalFrames}`)
|
console.log(`Total frames: ${header.totalFrames}`)
|
||||||
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
||||||
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
|
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
|
||||||
@@ -1825,7 +1858,19 @@ try {
|
|||||||
}
|
}
|
||||||
sys.free(dataBytes)
|
sys.free(dataBytes)
|
||||||
|
|
||||||
|
// Parse XFPS if present (always try, not just when fps=255)
|
||||||
|
if (key === "XFPS") {
|
||||||
|
if (parseXFPS(dataStr)) {
|
||||||
|
// Update frame timing with new fps
|
||||||
|
frametime = 1000000000.0 / header.fps
|
||||||
|
FRAME_TIME = 1.0 / header.fps
|
||||||
if (interactive) {
|
if (interactive) {
|
||||||
|
serial.println(` ${key}: ${dataStr} -> ${header.fps.toFixed(3)} fps`)
|
||||||
|
}
|
||||||
|
} else if (interactive) {
|
||||||
|
serial.println(` ${key}: "${dataStr}" (parse failed)`)
|
||||||
|
}
|
||||||
|
} else if (interactive) {
|
||||||
serial.println(` ${key}: "${dataStr}"`)
|
serial.println(` ${key}: "${dataStr}"`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -303,8 +303,10 @@ MMIO
|
|||||||
0: 560x448, 256 Colours, 1 layer
|
0: 560x448, 256 Colours, 1 layer
|
||||||
1: 280x224, 256 Colours, 4 layers
|
1: 280x224, 256 Colours, 4 layers
|
||||||
2: 280x224, 4096 Colours, 2 layers
|
2: 280x224, 4096 Colours, 2 layers
|
||||||
3: 560x448, 256 Colours, 2 layers (if bank 2 is not installed, will fall back to mode 0)
|
3: 560x448, 256 Colours, 2 layers (if bank 2 is not installed, mode change will not happen)
|
||||||
4: 560x448, 4096 Colours, 1 layer (if bank 2 is not installed, will fall back to mode 0)
|
4: 560x448, 4096 Colours, 1 layer (if bank 2 is not installed, mode change will not happen)
|
||||||
|
5: 560x448, 15-bit colour, 1 layer (if bank 2 is not installed, mode change will not happen)
|
||||||
|
8: 560x448, 24-bit colour, 1 layer (if bank 3 and 4 are not installed, mode change will not happen)
|
||||||
4096 is also known as "direct colour mode" (4096 colours * 16 transparency -> 65536 colours)
|
4096 is also known as "direct colour mode" (4096 colours * 16 transparency -> 65536 colours)
|
||||||
Two layers are grouped to make a frame, "low layer" contains RG colours and "high layer" has BA colours,
|
Two layers are grouped to make a frame, "low layer" contains RG colours and "high layer" has BA colours,
|
||||||
Red and Blue occupies MSBs
|
Red and Blue occupies MSBs
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun plotPixelMode1(x: Int, y: Int, colour: Int, plane: Int) {
|
||||||
|
getFirstGPU()?.let {
|
||||||
|
val planesize = it.config.width * it.config.height / 4
|
||||||
|
if (x in 0 until it.config.width/2 && y in 0 until it.config.height/2) {
|
||||||
|
it.poke(y.toLong() * it.config.width/2 + x + planesize * plane, colour.toByte())
|
||||||
|
it.applyDelay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets absolute position of scrolling
|
* Sets absolute position of scrolling
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -624,6 +624,8 @@ class VM(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val zeroBlock = ByteArray(MALLOC_UNIT)
|
||||||
|
|
||||||
internal fun malloc(size: Int): Int {
|
internal fun malloc(size: Int): Int {
|
||||||
if (size <= 0) throw IllegalArgumentException("Invalid malloc size: $size")
|
if (size <= 0) throw IllegalArgumentException("Invalid malloc size: $size")
|
||||||
|
|
||||||
@@ -635,6 +637,22 @@ class VM(
|
|||||||
return blockStart * MALLOC_UNIT
|
return blockStart * MALLOC_UNIT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun calloc(size: Int): Int {
|
||||||
|
if (size <= 0) throw IllegalArgumentException("Invalid malloc size: $size")
|
||||||
|
|
||||||
|
val allocBlocks = ceil(size.toDouble() / MALLOC_UNIT).toInt()
|
||||||
|
val blockStart = findEmptySpace(allocBlocks) ?: throw OutOfMemoryError("No space for $allocBlocks blocks ($size bytes requested)")
|
||||||
|
|
||||||
|
allocatedBlockCount += allocBlocks
|
||||||
|
mallocSizes[blockStart] = allocBlocks
|
||||||
|
|
||||||
|
for (i in 0 until allocBlocks) {
|
||||||
|
UnsafeHelper.memcpyRaw(zeroBlock, UnsafeHelper.getArrayOffset(zeroBlock), null, usermem.ptr + (blockStart + i) * MALLOC_UNIT, MALLOC_UNIT.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockStart * MALLOC_UNIT
|
||||||
|
}
|
||||||
|
|
||||||
internal fun free(ptr: Int) {
|
internal fun free(ptr: Int) {
|
||||||
val index = ptr / MALLOC_UNIT
|
val index = ptr / MALLOC_UNIT
|
||||||
val count = mallocSizes[index] ?: throw OutOfMemoryError("No allocation for pointer 0x${ptr.toHex()}")
|
val count = mallocSizes[index] ?: throw OutOfMemoryError("No allocation for pointer 0x${ptr.toHex()}")
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class VMJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
fun nanoTime() = System.nanoTime()
|
fun nanoTime() = System.nanoTime()
|
||||||
fun malloc(size: Int) = vm.malloc(size)
|
fun malloc(size: Int) = vm.malloc(size)
|
||||||
|
fun calloc(size: Int) = vm.calloc(size)
|
||||||
fun memset(dest: Int, ch: Int, count: Int) = vm.memset(dest, ch, count)
|
fun memset(dest: Int, ch: Int, count: Int) = vm.memset(dest, ch, count)
|
||||||
fun free(ptr: Int) = vm.free(ptr)
|
fun free(ptr: Int) = vm.free(ptr)
|
||||||
fun forceAlloc(ptr: Int, size: Int) = vm.forceAlloc(ptr, size)
|
fun forceAlloc(ptr: Int, size: Int) = vm.forceAlloc(ptr, size)
|
||||||
|
|||||||
Reference in New Issue
Block a user