graphics adapter: render framebuffer is back to 32bpp to support direct colour mode

This commit is contained in:
minjaesong
2022-04-18 01:16:13 +09:00
parent 59ce7e5fac
commit 9e990ccdf4
2 changed files with 193 additions and 16 deletions

View File

@@ -253,6 +253,69 @@ MMIO
current TTY background colour (useful for print() function) current TTY background colour (useful for print() function)
11 RO 11 RO
Number of Banks, or VRAM size (1 = 256 kB, max 4) Number of Banks, or VRAM size (1 = 256 kB, max 4)
12 RW
Graphics Mode
0: 560x448, 256 Colours, 1 layer
1: 280x224, 256 Colours, 4 layers
2: 280x224, 4096 Colours, 2 layers
3: 560x448, 256 Colours, 2 layers (if bank 2 is not installed, will fall back to mode 0)
4: 560x448, 4096 Colours, 1 layer (if bank 2 is not installed, will fall back to mode 0)
4096 is also known as "direct colour mode" (4096 colours * 16 transparency -> 65536 colours)
13 RW
Layer Arrangement
If 4 layers are used:
Num LO<->HI
0 1234
1 1243
2 1324
3 1342
4 1423
5 1432
6 2134
7 2143
8 2314
9 2341
10 2413
11 2431
12 3124
13 3142
14 3214
15 3241
16 3412
17 3421
18 4123
19 4132
20 4213
21 4231
22 4312
23 4321
If 2 layers are used:
Num LO<->HI
0 12
1 12
2 12
3 12
4 12
5 12
6 12
7 21
8 21
9 21
10 21
11 21
12 12
13 12
14 21
15 21
16 12
17 21
18 12
19 12
20 21
21 21
22 12
23 21
If 1 layer is used, this field will do nothing and always fall back to 0
Text-mode-font-ROM is immutable and does not belong to VRAM Text-mode-font-ROM is immutable and does not belong to VRAM
Even in the text mode framebuffer is still being drawn onto the screen, and the texts are drawn on top of it Even in the text mode framebuffer is still being drawn onto the screen, and the texts are drawn on top of it

View File

