From 9ff12edecd35f9f6abd98a2df0867971098eb329 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 5 Sep 2025 00:30:17 +0900 Subject: [PATCH] fix: FPS conversion not changing video header --- assets/disk0/tvdos/bin/autorun.js | 21 +++++++ assets/disk0/tvdos/bin/playtev.js | 4 +- assets/disk0/tvdos/include/seqreadtape.mjs | 42 ++++++++----- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 63 +++++++++++++------ video_encoder/encoder_tev.c | 25 +++++--- 5 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 assets/disk0/tvdos/bin/autorun.js diff --git a/assets/disk0/tvdos/bin/autorun.js b/assets/disk0/tvdos/bin/autorun.js new file mode 100644 index 0000000..bab004a --- /dev/null +++ b/assets/disk0/tvdos/bin/autorun.js @@ -0,0 +1,21 @@ +const fullFilePath = _G.shell.resolvePathInput(exec_args[1]) + +let seqread = undefined +let fullFilePathStr = fullFilePath.full +let mode = "" + +// Select seqread driver to use +if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) { + seqread = require("seqreadtape") + seqread.prepare(fullFilePathStr) + seqread.seek(0) + mode = "tape" +} else { + seqread = undefined +} + + +if ("tape" == mode) { + const prg = seqread.readString(65536) + eval(prg) +} \ No newline at end of file diff --git a/assets/disk0/tvdos/bin/playtev.js b/assets/disk0/tvdos/bin/playtev.js index d3012e0..c57ce3b 100644 --- a/assets/disk0/tvdos/bin/playtev.js +++ b/assets/disk0/tvdos/bin/playtev.js @@ -60,13 +60,13 @@ let fullFilePathStr = fullFilePath.full // Select seqread driver to use if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) { seqread = seqreadtape + seqread.prepare(fullFilePathStr) seqread.seek(0) } else { seqread = seqreadserial + seqread.prepare(fullFilePathStr) } -seqread.prepare(fullFilePathStr) - con.clear() con.curs_set(0) graphics.setGraphicsMode(4) // 4096-color mode diff --git a/assets/disk0/tvdos/include/seqreadtape.mjs b/assets/disk0/tvdos/include/seqreadtape.mjs index 20218d3..daffd51 100644 --- a/assets/disk0/tvdos/include/seqreadtape.mjs +++ b/assets/disk0/tvdos/include/seqreadtape.mjs @@ -38,7 +38,10 @@ function hsdpaDisableSequentialIO() { } function hsdpaRewind() { + // send rewind command to the tape drive sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_REWIND) + + readCount = 0 } function hsdpaSkip(bytes) { @@ -46,8 +49,10 @@ function hsdpaSkip(bytes) { sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2 - // Execute skip operation + // Execute skip operation (tape drive should fast forward) sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_SKIP) + + readCount += bytes } function hsdpaReadToMemory(bytes, vmMemoryPointer) { @@ -116,8 +121,7 @@ function prepare(fullPath) { // Reset position for actual reading hsdpaRewind() - readCount = 0 - + return 0 } catch (e) { @@ -190,12 +194,19 @@ function readString(length) { return s } -function skip(n) { - if (n <= 0) return - - // For HSDPA, we can skip efficiently without reading - hsdpaSkip(n) - readCount += n +function skip(n0) { + if (n0 <= 0) return + if (n0 < 16777215) { + hsdpaSkip(n0) + return + } + let n = n0 + while (n > 0) { + let skiplen = Math.min(n, 16777215) + serial.println(`skip ${skiplen}; remaining: ${n}`) + hsdpaSkip(skiplen) + n -= skiplen + } } function getReadCount() { @@ -226,12 +237,15 @@ function isReady() { } function seek(position) { - // Seek to absolute position - hsdpaRewind() - if (position > 0) { - hsdpaSkip(position) + let relPos = position - readCount + if (position == 0) { + return + } else if (position > 0) { + skip(relPos) + } else { + hsdpaRewind() + skip(position) } - readCount = position } function rewind() { seek(0) } diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index b93c8ed..0be242b 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -1318,23 +1318,46 @@ class GraphicsJSR223Delegate(private val vm: VM) { val rgbAddrIncVec = if (rgbAddr >= 0) 1 else -1 - val totalPixels = width * height + // Get native resolution + val nativeWidth = gpu.config.width + val nativeHeight = gpu.config.height - // Process in 8KB chunks to balance memory usage and performance + // Calculate centering offset + val offsetX = (nativeWidth - width) / 2 + val offsetY = (nativeHeight - height) / 2 + + // Clear framebuffer with transparent pixels first + val transparentRG = 0.toByte() // r=0, g=0 + val transparentBA = 0.toByte() // b=0, a=0 (transparent) + + // Fill entire framebuffer with transparent pixels + val totalNativePixels = (nativeWidth * nativeHeight).toLong() +// UnsafeHelper.unsafe.setMemory(gpu.framebuffer.ptr, totalNativePixels, transparentRG) +// UnsafeHelper.unsafe.setMemory(gpu.framebuffer2!!.ptr, totalNativePixels, transparentBA) + + // Process video pixels in 8KB chunks to balance memory usage and performance + val totalVideoPixels = width * height val chunkSize = 8192 val rgChunk = ByteArray(chunkSize) val baChunk = ByteArray(chunkSize) + val positionChunk = IntArray(chunkSize) // Store framebuffer positions var pixelsProcessed = 0 - while (pixelsProcessed < totalPixels) { - val pixelsInChunk = kotlin.math.min(chunkSize, totalPixels - pixelsProcessed) + while (pixelsProcessed < totalVideoPixels) { + val pixelsInChunk = kotlin.math.min(chunkSize, totalVideoPixels - pixelsProcessed) // Batch process chunk of pixels for (i in 0 until pixelsInChunk) { val pixelIndex = pixelsProcessed + i - val y = pixelIndex / width - val x = pixelIndex % width + val videoY = pixelIndex / width + val videoX = pixelIndex % width + + // Calculate position in native framebuffer (centered) + val nativeX = videoX + offsetX + val nativeY = videoY + offsetY + val nativePos = nativeY * nativeWidth + nativeX + positionChunk[i] = nativePos val rgbOffset = (pixelIndex.toLong() * 3) * rgbAddrIncVec @@ -1344,25 +1367,27 @@ class GraphicsJSR223Delegate(private val vm: VM) { val b = vm.peek(rgbAddr + rgbOffset + rgbAddrIncVec * 2)!!.toUint() // Apply Bayer dithering and convert to 4-bit - val r4 = ditherValue(r, x, y, frameCounter) - val g4 = ditherValue(g, x, y, frameCounter) - val b4 = ditherValue(b, x, y, frameCounter) + val r4 = ditherValue(r, videoX, videoY, frameCounter) + val g4 = ditherValue(g, videoX, videoY, frameCounter) + val b4 = ditherValue(b, videoX, videoY, frameCounter) // Pack and store in chunk buffers rgChunk[i] = ((r4 shl 4) or g4).toByte() baChunk[i] = ((b4 shl 4) or 15).toByte() } - // Batch write entire chunk to framebuffer - val pixelOffset = (pixelsProcessed).toLong() - - gpu.let { - UnsafeHelper.memcpyRaw( - rgChunk, UnsafeHelper.getArrayOffset(rgChunk), - null, it.framebuffer.ptr + pixelOffset, pixelsInChunk.toLong()) - UnsafeHelper.memcpyRaw( - baChunk, UnsafeHelper.getArrayOffset(baChunk), - null, it.framebuffer2!!.ptr + pixelOffset, pixelsInChunk.toLong()) + // Write pixels to their calculated positions in framebuffer + for (i in 0 until pixelsInChunk) { + val pos = positionChunk[i].toLong() + // Bounds check to ensure we don't write outside framebuffer + if (pos in 0 until totalNativePixels) { + UnsafeHelper.memcpyRaw( + rgChunk, UnsafeHelper.getArrayOffset(rgChunk) + i, + null, gpu.framebuffer.ptr + pos, 1L) + UnsafeHelper.memcpyRaw( + baChunk, UnsafeHelper.getArrayOffset(baChunk) + i, + null, gpu.framebuffer2!!.ptr + pos, 1L) + } } pixelsProcessed += pixelsInChunk diff --git a/video_encoder/encoder_tev.c b/video_encoder/encoder_tev.c index c46d28e..5fb7585 100644 --- a/video_encoder/encoder_tev.c +++ b/video_encoder/encoder_tev.c @@ -1527,7 +1527,7 @@ static int write_tev_header(FILE *output, tev_encoder_t *enc) { // Video parameters uint16_t width = enc->width; uint16_t height = enc->progressive_mode ? enc->height : enc->height * 2; - uint8_t fps = enc->fps; + uint8_t fps = enc->output_fps; uint32_t total_frames = enc->total_frames; uint8_t qualityY = enc->qualityY; uint8_t qualityCo = enc->qualityCo; @@ -1780,13 +1780,19 @@ static int get_video_metadata(tev_encoder_t *config) { config->total_frames = (int)(config->duration * config->fps); } + // calculate new total_frames if user has requested custom framerate + float inputFramerate; + if (config->is_ntsc_framerate) { + inputFramerate = config->fps * 1000.f / 1001.f; + } else { + inputFramerate = config->fps * 1.f; + } + + config->total_frames = (int)(config->total_frames * (config->output_fps / inputFramerate)); + fprintf(stderr, "Video metadata:\n"); fprintf(stderr, " Frames: %d\n", config->total_frames); - if (config->is_ntsc_framerate) { - fprintf(stderr, " FPS: %.2f\n", config->fps * 1000.f / 1001.f); - } else { - fprintf(stderr, " FPS: %d\n", config->fps); - } + fprintf(stderr, " FPS: %.2f\n", inputFramerate); fprintf(stderr, " Duration: %.2fs\n", config->duration); fprintf(stderr, " Audio: %s\n", config->has_audio ? "Yes" : "No"); fprintf(stderr, " Resolution: %dx%d (%s)\n", config->width, config->height, @@ -1923,7 +1929,7 @@ static int process_audio(tev_encoder_t *enc, int frame_num, FILE *output) { } // Calculate how much audio time each frame represents (in seconds) - double frame_audio_time = 1.0 / enc->fps; + double frame_audio_time = 1.0 / enc->output_fps; // Calculate how much audio time each MP2 packet represents // MP2 frame contains 1152 samples at 32kHz = 0.036 seconds @@ -2198,6 +2204,7 @@ int main(int argc, char *argv[]) { if (test_mode) { // Test mode: generate solid colour frames enc->fps = 1; + enc->output_fps = 1; enc->total_frames = 15; enc->has_audio = 0; printf("Test mode: Generating 15 solid colour frames\n"); @@ -2217,7 +2224,7 @@ int main(int argc, char *argv[]) { int format = detect_subtitle_format(enc->subtitle_file); const char *format_name = (format == 1) ? "SAMI" : "SubRip"; - enc->subtitle_list = parse_subtitle_file(enc->subtitle_file, enc->fps); + enc->subtitle_list = parse_subtitle_file(enc->subtitle_file, enc->output_fps); if (enc->subtitle_list) { enc->has_subtitles = 1; enc->current_subtitle = enc->subtitle_list; @@ -2398,7 +2405,7 @@ int main(int argc, char *argv[]) { printf("\nEncoding complete!\n"); printf(" Frames encoded: %d\n", frame_count); printf(" - sync packets: %d\n", sync_packet_count); - printf(" Framerate: %d\n", enc->fps); + printf(" Framerate: %d\n", enc->output_fps); printf(" Output size: %zu bytes\n", enc->total_output_bytes); // Calculate achieved bitrate