gpu: 5bpp mode

This commit is contained in:
minjaesong
2025-11-25 09:53:53 +09:00
parent 08bb33bf27
commit e3099195e4
3 changed files with 76 additions and 16 deletions

View File

@@ -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]

View File

@@ -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())

View File

@@ -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)