@@ -63,7 +63,7 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
protected val TAB_SIZE = 8 protected val TAB_SIZE = 8
internal val framebuffer = Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) internal val framebuffer = Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha)
internal val framebuffer2 = Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) internal val framebuffer2 = Pixmap(WIDTH, HEIGHT, Pixmap.Format.RGBA8888)
protected var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888) protected var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888)
internal val paletteOfFloats = FloatArray(1024) { internal val paletteOfFloats = FloatArray(1024) {
val rgba = DEFAULT_PALETTE[it / 4] val rgba = DEFAULT_PALETTE[it / 4]
@@ -108,6 +108,9 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
private val outFBOregion = Array(2) { TextureRegion(outFBOs[it].colorBufferTexture) } private val outFBOregion = Array(2) { TextureRegion(outFBOs[it].colorBufferTexture) }
private val outFBObatch = SpriteBatch() private val outFBObatch = SpriteBatch()
private var graphicsMode = 0
private var layerArrangement = 0
private val memTextCursorPosOffset = 0L private val memTextCursorPosOffset = 0L
private val memTextForeOffset = 2L private val memTextForeOffset = 2L
@@ -269,6 +272,8 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
9L -> ttyFore.toByte() 9L -> ttyFore.toByte()
10L -> ttyBack.toByte() 10L -> ttyBack.toByte()
11L -> sgr.bankCount.toByte() 11L -> sgr.bankCount.toByte()
12L -> graphicsMode.toByte()
13L -> layerArrangement.toByte()
in 0 until VM.MMIO_SIZE -> -1 in 0 until VM.MMIO_SIZE -> -1
else -> null else -> null
@@ -276,7 +281,16 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
} }
override fun mmio_write(addr: Long, byte: Byte) { override fun mmio_write(addr: Long, byte: Byte) {
TODO("Not yet implemented") val bi = byte.toUint()
when (addr) {
6L -> setTextmodeAttributes(byte)
7L -> setGraphicsAttributes(byte)
9L -> { ttyFore = bi }
10L -> { ttyBack = bi }
12L -> { graphicsMode = bi }
13L -> { layerArrangement = bi }
else -> null
}
} }
private fun runCommand(opcode: Byte) { private fun runCommand(opcode: Byte) {
@@ -713,6 +727,8 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
unusedArea[1].toInt().and(15).toFloat() / 15f, unusedArea[1].toInt().and(15).toFloat() / 15f,
unusedArea[2].toInt().and(15).toFloat() / 15f, 1f) unusedArea[2].toInt().and(15).toFloat() / 15f, 1f)
private val isRefSize = (config.width == 560 && config.height == 448)
open fun render(delta: Float, uiBatch: SpriteBatch, xoff: Float, yoff: Float, flipY: Boolean = false, uiFBO: FrameBuffer? = null) { open fun render(delta: Float, uiBatch: SpriteBatch, xoff: Float, yoff: Float, flipY: Boolean = false, uiFBO: FrameBuffer? = null) {
uiFBO?.end() uiFBO?.end()
@@ -722,21 +738,64 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi
chrrom.pixels.position(0) chrrom.pixels.position(0)
framebuffer2.setColor(-1);framebuffer2.fill() framebuffer2.setColor(-1);framebuffer2.fill()
for (y in 0 until config.height) { if (isRefSize && graphicsMode == 1) {
var xoff = unusedArea[20L + 2*y].toUint().shl(8) or unusedArea[20L + 2*y + 1].toUint() val layerOrder = LAYERORDERS4[layerArrangement]
if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt() for (y in 0..223) {
val xs = (0+xoff).coerceIn(0,config.width-1) .. (config.width-1+xoff).coerceIn(0,config.width-1) var xoff = unusedArea[20L + 2 * y].toUint().shl(8) or unusedArea[20L + 2 * y + 1].toUint()
if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt()
val xs =
(0 + xoff).coerceIn(0, 279)..(279 + xoff).coerceIn(0, 279)
if (xoff in -(config.width-1)..config.width-1) { if (xoff in -(280 - 1) until 280) {
for (x in xs) { for (x in xs) {
// this only works because framebuffer is guaranteed to be 8bpp val colour = layerOrder.map {
framebuffer2.pixels.put(y*config.width+x, val colourIndex = framebuffer.pixels.get(280 * 224 * it + (y * 224 + x)).toUint()
framebuffer.pixels.get(y*config.width + (x - xoff)) // coerceIn not required as (x - xoff) never escapes 0..559 Color(paletteOfFloats[4*colourIndex], paletteOfFloats[4*colourIndex+1], paletteOfFloats[4*colourIndex+2], paletteOfFloats[4*colourIndex+3])
) }.fold(Color(0)) { dest, src ->
// manually alpha compositing
// out_color = {src_color * src_alpha + dest_color * dest_alpha * (1-src_alpha)} / out_alpha
// see https://gamedev.stackexchange.com/a/115786
val outAlpha = (1f - (1f - dest.a) * (1f - src.a)).coerceIn(0.0001f, 1f)
// src.a + dest.a - src.a*dest.a)
Color(
(src.r * src.a + dest.r * dest.a * (1f - src.a)) / outAlpha,
(src.g * src.a + dest.g * dest.a * (1f - src.a)) / outAlpha,
(src.b * src.a + dest.b* dest.a * (1f - src.a)) / outAlpha,
outAlpha
)
}
framebuffer2.setColor(colour)
framebuffer2.drawPixel(x*2, y*2)
framebuffer2.drawPixel(x*2+1, y*2)
framebuffer2.drawPixel(x*2, y*2+1)
framebuffer2.drawPixel(x*2+1, y*2+1)
}
} }
} }
} }
else {
for (y in 0 until config.height) {
var xoff = unusedArea[20L + 2 * y].toUint().shl(8) or unusedArea[20L + 2 * y + 1].toUint()
if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt()
val xs =
(0 + xoff).coerceIn(0, config.width - 1)..(config.width - 1 + xoff).coerceIn(0, config.width - 1)
if (xoff in -(config.width - 1) until config.width) {
for (x in xs) {
// this only works because framebuffer is guaranteed to be 8bpp
/*framebuffer2.pixels.put(
y * config.width + x,
framebuffer.pixels.get(y * config.width + (x - xoff)) // coerceIn not required as (x - xoff) never escapes 0..559
)*/
val colourIndex = framebuffer.pixels.get(y * config.width + (x - xoff)).toUint() // coerceIn not required as (x - xoff) never escapes 0..559
framebuffer2.setColor(paletteOfFloats[4*colourIndex], paletteOfFloats[4*colourIndex+1], paletteOfFloats[4*colourIndex+2], paletteOfFloats[4*colourIndex+3])
framebuffer2.drawPixel(x, y)
}
}
}
}
chrrom0.dispose() chrrom0.dispose()
chrrom0 = Texture(chrrom) chrrom0 = Texture(chrrom)
@@ -973,7 +1032,7 @@ uniform sampler2D u_texture;
uniform vec4 pal[256]; uniform vec4 pal[256];
void main(void) { void main(void) {
gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)]; gl_FragColor = texture2D(u_texture, v_texCoords);
} }
""".trimIndent() """.trimIndent()
@@ -989,7 +1048,8 @@ float intensitySteps = 4.0;
uniform vec4 lcdBaseCol; uniform vec4 lcdBaseCol;
void main(void) { void main(void) {
vec4 palCol = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)]; // vec4 palCol = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)];
vec4 palCol = texture2D(u_texture, v_texCoords);
float lum = ceil((3.0 * palCol.r + 4.0 * palCol.g + palCol.b) / 8.0 * intensitySteps) / intensitySteps; float lum = ceil((3.0 * palCol.r + 4.0 * palCol.g + palCol.b) / 8.0 * intensitySteps) / intensitySteps;
vec4 outIntensity = vec4(vec3(1.0 - lum), palCol.a); vec4 outIntensity = vec4(vec3(1.0 - lum), palCol.a);
@@ -1010,8 +1070,8 @@ float intensitySteps = 4.0;
uniform vec4 lcdBaseCol; uniform vec4 lcdBaseCol;
void main(void) { void main(void) {
vec4 palCol = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)]; // vec4 palCol = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)];
float lum = floor((3.0 * palCol.r + 4.0 * palCol.g + palCol.b) / 8.0 * intensitySteps) / intensitySteps; vec4 palCol = texture2D(u_texture, v_texCoords); float lum = floor((3.0 * palCol.r + 4.0 * palCol.g + palCol.b) / 8.0 * intensitySteps) / intensitySteps;
vec4 outIntensity = vec4(vec3(lum), palCol.a); vec4 outIntensity = vec4(vec3(lum), palCol.a);
// LCD output will invert the luminosity. That is, normally white colour will be black on PM-LCD. // LCD output will invert the luminosity. That is, normally white colour will be black on PM-LCD.
@@ -1765,6 +1825,60 @@ void main() {
it.and(255).div(255f) it.and(255).div(255f)
) )
} }
val LAYERORDERS4 = listOf( // [drawn first, second, third, fourth], zero-indexed
"1234",
"1243",
"1324",
"1342",
"1423",
"1432",
"2134",
"2143",
"2314",
"2341",
"2413",
"2431",
"3124",
"3142",
"3214",
"3241",
"3412",
"3421",
"4123",
"4132",
"4213",
"4231",
"4312",
"4321",
).map { s -> (0..3).map { s[it].toInt() - 61 } }
val LAYERORDERS2 = listOf( // [drawn first, second], zero-indexed
"12",
"12",
"12",
"12",
"12",
"12",
"12",
"21",
"21",
"21",
"21",
"21",
"12",
"12",
"21",
"21",
"12",
"21",
"12",
"12",
"21",
"21",
"12",
"21",
).map { s -> (0..1).map { s[it].toInt() - 61 } }
} }
} }