From 5012ca4085b73e102603bbc622b5a3b37844f307 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 29 Sep 2025 01:35:19 +0900 Subject: [PATCH] TAV: decompression done on GPU --- CLAUDE.md | 4 ++ assets/disk0/tvdos/bin/playtav.js | 35 +++++-------- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 49 +++++++++++++++++++ 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 539d6eb..c4ea8f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,6 +120,10 @@ Peripheral memories can be accessed using `vm.peek()` and `vm.poke()` functions, - JavaScript test programs available in `assets/disk0/` - Videotron2K assembly examples in documentation +## Notes + +- The 'gzip' namespace in TSVM's JS programs is a misnomer: the actual 'gzip' functions (defined in CompressorDelegate.kt) call Zstd functions. + ## TVDOS ### TVDOS Movie Formats diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js index 3097377..04ea151 100644 --- a/assets/disk0/tvdos/bin/playtav.js +++ b/assets/disk0/tvdos/bin/playtav.js @@ -69,18 +69,17 @@ let notifHideTimer = 0 const NOTIF_SHOWUPTIME = 3000000000 let [cy, cx] = con.getyx() -let seqreadserial = require("seqread") -let seqreadtape = require("seqreadtape") +//let playgui = require("playgui") let seqread = undefined let fullFilePathStr = fullFilePath.full // Select seqread driver to use -if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\\\TAPE')) { - seqread = seqreadtape +if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) { + seqread = require("seqreadtape") seqread.prepare(fullFilePathStr) seqread.seek(0) } else { - seqread = seqreadserial + seqread = require("seqread") seqread.prepare(fullFilePathStr) } @@ -686,33 +685,20 @@ try { else if (packetType === TAV_PACKET_IFRAME || packetType === TAV_PACKET_PFRAME) { // Video packet const compressedSize = seqread.readInt() - const isKeyframe = (packetType === TAV_PACKET_IFRAME) // Read compressed tile data let compressedPtr = seqread.readBytes(compressedSize) updateDataRateBin(compressedSize) - let actualSize - let decompressStart = sys.nanoTime() try { - // Use gzip decompression (only compression format supported in TSVM JS) - actualSize = gzip.decompFromTo(compressedPtr, compressedSize, blockDataPtr) - decompressTime = (sys.nanoTime() - decompressStart) / 1000000.0 - } catch (e) { - decompressTime = (sys.nanoTime() - decompressStart) / 1000000.0 - console.log(`Frame ${frameCount}: Gzip decompression failed, skipping (compressed size: ${compressedSize}, error: ${e})`) - sys.free(compressedPtr) - continue - } - - try { -// serial.println(actualSize) let decodeStart = sys.nanoTime() - // Call TAV hardware decoder (like TEV's tevDecode but with RGB buffer outputs) - graphics.tavDecode( - blockDataPtr, - CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers (not float arrays!) + // Call new TAV hardware decoder that handles Zstd decompression internally + // Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively + graphics.tavDecodeCompressed( + compressedPtr, // Pass compressed data directly + compressedSize, // Size of compressed data + CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers header.width, header.height, header.qualityLevel, header.qualityY, header.qualityCo, header.qualityCg, frameCount, @@ -723,6 +709,7 @@ try { ) decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0 + decompressTime = 0 // Decompression time now included in decode time // Upload RGB buffer to display framebuffer (like TEV) let uploadStart = sys.nanoTime() diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index 1f9669a..2aa47a5 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -73,6 +73,8 @@ import kotlin.text.format import kotlin.text.lowercase import kotlin.text.toString import kotlin.times +import io.airlift.compress.zstd.ZstdInputStream +import java.io.ByteArrayInputStream class GraphicsJSR223Delegate(private val vm: VM) { @@ -4137,6 +4139,53 @@ class GraphicsJSR223Delegate(private val vm: VM) { private val tavDebugFrameTarget = -1 // use negative number to disable the debug print private var tavDebugCurrentFrameNumber = 0 + // New tavDecode function that accepts compressed data and decompresses internally + fun tavDecodeCompressed(compressedDataPtr: Long, compressedSize: Int, currentRGBAddr: Long, prevRGBAddr: Long, + width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, frameCount: Int, + waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1) { + + // Read compressed data from VM memory into byte array + val compressedData = ByteArray(compressedSize) + for (i in 0 until compressedSize) { + compressedData[i] = vm.peek(compressedDataPtr + i)!!.toByte() + } + + try { + // Decompress using Zstd + val bais = ByteArrayInputStream(compressedData) + val zis = ZstdInputStream(bais) + val decompressedData = zis.readBytes() + zis.close() + bais.close() + + // Allocate buffer for decompressed data + val decompressedBuffer = vm.malloc(decompressedData.size) + + try { + // Copy decompressed data to unsafe buffer + UnsafeHelper.memcpyRaw( + decompressedData, UnsafeHelper.getArrayOffset(decompressedData), + null, vm.usermem.ptr + decompressedBuffer.toLong(), + decompressedData.size.toLong() + ) + + // Call the existing tavDecode function with decompressed data + tavDecode(decompressedBuffer.toLong(), currentRGBAddr, prevRGBAddr, + width, height, qIndex, qYGlobal, qCoGlobal, qCgGlobal, frameCount, + waveletFilter, decompLevels, isLossless, tavVersion) + + } finally { + // Clean up allocated buffer + vm.free(decompressedBuffer) + } + + } catch (e: Exception) { + println("TAV Zstd decompression error: ${e.message}") + throw e + } + } + + // Original tavDecode function for backward compatibility (now handles decompressed data) fun tavDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long, width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, frameCount: Int, waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1) {