diff --git a/assets/disk0/tvdos/bin/flsh.js b/assets/disk0/tvdos/bin/flsh.js deleted file mode 100644 index 798e149..0000000 --- a/assets/disk0/tvdos/bin/flsh.js +++ /dev/null @@ -1,106 +0,0 @@ -let CURRENT_DRIVE = "A"; - -let shell_pwd = [""]; - -const welcome_text = "TSVM Disk Operating System, version " + _TVDOS.VERSION; - -function print_prompt_text() { - // oh-my-zsh-like prompt - con.color_pair(239,161); - print(" "+CURRENT_DRIVE+":"); - con.color_pair(161,253); - con.addch(16);con.curs_right(); - con.color_pair(0,253); - print(" /"+shell_pwd.join("/")+" "); - con.color_pair(253,255); - con.addch(16);con.curs_right(); - con.addch(32);con.curs_right(); - con.color_pair(239,255); -} - -function greet() { - con.color_pair(0,253); - //print(welcome_text + " ".repeat(_fsh.scrwidth - welcome_text.length)); - print(welcome_text + " ".repeat(80 - welcome_text.length)); - con.color_pair(239,255); - println(); -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -con.clear(); - -greet(); - -let cmdHistory = []; // zeroth element is the oldest -let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent -while (true) { - print_prompt_text(); - - let cmdbuf = ""; - - while (true) { - let key = con.getch(); - - // printable chars - if (key >= 32 && key <= 126) { - let s = String.fromCharCode(key); - cmdbuf += s; - print(s); - } - // backspace - else if (key === 8 && cmdbuf.length > 0) { - cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1); - print(String.fromCharCode(key)); - } - // enter - else if (key === 10 || key === 13) { - println(); - try { - println("You entered: " + cmdbuf); - } - catch (e) { - println(e); - } - finally { - if (cmdbuf.trim().length > 0) - cmdHistory.push(cmdbuf); - - cmdHistoryScroll = 0; - break; - } - } - // up arrow - else if (key === 19 && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) { - cmdHistoryScroll += 1; - - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; - // re-type the new command - print(cmdbuf); - - } - // down arrow - else if (key === 20) { - if (cmdHistoryScroll > 0) { - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; - // re-type the new command - print(cmdbuf); - - cmdHistoryScroll -= 1; - } - else { - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = ""; - } - } - } -} \ No newline at end of file diff --git a/assets/disk0/tvdos/bin/fsh.js b/assets/disk0/tvdos/bin/fsh.js index 9b19eba..eec9de6 100644 --- a/assets/disk0/tvdos/bin/fsh.js +++ b/assets/disk0/tvdos/bin/fsh.js @@ -1,4 +1,4 @@ -graphics.setBackground(3,3,3); +graphics.setBackground(2,1,3); graphics.resetPalette(); function captureUserInput() { @@ -18,7 +18,15 @@ _fsh.brandName = "f\xb3Sh"; _fsh.brandLogoTexSmall = new GL.Texture(24, 14, gzip.decomp(base64.atob( "H4sIAAAAAAAAAPv/Hy/4Qbz458+fIeILQQBIwoSh6qECuMVBukCmIJkDVQ+RQNgLE0MX/w+1lyhxqIUwTLJ/sQMAcIXsbVABAAA=" ))); -_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.apps_n_files"]; +_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.todo_list", "com.fsh.quick_access"]; + +_fsh.drawWallpaper = function() { + filesystem.open("A", "/tvdos/wall.bytes", "R") + let b = sys.malloc(250880) + dma.comToRam(0, 0, b, 250880) + dma.ramToFrame(b, 0, 250880) + sys.free(b) +}; _fsh.drawTitlebar = function(titletext) { GL.drawTexPattern(_fsh.titlebarTex, 0, 0, 560, 14); @@ -58,7 +66,7 @@ _fsh.registerNewWidget = function(widget) { _fsh.widgets[widget.identifier] = widget; } -var clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7); +var clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7*2); clockWidget.numberSheet = new GL.SpriteSheet(19, 22, new GL.Texture(190, 22, gzip.decomp(base64.atob( "H4sIAAAAAAAAAMWVW3LEMAgE739aHcFJJV5ZMD2I9ToVfcl4GBr80HF8r/FaR1ozMuIyoUu87lEXI0al5qVR5AebSwchSaNE6Nyo1Nw5HXF3SfPT4Bshl"+ "EycA8RD96mLlHbuhTgOrfLnUDZspafbSQWk56WEGvQEtWaWwgb8iz7a8AOXhsraO/q9Qw2/GnXovfVN+q2wM/p/oddn2cjF239GX3y11+SWCtc6FTHC1v"+ @@ -84,12 +92,12 @@ clockWidget.draw = function(charXoff, charYoff) { if (ordinalDay == 119) dayName = 7; // Verddag var years = ((timeInMinutes / (60*24*30*120))|0) + 125; // draw timepiece - GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff); - GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff); - GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5); - GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14); - GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff); - GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff); + GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff, 1); + GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff, 1); + GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5, 1); + GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14, 1); + GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff, 1); + GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff, 1); // print month and date con.move(1 + charYoff, 17 + charXoff); print(clockWidget.monthNames[months]+" "+visualDay); @@ -100,24 +108,100 @@ clockWidget.draw = function(charXoff, charYoff) { }; +var calendarWidget = new _fsh.Widget("com.fsh.calendar", (_fsh.scrwidth - 8) / 2, 7*6) +calendarWidget.dayLabels = [ + " 1 2 3 4 5 6 7 \xFA\xFA", + " 8 9 10 11 12 13 14 \xFA\xFA", + "15 16 17 18 19 20 21 \xFA\xFA", + "22 23 24 25 26 27 28 \xFA\xFA", + "29 30 1 2 3 4 5 \xFA\xFA", + " 6 7 8 9 10 11 12 \xFA\xFA", + "13 14 15 16 17 18 19 \xFA\xFA", + "20 21 22 23 24 25 26 \xFA\xFA", + "27 28 29 30 1 2 3 \xFA\xFA", + " 4 5 6 7 8 9 10 \xFA\xFA", + "11 12 13 14 15 16 17 \xFA\xFA", + "18 19 20 21 22 23 24 \xFA\xFA", + "25 26 27 28 29 30 1 \xFA\xFA", + " 2 3 4 5 6 7 8 \xFA\xFA", + " 9 10 11 12 13 14 15 \xFA\xFA", + "16 17 18 19 20 21 22 \xFA\xFA", + "23 24 25 26 27 28 29 30" +] +calendarWidget.seasonCols = [229,39,215,73,253] +calendarWidget.draw = function(charXoff, charYoff) { + con.color_pair(254, 255) + let xoff = charXoff * 7 + let yoff = charYoff * 14 + 3 + + let timeInMinutes = ((sys.currentTimeInMills() / 60000)|0) + let ordinalDay = ((timeInMinutes / (60*24))|0) % 120 + let offset = (ordinalDay / 7)|0 + + con.move(charXoff, charYoff) + print("Mo Ty Mi To Fr La Su Ve") + + for (let i = -3; i <= 3; i++) { + let lineOff = (offset + i) % 17 + let line = calendarWidget.dayLabels[lineOff] + let textCol = 0 + + con.move(charXoff + 4 + i, charYoff) + + for (let x = 0; x <= 23; x++) { + let paintingDayOrd = lineOff*7 + ((x/3)|0) + if (x >= 21 && lineOff != 16) textCol = calendarWidget.seasonCols[4] + else textCol = calendarWidget.seasonCols[(paintingDayOrd / 30)|0] + + // special colour for spaces between numbers + if (x % 3 == 2) con.color_pair(255,255) + // mark today + else if (paintingDayOrd == ordinalDay) con.color_pair(0,textCol) + // paint normal day number with seasonal colour + else con.color_pair(textCol,255) + + con.addch(line.charCodeAt(x)) + con.curs_right() + } + } +} + + +// change graphics mode and check if it's supported +graphics.setGraphicsMode(3) +if (graphics.getGraphicsMode() == 0) { + printerrln("Insufficient VRAM") + return 1 +} + // register widgets -_fsh.registerNewWidget(clockWidget); +_fsh.registerNewWidget(clockWidget) +_fsh.registerNewWidget(calendarWidget) // screen init -con.color_pair(254, 255); -con.clear(); -con.curs_set(0); -_fsh.drawTitlebar(); +con.color_pair(254, 255) +con.clear() +con.curs_set(0) +graphics.clearPixels(255) +graphics.clearPixels2(255) +graphics.setFramebufferScroll(0,0) +_fsh.drawWallpaper() +_fsh.drawTitlebar() // TEST con.move(2,1); -print("Hit backspace to exit"); +print("Hit backspace to exit") + +// TODO update for events: key down (updates some widgets), timer (updates clock and calendar widgets) while (true) { captureUserInput(); if (getKeyPushed(0) == 67) break; - _fsh.widgets["com.fsh.clock"].draw(25, 2); + _fsh.widgets["com.fsh.clock"].draw(25, 3); + _fsh.widgets["com.fsh.calendar"].draw(8, 12); + + sys.spin();sys.spin() } con.move(3,1); diff --git a/assets/disk0/tvdos/gl.js b/assets/disk0/tvdos/gl.js index 4969330..9bcc880 100644 --- a/assets/disk0/tvdos/gl.js +++ b/assets/disk0/tvdos/gl.js @@ -35,33 +35,34 @@ GL.SpriteSheet = function(tilew, tileh, tex) { } this.getOffX = function(x) { // THIS, or: GL.SpriteSheet.prototype.getOffX - var tx = this.tileWidth * (x|0); + let tx = this.tileWidth * (x|0); if (tx + this.tileWidth > this.texture.width) throw "Sprite x-offset of "+tx+" is greater than sprite width "+this.texture.width; return tx; }; this.getOffY = function(y) { - var ty = this.tileHeight * (y|0); + let ty = this.tileHeight * (y|0); if (ty + this.tileHeight > this.texture.height) throw "Sprite y-offset of "+ty+" is greater than sprite height "+this.texture.height; return ty; }; }; -GL.drawTexPattern = function(texture, x, y, width, height, fgcol, bgcol) { +GL.drawTexPattern = function(texture, x, y, width, height, framebuffer, fgcol, bgcol) { if (!(texture instanceof GL.Texture) && !(texture instanceof GL.MonoTex)) throw Error("Texture is not a GL Texture types"); + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 for (let yy = 0; yy < height; yy++) { for (let xx = 0; xx < width;) { let tx = xx % texture.width; let ty = yy % texture.height; if (texture instanceof GL.Texture) { let c = texture.texData[ty * texture.width + tx]; - graphics.plotPixel(x + xx, y + yy, c); + paint(x + xx, y + yy, c); } else if (texture instanceof GL.MonoTex) { let octet = texture.texData[ty * (texture.width >> 3) + (tx >> 3)]; for (let i = 0; i < 8; i++) { let bit = ((octet >>> (7 - i)) & 1 != 0) - graphics.plotPixel(x + xx + i, y + yy, bit ? bgcol : fgcol); + paint(x + xx + i, y + yy, bit ? bgcol : fgcol); } } @@ -69,24 +70,25 @@ GL.drawTexPattern = function(texture, x, y, width, height, fgcol, bgcol) { } } }; -GL.drawTexPatternOver = function(texture, x, y, width, height, fgcol) { +GL.drawTexPatternOver = function(texture, x, y, width, height, framebuffer, fgcol) { if (!(texture instanceof GL.Texture) && !(texture instanceof GL.MonoTex)) throw Error("Texture is not a GL Texture types"); + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 for (let yy = 0; yy < height; yy++) { for (let xx = 0; xx < width;) { let tx = xx % texture.width; let ty = yy % texture.height; if (texture instanceof GL.Texture) { + let c = texture.texData[ty * texture.width + tx]; if ((c & 255) != 255) { - let c = texture.texData[ty * texture.width + tx]; - graphics.plotPixel(x + xx, y + yy, c); + paint(x + xx, y + yy, c); } } else if (texture instanceof GL.MonoTex) { let octet = texture.texData[ty * (texture.width >> 3) + (tx >> 3)]; for (let i = 0; i < 8; i++) { let bit = ((octet >>> (7 - i)) & 1 != 0) - if (bit) graphics.plotPixel(x + xx + i, y + yy, fgcol); + if (bit) paint(x + xx + i, y + yy, fgcol); } } @@ -97,14 +99,14 @@ GL.drawTexPatternOver = function(texture, x, y, width, height, fgcol) { /* * Draws a texture verbatim - color of 255 will be written to the screen buffer */ -GL.drawTexImage = function(texture, x, y, fgcol, bgcol) { - GL.drawTexPattern(texture, x, y, texture.width, texture.height, fgcol, bgcol); +GL.drawTexImage = function(texture, x, y, framebuffer, fgcol, bgcol) { + GL.drawTexPattern(texture, x, y, texture.width, texture.height, framebuffer, fgcol, bgcol); }; /* * Draws texture with blitting - color of 255 will pass-thru what's already on the screen buffer */ -GL.drawTexImageOver = function(texture, x, y, fgcol) { - GL.drawTexPatternOver(texture, x, y, texture.width, texture.height, fgcol); +GL.drawTexImageOver = function(texture, x, y, framebuffer, fgcol) { + GL.drawTexPatternOver(texture, x, y, texture.width, texture.height, framebuffer, fgcol); }; /* * @param xi x-index in the spritesheet, ZERO-BASED INDEX @@ -114,16 +116,17 @@ GL.drawTexImageOver = function(texture, x, y, fgcol) { * @param overrideFG if the value is set and the current pixel of the sheet is not 255, plots this colour instead * @param overrideBG if the value is set and the current pixel of the sheet is 255, plots this colour instead */ -GL.drawSprite = function(sheet, xi, yi, x, y, overrideFG, overrideBG) { - var offx = sheet.getOffX(xi); - var offy = sheet.getOffY(yi); - for (var ty = 0; ty < sheet.tileHeight; ty++) { - for (var tx = 0; tx < sheet.tileWidth; tx++) { - var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; +GL.drawSprite = function(sheet, xi, yi, x, y, framebuffer, overrideFG, overrideBG) { + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 + let offx = sheet.getOffX(xi); + let offy = sheet.getOffY(yi); + for (let ty = 0; ty < sheet.tileHeight; ty++) { + for (let tx = 0; tx < sheet.tileWidth; tx++) { + let c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; if ((c & 255) == 255) - graphics.plotPixel(x + tx, (y + ty)|0, (overrideBG !== undefined) ? overrideBG : c); + paint(x + tx, (y + ty)|0, (overrideBG !== undefined) ? overrideBG : c); else - graphics.plotPixel(x + tx, (y + ty)|0, (overrideFG !== undefined) ? overrideFG : c); + paint(x + tx, (y + ty)|0, (overrideFG !== undefined) ? overrideFG : c); } } }; @@ -134,14 +137,15 @@ GL.drawSprite = function(sheet, xi, yi, x, y, overrideFG, overrideBG) { * @param y y-position on the framebuffer where the sprite will be drawn * @param overrideFG if the value is set and the current pixel of the sheet is not 255, plots this colour instead */ -GL.drawSpriteOver = function(sheet, xi, yi, x, y, overrideFG) { - var offx = sheet.getOffX(xi); - var offy = sheet.getOffY(yi); - for (var ty = 0; ty < sheet.tileHeight; ty++) { - for (var tx = 0; tx < sheet.tileWidth; tx++) { - var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; +GL.drawSpriteOver = function(sheet, xi, yi, x, y, framebuffer, overrideFG) { + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 + let offx = sheet.getOffX(xi); + let offy = sheet.getOffY(yi); + for (let ty = 0; ty < sheet.tileHeight; ty++) { + for (let tx = 0; tx < sheet.tileWidth; tx++) { + let c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; if ((c & 255) != 255) { - graphics.plotPixel(x + tx, (y + ty)|0, overrideFG || c); + paint(x + tx, (y + ty)|0, overrideFG || c); } } } diff --git a/assets/disk0/tvdos/wall.bytes b/assets/disk0/tvdos/wall.bytes new file mode 100644 index 0000000..a9919a3 --- /dev/null +++ b/assets/disk0/tvdos/wall.bytes @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9d409e3c294508bc15a8e549e4b4c5f3a630fe211678ba8644466a7ce1cd16a +size 250880 diff --git a/assets/disk0/tvdos/wall.png b/assets/disk0/tvdos/wall.png new file mode 100644 index 0000000..a819c10 Binary files /dev/null and b/assets/disk0/tvdos/wall.png differ diff --git a/assets/fsh_wallpaper.kra b/assets/fsh_wallpaper.kra new file mode 100644 index 0000000..cd885a1 --- /dev/null +++ b/assets/fsh_wallpaper.kra @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88031ab8a1bb82c99d77991c09e9bd04f8ff28199791064ea7272958a11279f1 +size 2245531 diff --git a/terranmon.txt b/terranmon.txt index 9268a24..ef3cfac 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -155,8 +155,9 @@ From the start of the memory space: 1 byte command (writing to this memory address changes the status) 1: reset palette to default - 2: fill framebuffer with given colour (arg1). If framebuffer 2 is there, it will be filled with arg2 - 3: do '1' then do '2' + 2: fill framebuffer with given colour (arg1) + 3: do '1' then do '2' (with arg1) then do '4' (with arg2) + 4: fill framebuffer2 with given colour (arg1) 16: copy Low Font ROM (char 0–127) to mapping area 17: copy High Font ROM (char 128–255) to mapping area diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index 8b7fdcf..c11c8b1 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -52,6 +52,14 @@ class GraphicsJSR223Delegate(val vm: VM) { } } + fun plotPixel2(x: Int, y: Int, color: Int) { + getFirstGPU()?.let { + if (x in 0 until it.config.width && y in 0 until it.config.height) { + it.poke(262144 + y.toLong() * it.config.width + x, color.toByte()) + } + } + } + /** * Sets absolute position of scrolling */ @@ -136,6 +144,11 @@ class GraphicsJSR223Delegate(val vm: VM) { getFirstGPU()?.poke(250883L, 2) } + fun clearPixels2(col: Int) { + getFirstGPU()?.poke(250884L, col.toByte()) + getFirstGPU()?.poke(250883L, 4) + } + /** * prints a char as-is; won't interpret them as an escape sequence */ @@ -211,7 +224,7 @@ class GraphicsJSR223Delegate(val vm: VM) { pixmap.dispose() UnsafeHelper.memcpyRaw(outData, UnsafeHelper.getArrayOffset(outData), null, vm.usermem.ptr + destPixmapPtr, outData.size.toLong()) - return intArrayOf(width, height, destPixmapPtr) + return intArrayOf(width, height, destPixmapPtr, outData.size / (width * height)) } /** diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index 4e66790..cfd6a7e 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -326,7 +326,9 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } 2 -> { framebuffer.fillWith(arg1.toByte()) - framebuffer2?.fillWith(arg2.toByte()) + } + 4 -> { + framebuffer2?.fillWith(arg1.toByte()) } 3 -> { for (it in 0 until 1024) { @@ -784,6 +786,54 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } } } + else if (graphicsMode == 3 && framebuffer2 != null) { + val layerOrder = (if (graphicsMode == 1) LAYERORDERS4 else LAYERORDERS2)[layerArrangement] + + val fb1 = if (layerOrder[0] == 0) framebuffer else framebuffer2 + val fb2 = if (layerOrder[0] == 0) framebuffer2 else framebuffer + + 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 colourIndex1 = fb1[y.toLong() * WIDTH + (x - xoff)].toUint() + val colourIndex2 = fb2[y.toLong() * WIDTH + (x - xoff)].toUint() + val colour1 = Color( + paletteOfFloats[4 * colourIndex1], + paletteOfFloats[4 * colourIndex1 + 1], + paletteOfFloats[4 * colourIndex1 + 2], + paletteOfFloats[4 * colourIndex1 + 3] + ) + val colour2 = Color( + paletteOfFloats[4 * colourIndex2], + paletteOfFloats[4 * colourIndex2 + 1], + paletteOfFloats[4 * colourIndex2 + 2], + paletteOfFloats[4 * colourIndex2 + 3] + ) + val colour = listOf(colour1, colour2).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 = (dest.a + (1f - dest.a) * src.a).coerceIn(0.0001f, 1f) // identical to 1 - (1 - dest.a) * (1 - src.a) but this is more optimised form + + // 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 + ) + } + + framebufferOut.setColor(colour) + framebufferOut.drawPixel(x, y) + } + } + } + } else if (isRefSize && (graphicsMode == 1 || graphicsMode == 2)) { val layerOrder = (if (graphicsMode == 1) LAYERORDERS4 else LAYERORDERS2)[layerArrangement] for (y in 0..223) { @@ -849,11 +899,6 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi if (xoff in -(WIDTH - 1) until WIDTH) { for (x in xs) { - // this only works because framebuffer is guaranteed to be 8bpp - /*framebuffer2.pixels.put( - y * WIDTH + x, - framebuffer.pixels.get(y * WIDTH + (x - xoff)) // coerceIn not required as (x - xoff) never escapes 0..559 - )*/ val colourIndex = framebuffer[y.toLong() * WIDTH + (x - xoff)].toUint() // coerceIn not required as (x - xoff) never escapes 0..559 framebufferOut.setColor(paletteOfFloats[4*colourIndex], paletteOfFloats[4*colourIndex+1], paletteOfFloats[4*colourIndex+2], paletteOfFloats[4*colourIndex+3]) framebufferOut.drawPixel(x, y)