package mtmc.view 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.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() { override fun HtmlBuilder.render() { div("display") { include(display) } } } class DisplayView( val computer: MonTanaMiniComputer ) : Komponent() { var ctx: WebGLRenderingContext? = null var program: WebGLProgram? = null var texture: WebGLTexture? = null var buffer: WebGLBuffer? = null override fun HtmlBuilder.render() { canvas("display-canvas") { width = "160px" height = "144px" val cv = currentElement() as? HTMLCanvasElement ctx = cv?.getContext("webgl")?.unsafeCast() 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) } 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); } } } }