generated from rnentjes/kotlin-server-web-undertow
Refactor BufferedImage logic with WebGL support, update DisplayView for GPU-based rendering, transition timing functions to Double, optimize MTMCClock frame logic, and add dynamic control for update state in ControlView.
This commit is contained in:
@@ -4,6 +4,8 @@ expect fun createBufferedImage(width: Int, height: Int): BufferedImage
|
||||
|
||||
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
|
||||
|
||||
expect fun createGLImage(width: Int, height: Int): BufferedImage
|
||||
|
||||
interface BufferedImage {
|
||||
val width: Int
|
||||
val height: Int
|
||||
|
||||
@@ -41,7 +41,7 @@ class MTMCClock(
|
||||
val delta = time - lastFrame
|
||||
lastFrame = time
|
||||
|
||||
val actualTime = min(delta / 1000.0, 0.05)
|
||||
val actualTime = min(delta / 1000.0, 0.0166)
|
||||
|
||||
// assume 1Hz = 1 instruction/second
|
||||
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
||||
@@ -58,7 +58,8 @@ class MTMCClock(
|
||||
val ir = computer.pulse(pulse)
|
||||
instructions += ir
|
||||
if (frame % 100 == 0) {
|
||||
println("Instructions ran: $ir (delta = $delta, actualTime = $actualTime, speed = $speed, duration = ${currentTimeMillis() - time})")
|
||||
val duration = currentTimeMillis() - time
|
||||
println("Instructions ran: $ir (delta = ${delta.toFloat()}, actualTime = ${actualTime.toFloat()}, speed = $speed (actual=${(ir / delta * 1000).toLong()}), duration = $duration)")
|
||||
}
|
||||
|
||||
virtual += instructions
|
||||
|
||||
@@ -3,7 +3,7 @@ package mtmc.emulator
|
||||
import kotlin.math.min
|
||||
|
||||
class MTMCDisplay(private val computer: MonTanaMiniComputer) {
|
||||
val buffer: BufferedImage = createCanvasImage(COLS, ROWS)
|
||||
val buffer: BufferedImage = createGLImage(COLS, ROWS)
|
||||
private var currentColor: DisplayColor? = null
|
||||
private var graphics: Array<BufferedImage> = arrayOf()
|
||||
private var byteArray: ByteArray = ByteArray(0)
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlin.math.min
|
||||
import kotlin.random.Random
|
||||
|
||||
class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
private var timer: Long = 0
|
||||
private var timer: Double = 0.0
|
||||
var random: Random = Random.Default
|
||||
|
||||
// Editor support
|
||||
@@ -340,7 +340,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
|
||||
computer.setRegisterValue(
|
||||
Register.RV,
|
||||
max(0, this.timer - currentTimeMillis()).toInt()
|
||||
max(0.0, this.timer - currentTimeMillis()).toInt()
|
||||
)
|
||||
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
||||
val image = computer.getRegisterValue(Register.A0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package mtmc.util
|
||||
|
||||
expect fun currentTimeMillis(): Long
|
||||
expect fun currentTimeMillis(): Double
|
||||
|
||||
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mtmc.emulator
|
||||
|
||||
import kotlinx.browser.document
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.khronos.webgl.get
|
||||
import org.khronos.webgl.set
|
||||
import org.w3c.dom.CanvasRenderingContext2D
|
||||
@@ -67,9 +68,49 @@ class BufferedImageData(
|
||||
}
|
||||
}
|
||||
|
||||
class BufferedImageDataWebGl(
|
||||
override val width: Int,
|
||||
override val height: Int,
|
||||
val data: Uint8Array = Uint8Array(width * height * 4)
|
||||
) : BufferedImage {
|
||||
init {
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
val offset = (x * 4 + y * width * 4)
|
||||
data[offset + 3] = 255.toShort().asDynamic()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRGB(x: Int, y: Int): Int {
|
||||
check(x in 0 until width && y in 0 until height)
|
||||
|
||||
val offset = (x * 4 + y * width * 4)
|
||||
val display = data
|
||||
return (display[offset + 0].toInt() and 0xff) shl 16 +
|
||||
(display[offset + 1].toInt() and 0xff) shl 8 +
|
||||
(display[offset + 2].toInt() and 0xff) shl 0 + 255
|
||||
}
|
||||
|
||||
override fun setRGB(x: Int, y: Int, intVal: Int) {
|
||||
check(x in 0 until width && y in 0 until height)
|
||||
|
||||
val offset = (x * 4 + y * width * 4)
|
||||
|
||||
data[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
|
||||
data[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
|
||||
data[offset + 2] = ((intVal shr 0) and 0xff).toShort().asDynamic()
|
||||
//data[offset + 3] = 255.toShort().asDynamic()
|
||||
}
|
||||
}
|
||||
|
||||
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||
|
||||
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
||||
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
|
||||
}
|
||||
|
||||
actual fun createGLImage(width: Int, height: Int): BufferedImage {
|
||||
return BufferedImageDataWebGl(width, height)
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ import mtmc.mainView
|
||||
import kotlin.js.Date
|
||||
|
||||
var lastMemoryUpdate = currentTimeMillis()
|
||||
var updateState = true
|
||||
|
||||
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
|
||||
actual fun currentTimeMillis(): Double = Date().getTime()
|
||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||
window.requestAnimationFrame {
|
||||
action(it)
|
||||
|
||||
display.requestUpdate()
|
||||
if (currentTimeMillis() - lastMemoryUpdate > 125) {
|
||||
mainView.registerView.requestUpdate()
|
||||
mainView.memoryView.requestUpdate()
|
||||
if (updateState) {
|
||||
mainView.registerView.requestUpdate()
|
||||
mainView.memoryView.requestUpdate()
|
||||
}
|
||||
lastMemoryUpdate = currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package mtmc.view
|
||||
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.button
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.i
|
||||
import kotlinx.html.input
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.html.label
|
||||
@@ -12,6 +14,7 @@ import kotlinx.html.span
|
||||
import mtmc.display
|
||||
import mtmc.emulator.MonTanaMiniComputer
|
||||
import mtmc.mainView
|
||||
import mtmc.util.updateState
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
@@ -42,6 +45,18 @@ class ControlView(
|
||||
+"MonTana Mini-Computer"
|
||||
}
|
||||
div("control-buttons") {
|
||||
label {
|
||||
+"Update:"
|
||||
htmlFor = "update-state"
|
||||
input {
|
||||
type = InputType.checkBox
|
||||
checked = updateState
|
||||
onClickFunction = {
|
||||
updateState = !updateState
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
label {
|
||||
select {
|
||||
name = "speed"
|
||||
@@ -75,6 +90,10 @@ class ControlView(
|
||||
value = "5000000"
|
||||
+"5 Mhz"
|
||||
}
|
||||
option {
|
||||
value = "10000000"
|
||||
+"10 Mhz"
|
||||
}
|
||||
|
||||
onChangeFunction = {
|
||||
val target = it.target as? HTMLSelectElement
|
||||
|
||||
@@ -4,13 +4,43 @@ import kotlinx.html.canvas
|
||||
import kotlinx.html.div
|
||||
import mtmc.display
|
||||
import mtmc.emulator.BufferedImageData
|
||||
import mtmc.emulator.BufferedImageDataWebGl
|
||||
import mtmc.emulator.MonTanaMiniComputer
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.currentElement
|
||||
import org.w3c.dom.CanvasRenderingContext2D
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.WebGLBuffer
|
||||
import org.khronos.webgl.WebGLProgram
|
||||
import org.khronos.webgl.WebGLRenderingContext
|
||||
import org.khronos.webgl.WebGLShader
|
||||
import org.khronos.webgl.WebGLTexture
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
|
||||
// language=GLSL
|
||||
val vertexShader = """
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_uv;
|
||||
varying vec2 v_uv;
|
||||
void main() {
|
||||
v_uv = a_uv;
|
||||
gl_Position = vec4(a_pos, 0.0, 1.0);
|
||||
}
|
||||
"""
|
||||
|
||||
// language=GLSL
|
||||
val fragmentShader = """
|
||||
precision mediump float;
|
||||
varying vec2 v_uv;
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(u_tex, v_uv);
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
typealias GL = WebGLRenderingContext
|
||||
|
||||
class DiplayControlView(
|
||||
val computer: MonTanaMiniComputer
|
||||
) : Komponent() {
|
||||
@@ -25,7 +55,10 @@ class DiplayControlView(
|
||||
class DisplayView(
|
||||
val computer: MonTanaMiniComputer
|
||||
) : Komponent() {
|
||||
var ctx: CanvasRenderingContext2D? = null
|
||||
var ctx: WebGLRenderingContext? = null
|
||||
var program: WebGLProgram? = null
|
||||
var texture: WebGLTexture? = null
|
||||
var buffer: WebGLBuffer? = null
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
canvas("display-canvas") {
|
||||
@@ -34,18 +67,107 @@ class DisplayView(
|
||||
|
||||
val cv = currentElement() as? HTMLCanvasElement
|
||||
|
||||
ctx = cv?.getContext("2d")?.unsafeCast<CanvasRenderingContext2D>()
|
||||
ctx = cv?.getContext("webgl")?.unsafeCast<WebGLRenderingContext>()
|
||||
|
||||
ctx?.fillStyle = "#400040"
|
||||
ctx?.fillRect(0.0, 0.0, 160.0, 144.0)
|
||||
if (program == null) {
|
||||
createProgram()
|
||||
createBuffer()
|
||||
createTexture()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTexture() {
|
||||
ctx?.let { gl: WebGLRenderingContext ->
|
||||
texture = gl.createTexture()
|
||||
gl.bindTexture(GL.TEXTURE_2D, texture)
|
||||
|
||||
// Set texture parameters for pixel-perfect rendering
|
||||
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE)
|
||||
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE)
|
||||
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST)
|
||||
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBuffer() {
|
||||
ctx?.let { gl ->
|
||||
// Create quad vertices
|
||||
val vertices = Float32Array(
|
||||
arrayOf(
|
||||
-1f, -1f, 0f, 1f, // position, texCoord
|
||||
1f, -1f, 1f, 1f,
|
||||
-1f, 1f, 0f, 0f,
|
||||
1f, 1f, 1f, 0f
|
||||
)
|
||||
)
|
||||
|
||||
buffer = gl.createBuffer();
|
||||
gl.bindBuffer(GL.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(GL.ARRAY_BUFFER, vertices, GL.STATIC_DRAW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createProgram() {
|
||||
val vs = createShader(WebGLRenderingContext.VERTEX_SHADER, vertexShader)
|
||||
val fs = createShader(WebGLRenderingContext.FRAGMENT_SHADER, fragmentShader)
|
||||
val prog = ctx?.createProgram()
|
||||
if (vs != null && fs != null && prog != null) {
|
||||
ctx?.attachShader(prog, vs)
|
||||
ctx?.attachShader(prog, fs)
|
||||
ctx?.linkProgram(prog)
|
||||
}
|
||||
program = prog
|
||||
}
|
||||
|
||||
private fun createShader(type: Int, source: String): WebGLShader? {
|
||||
var result: WebGLShader? = null
|
||||
ctx?.let { gl ->
|
||||
result = gl.createShader(type)
|
||||
result?.let { shader ->
|
||||
gl.shaderSource(shader, source)
|
||||
gl.compileShader(shader)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun renderUpdate() {
|
||||
// move data to canvas
|
||||
val buffer = computer.display.buffer
|
||||
if (buffer is BufferedImageData) {
|
||||
ctx?.putImageData(buffer.imageData, 0.0, 0.0)
|
||||
//ctx?.putImageData(buffer.imageData, 0.0, 0.0)
|
||||
} else if (buffer is BufferedImageDataWebGl) {
|
||||
ctx?.let { gl ->
|
||||
gl.clear(GL.COLOR_BUFFER_BIT)
|
||||
|
||||
gl.useProgram(program)
|
||||
|
||||
val positionLocation = gl.getAttribLocation(program, "a_pos")
|
||||
val texCoordLocation = gl.getAttribLocation(program, "a_uv")
|
||||
|
||||
gl.bindBuffer(GL.ARRAY_BUFFER, this.buffer)
|
||||
gl.enableVertexAttribArray(positionLocation)
|
||||
gl.vertexAttribPointer(positionLocation, 2, GL.FLOAT, false, 16, 0)
|
||||
gl.enableVertexAttribArray(texCoordLocation)
|
||||
gl.vertexAttribPointer(texCoordLocation, 2, GL.FLOAT, false, 16, 8)
|
||||
gl.bindTexture(GL.TEXTURE_2D, texture);
|
||||
gl.texImage2D(
|
||||
GL.TEXTURE_2D,
|
||||
0, // level
|
||||
GL.RGBA, // internal format
|
||||
buffer.width,
|
||||
buffer.height,
|
||||
0, // border
|
||||
GL.RGBA, // format
|
||||
GL.UNSIGNED_BYTE, // type
|
||||
buffer.data // data
|
||||
)
|
||||
|
||||
// Draw quad
|
||||
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ class MemoryView(
|
||||
else -> +"?"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,8 @@ actual fun createBufferedImage(width: Int, height: Int): BufferedImage {
|
||||
|
||||
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun createGLImage(width: Int, height: Int): BufferedImage {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package mtmc.util
|
||||
|
||||
actual fun currentTimeMillis(): Long = System.currentTimeMillis()
|
||||
actual fun currentTimeMillis(): Double = System.currentTimeMillis().toDouble()
|
||||
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||
error("requestAnimationFrame is not supported on JVM")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user