diff --git a/assets/disk0/tvdos/bin/playtev.js b/assets/disk0/tvdos/bin/playtev.js index d28671f..6f30d88 100644 --- a/assets/disk0/tvdos/bin/playtev.js +++ b/assets/disk0/tvdos/bin/playtev.js @@ -436,6 +436,11 @@ const FRAME_SIZE = 560*448*3 // Total frame size = 752,640 bytes const RGB_BUFFER_A = sys.malloc(FRAME_SIZE) const RGB_BUFFER_B = sys.malloc(FRAME_SIZE) +// Static Yadif deinterlacing buffers (half-height field buffers for interlaced mode) +const FIELD_SIZE = 560 * 224 * 3 // Half-height field buffer size +const TEMP_FIELD_BUFFER = sys.malloc(FIELD_SIZE) +const PREV_FIELD_BUFFER = sys.malloc(FIELD_SIZE) + // Ping-pong buffer pointers (swap instead of copy) let CURRENT_RGB_ADDR = RGB_BUFFER_A let PREV_RGB_ADDR = RGB_BUFFER_B @@ -444,6 +449,10 @@ let PREV_RGB_ADDR = RGB_BUFFER_B sys.memset(RGB_BUFFER_A, 0, FRAME_PIXELS * 3) sys.memset(RGB_BUFFER_B, 0, FRAME_PIXELS * 3) +// Initialize Yadif field buffers to black +sys.memset(TEMP_FIELD_BUFFER, 0, FIELD_SIZE) +sys.memset(PREV_FIELD_BUFFER, 0, FIELD_SIZE) + // Initialize display framebuffer to black sys.memset(DISPLAY_RG_ADDR, 0, FRAME_PIXELS) // Black in RG plane sys.memset(DISPLAY_BA_ADDR, 15, FRAME_PIXELS) // Black with alpha=15 (opaque) in BA plane @@ -504,7 +513,7 @@ function setBiasLighting() { graphics.setBackground(Math.round(bgr * 255), Math.round(bgg * 255), Math.round(bgb * 255)) } -let blockDataPtr = sys.malloc(560 * 448 * 3) +let blockDataPtr = sys.malloc(FRAME_SIZE) // Main decoding loop - simplified for performance try { @@ -573,7 +582,7 @@ try { // Hardware-accelerated TEV decoding to RGB buffers (YCoCg-R or XYB based on version) try { let decodeStart = sys.nanoTime() - graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, [qualityY, qualityCo, qualityCg], frameCount, debugMotionVectors, version, isInterlaced) + graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, [qualityY, qualityCo, qualityCg], frameCount, debugMotionVectors, version, isInterlaced, TEMP_FIELD_BUFFER, PREV_FIELD_BUFFER) decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0 // Convert to milliseconds // Upload RGB buffer to display framebuffer with dithering @@ -661,9 +670,11 @@ catch (e) { } finally { // Cleanup working memory (graphics memory is automatically managed) - sys.free(blockDataPtr) + if (blockDataPtr > 0) sys.free(blockDataPtr) if (RGB_BUFFER_A > 0) sys.free(RGB_BUFFER_A) if (RGB_BUFFER_B > 0) sys.free(RGB_BUFFER_B) + if (TEMP_FIELD_BUFFER > 0) sys.free(TEMP_FIELD_BUFFER) + if (PREV_FIELD_BUFFER > 0) sys.free(PREV_FIELD_BUFFER) audio.stop(0) audio.purgeQueue(0) diff --git a/assets/disk0/tvdos/bin/zfm.js b/assets/disk0/tvdos/bin/zfm.js index 507f7aa..55d074d 100644 --- a/assets/disk0/tvdos/bin/zfm.js +++ b/assets/disk0/tvdos/bin/zfm.js @@ -35,6 +35,7 @@ const COL_HL_EXT = { "ipf2": 191, "txt": 223, "md": 223, + "log": 223 } const EXEC_FUNS = { @@ -49,7 +50,9 @@ const EXEC_FUNS = { "ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`), "ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`), "bas": (f) => _G.shell.execute(`basic "${f}"`), - "txt": (f) => _G.shell.execute(`less "${f}"`) + "txt": (f) => _G.shell.execute(`less "${f}"`), + "md": (f) => _G.shell.execute(`less "${f}"`), + "log": (f) => _G.shell.execute(`less "${f}"`) } let windowMode = 0 // 0 == left, 1 == right diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index d05b3d2..f899c90 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -485,33 +485,6 @@ class GraphicsJSR223Delegate(private val vm: VM) { ) ).map{ it.map { (it.toFloat() + 0.5f) / 16f }.toFloatArray() } - private val bayerKernels2 = arrayOf( - intArrayOf( - 0,8,2,10, - 12,4,14,6, - 3,11,1,9, - 15,7,13,5, - ), - intArrayOf( - 8,2,10,0, - 4,14,6,12, - 11,1,9,3, - 7,13,5,15, - ), - intArrayOf( - 7,13,5,15, - 8,2,10,0, - 4,14,6,12, - 11,1,9,3, - ), - intArrayOf( - 15,7,13,5, - 0,8,2,10, - 12,4,14,6, - 3,11,1,9, - ) - ) - /** * This method always assume that you're using the default palette * @@ -1519,6 +1492,27 @@ class GraphicsJSR223Delegate(private val vm: VM) { // Temporary buffer for interlaced field processing private val interlacedFieldBuffer = IntArray(560 * 224 * 3) // Half-height RGB buffer + /** + * Extract a specific field (even or odd lines) from a progressive frame + * Used for temporal prediction in Yadif deinterlacing + */ + private fun extractFieldFromProgressive(progressiveAddr: Long, fieldAddr: Long, width: Int, height: Int, + fieldParity: Int, addrIncVec: Int) { + val fieldHeight = height / 2 + for (y in 0 until fieldHeight) { + val progressiveY = y * 2 + fieldParity // Extract even (0) or odd (1) lines + val progressiveOffset = (progressiveY * width) * 3 + val fieldOffset = (y * width) * 3 + + for (x in 0 until width) { + for (c in 0..2) { + val pixel = vm.peek(progressiveAddr + (progressiveOffset + x * 3 + c) * addrIncVec)!! + vm.poke(fieldAddr + (fieldOffset + x * 3 + c) * addrIncVec, pixel) + } + } + } + } + /** * YADIF (Yet Another Deinterlacing Filter) implementation * Converts interlaced field to progressive frame with temporal/spatial interpolation @@ -1550,18 +1544,49 @@ class GraphicsJSR223Delegate(private val vm: VM) { val below = vm.peek(fieldRGBAddr + (fieldOffset + width * 3 + c) * fieldIncVec)!!.toInt() and 0xFF val current = vm.peek(fieldRGBAddr + (fieldOffset + c) * fieldIncVec)!!.toInt() and 0xFF - // Simple spatial interpolation (can be enhanced with temporal prediction) + // Spatial interpolation val spatialInterp = (above + below) / 2 - // Apply edge-directed interpolation bias - val edgeBias = if (kotlin.math.abs(above - below) < 32) { - (current + spatialInterp) / 2 // Low edge activity: blend with current + // Temporal prediction using previous and next fields + var temporalPred = spatialInterp + if (prevFieldAddr != 0L && nextFieldAddr != 0L) { + // Get temporal neighbors from same spatial position + val prevPixel = (vm.peek(prevFieldAddr + (fieldOffset + c) * fieldIncVec)?.toInt() ?: current) and 0xFF + val nextPixel = (vm.peek(nextFieldAddr + (fieldOffset + c) * fieldIncVec)?.toInt() ?: current) and 0xFF + + // Simple temporal interpolation + val tempInterp = (prevPixel + nextPixel) / 2 + + // Yadif edge-directed temporal-spatial decision + val spatialDiff = kotlin.math.abs(above - below) + val temporalDiff = kotlin.math.abs(prevPixel - nextPixel) + + // Choose between spatial and temporal prediction based on local characteristics + temporalPred = when { + spatialDiff < 32 && temporalDiff < 32 -> { + // Low spatial and temporal variation: blend all + (spatialInterp + tempInterp + current) / 3 + } + spatialDiff < temporalDiff -> { + // Prefer spatial interpolation + (spatialInterp * 3 + tempInterp) / 4 + } + else -> { + // Prefer temporal interpolation + (tempInterp * 3 + spatialInterp) / 4 + } + } + } + + // Final edge-directed filtering + val finalValue = if (kotlin.math.abs(above - below) < 16) { + (current + temporalPred) / 2 // Very low edge activity: blend with current } else { - spatialInterp // High edge activity: use spatial only + temporalPred // Higher edge activity: use prediction } vm.poke(outputRGBAddr + (interpOutputOffset + c) * outputIncVec, - edgeBias.coerceIn(0, 255).toByte()) + finalValue.coerceIn(0, 255).toByte()) } } } @@ -1798,7 +1823,8 @@ class GraphicsJSR223Delegate(private val vm: VM) { */ fun tevDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long, width: Int, height: Int, qualityIndices: IntArray, frameCounter: Int, - debugMotionVectors: Boolean = false, tevVersion: Int = 2, isInterlaced: Boolean = false) { + debugMotionVectors: Boolean = false, tevVersion: Int = 2, isInterlaced: Boolean = false, + tempFieldBuffer: Long = 0L, prevFieldBuffer: Long = 0L) { // height doesn't change when interlaced, because that's the encoder's output @@ -2193,21 +2219,32 @@ class GraphicsJSR223Delegate(private val vm: VM) { // Apply Yadif deinterlacing if this is an interlaced frame if (isInterlaced) { - // Decode produced a field at half-height, now deinterlace to full progressive frame - val tempFieldBuffer = vm.malloc(width * decodingHeight * 3) + // Use static buffers provided by playtev.js for better performance +// require(tempFieldBuffer != 0L) { "tempFieldBuffer must be provided for interlaced decoding" } +// require(prevFieldBuffer != 0L) { "prevFieldBuffer must be provided for interlaced decoding" } // Copy the decoded field to temporary buffer vm.memcpy(currentRGBAddr.toInt(), tempFieldBuffer.toInt(), width * decodingHeight * 3) // Apply Yadif deinterlacing: field -> progressive frame + // For temporal prediction, we need proper field management + val fieldParity = frameCounter % 2 + val prevFieldAddr = if (prevRGBAddr != 0L) { + // Extract the corresponding field from the previous progressive frame + // Even field lines: y = 0, 2, 4, 6... + // Odd field lines: y = 1, 3, 5, 7... + extractFieldFromProgressive(prevRGBAddr, prevFieldBuffer, width, height, fieldParity, thisAddrIncVec) + prevFieldBuffer + } else { + 0L + } + yadifDeinterlace( - tempFieldBuffer.toLong(), currentRGBAddr, width, height, - prevRGBAddr, prevRGBAddr, // TODO: Implement proper temporal prediction - frameCounter % 2, // Field parity (0=even field first) + tempFieldBuffer, currentRGBAddr, width, height, + prevFieldAddr, 0L, // Use previous field, no next field available + fieldParity, thisAddrIncVec, thisAddrIncVec ) - - vm.free(tempFieldBuffer.toInt()) } } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 29a0fec..132597e 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -1850,11 +1850,11 @@ void main() { val DEFAULT_CONFIG_COLOR_CRT = AdapterConfig( "crt_color", - 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.32f, TEXT_TILING_SHADER_COLOUR + 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.62f, TEXT_TILING_SHADER_COLOUR ) val DEFAULT_CONFIG_PMLCD = AdapterConfig( "pmlcd_inverted", - 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.64f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD + 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.88f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD ) val DEFAULT_CONFIG_FOR_TESTING = AdapterConfig( diff --git a/video_encoder/encoder_tev.c b/video_encoder/encoder_tev.c index f3f588f..838e29c 100644 --- a/video_encoder/encoder_tev.c +++ b/video_encoder/encoder_tev.c @@ -1814,14 +1814,14 @@ static int start_video_conversion(tev_encoder_t *enc) { // Frame rate conversion requested snprintf(command, sizeof(command), "ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 " - "-vf \"fps=%d,scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top,separatefields\" " + "-vf \"fps=%d,scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top:cvlpf,separatefields\" " "-y - 2>&1", enc->input_file, enc->output_fps, enc->width, enc->height * 2, enc->width, enc->height * 2); } else { // No frame rate conversion snprintf(command, sizeof(command), "ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 " - "-vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top,separatefields\" " + "-vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top:cvlpf,separatefields\" " "-y -", enc->input_file, enc->width, enc->height * 2, enc->width, enc->height * 2); }