From e3099195e48f5ded5ef1f82fc482c2af1c5cf607 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 25 Nov 2025 09:53:53 +0900 Subject: [PATCH] gpu: 5bpp mode --- assets/disk0/tvdos/bin/playtav.js | 8 ++- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 57 +++++++++++++++---- .../tsvm/peripheral/GraphicsAdapter.kt | 27 ++++++++- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js index 0c7f5c1..2851b12 100644 --- a/assets/disk0/tvdos/bin/playtav.js +++ b/assets/disk0/tvdos/bin/playtav.js @@ -142,7 +142,7 @@ if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAP con.clear() con.curs_set(0) graphics.setGraphicsMode(4) // initially set to 4bpp mode -//graphics.setGraphicsMode(5) // set to 8bpp mode. If GPU don't support it, mode will remain to 4 +graphics.setGraphicsMode(5) // then try to set to 5bpp mode graphics.clearPixels(0) graphics.clearPixels2(0) graphics.clearPixels3(0) @@ -542,9 +542,11 @@ function getRGBfromScr(x, y) { let offset = y * WIDTH + x let fb1 = sys.peek(-1048577 - offset) let fb2 = sys.peek(-1310721 - offset) - let fb3 = sys.peek(-1310721 - (262144 * (gpuGraphicsMode - 4)) - offset) + let fb3 = sys.peek(-1310721 - 262144 - offset) - if (gpuGraphicsMode == 4) + if (gpuGraphicsMode == 5) + return [((fb1 >>> 2) & 31) / 31.0, (((fb1 & 3) << 3) | ((fb2 >>> 5) & 7)) / 31.0, (fb2 & 31) / 31.0] + else if (gpuGraphicsMode == 4) return [(fb1 >>> 4) / 15.0, (fb1 & 15) / 15.0, (fb2 >>> 4) / 15.0] else return [fb1 / 255.0, fb2 / 255.0, fb3 / 255.0] diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index f3269cd..f95c473 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -1495,7 +1495,7 @@ class GraphicsJSR223Delegate(private val vm: VM) { val videoY = (pixelIndex / width) + startY val videoX = (pixelIndex % width) + startX val nativePos = videoY * nativeWidth + videoX - if (graphicsMode == 4) { + if (graphicsMode == 4 || graphicsMode == 5) { UnsafeHelper.memcpyRaw( rgChunk, UnsafeHelper.getArrayOffset(rgChunk), null, gpu.framebuffer.ptr + nativePos, pixelsInChunk.toLong() @@ -1530,14 +1530,23 @@ class GraphicsJSR223Delegate(private val vm: VM) { fun writeToChunk(r: Int, g: Int, b: Int, videoX: Int, videoY: Int, i: Int, coordInVideoFrame: Boolean = true) { if (graphicsMode == 4) { // Apply Bayer dithering and convert to 4-bit - val r4 = ditherValue(r, videoX, videoY, frameCount) - val g4 = ditherValue(g, videoX, videoY, frameCount) - val b4 = ditherValue(b, videoX, videoY, frameCount) + val r4 = ditherValue4(r, videoX, videoY, frameCount) + val g4 = ditherValue4(g, videoX, videoY, frameCount) + val b4 = ditherValue4(b, videoX, videoY, frameCount) // Pack RGB values and store in chunk arrays for batch processing rgChunk[i] = ((r4 shl 4) or g4).toByte() baChunk[i] = ((b4 shl 4) or coordInVideoFrame.toInt().times(15)).toByte() + } + else if (graphicsMode == 5) { + // Apply Bayer dithering and convert to 4-bit + val r5 = ditherValue5(r, videoX, videoY, frameCount) + val g5 = ditherValue5(g, videoX, videoY, frameCount) + val b5 = ditherValue5(b, videoX, videoY, frameCount) + // Pack RGB values and store in chunk arrays for batch processing + rgChunk[i] = (coordInVideoFrame.toInt(8) or r5.shl(2) or g5.ushr(3)).toByte() + baChunk[i] = (g5.and(7).shl(5) or b5).toByte() } else { rChunk[i] = r.toByte() @@ -1647,7 +1656,7 @@ class GraphicsJSR223Delegate(private val vm: VM) { /** * Apply Bayer dithering to reduce banding when quantising to 4-bit */ - private fun ditherValue(value: Int, x: Int, y: Int, f: Int): Int { + private fun ditherValue4(value: Int, x: Int, y: Int, f: Int): Int { // Preserve pure values (0 and 255) exactly to maintain colour primaries if (value == 0) return 0 if (value == 255) return 15 @@ -1657,6 +1666,16 @@ class GraphicsJSR223Delegate(private val vm: VM) { return round(15f * q) } + private fun ditherValue5(value: Int, x: Int, y: Int, f: Int): Int { + // Preserve pure values (0 and 255) exactly to maintain colour primaries + if (value == 0) return 0 + if (value == 255) return 31 + + val t = bayerKernels[f % 4][4 * (y % 4) + (x % 4)] // use rotating bayerKernel to time-dither the static pattern for even better visuals + val q = floor((t / 31f + (value / 255f)) * 31f) / 31f + return round(31f * q) + } + /** * Sample RGB values using bilinear interpolation * @param rgbAddr Source RGB buffer address @@ -6694,14 +6713,28 @@ class GraphicsJSR223Delegate(private val vm: VM) { if (graphicsMode == 4) { // 4bpp mode: dithered RGB (RG in fb1, B_ in fb2) - val threshold = bayerKernelsInt[frameCount % 4][4 * (y % 4) + (x % 4)] - val rDithered = ((r + (threshold - 8)) shr 4).coerceIn(0, 15) - val gDithered = ((g + (threshold - 8)) shr 4).coerceIn(0, 15) - val bDithered = ((b + (threshold - 8)) shr 4).coerceIn(0, 15) +// val threshold = bayerKernelsInt[frameCount % 4][4 * (y % 4) + (x % 4)] +// val r4 = ((r + (threshold - 8)) shr 4).coerceIn(0, 15) +// val g4 = ((g + (threshold - 8)) shr 4).coerceIn(0, 15) +// val b4 = ((b + (threshold - 8)) shr 4).coerceIn(0, 15) - gpu.framebuffer[screenPixelIdx] = ((rDithered shl 4) or gDithered).toByte() - gpu.framebuffer2?.set(screenPixelIdx, ((bDithered shl 4) or 15).toByte()) - } else if (graphicsMode == 5) { + val r4 = ditherValue4(r, x, y, frameCount) + val g4 = ditherValue4(g, x, y, frameCount) + val b4 = ditherValue4(b, x, y, frameCount) + + gpu.framebuffer[screenPixelIdx] = ((r4 shl 4) or g4).toByte() + gpu.framebuffer2?.set(screenPixelIdx, ((b4 shl 4) or 15).toByte()) + } + else if (graphicsMode == 5) { + // 5bpp mode: dithered RGB (ARRRRRGG in fb1, GGGBBBBB in fb2; aka Targa 16bpp pixel format) + val r5 = ditherValue5(r, x, y, frameCount) + val g5 = ditherValue5(g, x, y, frameCount) + val b5 = ditherValue5(b, x, y, frameCount) + + gpu.framebuffer[screenPixelIdx] = (0x80 or r5.shl(2) or g5.ushr(3)).toByte() + gpu.framebuffer2?.set(screenPixelIdx, (g5.and(7).shl(5) or b5).toByte()) + } + else if (graphicsMode == 8) { // 8bpp mode: full RGB (R in fb1, G in fb2, B in fb3) gpu.framebuffer[screenPixelIdx] = r.toByte() gpu.framebuffer2?.set(screenPixelIdx, g.toByte()) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 70db451..85c3ccd 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -994,7 +994,7 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi chrrom.pixels.position(0) framebufferOut.setColor(-1);framebufferOut.fill() - if (graphicsMode == 5 && framebuffer4 != null && framebuffer3 != null && framebuffer2 != null) { + if (graphicsMode == 8 && framebuffer4 != null && framebuffer3 != null && framebuffer2 != null) { for (y in 0 until HEIGHT) { var xoff = scanlineOffsets[2L * y].toUint() or scanlineOffsets[2L * y + 1].toUint().shl(8) if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt() @@ -1014,6 +1014,31 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } } } + else if (graphicsMode == 5 && framebuffer2 != null) { + for (y in 0 until HEIGHT) { + var xoff = scanlineOffsets[2L * y].toUint() or scanlineOffsets[2L * y + 1].toUint().shl(8) + if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt() + val xs = (0 + xoff).coerceIn(0, WIDTH - 1)..(WIDTH - 1 + xoff).coerceIn(0, WIDTH - 1) + + if (xoff in -(WIDTH - 1) until WIDTH) { + for (x in xs) { + val rg = framebuffer[y.toLong() * WIDTH + (x - xoff)].toUint() // coerceIn not required as (x - xoff) never escapes 0..559 + val ba = framebuffer2[y.toLong() * WIDTH + (x - xoff)].toUint() // coerceIn not required as (x - xoff) never escapes 0..559 + val r = rg.ushr(2).and(31) + val g = rg.and(3).shl(3) or ba.ushr(5) + val b = ba.and(31) + val a = rg.ushr(7) * 255 + framebufferOut.setColor( + r.shl(27) or r.ushr(2).shl(24) or + g.shl(19) or g.ushr(2).shl(16) or + b.shl(11) or b.ushr(2).shl(8) or + a + ) + framebufferOut.drawPixel(x, y) + } + } + } + } else if (graphicsMode == 4 && framebuffer2 != null) { for (y in 0 until HEIGHT) { var xoff = scanlineOffsets[2L * y].toUint() or scanlineOffsets[2L * y + 1].toUint().shl(8)