mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-12 07:44:03 +09:00
TAV: hopefully more steady playback
This commit is contained in:
@@ -361,7 +361,7 @@ const FRAME_SIZE = FRAME_PIXELS * 3 // RGB buffer size
|
|||||||
|
|
||||||
// Triple-buffering: Fixed slot sizes in videoBuffer (48 MB total)
|
// Triple-buffering: Fixed slot sizes in videoBuffer (48 MB total)
|
||||||
const BUFFER_SLOTS = 3 // Three slots: playing, ready, decoding
|
const BUFFER_SLOTS = 3 // Three slots: playing, ready, decoding
|
||||||
const MAX_GOP_SIZE = 21 // Maximum frames per slot (21 * 752KB = ~15.8MB per slot)
|
const MAX_GOP_SIZE = 24 // Maximum frames per slot (24 * 752KB = ~18.1MB per slot)
|
||||||
const SLOT_SIZE = MAX_GOP_SIZE * FRAME_SIZE // Fixed slot size regardless of actual GOP size
|
const SLOT_SIZE = MAX_GOP_SIZE * FRAME_SIZE // Fixed slot size regardless of actual GOP size
|
||||||
|
|
||||||
console.log(`Triple-buffering: ${BUFFER_SLOTS} slots, max ${MAX_GOP_SIZE} frames/slot, ${(SLOT_SIZE / 1048576).toFixed(1)}MB per slot`)
|
console.log(`Triple-buffering: ${BUFFER_SLOTS} slots, max ${MAX_GOP_SIZE} frames/slot, ${(SLOT_SIZE / 1048576).toFixed(1)}MB per slot`)
|
||||||
@@ -505,6 +505,9 @@ let asyncDecodePtr = 0 // Compressed data pointer to free after decode
|
|||||||
let asyncDecodeStartTime = 0 // When async decode started (for diagnostics)
|
let asyncDecodeStartTime = 0 // When async decode started (for diagnostics)
|
||||||
let shouldReadPackets = true // Gate packet reading: false when all 3 buffers are full
|
let shouldReadPackets = true // Gate packet reading: false when all 3 buffers are full
|
||||||
|
|
||||||
|
// Overflow queue for GOPs when all 3 buffers are full (prevents Case 5 discards)
|
||||||
|
let overflowQueue = [] // Queue of {gopSize, compressedPtr, compressedSize}
|
||||||
|
|
||||||
// Pre-decoded audio state (for bundled audio packet 0x40)
|
// Pre-decoded audio state (for bundled audio packet 0x40)
|
||||||
let predecodedPcmBuffer = null // Buffer holding pre-decoded PCM data
|
let predecodedPcmBuffer = null // Buffer holding pre-decoded PCM data
|
||||||
let predecodedPcmSize = 0 // Total size of pre-decoded PCM
|
let predecodedPcmSize = 0 // Total size of pre-decoded PCM
|
||||||
@@ -1240,11 +1243,15 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Case 5: All 3 buffers full (playing + ready + decoding) - ignore packet
|
// Case 5: All 3 buffers full - add to overflow queue instead of discarding
|
||||||
|
overflowQueue.push({
|
||||||
|
gopSize: gopSize,
|
||||||
|
compressedPtr: compressedPtr,
|
||||||
|
compressedSize: compressedSize
|
||||||
|
})
|
||||||
if (interactive) {
|
if (interactive) {
|
||||||
console.log(`[GOP] Case 5: Discarding GOP ${gopSize} frames (current=${currentGopSize}, ready=${readyGopData !== null}, decoding=${decodingGopData !== null}, asyncInProgress=${asyncDecodeInProgress})`)
|
console.log(`[GOP] Case 5: Buffered GOP ${gopSize} frames to overflow queue (queue size: ${overflowQueue.length})`)
|
||||||
}
|
}
|
||||||
sys.free(compressedPtr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (packetType === TAV_PACKET_GOP_SYNC) {
|
else if (packetType === TAV_PACKET_GOP_SYNC) {
|
||||||
@@ -1715,6 +1722,77 @@ try {
|
|||||||
if (interactive) {
|
if (interactive) {
|
||||||
console.log(`[GOP] Transition complete - resuming packet reading (asyncInProgress=${asyncDecodeInProgress})`)
|
console.log(`[GOP] Transition complete - resuming packet reading (asyncInProgress=${asyncDecodeInProgress})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process overflow queue if it has GOPs waiting
|
||||||
|
if (overflowQueue.length > 0 && !asyncDecodeInProgress && graphics.tavDecodeGopIsComplete()) {
|
||||||
|
const overflow = overflowQueue.shift()
|
||||||
|
|
||||||
|
// Determine which slot to decode to
|
||||||
|
let targetSlot
|
||||||
|
if (readyGopData === null) {
|
||||||
|
// Decode to ready slot
|
||||||
|
targetSlot = (currentGopBufferSlot + 1) % BUFFER_SLOTS
|
||||||
|
} else if (decodingGopData === null) {
|
||||||
|
// Decode to decoding slot
|
||||||
|
targetSlot = (currentGopBufferSlot + 2) % BUFFER_SLOTS
|
||||||
|
} else {
|
||||||
|
// This shouldn't happen - put it back in queue
|
||||||
|
overflowQueue.unshift(overflow)
|
||||||
|
if (interactive) {
|
||||||
|
console.log(`[GOP] Overflow queue: no slots available, keeping in queue`)
|
||||||
|
}
|
||||||
|
targetSlot = -1 // Skip decode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only proceed if we got a valid slot
|
||||||
|
if (targetSlot >= 0) {
|
||||||
|
const targetOffset = targetSlot * SLOT_SIZE
|
||||||
|
const framesRemaining = currentGopSize - currentGopFrameIndex
|
||||||
|
const timeRemaining = framesRemaining * FRAME_TIME * 1000.0
|
||||||
|
|
||||||
|
// Start async decode
|
||||||
|
graphics.tavDecodeGopToVideoBufferAsync(
|
||||||
|
overflow.compressedPtr, overflow.compressedSize, overflow.gopSize,
|
||||||
|
header.width, header.height,
|
||||||
|
header.qualityLevel,
|
||||||
|
QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
||||||
|
header.channelLayout,
|
||||||
|
header.waveletFilter, header.decompLevels, 2,
|
||||||
|
header.entropyCoder,
|
||||||
|
targetOffset
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncDecodeInProgress = true
|
||||||
|
asyncDecodeSlot = targetSlot
|
||||||
|
asyncDecodeGopSize = overflow.gopSize
|
||||||
|
asyncDecodePtr = overflow.compressedPtr
|
||||||
|
asyncDecodeStartTime = sys.nanoTime()
|
||||||
|
|
||||||
|
if (readyGopData === null) {
|
||||||
|
readyGopData = {
|
||||||
|
gopSize: overflow.gopSize,
|
||||||
|
slot: targetSlot,
|
||||||
|
compressedPtr: overflow.compressedPtr,
|
||||||
|
startTime: asyncDecodeStartTime,
|
||||||
|
timeRemaining: timeRemaining
|
||||||
|
}
|
||||||
|
if (interactive) {
|
||||||
|
console.log(`[GOP] Overflow: Started decode of queued GOP ${overflow.gopSize} frames to ready slot ${targetSlot} (${overflowQueue.length} left in queue)`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decodingGopData = {
|
||||||
|
gopSize: overflow.gopSize,
|
||||||
|
slot: targetSlot,
|
||||||
|
compressedPtr: overflow.compressedPtr,
|
||||||
|
startTime: asyncDecodeStartTime,
|
||||||
|
timeRemaining: timeRemaining
|
||||||
|
}
|
||||||
|
if (interactive) {
|
||||||
|
console.log(`[GOP] Overflow: Started decode of queued GOP ${overflow.gopSize} frames to decoding slot ${targetSlot} (${overflowQueue.length} left in queue)`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // End if (targetSlot >= 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No ready GOP available - hiccup (shouldn't happen with triple-buffering)
|
// No ready GOP available - hiccup (shouldn't happen with triple-buffering)
|
||||||
@@ -1793,6 +1871,12 @@ finally {
|
|||||||
sys.free(predecodedPcmBuffer)
|
sys.free(predecodedPcmBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free any remaining overflow queue GOPs
|
||||||
|
while (overflowQueue.length > 0) {
|
||||||
|
const overflow = overflowQueue.shift()
|
||||||
|
sys.free(overflow.compressedPtr)
|
||||||
|
}
|
||||||
|
|
||||||
con.curs_set(1)
|
con.curs_set(1)
|
||||||
con.clear()
|
con.clear()
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
|
|||||||
internal val unusedArea = UnsafeHelper.allocate(1024, this)
|
internal val unusedArea = UnsafeHelper.allocate(1024, this)
|
||||||
internal val scanlineOffsets = UnsafeHelper.allocate(1024, this)
|
internal val scanlineOffsets = UnsafeHelper.allocate(1024, this)
|
||||||
|
|
||||||
internal val videoBuffer = UnsafeHelper.allocate(48 * 1024 * 1024, this) // 48 MB for triple-buffering (3 slots × 21 frames × 752 kB)
|
internal val videoBuffer = UnsafeHelper.allocate(58 * 1024 * 1024, this) // 48 MB for triple-buffering (3 slots × 24 frames × 752 kB)
|
||||||
|
|
||||||
protected val paletteShader = LoadShader(DRAW_SHADER_VERT, config.paletteShader)
|
protected val paletteShader = LoadShader(DRAW_SHADER_VERT, config.paletteShader)
|
||||||
protected val textShader = LoadShader(DRAW_SHADER_VERT, config.fragShader)
|
protected val textShader = LoadShader(DRAW_SHADER_VERT, config.fragShader)
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ static int needs_alpha_channel(int channel_layout) {
|
|||||||
#define DEFAULT_QUALITY 3
|
#define DEFAULT_QUALITY 3
|
||||||
#define DEFAULT_ZSTD_LEVEL 15
|
#define DEFAULT_ZSTD_LEVEL 15
|
||||||
#define DEFAULT_PCM_ZSTD_LEVEL 3
|
#define DEFAULT_PCM_ZSTD_LEVEL 3
|
||||||
#define TEMPORAL_GOP_SIZE 20
|
#define TEMPORAL_GOP_SIZE 24
|
||||||
#define TEMPORAL_GOP_SIZE_MIN 8 // Minimum GOP size to avoid decoder hiccups
|
#define TEMPORAL_GOP_SIZE_MIN 8 // Minimum GOP size to avoid decoder hiccups
|
||||||
#define TEMPORAL_DECOMP_LEVEL 2
|
#define TEMPORAL_DECOMP_LEVEL 2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user