diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index 14d1201..96d4160 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -39,7 +39,7 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() // TEST LUA PRG - vmLua.lua.load(gpuTestPalette).call() + //vmLua.lua.load(gpuTestPalette).call() } private var updateAkku = 0.0 @@ -64,7 +64,7 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() } private fun updateGame(delta: Float) { - //paintTestPalette() + paintTestPalette() } private fun paintTestPalette() { @@ -85,9 +85,17 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() } } - vm.poke(-262143L - hwoff, Math.random().times(255.0).toByte()) - vm.poke(-262144L - hwoff, Math.random().times(255.0).toByte()) + //vm.poke(-262143L - hwoff, Math.random().times(255.0).toByte()) + //vm.poke(-262144L - hwoff, Math.random().times(255.0).toByte()) + for (k in 0 until 2240) { + // text foreground + vm.poke(-(254820 + k + 1) - hwoff, 239.toByte()) // white + // text background + vm.poke(-(257060 + k + 1) - hwoff, -1) // transparent + // texts + vm.poke(-(259300 + k + 1) - hwoff, k.toByte()) + } } private val gpuTestPalette = """ diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 325cc3e..9ff0617 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -5,9 +5,12 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.UnsafeHelper +import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.tsvm.AppLoader import net.torvald.tsvm.VM import net.torvald.tsvm.kB +import kotlin.experimental.and class GraphicsAdapter : PeriBase { @@ -18,27 +21,55 @@ class GraphicsAdapter : PeriBase { val channel = it % 4 rgba.shr((3 - channel) * 8).and(255) / 255f } + private val chrrom0 = Texture("./EGA8x14.png") + private val faketex: Texture + + private val spriteAndTextArea = UnsafeHelper.allocate(10660L) + private val unusedArea = ByteArray(92) private val paletteShader = AppLoader.loadShaderInline(DRAW_SHADER_VERT, DRAW_SHADER_FRAG) + private val textShader = AppLoader.loadShaderInline(DRAW_SHADER_VERT, TEXT_TILING_SHADER) private var textmodeBlinkCursor = true - private var graphicsUseSprites = false - private var lastUsedColour = (-1).toByte() + private var currentChrRom = 0 + + + private val textForePixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) + private val textBackPixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) + private val textPixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) + + private var textForeTex = Texture(textForePixmap) + private var textBackTex = Texture(textBackPixmap) + private var textTex = Texture(textPixmap) + + init { framebuffer.blending = Pixmap.Blending.None + textForePixmap.blending = Pixmap.Blending.None + textBackPixmap.blending = Pixmap.Blending.None framebuffer.setColor(-1) framebuffer.fill() + + val pm = Pixmap(1, 1, Pixmap.Format.RGBA8888) + pm.drawPixel(0, 0, -1) + faketex = Texture(pm) + pm.dispose() } override fun peek(addr: Long): Byte? { val adi = addr.toInt() return when (addr) { in 0 until 250880 -> framebuffer.getPixel(adi % WIDTH, adi / WIDTH).toByte() + in 250880 until 250972 -> unusedArea[adi - 250880] + in 250972 until 261632 -> spriteAndTextArea[addr - 250972] in 261632 until 262144 -> peekPalette(adi - 261632) - in 0 until VM.HW_RESERVE_SIZE -> peek(addr % VRAM_SIZE) // HW mirroring + in 0 until VM.HW_RESERVE_SIZE -> { + println("[GraphicsAdapter] mirroring with input address $addr") + peek(addr % VRAM_SIZE) + } // HW mirroring else -> null } } @@ -51,21 +82,39 @@ class GraphicsAdapter : PeriBase { lastUsedColour = byte framebuffer.drawPixel(adi % WIDTH, adi / WIDTH, bi.shl(24)) } + in 250880 until 250972 -> unusedArea[adi - 250880] = byte + in 250972 until 261632 -> spriteAndTextArea[addr - 250972] = byte in 261632 until 262144 -> pokePalette(adi - 261632, byte) - in 0 until VM.HW_RESERVE_SIZE -> poke(addr % VRAM_SIZE, byte) // HW mirroring + in 0 until VM.HW_RESERVE_SIZE -> { + println("[GraphicsAdapter] mirroring with input address $addr") + poke(addr % VRAM_SIZE, byte) + } // HW mirroring } } + private fun getTextmodeAttirbutes(): Byte = (currentChrRom.and(15).shl(4) or textmodeBlinkCursor.toInt()).toByte() + + private fun getGraphicsAttributes(): Byte = graphicsUseSprites.toInt().toByte() + + private fun setTextmodeAttributes(rawbyte: Byte) { + currentChrRom = rawbyte.toInt().and(0b11110000).ushr(4) + textmodeBlinkCursor = rawbyte.and(1) == 1.toByte() + } + + private fun setGraphicsAttributes(rawbyte: Byte) { + graphicsUseSprites = rawbyte.and(1) == 1.toByte() + } + override fun mmio_read(addr: Long): Byte? { return when (addr) { 0L -> (WIDTH % 256).toByte() 1L -> (WIDTH / 256).toByte() 2L -> (HEIGHT % 256).toByte() 3L -> (HEIGHT / 256).toByte() - 4L -> 70 - 5L -> 32 - 6L -> textmodeBlinkCursor.toInt().toByte() - 7L -> graphicsUseSprites.toInt().toByte() + 4L -> TEXT_COLS.toByte() + 5L -> TEXT_ROWS.toByte() + 6L -> getTextmodeAttirbutes() + 7L -> getGraphicsAttributes() 8L -> lastUsedColour in 0 until VM.MMIO_SIZE -> -1 @@ -80,19 +129,104 @@ class GraphicsAdapter : PeriBase { override fun dispose() { framebuffer.dispose() rendertex.dispose() + spriteAndTextArea.destroy() + textForePixmap.dispose() + textBackPixmap.dispose() + textPixmap.dispose() + paletteShader.dispose() + textShader.dispose() + faketex.dispose() + + try { textForeTex.dispose() } catch (_: Throwable) {} + try { textBackTex.dispose() } catch (_: Throwable) {} + + chrrom0.dispose() } fun render(batch: SpriteBatch, x: Float, y: Float) { rendertex.dispose() rendertex = Texture(framebuffer) + batch.begin() + + // initiialise draw batch.color = Color.WHITE batch.shader = paletteShader - paletteShader.setUniform4fv("pal", paletteOfFloats, 0, paletteOfFloats.size) + + // feed palette data // must be done every time the shader is "actually loaded" // try this: if above line precedes 'batch.shader = paletteShader', it won't work + paletteShader.setUniform4fv("pal", paletteOfFloats, 0, paletteOfFloats.size) + + // draw framebuffer batch.draw(rendertex, x, y) + + batch.end() + + + // draw texts or sprites + batch.begin() + + batch.color = Color.WHITE + + if (!graphicsUseSprites) { + // draw texts + + // prepare char buffer texture + for (y in 0 until TEXT_ROWS) { + for (x in 0 until TEXT_COLS) { + val addr = y.toLong() * TEXT_COLS + x + val char = spriteAndTextArea[3940 + 2240 + 2240 + addr].toInt().and(255) + val back = spriteAndTextArea[3940 + 2240 + addr].toInt().and(255) + val fore = spriteAndTextArea[3940 + addr].toInt().and(255) + + textPixmap.setColor(Color(paletteOfFloats[4 * char], paletteOfFloats[4 * char + 1], paletteOfFloats[4 * char + 2], paletteOfFloats[4 * char + 3])) + textPixmap.drawPixel(x, y) + textBackPixmap.setColor(Color(paletteOfFloats[4 * back], paletteOfFloats[4 * back + 1], paletteOfFloats[4 * back + 2], paletteOfFloats[4 * back + 3])) + textBackPixmap.drawPixel(x, y) + textForePixmap.setColor(Color(paletteOfFloats[4 * fore], paletteOfFloats[4 * fore + 1], paletteOfFloats[4 * fore + 2], paletteOfFloats[4 * fore + 3])) + textForePixmap.drawPixel(x, y) + } + } + + // bake char buffer texture + textForeTex.dispose() + textBackTex.dispose() + textTex.dispose() + textForeTex = Texture(textForePixmap) + textBackTex = Texture(textBackPixmap) + textTex = Texture(textPixmap) + + textForeTex.bind(4) + textBackTex.bind(3) + textTex.bind(2) + chrrom0.bind(1) + faketex.bind(0) + + batch.shader = textShader + textShader.setUniformi("tilesAtlas", 1) + textShader.setUniformi("foreColours", 4) + textShader.setUniformi("backColours", 3) + textShader.setUniformi("tilemap", 2) + textShader.setUniformi("u_texture", 0) + textShader.setUniformf("tilesInAxes", TEXT_COLS.toFloat(), TEXT_ROWS.toFloat()) + textShader.setUniformf("screenDimension", WIDTH.toFloat(), HEIGHT.toFloat()) + + batch.draw(faketex, 0f, 0f, WIDTH.toFloat(), HEIGHT.toFloat()) + } + else { + // draw sprites + batch.shader = paletteShader + + // feed palette data + // must be done every time the shader is "actually loaded" + // try this: if above line precedes 'batch.shader = paletteShader', it won't work + paletteShader.setUniform4fv("pal", paletteOfFloats, 0, paletteOfFloats.size) + TODO("sprite draw") + } + + batch.end() batch.shader = null @@ -118,45 +252,142 @@ class GraphicsAdapter : PeriBase { companion object { const val WIDTH = 560 const val HEIGHT = 448 + const val TEXT_COLS = 70 + const val TEXT_ROWS = 32 val VRAM_SIZE = 256.kB() val DRAW_SHADER_FRAG = """ - #version 120 - - varying vec4 v_color; - varying vec2 v_texCoords; - uniform sampler2D u_texture; - uniform vec4 pal[256]; - - float rand(vec2 co){ - return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); - } - - void main(void) { - gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).r * 255.0)]; - //gl_FragColor = vec4(texture2D(u_texture, v_texCoords).rrr, 1.0); - } +#version 120 + +varying vec4 v_color; +varying vec2 v_texCoords; +uniform sampler2D u_texture; +uniform vec4 pal[256]; + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main(void) { + gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).r * 255.0)]; + //gl_FragColor = vec4(texture2D(u_texture, v_texCoords).rrr, 1.0); +} """.trimIndent() val DRAW_SHADER_VERT = """ - #version 120 - - attribute vec4 a_position; - attribute vec4 a_color; - attribute vec2 a_texCoord0; +#version 120 - uniform mat4 u_projTrans; +attribute vec4 a_position; +attribute vec4 a_color; +attribute vec2 a_texCoord0; - varying vec4 v_color; - varying vec2 v_texCoords; +uniform mat4 u_projTrans; - void main() { - v_color = a_color; - v_texCoords = a_texCoord0; - gl_Position = u_projTrans * a_position; - } +varying vec4 v_color; +varying vec2 v_texCoords; + +void main() { + v_color = a_color; + v_texCoords = a_texCoord0; + gl_Position = u_projTrans * a_position; +} """.trimIndent() + val TEXT_TILING_SHADER = """ +#version 120 +#ifdef GL_ES +precision mediump float; +#endif +#extension GL_EXT_gpu_shader4 : enable + +//layout(origin_upper_left) in vec4 gl_FragCoord; // commented; requires #version 150 or later +// gl_FragCoord is origin to bottom-left + +varying vec4 v_color; +varying vec2 v_texCoords; +uniform sampler2D u_texture; + + +uniform vec2 screenDimension; +uniform vec2 tilesInAxes; // basically a screen dimension; vec2(tiles_in_horizontal, tiles_in_vertical) + +uniform sampler2D tilesAtlas; +uniform sampler2D foreColours; +uniform sampler2D backColours; +uniform sampler2D tilemap; + +uniform ivec2 tilesInAtlas = ivec2(16, 16); +uniform ivec2 atlasTexSize = ivec2(128, 224); +ivec2 tileSizeInPx = atlasTexSize / tilesInAtlas; // should be like ivec2(16, 16) + +ivec2 getTileXY(int tileNumber) { + return ivec2(tileNumber % int(tilesInAtlas.x), tileNumber / int(tilesInAtlas.x)); +} + +// return: int=0xaarrggbb +int _colToInt(vec4 color) { + return int(color.b * 255) | (int(color.g * 255) << 8) | (int(color.r * 255) << 16) | (int(color.a * 255) << 24); +} + +// 0x0rggbb where int=0xaarrggbb +// return: [0..1048575] +int getTileFromColor(vec4 color) { + return _colToInt(color) & 0xFFFFF; +} + +void main() { + + // READ THE FUCKING MANUAL, YOU DONKEY !! // + // This code purposedly uses flipped fragcoord. // + // Make sure you don't use gl_FragCoord unknowingly! // + // Remember, if there's a compile error, shader SILENTLY won't do anything // + + + // default gl_FragCoord takes half-integer (represeting centre of the pixel) -- could be useful for phys solver? + // This one, however, takes exact integer by rounding down. // + vec2 flippedFragCoord = vec2(gl_FragCoord.x, screenDimension.y - gl_FragCoord.y); // NO IVEC2!!; this flips Y + + // get required tile numbers // + + vec4 tileFromMap = texture2D(tilemap, flippedFragCoord / tilesInAxes); // raw tile number + vec4 foreColFromMap = texture2D(foreColours, flippedFragCoord / tilesInAxes); + vec4 backColFromMap = texture2D(backColours, flippedFragCoord / tilesInAxes); + + int tile = getTileFromColor(tileFromMap); + ivec2 tileXY = getTileXY(tile); + + // cauculate the UV coord value for texture sampling // + + vec2 coordInTile = mod(flippedFragCoord, tileSizeInPx) / tileSizeInPx; // 0..1 regardless of tile position in atlas + + // don't really need highp here; read the GLES spec + vec2 singleTileSizeInUV = vec2(1) / tilesInAtlas; // constant 0.00390625 for unmodified default uniforms + + vec2 uvCoordForTile = coordInTile * singleTileSizeInUV; // 0..0.00390625 regardless of tile position in atlas + + vec2 uvCoordOffsetTile = tileXY * singleTileSizeInUV; // where the tile starts in the atlas, using uv coord (0..1) + + // get final UV coord for the actual sampling // + + vec2 finalUVCoordForTile = (uvCoordForTile + uvCoordOffsetTile);// where we should be actually looking for in atlas, using UV coord (0..1) + + // blending a breakage tex with main tex // + + vec4 tileCol = texture2D(tilesAtlas, finalUVCoordForTile); + + // apply colour + if (tileCol.a > 0.1) { + gl_FragColor = foreColFromMap; + } + else { + gl_FragColor = backColFromMap; + } + +} + + +""".trimIndent() + val DEFAULT_PALETTE = intArrayOf( // 0b rrrrrrrr gggggggg bbbbbbbb aaaaaaaa 255, 17663, diff --git a/terranmon.txt b/terranmon.txt index a596f66..2fa0fb5 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -39,35 +39,34 @@ User area: 8 MB, hardware area: 8 MB VRAM Bank 0 (256 kB) -From the end of the memory space: -512 bytes - Stored in following pattern: 0b rrrr gggg, 0b bbbb aaaa, .... -IF graphics_mode THEN - (41 sprites : 260 bytes each -> 10660 bytes) - 0th sprite is always the GUI cursor - 256 bytes - 16x16 texture for the sprite - 2 bytes - 0b rr0000xx xxxxxxxx (r: rotation, x: x-position) - 2 bytes - Ob hv0000xy yyyyyyyy (h: horizontal flip, v: vertical flip, x: show/hide, y: y-position) -ELSE - 2240 bytes - Text buffer of 70x32 (8x14 character size) - 2240 bytes - Text background colours - 2240 bytes - Text foreground colours - 2 bytes - Cursor position in: (y*32 + x) - 3938 bytes - *Unused* -FI From the start of the memory space: 250880 bytes Framebuffer 92 bytes *Unused* +IF graphics_mode THEN + (41 sprites : 260 bytes each -> 10660 bytes) + 0th sprite is always the GUI cursor + 2 bytes + Ob hv0000xy yyyyyyyy (h: horizontal flip, v: vertical flip, x: show/hide, y: y-position) + 2 bytes + 0b rr0000xx xxxxxxxx (r: rotation, x: x-position) + 256 bytes + 16x16 texture for the sprite +ELSE + 3938 bytes + *Unused* + 2 bytes + Cursor position in: (y*32 + x) + 2240 bytes + Text foreground colours + 2240 bytes + Text background colours + 2240 bytes + Text buffer of 70x32 (8x14 character size, and yes: actual character data is on the bottom) +FI +512 bytes + Palette stored in following pattern: 0b rrrr gggg, 0b bbbb aaaa, .... MMIO @@ -81,7 +80,7 @@ MMIO Text mode rows 1 bytes RW Text-mode attributes - 0b 0000 000c (c: Cursor blink) + 0b kkkk 000c (k: currently using character rom, c: Cursor blink) 1 bytes RW Graphics-mode attributes 0b 0000 000g (g: Use sprites(wipes out text buffer))