From cbcd1ac7af87d80a5ac559c5e69ca3cff41fc2f6 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 9 May 2020 03:13:52 +0900 Subject: [PATCH] gpu to actually use 8bpp texture format to make memcpying easier --- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 21 +++ src/net/torvald/tsvm/VM.kt | 53 ++++++- src/net/torvald/tsvm/VMGUI.kt | 62 ++++---- src/net/torvald/tsvm/VMJSR223Delegate.kt | 10 +- src/net/torvald/tsvm/firmware/Firmware.kt | 2 +- src/net/torvald/tsvm/peripheral/GlassTty.kt | 137 ++++++++++++++++++ .../tsvm/peripheral/GraphicsAdapter.kt | 64 ++++---- src/net/torvald/tsvm/peripheral/TermSim.kt | 7 + terranmon.txt | 2 +- 9 files changed, 280 insertions(+), 78 deletions(-) create mode 100644 src/net/torvald/tsvm/peripheral/GlassTty.kt create mode 100644 src/net/torvald/tsvm/peripheral/TermSim.kt diff --git a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index fe46a9c..81514a5 100644 --- a/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -1,6 +1,8 @@ package net.torvald.tsvm +import net.torvald.UnsafeHelper import net.torvald.tsvm.peripheral.GraphicsAdapter +import sun.nio.ch.DirectBuffer class GraphicsJSR223Delegate(val vm: VM) { @@ -25,4 +27,23 @@ class GraphicsJSR223Delegate(val vm: VM) { } } + fun loadBulk(fromAddr: Int, toAddr: Int, length: Int) { + getFirstGPU()?.let { + it._loadbulk(fromAddr, toAddr, length) + } + } + + private fun GraphicsAdapter._loadbulk(fromAddr: Int, toAddr: Int, length: Int) { + if (toAddr < 250880) { + UnsafeHelper.memcpy( + vm.usermem.ptr + fromAddr, + (this.framebuffer.pixels as DirectBuffer).address() + toAddr, + length.toLong() + ) + } + else if (toAddr < 250972) { + + } + } + } \ No newline at end of file diff --git a/src/net/torvald/tsvm/VM.kt b/src/net/torvald/tsvm/VM.kt index e3e105f..8a718e7 100644 --- a/src/net/torvald/tsvm/VM.kt +++ b/src/net/torvald/tsvm/VM.kt @@ -7,6 +7,8 @@ import net.torvald.tsvm.firmware.Firmware.Companion.toLuaValue import net.torvald.tsvm.peripheral.IOSpace import net.torvald.tsvm.peripheral.PeriBase import org.luaj.vm2.LuaValue +import java.util.* +import kotlin.math.ceil import kotlin.random.Random /** @@ -55,6 +57,8 @@ class VM( val id = java.util.Random().nextInt() val memsize = minOf(USER_SPACE_SIZE, _memsize.toLong()) + private val MALLOC_UNIT = 64 + private val mallocBlockSize = (memsize / MALLOC_UNIT).toInt() internal val usermem = UnsafeHelper.allocate(memsize) @@ -135,8 +139,55 @@ class VM( (memspace as PeriBase).peek(offset) } - fun Byte.toLuaValue() = LuaValue.valueOf(this.toInt()) + private val mallocMap = BitSet(mallocBlockSize) + private val mallocSizes = HashMap() // HashMap + private fun findEmptySpace(blockSize: Int): Int? { + var cursorHead = 0 + var cursorTail: Int + val cursorHeadMaxInclusive = mallocBlockSize - blockSize + while (cursorHead <= cursorHeadMaxInclusive) { + cursorHead = mallocMap.nextClearBit(cursorHead) + cursorTail = cursorHead + blockSize - 1 + if (cursorTail > mallocBlockSize) return null + if (mallocMap.get(cursorTail) == false) { + var isNotEmpty = false + for (k in cursorHead..cursorTail) { + isNotEmpty = isNotEmpty or mallocMap[k] + } + + if (!isNotEmpty) { + mallocMap.set(cursorHead, cursorTail + 1) + return cursorHead + } + } + cursorHead = cursorTail + 1 + } + return null + } + + internal fun malloc(size: Int): Int { + val allocBlocks = ceil(size.toDouble() / MALLOC_UNIT).toInt() + val blockStart = findEmptySpace(allocBlocks) + if (blockStart == null) throw OutOfMemoryError() + + mallocSizes[blockStart] = allocBlocks + return blockStart * MALLOC_UNIT + } + + internal fun free(ptr: Int) { + val index = ptr / MALLOC_UNIT + val count = mallocSizes[index] + if (count == null) throw OutOfMemoryError() + + mallocMap.set(index, index + count, false) + mallocSizes.remove(index) + } + + //fun Byte.toLuaValue() = LuaValue.valueOf(this.toInt()) + + + internal data class VMNativePtr(val address: Int, val size: Int) } data class PeripheralEntry( diff --git a/src/net/torvald/tsvm/VMGUI.kt b/src/net/torvald/tsvm/VMGUI.kt index dcb3d30..9eef719 100644 --- a/src/net/torvald/tsvm/VMGUI.kt +++ b/src/net/torvald/tsvm/VMGUI.kt @@ -25,7 +25,7 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() override fun create() { super.create() - gpu = GraphicsAdapter(lcdMode = true) + gpu = GraphicsAdapter(lcdMode = false) vm.peripheralTable[1] = PeripheralEntry( VM.PERITYPE_GRAPHICS, @@ -43,15 +43,10 @@ class VMGUI(val appConfig: LwjglApplicationConfiguration) : ApplicationAdapter() Gdx.gl20.glViewport(0, 0, appConfig.width, appConfig.height) - // TEST LUA PRG - //vmRunner = VMRunnerFactory(vm, "lua") - //vmRunner.executeCommand(gpuTestPalette) - // TEST KTS PRG - vmRunner = VMRunnerFactory(vm, "kts") - //vmRunner.executeCommand(gpuTestPaletteKt) - //launch { vmRunner.executeCommand(gpuTestPaletteKt) } } + // TEST PRG + vmRunner = VMRunnerFactory(vm, "js") coroutineJob = GlobalScope.launch { - vmRunner.executeCommand(gpuTestPaletteKt) + vmRunner.executeCommand(gpuTestPaletteJs) } } @@ -166,6 +161,7 @@ while (true) { } """.trimIndent() + private val gpuTestPalette = """ local vm = require("rawmem") local bit = require("bit32") @@ -213,55 +209,54 @@ while true do end """.trimIndent() - private val gpuTestPaletteJs = """ -var w = 560 -var h = 448 -var hwoff = 1048576 - -print(typeof print) //function -print(typeof vm.poke) //function + private val jscode = """ +var w = 560; +var h = 448; +var hwoff = 1048576; function inthash(x) { - x = ((x >> 16) ^ x) * 0x45d9f3b - x = ((x >> 16) ^ x) * 0x45d9f3b - x = (x >> 16) ^ x - return x + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; } -var rng = Math.floor(Math.random() * 2147483647) + 1 +var rng = Math.floor(Math.random() * 2147483647) + 1; while (true) { - var tstart = vm.nanoTime() + var tstart = vm.nanoTime(); for (var y = 0; y < 360; y++) { for (var x = 0; x < w; x++) { - var palnum = 20 * Math.floor(y / 30) + Math.floor(x / 28) - vm.poke(-(y * w + x + 1) - hwoff, inthash(palnum + rng)) + var palnum = 20 * Math.floor(y / 30) + Math.floor(x / 28); + vm.poke(-(y * w + x + 1) - hwoff, inthash(palnum + rng)); } } for (var y = 360; y < h; y++) { for (var x = 0; x < w; x++) { - var palnum = 240 + Math.floor(x / 35) - vm.poke(-(y * w + x + 1) - hwoff, palnum) + var palnum = 240 + Math.floor(x / 35); + vm.poke(-(y * w + x + 1) - hwoff, palnum); } } for (var k = 0; k < 2560; k++) { - vm.poke(-(253952 + k + 1) - hwoff, -2) // white - vm.poke(-(253952 + 2560 + k + 1) - hwoff, -1) // transparent - vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255)) + vm.poke(-(253952 + k + 1) - hwoff, -2); // transparent + vm.poke(-(253952 + 2560 + k + 1) - hwoff, -1); // white + vm.poke(-(253952 + 2560*2 + k + 1) - hwoff, Math.round(Math.random() * 255)); } - rng = inthash(rng) + rng = inthash(rng); - var tend = vm.nanoTime() + var tend = vm.nanoTime(); - print("Apparent FPS: " + (1000000000 / (tend - tstart))) + print("Apparent FPS: " + (1000000000 / (tend - tstart))); } +""".trimIndent() + + private val gpuTestPaletteJs = "eval('${jscode.replace(Regex("//[^\\n]*"), "").replace('\n', ' ')}')" - """.trimIndent() private val gpuTestPaletteJava = """ int w = 560; @@ -370,6 +365,7 @@ while True: super.dispose() batch.dispose() coroutineJob.cancel() + vm.dispose() } } diff --git a/src/net/torvald/tsvm/VMJSR223Delegate.kt b/src/net/torvald/tsvm/VMJSR223Delegate.kt index fddcf90..7da90c4 100644 --- a/src/net/torvald/tsvm/VMJSR223Delegate.kt +++ b/src/net/torvald/tsvm/VMJSR223Delegate.kt @@ -10,13 +10,7 @@ class VMJSR223Delegate(val vm: VM) { fun poke(addr: Int, value: Int) = vm.poke(addr.toLong(), value.toByte()) fun peek(addr: Int) = vm.peek(addr.toLong()) fun nanoTime() = System.nanoTime() - fun dmagload(from: Int, to: Int, length: Int) { - val periid = vm.findPeribyType("gpu") - if (periid == null) - throw IllegalStateException("GPU not found") - else { - (vm.peripheralTable[periid].peripheral as GraphicsAdapter).bulkLoad(vm, from.toLong(), to.toLong(), length.toLong()) - } - } + fun malloc(size: Int) = vm.malloc(size) + fun free(ptr: Int) = vm.free(ptr) } \ No newline at end of file diff --git a/src/net/torvald/tsvm/firmware/Firmware.kt b/src/net/torvald/tsvm/firmware/Firmware.kt index 5dce021..d92fd7d 100644 --- a/src/net/torvald/tsvm/firmware/Firmware.kt +++ b/src/net/torvald/tsvm/firmware/Firmware.kt @@ -53,7 +53,7 @@ internal class Firmware(val vm: VM) : TwoArgFunction() { val t = LuaTable() t["poke"] = Poke(vm) t["peek"] = Peek(vm) - t["nanotime"] = object : ZeroArgFunction() { + t["nanoTime"] = object : ZeroArgFunction() { override fun call(): LuaValue { return LuaValue.valueOf(System.nanoTime().toDouble()) } diff --git a/src/net/torvald/tsvm/peripheral/GlassTty.kt b/src/net/torvald/tsvm/peripheral/GlassTty.kt new file mode 100644 index 0000000..a4952a9 --- /dev/null +++ b/src/net/torvald/tsvm/peripheral/GlassTty.kt @@ -0,0 +1,137 @@ +package net.torvald.tsvm.peripheral + +import com.badlogic.gdx.utils.Queue + +/** + * Implements standard TTY that can interpret some of the ANSI escape sequences + */ +abstract class GlassTty(val TEXT_ROWS: Int, val TEXT_COLS: Int) { + + abstract fun getCursorPos(): Pair + abstract fun setCursorPos(x: Int, y: Int) + + abstract var rawCursorPos: Int + abstract var blinkCursor: Boolean + + abstract var ttyFore: Int + abstract var ttyBack: Int + abstract var ttyRawMode: Boolean + + abstract fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte = ttyFore.toByte(), backColour: Byte = ttyBack.toByte()) + + private var ttyMode = Queue() // stores escape sequences like: [ + + /** + * ONLY accepts a character to either process the escape sequence, or say the input character is allowed to print. + * This function will alter the internal state of the TTY intepreter (aka this very class) + * + * Any unrecognisable escape sequence will result the internal state to be reset but the character WILL NOT be marked + * as printable. + * + * @return true if character should be printed as-is + */ + private fun acceptChar(char: Byte): Boolean { + TODO() + + if (ESC == char) { + // beginning of the escape sequence + if (ttyMode.isEmpty) { + ttyMode.addLast(char) + } + else { + return true + } + } + // Any escape sequences + else if (ttyMode.size >= 1) { + // make a state machine; if the machine should move into accepting state: accept a char, and return false; + // for a rejecting state (sequence not in the transition table): clear the ttyMode, and return false; + // for a terminating state (escape sequence is terminated successfully): run interpretCSI(), and return false. + + + return false + } + } + + private fun interpretEscapeSequence() { + TODO() + } + + + + private val ESC = 0x1B.toByte() + private val LBRACKET = 0x5B.toByte() + + private val FORE_DEFAULT = 254 + private val BACK_DEFAULT = 255 + +} + +/* +Note 1. State machine for Escape sequence + +digraph G { + + ESC -> Reset [label="c"] + ESC -> CSI [label="["] + + CSI -> numeral [label="0..9"] + CSI -> CursorUp [label="A"] + CSI -> CursorDown [label="B"] + CSI -> CursorFwd [label="C"] + CSI -> CursorBack [label="D"] + CSI -> CursorNextLine [label="E"] + CSI -> CursorPrevLine [label="F"] + CSI -> CursorY [label="G"] + CSI -> EraseDisp [label="J"] + CSI -> EraseLine [label="K"] + CSI -> ScrollUp [label="S"] + CSI -> ScrollDown [label="T"] + + numeral -> numeral [label="0..9"] + numeral -> CursorUp [label="A"] + numeral -> CursorDown [label="B"] + numeral -> CursorFwd [label="C"] + numeral -> CursorBack [label="D"] + numeral -> CursorNextLine [label="E"] + numeral -> CursorPrevLine [label="F"] + numeral -> CursorY [label="G"] + numeral -> EraseDisp [label="J"] + numeral -> EraseLine [label="K"] + numeral -> ScrollUp [label="S"] + numeral -> ScrollDown [label="T"] + + numeral -> SGR [label="(any unacceptable char)"] + + numeral -> separator1 [label=";"] + separator1 -> numeral2 [label="0..9"] + numeral2 -> numeral2 [label="0..9"] + numeral2 -> CursorPos [label="H"] + + numeral2 -> separator2 [label=";"] + + separator2 -> numeral3 [label="0..9"] + numeral3 -> numeral3 [label="0..9"] + + numeral3 -> "SGR-Colour" [label="(any unacceptable char)"] + + ESC [shape=Mdiamond] + Reset -> end + CursorUp -> end + CursorDown -> end + CursorFwd -> end + CursorBack -> end + CursorNextLine -> end + CursorPrevLine -> end + CursorY -> end + EraseDisp -> end + EraseLine -> end + ScrollUp -> end + ScrollDown -> end + CursorPos -> end + SGR -> end + "SGR-Colour" -> end + end [shape=Msquare] +} + + */ \ No newline at end of file diff --git a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 11dac9d..bf52ec5 100644 --- a/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -11,9 +11,9 @@ import net.torvald.tsvm.kB import sun.nio.ch.DirectBuffer import kotlin.experimental.and -class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { +class GraphicsAdapter(val lcdMode: Boolean = false) : GlassTty(Companion.TEXT_ROWS, Companion.TEXT_COLS), PeriBase { - internal val framebuffer = Pixmap(WIDTH, HEIGHT, Pixmap.Format.RGBA8888) + internal val framebuffer = Pixmap(WIDTH, HEIGHT, Pixmap.Format.Alpha) private var rendertex = Texture(1, 1, Pixmap.Format.RGBA8888) internal val paletteOfFloats = FloatArray(1024) { val rgba = DEFAULT_PALETTE[it / 4] @@ -29,15 +29,16 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { private val paletteShader = AppLoader.loadShaderInline(DRAW_SHADER_VERT, if (lcdMode) DRAW_SHADER_FRAG_LCD else DRAW_SHADER_FRAG) private val textShader = AppLoader.loadShaderInline(DRAW_SHADER_VERT, if (lcdMode) TEXT_TILING_SHADER_LCD else TEXT_TILING_SHADER) - private var textmodeBlinkCursor = true + override var blinkCursor = true + override var ttyRawMode = false private var graphicsUseSprites = false private var lastUsedColour = (-1).toByte() private var currentChrRom = 0 private var chrWidth = 7f private var chrHeight = 14f - private var ttyFore: Int = 254 // cannot be Byte - private var ttyBack: Int = 255 // cannot be Byte + override var ttyFore: Int = 254 // cannot be Byte + override var ttyBack: Int = 255 // cannot be Byte private val textForePixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) private val textBackPixmap = Pixmap(TEXT_COLS, TEXT_ROWS, Pixmap.Format.RGBA8888) @@ -52,7 +53,14 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { private val memTextBackOffset = 2980L + 2560 private val memTextOffset = 2980L + 2560 + 2560 - private fun getTtyCursorPos() = spriteAndTextArea.getShort(memTextCursorPosOffset) % TEXT_COLS to spriteAndTextArea.getShort(3938L) / TEXT_COLS + override var rawCursorPos: Int + get() = spriteAndTextArea.getShort(memTextCursorPosOffset).toInt() + set(value) { spriteAndTextArea.setShort(memTextCursorPosOffset, value.toShort()) } + + override fun getCursorPos() = rawCursorPos % TEXT_COLS to rawCursorPos / TEXT_COLS + override fun setCursorPos(x: Int, y: Int) { + rawCursorPos = toTtyTextOffset(x, y) + } private fun toTtyTextOffset(x: Int, y: Int) = y * TEXT_COLS + x init { @@ -71,12 +79,15 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { // when in text mode, and that's undesired behaviour // -1 is preferred because it points to the colour CLEAR, and it's constant. spriteAndTextArea.fillWith(-1) + + + println(framebuffer.pixels.limit()) } 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 0 until 250880 -> framebuffer.pixels.get(adi)//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) @@ -94,7 +105,7 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { when (addr) { in 0 until 250880 -> { lastUsedColour = byte - framebuffer.drawPixel(adi % WIDTH, adi / WIDTH, bi.shl(24)) + framebuffer.pixels.put(adi, byte) } 250883L -> { unusedArea[adi - 250880] = byte @@ -110,13 +121,16 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { } } - private fun getTextmodeAttirbutes(): Byte = (currentChrRom.and(15).shl(4) or textmodeBlinkCursor.toInt()).toByte() + private fun getTextmodeAttirbutes(): Byte = (currentChrRom.and(15).shl(4) or + ttyRawMode.toInt().shl(1) or + blinkCursor.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() + blinkCursor = rawbyte.and(0b0001) != 0.toByte() + ttyRawMode = rawbyte.and(0b0010) != 0.toByte() } private fun setGraphicsAttributes(rawbyte: Byte) { @@ -173,35 +187,17 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { * @param to memory "offset" in Graphics Adapter's memory space, starts from zero. * @param length how many bytes should be moved */ - fun bulkLoad(vm: VM, from: Long, to: Long, length: Long) { + /*fun bulkLoad(vm: VM, from: Long, to: Long, length: Long) { UnsafeHelper.unsafe.copyMemory(null, vm.usermem.ptr + from, (framebuffer.pixels as DirectBuffer).address(), to, length) - } + }*/ - private fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte = ttyFore.toByte(), backColour: Byte = ttyBack.toByte()) { + override fun putChar(x: Int, y: Int, text: Byte, foreColour: Byte, backColour: Byte) { val textOff = toTtyTextOffset(x, y) spriteAndTextArea[memTextForeOffset + textOff] = foreColour spriteAndTextArea[memTextBackOffset + textOff] = backColour spriteAndTextArea[memTextOffset + textOff] = text } - private fun advanceCursor() { - spriteAndTextArea.setShort( - memTextCursorPosOffset, - ((spriteAndTextArea.getShort(memTextCursorPosOffset) + 1) % (TEXT_COLS * TEXT_ROWS)).toShort() - ) - } - - // how TTY should work with all those ASCII control characters - fun print(char: Byte) { - val (cx, cy) = getTtyCursorPos() - when (char) { - in 0x20..0x7E, in 0x80..0xFF -> { - putChar(cx, cy, char) - advanceCursor() - } - } - } - override fun dispose() { framebuffer.dispose() rendertex.dispose() @@ -225,7 +221,7 @@ class GraphicsAdapter(val lcdMode: Boolean = false) : PeriBase { fun render(delta: Float, batch: SpriteBatch, x: Float, y: Float) { rendertex.dispose() - rendertex = Texture(framebuffer) + rendertex = Texture(framebuffer, Pixmap.Format.RGBA8888, false) batch.shader = null @@ -383,7 +379,7 @@ float rand(vec2 co){ } void main(void) { - gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).r * 255.0)]; + gl_FragColor = pal[int(texture2D(u_texture, v_texCoords).a * 255.0)]; } """.trimIndent() @@ -403,7 +399,7 @@ float rand(vec2 co){ } void main(void) { - vec4 palCol = pal[int(texture2D(u_texture, v_texCoords).r * 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 outIntensity = vec4(vec3(1.0 - lum), palCol.a); diff --git a/src/net/torvald/tsvm/peripheral/TermSim.kt b/src/net/torvald/tsvm/peripheral/TermSim.kt new file mode 100644 index 0000000..743c7e2 --- /dev/null +++ b/src/net/torvald/tsvm/peripheral/TermSim.kt @@ -0,0 +1,7 @@ +package net.torvald.tsvm.peripheral + +internal class TermSim { + + + +} \ No newline at end of file diff --git a/terranmon.txt b/terranmon.txt index 3c57cfa..da12a99 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -92,7 +92,7 @@ MMIO Text mode rows 1 bytes RW Text-mode attributes - 0b kkkk 000c (k: currently using character rom, c: Cursor blink) + 0b kkkk 00rc (k: currently using character rom, r: TTY Raw mode, c: Cursor blink) 1 bytes RW Graphics-mode attributes 0b 0000 000g (g: Use sprites(wipes out text buffer))