diff --git a/.gitignore b/.gitignore index 3cd5d70..06d34f2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ nimbledeps /examples/emscripten/*.data /examples/emscripten/*.html !/examples/emscripten/emscripten.html +/examples/*.js diff --git a/README.md b/README.md index a971c7a..b8b1189 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ For a complete working example (window + GL context + render loop), see: - `examples/opengl_windy_renderlist.nim` - `examples/sdl2_renderlist.nim` +For a Nim JS + WebGL example (no Windy), see: + +- `examples/webgl_renderlist.nim` (paired with `examples/webgl_renderlist.html`) + ## Run Tests Runs all tests + compiles all examples listed in `config.nims`: diff --git a/config.nims b/config.nims index 3015145..1917df5 100644 --- a/config.nims +++ b/config.nims @@ -15,6 +15,8 @@ task test, "run unit test": exec("nim c examples/opengl_windy_text.nim") exec("nim c examples/sdl2_renderlist.nim") exec("nim c examples/sdl2_renderlist_100.nim") + exec("nim js examples/webgl_renderlist.nim") + exec("nim js examples/webgl_renderlist_100.nim") task emscripten, "build emscripten examples": exec("nim c -d:emscripten examples/opengl_windy_renderlist.nim") diff --git a/examples/webgl_renderlist.html b/examples/webgl_renderlist.html new file mode 100644 index 0000000..878fa45 --- /dev/null +++ b/examples/webgl_renderlist.html @@ -0,0 +1,22 @@ + + + + + + FigDraw WebGL RenderList (Nim JS) + + + + + + + diff --git a/examples/webgl_renderlist.nim b/examples/webgl_renderlist.nim new file mode 100644 index 0000000..c6ba6ed --- /dev/null +++ b/examples/webgl_renderlist.nim @@ -0,0 +1,124 @@ +when not defined(js) and not defined(nimsuggest): + {.fatal: "This example requires the Nim JS backend (nim js).".} + +import std/[dom, jsconsole, jsffi] +import chroma + +import figdraw/commons +import figdraw/fignodes +import figdraw/opengl/renderer as glrenderer +import figdraw/opengl/glapi +import figdraw/utils/glutils +import figdraw/webgl/api as webgl + +proc makeRenderTree*(w, h: float32): Renders = + var list = RenderList() + + let rootIdx = list.addRoot(Fig( + kind: nkRectangle, + childCount: 0, + zlevel: 0.ZLevel, + screenBox: rect(0, 0, w, h), + fill: rgba(255, 255, 255, 255).color, + )) + + list.addChild(rootIdx, Fig( + kind: nkRectangle, + childCount: 0, + zlevel: 0.ZLevel, + corners: [10.0'f32, 20.0, 30.0, 40.0], + screenBox: rect(60, 60, 220, 140), + fill: rgba(220, 40, 40, 255).color, + stroke: RenderStroke(weight: 5.0, color: rgba(0, 0, 0, 255).color) + )) + list.addChild(rootIdx, Fig( + kind: nkRectangle, + childCount: 0, + zlevel: 0.ZLevel, + screenBox: rect(320, 120, 220, 140), + fill: rgba(40, 180, 90, 255).color, + shadows: [ + RenderShadow( + style: DropShadow, + blur: 10, + spread: 10, + x: 10, + y: 10, + color: rgba(0, 0, 0, 55).color, + ), + RenderShadow(), + RenderShadow(), + RenderShadow(), + ], + )) + list.addChild(rootIdx, Fig( + kind: nkRectangle, + childCount: 0, + zlevel: 0.ZLevel, + screenBox: rect(180, 300, 220, 140), + fill: rgba(60, 90, 220, 255).color, + )) + + result = Renders(layers: initOrderedTable[ZLevel, RenderList]()) + result.layers[0.ZLevel] = list + +proc main() = + app.running = true + app.autoUiScale = false + app.uiScale = 1.0 + + let canvas = webgl.asCanvas(document.createElement("canvas")) + document.body.appendChild(canvas) + + document.body.style.margin = "0" + document.body.style.overflow = "hidden" + document.body.style.background = "#0c0f16" + canvas.style.display = "block" + + let gl = cast[glapi.WebGL2RenderingContext](canvas.getContext("webgl2")) + if gl.isNull or gl.isUndefined: + console.error("WebGL2 not available") + return + + setWebGLContext(gl) + startOpenGL(openglVersion) + + let renderer = glrenderer.newOpenGLRenderer( + atlasSize = 192, + pixelScale = 1.0, + ) + + var lastCssSize = vec2(0.0'f32, 0.0'f32) + var renders = makeRenderTree(0.0'f32, 0.0'f32) + + proc resizeAndRender() = + let dpr = if window.devicePixelRatio <= 0: 1.0 else: window.devicePixelRatio + let cssWidth = max(window.innerWidth, 1) + let cssHeight = max(window.innerHeight, 1) + + app.pixelScale = dpr.float32 + renderer.ctx.pixelScale = app.pixelScale + + canvas.width = int(cssWidth.float * dpr) + canvas.height = int(cssHeight.float * dpr) + canvas.style.width = cstring($cssWidth & "px") + canvas.style.height = cstring($cssHeight & "px") + + let cssSize = vec2(cssWidth.float32, cssHeight.float32) + if cssSize != lastCssSize: + lastCssSize = cssSize + renders = makeRenderTree(cssSize.x, cssSize.y) + + renderer.renderFrame( + renders, + vec2(canvas.width.float32, canvas.height.float32), + ) + + proc onResize(e: Event) = + resizeAndRender() + + window.addEventListener("resize", onResize) + resizeAndRender() + +when isMainModule: + main() diff --git a/examples/webgl_renderlist_100.html b/examples/webgl_renderlist_100.html new file mode 100644 index 0000000..9a53cb5 --- /dev/null +++ b/examples/webgl_renderlist_100.html @@ -0,0 +1,22 @@ + + + + + + FigDraw WebGL RenderList 100 (Nim JS) + + + + + + + diff --git a/examples/webgl_renderlist_100.nim b/examples/webgl_renderlist_100.nim new file mode 100644 index 0000000..f1b1ab0 --- /dev/null +++ b/examples/webgl_renderlist_100.nim @@ -0,0 +1,125 @@ +when not defined(js) and not defined(nimsuggest): + {.fatal: "This example requires the Nim JS backend (nim js).".} + +import std/[dom, jsconsole, jsffi, strutils] + +import figdraw/commons +import figdraw/fignodes +import figdraw/opengl/renderer as glrenderer +import figdraw/opengl/glapi +import figdraw/utils/glutils +import figdraw/webgl/api as webgl + +import renderlist_100_common + +var globalFrame = 0 + +proc main() = + app.running = true + app.autoUiScale = false + app.uiScale = 1.0 + app.pixelScale = 1.0 + + let canvas = webgl.asCanvas(document.createElement("canvas")) + + document.body.style.margin = "0" + document.body.style.overflow = "hidden" + document.body.style.background = "#0c0f16" + document.body.style.height = "100%" + + let root = document.createElement("div") + root.style.position = "relative" + root.style.width = "100%" + root.style.height = "100%" + root.style.overflow = "hidden" + document.body.appendChild(root) + + canvas.style.display = "block" + canvas.style.position = "absolute" + canvas.style.left = "0" + canvas.style.top = "0" + root.appendChild(canvas) + + let textLayer = document.createElement("div") + textLayer.style.position = "absolute" + textLayer.style.left = "0" + textLayer.style.top = "0" + textLayer.style.width = "100%" + textLayer.style.height = "100%" + textLayer.style.pointerEvents = "none" + textLayer.style.zIndex = "2" + root.appendChild(textLayer) + + let fpsNode = document.createElement("div") + fpsNode.style.position = "absolute" + fpsNode.style.left = "12px" + fpsNode.style.top = "10px" + fpsNode.style.padding = "4px 6px" + fpsNode.style.borderRadius = "6px" + fpsNode.style.color = "#cfe2ff" + fpsNode.style.background = "rgba(12, 15, 22, 0.55)" + fpsNode.style.fontFamily = + "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" + fpsNode.style.fontSize = "12px" + fpsNode.style.letterSpacing = "0.3px" + fpsNode.textContent = "FPS: --" + textLayer.appendChild(fpsNode) + + let gl = cast[glapi.WebGL2RenderingContext](canvas.getContext("webgl2")) + if gl.isNull or gl.isUndefined: + console.error("WebGL2 not available") + return + + setWebGLContext(gl) + startOpenGL(openglVersion) + + let renderer = glrenderer.newOpenGLRenderer( + atlasSize = when not defined(useFigDrawTextures): 1024 else: 2048, + pixelScale = app.pixelScale, + ) + + proc updateCanvas(): Vec2 = + let dpr = if window.devicePixelRatio <= 0: 1.0 else: window.devicePixelRatio + let cssWidth = max(window.innerWidth, 1) + let cssHeight = max(window.innerHeight, 1) + + app.pixelScale = dpr.float32 + renderer.ctx.pixelScale = app.pixelScale + + let pixelWidth = int(cssWidth.float * dpr) + let pixelHeight = int(cssHeight.float * dpr) + if canvas.width != pixelWidth: + canvas.width = pixelWidth + if canvas.height != pixelHeight: + canvas.height = pixelHeight + canvas.style.width = cstring($cssWidth & "px") + canvas.style.height = cstring($cssHeight & "px") + + result = vec2(cssWidth.float32, cssHeight.float32) + + var fpsLastTime = 0.0 + var fpsFrames = 0 + + proc drawFrame(time: float) = + inc globalFrame + let cssSize = updateCanvas() + var renders = makeRenderTree(cssSize.x, cssSize.y, globalFrame) + renderer.renderFrame( + renders, + vec2(canvas.width.float32, canvas.height.float32), + ) + inc fpsFrames + if fpsLastTime == 0.0: + fpsLastTime = time + let elapsed = time - fpsLastTime + if elapsed >= 500.0: + let fps = fpsFrames.float * 1000.0 / elapsed + fpsNode.textContent = cstring("FPS: " & formatFloat(fps, ffDecimal, 1)) + fpsFrames = 0 + fpsLastTime = time + discard window.requestAnimationFrame(drawFrame) + + discard window.requestAnimationFrame(drawFrame) + +when isMainModule: + main() diff --git a/src/figdraw/common/fontutils.nim b/src/figdraw/common/fontutils.nim index b7d2014..9cf0d5a 100644 --- a/src/figdraw/common/fontutils.nim +++ b/src/figdraw/common/fontutils.nim @@ -4,7 +4,7 @@ import std/isolation import pkg/vmath import pkg/pixie import pkg/pixie/fonts -import pkg/chronicles +import ../utils/logging import ./rchannels import ./imgutils diff --git a/src/figdraw/common/fontutils_js.nim b/src/figdraw/common/fontutils_js.nim new file mode 100644 index 0000000..6af8239 --- /dev/null +++ b/src/figdraw/common/fontutils_js.nim @@ -0,0 +1,28 @@ +import fonttypes +import uimaths + +type TypeFaceKinds* = enum + TTF + OTF + SVG + +type Box* = Rect + +proc getTypefaceImpl*(name: string): FontId = + FontId(0) + +proc getTypefaceImpl*(name, data: string, kind: TypeFaceKinds): FontId = + FontId(0) + +proc getLineHeightImpl*(font: UiFont): float32 = + 0.0 + +proc getTypesetImpl*( + box: Box, + spans: openArray[(UiFont, string)], + hAlign = Left, + vAlign = Top, + minContent = false, + wrap = true, +): GlyphArrangement = + GlyphArrangement() diff --git a/src/figdraw/common/imgutils.nim b/src/figdraw/common/imgutils.nim index f65cc8a..c1fbe8c 100644 --- a/src/figdraw/common/imgutils.nim +++ b/src/figdraw/common/imgutils.nim @@ -4,7 +4,7 @@ import std/[isolation, locks, times] import pkg/vmath import pkg/pixie import pkg/pixie/fonts -import chronicles +import ../utils/logging import ./rchannels import ./formatflippy diff --git a/src/figdraw/common/imgutils_js.nim b/src/figdraw/common/imgutils_js.nim new file mode 100644 index 0000000..4b8384b --- /dev/null +++ b/src/figdraw/common/imgutils_js.nim @@ -0,0 +1,28 @@ +import std/hashes + +type + ImageId* = distinct Hash + + ImgObj* = object + id*: ImageId + + ImageChannel* = object + +var imageChan* = ImageChannel() + +proc `==`*(a, b: ImageId): bool {.borrow.} + +proc imgId*(name: string): ImageId = + hash(name).ImageId + +proc tryRecv*(ch: ImageChannel; msg: var ImgObj): bool = + false + +proc hasImage*(id: ImageId): bool = + false + +proc loadImage*(filePath: string): ImageId = + imgId(filePath) + +proc loadImage*(id: ImageId; image: auto) = + discard diff --git a/src/figdraw/common/rchannels_js.nim b/src/figdraw/common/rchannels_js.nim new file mode 100644 index 0000000..4346098 --- /dev/null +++ b/src/figdraw/common/rchannels_js.nim @@ -0,0 +1,14 @@ +type + RChan*[T] = object + +proc newRChan*[T](size: int = 0): RChan[T] = + RChan[T]() + +proc tryRecv*[T](ch: RChan[T], msg: var T): bool = + false + +proc send*[T](ch: var RChan[T], msg: T) = + discard + +proc push*[T](ch: var RChan[T], msg: T) = + discard diff --git a/src/figdraw/common/shared.nim b/src/figdraw/common/shared.nim index 6e26eda..bc4411a 100644 --- a/src/figdraw/common/shared.nim +++ b/src/figdraw/common/shared.nim @@ -1,10 +1,12 @@ import std/[sequtils, tables, hashes] import std/[unicode, strformat] -import std/os -import pkg/variant +when not defined(js): + import std/os + import pkg/variant export sequtils, strformat, tables, hashes -export variant +when not defined(js): + export variant import extras, uimaths export extras, uimaths @@ -56,7 +58,11 @@ type pixelScale*: float32 var - dataDirStr* {.runtimeVar.}: string = os.getCurrentDir() / "data" + dataDirStr* {.runtimeVar.}: string = + when defined(js): + "data" + else: + os.getCurrentDir() / "data" app* {.runtimeVar.} = AppState(running: true, uiScale: 1.0, autoUiScale: true, pixelScale: 1.0) diff --git a/src/figdraw/common/transfer.nim b/src/figdraw/common/transfer.nim index f6bbdbb..91b2375 100644 --- a/src/figdraw/common/transfer.nim +++ b/src/figdraw/common/transfer.nim @@ -1,5 +1,6 @@ import std/sequtils -import stack_strings +when not defined(js): + import stack_strings import ../fignodes type RenderTree* = ref object diff --git a/src/figdraw/commons.nim b/src/figdraw/commons.nim index 8082e4a..7d89a6b 100644 --- a/src/figdraw/commons.nim +++ b/src/figdraw/commons.nim @@ -1,13 +1,17 @@ import common/shared import common/uimaths -import common/rchannels import common/transfer import common/appframes -import common/fontutils -import common/imgutils +when defined(js): + import common/rchannels_js as rchannels + import common/imgutils_js as imgutils + import common/fontutils_js as fontutils +else: + import common/imgutils + import common/fontutils + import common/rchannels export shared, uimaths, rchannels export transfer, appframes export fontutils export imgutils - diff --git a/src/figdraw/figbasics.nim b/src/figdraw/figbasics.nim index 0263863..7654035 100644 --- a/src/figdraw/figbasics.nim +++ b/src/figdraw/figbasics.nim @@ -1,12 +1,15 @@ import std/[options, hashes] -import chroma, stack_strings +import chroma import common/uimaths import common/fonttypes -import common/imgutils +when defined(js): + import common/imgutils_js as imgutils +else: + import common/imgutils export uimaths, fonttypes, imgutils -export options, chroma, stack_strings +export options, chroma const FigStringCap* {.intdefine.} = 48 @@ -14,9 +17,15 @@ const FigDrawNames* {.booldefine: "figdraw.names".}: bool = false type - FigName* = StackString[FigStringCap] FigID* = int64 +when defined(js): + type FigName* = string +else: + import stack_strings + export stack_strings + type FigName* = StackString[FigStringCap] + type Directions* = enum dTop @@ -75,4 +84,3 @@ type ImageStyle* = object color*: Color id*: ImageId - diff --git a/src/figdraw/fignodes.nim b/src/figdraw/fignodes.nim index f6aab6c..b35d89a 100644 --- a/src/figdraw/fignodes.nim +++ b/src/figdraw/fignodes.nim @@ -46,10 +46,14 @@ proc `$`*(id: FigIdx): string = "FigIdx(" & $(int(id)) & ")" proc toFigName*(s: string): FigName = - toStackString(s[0 ..< min(s.len(), s.len())], FigStringCap) - -proc toFigName*(s: FigName): FigName = - s + when defined(js): + s + else: + toStackString(s[0 ..< min(s.len(), s.len())], FigStringCap) + +when not defined(js): + proc toFigName*(s: FigName): FigName = + s proc `+`*(a, b: FigIdx): FigIdx {.borrow.} proc `<=`*(a, b: FigIdx): bool {.borrow.} diff --git a/src/figdraw/opengl/buffers.nim b/src/figdraw/opengl/buffers.nim index 94c4d78..420d134 100644 --- a/src/figdraw/opengl/buffers.nim +++ b/src/figdraw/opengl/buffers.nim @@ -1,4 +1,4 @@ -import opengl +import glapi type BufferKind* = enum @@ -16,7 +16,7 @@ type kind*: BufferKind normalized*: bool usage*: GLenum - bufferId*: GLuint + bufferId*: GlBufferId byteCapacity*: int func size*(componentType: GLenum): Positive = @@ -39,18 +39,42 @@ func componentCount*(bufferKind: BufferKind): Positive = of bkMAT3: 9 of bkMAT4: 16 -proc bindBufferData*(buffer: ptr Buffer, data: pointer) = - if buffer.bufferId == 0: - glGenBuffers(1, buffer.bufferId.addr) +when defined(js): + proc bindBufferData*[T](buffer: ptr Buffer, data: openArray[T]) = + if buffer.bufferId.isNil: + buffer.bufferId = glCreateBuffer() - let byteLength = - buffer.count * buffer.kind.componentCount() * buffer.componentType.size() - - glBindBuffer(buffer.target, buffer.bufferId) - if buffer.byteCapacity < byteLength: + let byteLength = + buffer.count * buffer.kind.componentCount() * buffer.componentType.size() let usage = if buffer.usage == 0.GLenum: GL_STATIC_DRAW else: buffer.usage - glBufferData(buffer.target, byteLength, nil, usage) + + glBindBuffer(buffer.target, buffer.bufferId) + if byteLength <= 0: + return + + when T is float32: + glBufferData(buffer.target, newFloat32Array(data), usage) + elif T is uint16: + glBufferData(buffer.target, newUint16Array(data), usage) + elif T is uint8: + glBufferData(buffer.target, newUint8Array(data), usage) + else: + {.fatal: "Unsupported buffer data type for WebGL.".} + buffer.byteCapacity = byteLength +else: + proc bindBufferData*(buffer: ptr Buffer, data: pointer) = + if buffer.bufferId == 0: + glGenBuffers(1, buffer.bufferId.addr) + + let byteLength = + buffer.count * buffer.kind.componentCount() * buffer.componentType.size() + + glBindBuffer(buffer.target, buffer.bufferId) + if buffer.byteCapacity < byteLength: + let usage = if buffer.usage == 0.GLenum: GL_STATIC_DRAW else: buffer.usage + glBufferData(buffer.target, byteLength, nil, usage) + buffer.byteCapacity = byteLength - if data != nil and byteLength > 0: - glBufferSubData(buffer.target, 0, byteLength, data) + if data != nil and byteLength > 0: + glBufferSubData(buffer.target, 0, byteLength, data) diff --git a/src/figdraw/opengl/glapi.nim b/src/figdraw/opengl/glapi.nim new file mode 100644 index 0000000..b11112f --- /dev/null +++ b/src/figdraw/opengl/glapi.nim @@ -0,0 +1,327 @@ +when defined(js): + import std/jsffi + import ../webgl/api as webgl + + export webgl.GLenum, webgl.GLboolean, webgl.GLbitfield, webgl.GLbyte, + webgl.GLshort, webgl.GLint, webgl.GLsizei, webgl.GLintptr, webgl.GLsizeiptr, + webgl.GLubyte, webgl.GLushort, webgl.GLuint, webgl.GLfloat, webgl.GLclampf + + type + GlBufferId* = webgl.WebGLBuffer + GlTextureId* = webgl.WebGLTexture + GlProgramId* = webgl.WebGLProgram + GlShaderId* = webgl.WebGLShader + GlVertexArrayId* = webgl.WebGLVertexArrayObject + GlFramebufferId* = webgl.WebGLFramebuffer + GlUniformLocation* = webgl.WebGLUniformLocation + WebGL2RenderingContext* = webgl.WebGL2RenderingContext + + const + GL_FALSE* = false + GL_TRUE* = true + + GL_NO_ERROR* = 0.GLenum + + GL_BYTE* = 0x1400.GLenum + GL_UNSIGNED_BYTE* = 0x1401.GLenum + GL_SHORT* = 0x1402.GLenum + GL_UNSIGNED_SHORT* = 0x1403.GLenum + GL_INT* = 0x1404.GLenum + GL_UNSIGNED_INT* = 0x1405.GLenum + GL_FLOAT* = 0x1406.GLenum + + GL_TEXTURE_2D* = 0x0DE1.GLenum + GL_TEXTURE_BUFFER* = 0x8C2A.GLenum + GL_TEXTURE0* = 0x84C0.GLenum + GL_TEXTURE1* = 0x84C1.GLenum + GL_TEXTURE_MAG_FILTER* = 0x2800.GLenum + GL_TEXTURE_MIN_FILTER* = 0x2801.GLenum + GL_TEXTURE_WRAP_S* = 0x2802.GLenum + GL_TEXTURE_WRAP_T* = 0x2803.GLenum + + GL_NEAREST* = 0x2600.GLenum + GL_LINEAR* = 0x2601.GLenum + GL_NEAREST_MIPMAP_NEAREST* = 0x2700.GLenum + GL_LINEAR_MIPMAP_NEAREST* = 0x2701.GLenum + GL_NEAREST_MIPMAP_LINEAR* = 0x2702.GLenum + GL_LINEAR_MIPMAP_LINEAR* = 0x2703.GLenum + + GL_REPEAT* = 0x2901.GLenum + GL_CLAMP_TO_EDGE* = 0x812F.GLenum + GL_MIRRORED_REPEAT* = 0x8370.GLenum + + GL_RGBA* = 0x1908.GLenum + GL_RGBA8* = 0x8058.GLenum + GL_R8* = 0x8229.GLenum + + GL_FRAMEBUFFER* = 0x8D40.GLenum + GL_COLOR_ATTACHMENT0* = 0x8CE0.GLenum + GL_FRAMEBUFFER_COMPLETE* = 0x8CD5.GLenum + + GL_ARRAY_BUFFER* = 0x8892.GLenum + GL_ELEMENT_ARRAY_BUFFER* = 0x8893.GLenum + GL_UNIFORM_BUFFER* = 0x8A11.GLenum + + GL_STREAM_DRAW* = 0x88E0.GLenum + GL_STATIC_DRAW* = 0x88E4.GLenum + + GL_TRIANGLES* = 0x0004.GLenum + + GL_COLOR_BUFFER_BIT* = 0x4000.GLenum + GL_DEPTH_BUFFER_BIT* = 0x0100.GLenum + + GL_BLEND* = 0x0BE2.GLenum + GL_SRC_ALPHA* = 0x0302.GLenum + GL_ONE_MINUS_SRC_ALPHA* = 0x0303.GLenum + GL_ONE* = 1.GLenum + + GL_DEPTH_TEST* = 0x0B71.GLenum + GL_LEQUAL* = 0x0203.GLenum + + GL_INFO_LOG_LENGTH* = 0x8B84.GLenum + GL_COMPILE_STATUS* = 0x8B81.GLenum + GL_LINK_STATUS* = 0x8B82.GLenum + GL_ACTIVE_ATTRIBUTES* = 0x8B89.GLenum + GL_ACTIVE_UNIFORMS* = 0x8B86.GLenum + + GL_VERTEX_SHADER* = 0x8B31.GLenum + GL_FRAGMENT_SHADER* = 0x8B30.GLenum + GL_COMPUTE_SHADER* = 0x91B9.GLenum + + GL_CONTEXT_FLAGS* = 0x821E.GLenum + GL_CONTEXT_FLAG_DEBUG_BIT* = 0x00000002.GLenum + GL_DEBUG_SEVERITY_NOTIFICATION* = 0x826B.GLenum + GL_DEBUG_OUTPUT_SYNCHRONOUS* = 0x8242.GLenum + GL_DEBUG_OUTPUT* = 0x92E0.GLenum + + cGL_FLOAT* = GL_FLOAT + cGL_INT* = GL_INT + cGL_BYTE* = GL_BYTE + cGL_SHORT* = GL_SHORT + cGL_UNSIGNED_BYTE* = GL_UNSIGNED_BYTE + cGL_UNSIGNED_SHORT* = GL_UNSIGNED_SHORT + + var glCtx* {.importc: "glCtx", nodecl.}: WebGL2RenderingContext + + proc setWebGLContext*(ctx: WebGL2RenderingContext) = + glCtx = ctx + + proc newFloat32Array*(data: openArray[float32]): webgl.Float32Array + {.importjs: "new Float32Array(#)".} + proc newUint16Array*(data: openArray[uint16]): webgl.Uint16Array + {.importjs: "new Uint16Array(#)".} + proc newUint8Array*(data: openArray[uint8]): webgl.Uint8Array + {.importjs: "new Uint8Array(#)".} + + proc glCreateBuffer*(): GlBufferId {.importjs: "glCtx.createBuffer()".} + proc glCreateTexture*(): GlTextureId {.importjs: "glCtx.createTexture()".} + proc glCreateVertexArray*(): GlVertexArrayId + {.importjs: "glCtx.createVertexArray()".} + proc glCreateFramebuffer*(): GlFramebufferId + {.importjs: "glCtx.createFramebuffer()".} + + proc glCreateShader*(kind: GLenum): GlShaderId + {.importjs: "glCtx.createShader(#)".} + proc glShaderSource*(shader: GlShaderId; source: cstring) + {.importjs: "glCtx.shaderSource(#, #)".} + proc glCompileShader*(shader: GlShaderId) + {.importjs: "glCtx.compileShader(#)".} + proc glGetShaderInfoLog*(shader: GlShaderId): cstring + {.importjs: "glCtx.getShaderInfoLog(#)".} + proc glGetShaderParameter*(shader: GlShaderId; pname: GLenum): bool + {.importjs: "glCtx.getShaderParameter(#, #)".} + + proc glCreateProgram*(): GlProgramId {.importjs: "glCtx.createProgram()".} + proc glAttachShader*(program: GlProgramId; shader: GlShaderId) + {.importjs: "glCtx.attachShader(#, #)".} + proc glLinkProgram*(program: GlProgramId) + {.importjs: "glCtx.linkProgram(#)".} + proc glGetProgramInfoLog*(program: GlProgramId): cstring + {.importjs: "glCtx.getProgramInfoLog(#)".} + proc glGetProgramParameter*(program: GlProgramId; pname: GLenum): int + {.importjs: "glCtx.getProgramParameter(#, #)".} + + proc glUseProgram*(program: GlProgramId) + {.importjs: "glCtx.useProgram(#)".} + + proc glGetAttribLocation*(program: GlProgramId; name: cstring): GLint + {.importjs: "glCtx.getAttribLocation(#, #)".} + proc glGetUniformLocation*(program: GlProgramId; + name: cstring): GlUniformLocation + {.importjs: "glCtx.getUniformLocation(#, #)".} + + proc glGetActiveAttrib*(program: GlProgramId; + index: GLuint): webgl.WebGLActiveInfo + {.importjs: "glCtx.getActiveAttrib(#, #)".} + proc glGetActiveUniform*(program: GlProgramId; + index: GLuint): webgl.WebGLActiveInfo + {.importjs: "glCtx.getActiveUniform(#, #)".} + + proc glUniform1i*(location: GlUniformLocation; v0: GLint) + {.importjs: "glCtx.uniform1i(#, #)".} + proc glUniform2i*(location: GlUniformLocation; v0, v1: GLint) + {.importjs: "glCtx.uniform2i(#, #, #)".} + proc glUniform3i*(location: GlUniformLocation; v0, v1, v2: GLint) + {.importjs: "glCtx.uniform3i(#, #, #, #)".} + proc glUniform4i*(location: GlUniformLocation; v0, v1, v2, v3: GLint) + {.importjs: "glCtx.uniform4i(#, #, #, #, #)".} + + proc glUniform1f*(location: GlUniformLocation; v0: GLfloat) + {.importjs: "glCtx.uniform1f(#, #)".} + proc glUniform2f*(location: GlUniformLocation; v0, v1: GLfloat) + {.importjs: "glCtx.uniform2f(#, #, #)".} + proc glUniform3f*(location: GlUniformLocation; v0, v1, v2: GLfloat) + {.importjs: "glCtx.uniform3f(#, #, #, #)".} + proc glUniform4f*(location: GlUniformLocation; v0, v1, v2, v3: GLfloat) + {.importjs: "glCtx.uniform4f(#, #, #, #, #)".} + + proc glUniformMatrix4fvRaw*(location: GlUniformLocation; transpose: GLboolean; + value: webgl.Float32Array) {.importjs: "glCtx.uniformMatrix4fv(#, #, #)".} + + proc glUniformMatrix4fv*( + location: GlUniformLocation; count: GLsizei; transpose: GLboolean; + value: openArray[float32]; + ) = + if count <= 0: + return + glUniformMatrix4fvRaw(location, transpose, newFloat32Array(value)) + + proc glBindBuffer*(target: GLenum; buffer: GlBufferId) + {.importjs: "glCtx.bindBuffer(#, #)".} + + proc glBufferData*(target: GLenum; size: int; usage: GLenum) + {.importjs: "glCtx.bufferData(#, #, #)".} + proc glBufferData*(target: GLenum; data: webgl.Float32Array; usage: GLenum) + {.importjs: "glCtx.bufferData(#, #, #)".} + proc glBufferData*(target: GLenum; data: webgl.Uint16Array; usage: GLenum) + {.importjs: "glCtx.bufferData(#, #, #)".} + proc glBufferData*(target: GLenum; data: webgl.Uint8Array; usage: GLenum) + {.importjs: "glCtx.bufferData(#, #, #)".} + + proc glBufferSubData*(target: GLenum; offset: GLintptr; + data: webgl.Float32Array) + {.importjs: "glCtx.bufferSubData(#, #, #)".} + proc glBufferSubData*(target: GLenum; offset: GLintptr; + data: webgl.Uint16Array) + {.importjs: "glCtx.bufferSubData(#, #, #)".} + proc glBufferSubData*(target: GLenum; offset: GLintptr; + data: webgl.Uint8Array) + {.importjs: "glCtx.bufferSubData(#, #, #)".} + + proc glBindTexture*(target: GLenum; texture: GlTextureId) + {.importjs: "glCtx.bindTexture(#, #)".} + proc glActiveTexture*(texture: GLenum) + {.importjs: "glCtx.activeTexture(#)".} + + proc glTexImage2D*( + target: GLenum; + level: GLint; + internalFormat: GLint; + width: GLsizei; + height: GLsizei; + border: GLint; + format: GLenum; + typ: GLenum; + data: JsObject; + ) {.importjs: "glCtx.texImage2D(#, #, #, #, #, #, #, #, #)".} + + proc glTexSubImage2D*( + target: GLenum; + level: GLint; + xoffset: GLint; + yoffset: GLint; + width: GLsizei; + height: GLsizei; + format: GLenum; + typ: GLenum; + data: JsObject; + ) {.importjs: "glCtx.texSubImage2D(#, #, #, #, #, #, #, #, #)".} + + proc glTexParameteri*(target: GLenum; pname: GLenum; param: GLint) + {.importjs: "glCtx.texParameteri(#, #, #)".} + proc glGenerateMipmap*(target: GLenum) + {.importjs: "glCtx.generateMipmap(#)".} + + proc glBindFramebuffer*(target: GLenum; framebuffer: GlFramebufferId) + {.importjs: "glCtx.bindFramebuffer(#, #)".} + proc glFramebufferTexture2D*( + target: GLenum; + attachment: GLenum; + textarget: GLenum; + texture: GlTextureId; + level: GLint; + ) {.importjs: "glCtx.framebufferTexture2D(#, #, #, #, #)".} + proc glCheckFramebufferStatus*(target: GLenum): GLenum + {.importjs: "glCtx.checkFramebufferStatus(#)".} + + proc glBindVertexArray*(vao: GlVertexArrayId) + {.importjs: "glCtx.bindVertexArray(#)".} + + proc glVertexAttribPointer*( + index: GLuint; + size: GLint; + typ: GLenum; + normalized: GLboolean; + stride: GLsizei; + offset: GLintptr; + ) {.importjs: "glCtx.vertexAttribPointer(#, #, #, #, #, #)".} + + proc glVertexAttribIPointer*( + index: GLuint; + size: GLint; + typ: GLenum; + stride: GLsizei; + offset: GLintptr; + ) {.importjs: "glCtx.vertexAttribIPointer(#, #, #, #, #)".} + + proc glEnableVertexAttribArray*(index: GLuint) + {.importjs: "glCtx.enableVertexAttribArray(#)".} + + proc glDrawElements*(mode: GLenum; count: GLsizei; typ: GLenum; + offset: GLintptr) + {.importjs: "glCtx.drawElements(#, #, #, #)".} + + proc glViewport*(x, y: GLint; width, height: GLsizei) + {.importjs: "glCtx.viewport(#, #, #, #)".} + proc glClearColor*(r, g, b, a: GLclampf) + {.importjs: "glCtx.clearColor(#, #, #, #)".} + proc glClear*(mask: GLbitfield) + {.importjs: "glCtx.clear(#)".} + + proc glEnable*(cap: GLenum) {.importjs: "glCtx.enable(#)".} + proc glDisable*(cap: GLenum) {.importjs: "glCtx.disable(#)".} + proc glBlendFunc*(sfactor, dfactor: GLenum) + {.importjs: "glCtx.blendFunc(#, #)".} + proc glBlendFuncSeparate*(srcRGB, dstRGB, srcAlpha, dstAlpha: GLenum) + {.importjs: "glCtx.blendFuncSeparate(#, #, #, #)".} + proc glDepthMask*(flag: GLboolean) + {.importjs: "glCtx.depthMask(#)".} + proc glDepthFunc*(mode: GLenum) + {.importjs: "glCtx.depthFunc(#)".} + + proc glGetError*(): GLenum {.importjs: "glCtx.getError()".} + proc glGetInteger*(pname: GLenum): GLint + {.importjs: "glCtx.getParameter(#)".} + proc glGetString*(pname: GLenum): cstring + {.importjs: "glCtx.getParameter(#)".} + + proc glBindBufferBase*(target: GLenum; index: GLuint; buffer: GlBufferId) + {.importjs: "glCtx.bindBufferBase(#, #, #)".} + proc glGetUniformBlockIndex*(program: GlProgramId; name: cstring): GLuint + {.importjs: "glCtx.getUniformBlockIndex(#, #)".} + proc glUniformBlockBinding*(program: GlProgramId; index: GLuint; + binding: GLuint) + {.importjs: "glCtx.uniformBlockBinding(#, #, #)".} + +else: + import pkg/opengl as opengl + export opengl + + type + GlBufferId* = GLuint + GlTextureId* = GLuint + GlProgramId* = GLuint + GlShaderId* = GLuint + GlVertexArrayId* = GLuint + GlFramebufferId* = GLuint + GlUniformLocation* = GLint diff --git a/src/figdraw/opengl/glcontext.nim b/src/figdraw/opengl/glcontext.nim index a6f02cf..3d9cce4 100644 --- a/src/figdraw/opengl/glcontext.nim +++ b/src/figdraw/opengl/glcontext.nim @@ -1,21 +1,25 @@ +## Copied from Fidget backend, copyright from @treeform applies + import - buffers, chroma, pixie, hashes, opengl, os, shaders, strformat, strutils, tables, + buffers, chroma, hashes, glapi, os, shaders, strformat, strutils, tables, textures, times -## Copied from Fidget backend, copyright from @treeform applies - -import pixie/simd +when defined(js): + type Flippy* = object + type Image* = object +else: + import pixie + import pixie/simd -import pkg/chronicles + import ../common/formatflippy + import ../utils/drawextras + import ../utils/drawboxes + import ../utils/drawshadows + export drawextras +import ../utils/logging import ../commons -import ../common/formatflippy import ../fignodes -import ../utils/drawextras -import ../utils/drawboxes -import ../utils/drawshadows - -export drawextras logScope: scope = "opengl" @@ -25,7 +29,7 @@ proc round*(v: Vec2): Vec2 = const quadLimit = 10_921 -when defined(emscripten): +when defined(emscripten) or defined(js): type SdfModeData = float32 else: type SdfModeData = uint16 @@ -33,22 +37,23 @@ else: type Context* = ref object mainShader, maskShader, activeShader: Shader atlasTexture: Texture - maskTextureWrite: int ## Index into max textures for writing. - maskTextures: seq[Texture] ## Masks array for pushing and popping. - atlasSize: int ## Size x size dimensions of the atlas - atlasMargin: int ## Default margin between images - quadCount: int ## Number of quads drawn so far - maxQuads: int ## Max quads to draw before issuing an OpenGL call - mat*: Mat4 ## Current matrix - mats: seq[Mat4] ## Matrix stack + maskTextureWrite: int ## Index into max textures for writing. + maskTextures: seq[Texture] ## Masks array for pushing and popping. + atlasSize: int ## Size x size dimensions of the atlas + atlasMargin: int ## Default margin between images + quadCount: int ## Number of quads drawn so far + maxQuads: int ## Max quads to draw before issuing an OpenGL call + mat*: Mat4 ## Current matrix + mats: seq[Mat4] ## Matrix stack entries*: Table[Hash, Rect] ## Mapping of image name to atlas UV position - heights: seq[uint16] ## Height map of the free space in the atlas + heights: seq[uint16] ## Height map of the free space in the atlas proj*: Mat4 - frameSize: Vec2 ## Dimensions of the window frame - vertexArrayId, maskFramebufferId: GLuint + frameSize: Vec2 ## Dimensions of the window frame + vertexArrayId: GlVertexArrayId + maskFramebufferId: GlFramebufferId frameBegun, maskBegun: bool - pixelate*: bool ## Makes texture look pixelated, like a pixel game. - pixelScale*: float32 ## Multiple scaling factor. + pixelate*: bool ## Makes texture look pixelated, like a pixel game. + pixelScale*: float32 ## Multiple scaling factor. # Buffer data for OpenGL indices: tuple[buffer: Buffer, data: seq[uint16]] @@ -79,17 +84,28 @@ proc upload(ctx: Context) = ctx.colors.buffer.count = ctx.quadCount * 4 ctx.uvs.buffer.count = ctx.quadCount * 4 ctx.indices.buffer.count = ctx.quadCount * 6 - bindBufferData(ctx.positions.buffer.addr, ctx.positions.data[0].addr) - bindBufferData(ctx.colors.buffer.addr, ctx.colors.data[0].addr) - bindBufferData(ctx.uvs.buffer.addr, ctx.uvs.data[0].addr) + when defined(js): + bindBufferData(ctx.positions.buffer.addr, ctx.positions.data) + bindBufferData(ctx.colors.buffer.addr, ctx.colors.data) + bindBufferData(ctx.uvs.buffer.addr, ctx.uvs.data) + else: + bindBufferData(ctx.positions.buffer.addr, ctx.positions.data[0].addr) + bindBufferData(ctx.colors.buffer.addr, ctx.colors.data[0].addr) + bindBufferData(ctx.uvs.buffer.addr, ctx.uvs.data[0].addr) ctx.sdfParams.buffer.count = ctx.quadCount * 4 ctx.sdfRadii.buffer.count = ctx.quadCount * 4 ctx.sdfModeAttr.buffer.count = ctx.quadCount * 4 ctx.sdfFactors.buffer.count = ctx.quadCount * 4 - bindBufferData(ctx.sdfParams.buffer.addr, ctx.sdfParams.data[0].addr) - bindBufferData(ctx.sdfRadii.buffer.addr, ctx.sdfRadii.data[0].addr) - bindBufferData(ctx.sdfModeAttr.buffer.addr, ctx.sdfModeAttr.data[0].addr) - bindBufferData(ctx.sdfFactors.buffer.addr, ctx.sdfFactors.data[0].addr) + when defined(js): + bindBufferData(ctx.sdfParams.buffer.addr, ctx.sdfParams.data) + bindBufferData(ctx.sdfRadii.buffer.addr, ctx.sdfRadii.data) + bindBufferData(ctx.sdfModeAttr.buffer.addr, ctx.sdfModeAttr.data) + bindBufferData(ctx.sdfFactors.buffer.addr, ctx.sdfFactors.data) + else: + bindBufferData(ctx.sdfParams.buffer.addr, ctx.sdfParams.data[0].addr) + bindBufferData(ctx.sdfRadii.buffer.addr, ctx.sdfRadii.data[0].addr) + bindBufferData(ctx.sdfModeAttr.buffer.addr, ctx.sdfModeAttr.data[0].addr) + bindBufferData(ctx.sdfFactors.buffer.addr, ctx.sdfFactors.data[0].addr) proc setUpMaskFramebuffer(ctx: Context) = glBindFramebuffer(GL_FRAMEBUFFER, ctx.maskFramebufferId) @@ -102,8 +118,12 @@ proc setUpMaskFramebuffer(ctx: Context) = ) proc createAtlasTexture(ctx: Context, size: int): Texture = - result.width = size.GLint - result.height = size.GLint + when defined(js): + result.width = int32(size) + result.height = int32(size) + else: + result.width = size.GLint + result.height = size.GLint result.componentType = GL_UNSIGNED_BYTE result.format = GL_RGBA result.internalFormat = GL_RGBA8 @@ -123,7 +143,7 @@ proc addMaskTexture(ctx: Context, frameSize = vec2(1, 1)) = maskTexture.height = frameSize.y.int32 maskTexture.componentType = GL_UNSIGNED_BYTE maskTexture.format = GL_RGBA - when defined(emscripten): + when defined(emscripten) or defined(js): maskTexture.internalFormat = GL_RGBA8 else: maskTexture.internalFormat = GL_R8 @@ -161,7 +181,12 @@ proc newContext*( result.addMaskTexture() - when defined(emscripten) or defined(useOpenGlEs): + when defined(js): + result.maskShader = + newShaderStatic("glsl/webgl2/atlas.vert", "glsl/webgl2/mask.frag") + result.mainShader = + newShaderStatic("glsl/webgl2/atlas.vert", "glsl/webgl2/atlas.frag") + elif defined(emscripten) or defined(useOpenGlEs): result.maskShader = newShaderStatic("glsl/emscripten/atlas.vert", "glsl/emscripten/mask.frag") result.mainShader = @@ -213,7 +238,7 @@ proc newContext*( result.sdfRadii.data = newSeq[float32](result.sdfRadii.buffer.kind.componentCount() * maxQuads * 4) - when defined(emscripten): + when defined(emscripten) or defined(js): result.sdfModeAttr.buffer.componentType = cGL_FLOAT else: result.sdfModeAttr.buffer.componentType = GL_UNSIGNED_SHORT @@ -221,7 +246,8 @@ proc newContext*( result.sdfModeAttr.buffer.target = GL_ARRAY_BUFFER result.sdfModeAttr.buffer.usage = GL_STREAM_DRAW result.sdfModeAttr.data = - newSeq[SdfModeData](result.sdfModeAttr.buffer.kind.componentCount() * maxQuads * 4) + newSeq[SdfModeData](result.sdfModeAttr.buffer.kind.componentCount() * + maxQuads * 4) result.sdfFactors.buffer.componentType = cGL_FLOAT result.sdfFactors.buffer.kind = bkVEC2 @@ -250,14 +276,21 @@ proc newContext*( ) # Indices are only uploaded once - bindBufferData(result.indices.buffer.addr, result.indices.data[0].addr) + when defined(js): + bindBufferData(result.indices.buffer.addr, result.indices.data) + else: + bindBufferData(result.indices.buffer.addr, result.indices.data[0].addr) result.upload() result.activeShader = result.mainShader - glGenVertexArrays(1, result.vertexArrayId.addr) - glBindVertexArray(result.vertexArrayId) + when defined(js): + result.vertexArrayId = glCreateVertexArray() + glBindVertexArray(result.vertexArrayId) + else: + glGenVertexArrays(1, result.vertexArrayId.addr) + glBindVertexArray(result.vertexArrayId) # Main shader (atlas + SDF). result.mainShader.bindAttrib("vertexPos", result.positions.buffer) @@ -277,14 +310,20 @@ proc newContext*( result.maskShader.bindAttrib("vertexSdfMode", result.sdfModeAttr.buffer) # Create mask framebuffer - glGenFramebuffers(1, result.maskFramebufferId.addr) + when defined(js): + result.maskFramebufferId = glCreateFramebuffer() + else: + glGenFramebuffers(1, result.maskFramebufferId.addr) result.setUpMaskFramebuffer() let status = glCheckFramebufferStatus(GL_FRAMEBUFFER) if status != GL_FRAMEBUFFER_COMPLETE: quit(&"Something wrong with mask framebuffer: {toHex(status.int32, 4)}") - glBindFramebuffer(GL_FRAMEBUFFER, 0) + when defined(js): + glBindFramebuffer(GL_FRAMEBUFFER, nil) + else: + glBindFramebuffer(GL_FRAMEBUFFER, 0) func `[]=`(t: var Table[Hash, Rect], key: string, rect: Rect) = t[hash(key)] = rect @@ -347,53 +386,73 @@ proc findEmptyRect(ctx: Context, width, height: int): Rect = return rect -proc putImage*(ctx: Context, path: Hash, image: Image) = - # Reminder: This does not set mipmaps (used for text, should it?) - let rect = ctx.findEmptyRect(image.width, image.height) - ctx.entries[path] = rect / float(ctx.atlasSize) - updateSubImage(ctx.atlasTexture, int(rect.x), int(rect.y), image) - -proc addImage*(ctx: Context, key: Hash, image: Image) = - ctx.putImage(key, image) - -proc updateImage*(ctx: Context, path: Hash, image: Image) = - ## Updates an image that was put there with putImage. - ## Useful for things like video. - ## * Must be the same size. - ## * This does not set mipmaps. - let rect = ctx.entries[path] - assert rect.w == image.width.float / float(ctx.atlasSize) - assert rect.h == image.height.float / float(ctx.atlasSize) - updateSubImage( - ctx.atlasTexture, - int(rect.x * ctx.atlasSize.float), - int(rect.y * ctx.atlasSize.float), - image, - ) +when not defined(js): + proc putImage*(ctx: Context, path: Hash, image: Image) = + # Reminder: This does not set mipmaps (used for text, should it?) + let rect = ctx.findEmptyRect(image.width, image.height) + ctx.entries[path] = rect / float(ctx.atlasSize) + updateSubImage(ctx.atlasTexture, int(rect.x), int(rect.y), image) + + proc addImage*(ctx: Context, key: Hash, image: Image) = + ctx.putImage(key, image) + + proc updateImage*(ctx: Context, path: Hash, image: Image) = + ## Updates an image that was put there with putImage. + ## Useful for things like video. + ## * Must be the same size. + ## * This does not set mipmaps. + let rect = ctx.entries[path] + assert rect.w == image.width.float / float(ctx.atlasSize) + assert rect.h == image.height.float / float(ctx.atlasSize) + updateSubImage( + ctx.atlasTexture, + int(rect.x * ctx.atlasSize.float), + int(rect.y * ctx.atlasSize.float), + image, + ) +else: + proc putImage*(ctx: Context, path: Hash, image: Image) = + discard + + proc addImage*(ctx: Context, key: Hash, image: Image) = + discard + + proc updateImage*(ctx: Context, path: Hash, image: Image) = + discard + +when not defined(js): + proc logFlippy(flippy: Flippy, file: string) = + debug "putFlippy file", + fwidth = $flippy.width, fheight = $flippy.height, flippyPath = file + + proc putFlippy*(ctx: Context, path: Hash, flippy: Flippy) = + logFlippy(flippy, $path) + let rect = ctx.findEmptyRect(flippy.width, flippy.height) + ctx.entries[path] = rect / float(ctx.atlasSize) + var + x = int(rect.x) + y = int(rect.y) + for level, mip in flippy.mipmaps: + updateSubImage(ctx.atlasTexture, x, y, mip, level) + x = x div 2 + y = y div 2 + + proc putImage*(ctx: Context, imgObj: ImgObj) = + ## puts an ImgObj wrapper with either a flippy or image format + case imgObj.kind: + of FlippyImg: + ctx.putFlippy(imgObj.id.Hash, imgObj.flippy) + of PixieImg: + ctx.putImage(imgObj.id.Hash, imgObj.pimg) +else: + proc logFlippy(flippy: Flippy, file: string) = + discard -proc logFlippy(flippy: Flippy, file: string) = - debug "putFlippy file", - fwidth = $flippy.width, fheight = $flippy.height, flippyPath = file - -proc putFlippy*(ctx: Context, path: Hash, flippy: Flippy) = - logFlippy(flippy, $path) - let rect = ctx.findEmptyRect(flippy.width, flippy.height) - ctx.entries[path] = rect / float(ctx.atlasSize) - var - x = int(rect.x) - y = int(rect.y) - for level, mip in flippy.mipmaps: - updateSubImage(ctx.atlasTexture, x, y, mip, level) - x = x div 2 - y = y div 2 - -proc putImage*(ctx: Context, imgObj: ImgObj) = - ## puts an ImgObj wrapper with either a flippy or image format - case imgObj.kind: - of FlippyImg: - ctx.putFlippy(imgObj.id.Hash, imgObj.flippy) - of PixieImg: - ctx.putImage(imgObj.id.Hash, imgObj.pimg) + proc putFlippy*(ctx: Context, path: Hash, flippy: Flippy) = + discard + + proc putImage*(ctx: Context, imgObj: ImgObj) = + discard proc flush(ctx: Context, maskTextureRead: int = ctx.maskTextureWrite) = ## Flips - draws current buffer and starts a new one. @@ -429,9 +488,20 @@ proc flush(ctx: Context, maskTextureRead: int = ctx.maskTextureWrite) = ctx.activeShader.bindUniforms() glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ctx.indices.buffer.bufferId) - glDrawElements( - GL_TRIANGLES, ctx.indices.buffer.count.GLint, ctx.indices.buffer.componentType, nil - ) + when defined(js): + glDrawElements( + GL_TRIANGLES, + int32(ctx.indices.buffer.count), + ctx.indices.buffer.componentType, + 0.GLintptr, + ) + else: + glDrawElements( + GL_TRIANGLES, + ctx.indices.buffer.count.GLint, + ctx.indices.buffer.componentType, + nil, + ) ctx.quadCount = 0 @@ -504,7 +574,7 @@ proc drawQuad*( ctx.sdfFactors.data.setVert2(offset + 3, defaultFactors) # atlas fragment mode - when defined(emscripten): + when defined(emscripten) or defined(js): let modeVal = 0.0'f32 else: let modeVal = 0'u16 @@ -584,7 +654,7 @@ proc drawUvRect(ctx: Context, at, to: Vec2, uvAt, uvTo: Vec2, color: Color) = ctx.sdfFactors.data.setVert2(offset + 2, defaultFactors) ctx.sdfFactors.data.setVert2(offset + 3, defaultFactors) - when defined(emscripten): + when defined(emscripten) or defined(js): let modeVal = 0.0'f32 else: let modeVal = 0'u16 @@ -601,84 +671,155 @@ proc drawUvRect(ctx: Context, rect, uvRect: Rect, color: Color) = proc getImageRect(ctx: Context, imageId: Hash): Rect = return ctx.entries[imageId] -proc drawImage*( - ctx: Context, - imageId: Hash, - pos: Vec2 = vec2(0, 0), - color = color(1, 1, 1, 1), - scale = 1.0, -) = - ## Draws image the UI way - pos at top-left. - let - rect = ctx.getImageRect(imageId) - wh = rect.wh * ctx.atlasSize.float32 * scale - ctx.drawUvRect(pos, pos + wh, rect.xy, rect.xy + rect.wh, color) - -proc drawImage*( - ctx: Context, - imageId: Hash, - pos: Vec2 = vec2(0, 0), - color = color(1, 1, 1, 1), - size: Vec2, -) = - ## Draws image the UI way - pos at top-left. - let rect = ctx.getImageRect(imageId) - ctx.drawUvRect(pos, pos + size, rect.xy, rect.xy + rect.wh, color) - -proc drawImageAdj*( - ctx: Context, - imageId: Hash, - pos: Vec2 = vec2(0, 0), - color = color(1, 1, 1, 1), - size: Vec2, -) = - ## Draws image the UI way - pos at top-left. - let - rect = ctx.getImageRect(imageId) - adj = vec2(2 / ctx.atlasSize.float32) - ctx.drawUvRect(pos, pos + size, rect.xy + adj, rect.xy + rect.wh - adj, color) - -proc drawSprite*( - ctx: Context, - imageId: Hash, - pos: Vec2 = vec2(0, 0), - color = color(1, 1, 1, 1), - scale = 1.0, -) = - ## Draws image the game way - pos at center. - let - rect = ctx.getImageRect(imageId) - wh = rect.wh * ctx.atlasSize.float32 * scale - ctx.drawUvRect(pos - wh / 2, pos + wh / 2, rect.xy, rect.xy + rect.wh, color) +when not defined(js): + proc drawImage*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + scale = 1.0, + ) = + ## Draws image the UI way - pos at top-left. + let + rect = ctx.getImageRect(imageId) + wh = rect.wh * ctx.atlasSize.float32 * scale + ctx.drawUvRect(pos, pos + wh, rect.xy, rect.xy + rect.wh, color) +else: + proc drawImage*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + scale = 1.0, + ) = + discard + +when not defined(js): + proc drawImage*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + ## Draws image the UI way - pos at top-left. + let rect = ctx.getImageRect(imageId) + ctx.drawUvRect(pos, pos + size, rect.xy, rect.xy + rect.wh, color) +else: + proc drawImage*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + discard + +when not defined(js): + proc drawImageAdj*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + ## Draws image the UI way - pos at top-left. + let + rect = ctx.getImageRect(imageId) + adj = vec2(2 / ctx.atlasSize.float32) + ctx.drawUvRect(pos, pos + size, rect.xy + adj, rect.xy + rect.wh - adj, color) +else: + proc drawImageAdj*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + discard + +when not defined(js): + proc drawSprite*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + scale = 1.0, + ) = + ## Draws image the game way - pos at center. + let + rect = ctx.getImageRect(imageId) + wh = rect.wh * ctx.atlasSize.float32 * scale + ctx.drawUvRect(pos - wh / 2, pos + wh / 2, rect.xy, rect.xy + rect.wh, color) +else: + proc drawSprite*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + scale = 1.0, + ) = + discard + +when not defined(js): + proc drawSprite*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + ## Draws image the game way - pos at center. + let rect = ctx.getImageRect(imageId) + ctx.drawUvRect( + pos - size / 2, pos + size / 2, rect.xy, rect.xy + rect.wh, color + ) +else: + proc drawSprite*( + ctx: Context, + imageId: Hash, + pos: Vec2 = vec2(0, 0), + color = color(1, 1, 1, 1), + size: Vec2, + ) = + discard -proc drawSprite*( +proc drawRoundedRectSdf*( ctx: Context, - imageId: Hash, - pos: Vec2 = vec2(0, 0), - color = color(1, 1, 1, 1), - size: Vec2, -) = - ## Draws image the game way - pos at center. - let rect = ctx.getImageRect(imageId) - ctx.drawUvRect(pos - size / 2, pos + size / 2, rect.xy, rect.xy + rect.wh, color) + rect: Rect, + color: Color, + radii: array[DirectionCorners, float32], + mode: SdfMode = sdfModeClipAA, + factor: float32 = 4.0, + spread: float32 = 0.0, + shapeSize: Vec2 = vec2(0.0'f32, 0.0'f32), +) -proc drawRect*(ctx: Context, rect: Rect, color: Color) = - const imgKey = hash("rect") - if imgKey notin ctx.entries: - var image = newImage(4, 4) - image.fill(rgba(255, 255, 255, 255)) - ctx.putImage(imgKey, image) +when not defined(js): + proc drawRect*(ctx: Context, rect: Rect, color: Color) = + const imgKey = hash("rect") + if imgKey notin ctx.entries: + var image = newImage(4, 4) + image.fill(rgba(255, 255, 255, 255)) + ctx.putImage(imgKey, image) - let - uvRect = ctx.entries[imgKey] - wh = rect.wh * float32(ctx.atlasSize) - ctx.drawUvRect( - rect.xy, - rect.xy + rect.wh, - uvRect.xy + uvRect.wh / 2, - uvRect.xy + uvRect.wh / 2, - color, - ) + let + uvRect = ctx.entries[imgKey] + wh = rect.wh * float32(ctx.atlasSize) + ctx.drawUvRect( + rect.xy, + rect.xy + rect.wh, + uvRect.xy + uvRect.wh / 2, + uvRect.xy + uvRect.wh / 2, + color, + ) +else: + proc drawRect*(ctx: Context, rect: Rect, color: Color) = + ctx.drawRoundedRectSdf( + rect = rect, + color = color, + radii = [0'f32, 0'f32, 0'f32, 0'f32], + ) proc drawRoundedRectSdf*( ctx: Context, @@ -699,28 +840,30 @@ proc drawRoundedRectSdf*( let quadHalfExtents = rect.wh * 0.5'f32 resolvedShapeSize = - (if shapeSize.x > 0.0'f32 and shapeSize.y > 0.0'f32: shapeSize else: rect.wh) + (if shapeSize.x > 0.0'f32 and shapeSize.y > + 0.0'f32: shapeSize else: rect.wh) shapeHalfExtents = resolvedShapeSize * 0.5'f32 params = - vec4(quadHalfExtents.x, quadHalfExtents.y, shapeHalfExtents.x, shapeHalfExtents.y) + vec4(quadHalfExtents.x, quadHalfExtents.y, shapeHalfExtents.x, + shapeHalfExtents.y) maxRadius = min(shapeHalfExtents.x, shapeHalfExtents.y) radiiClamped = [ dcTopLeft: ( if radii[dcTopLeft] <= 0.0'f32: 0.0'f32 - else: max(1.0'f32, min(radii[dcTopLeft], maxRadius)).round() - ), + else: max(1.0'f32, min(radii[dcTopLeft], maxRadius)).round() + ), dcTopRight: ( if radii[dcTopRight] <= 0.0'f32: 0.0'f32 - else: max(1.0'f32, min(radii[dcTopRight], maxRadius)).round() - ), + else: max(1.0'f32, min(radii[dcTopRight], maxRadius)).round() + ), dcBottomLeft: ( if radii[dcBottomLeft] <= 0.0'f32: 0.0'f32 - else: max(1.0'f32, min(radii[dcBottomLeft], maxRadius)).round() - ), + else: max(1.0'f32, min(radii[dcBottomLeft], maxRadius)).round() + ), dcBottomRight: ( if radii[dcBottomRight] <= 0.0'f32: 0.0'f32 - else: max(1.0'f32, min(radii[dcBottomRight], maxRadius)).round() - ), + else: max(1.0'f32, min(radii[dcBottomRight], maxRadius)).round() + ), ] # (top-right, bottom-right, top-left, bottom-left) r4 = vec4( @@ -784,7 +927,7 @@ proc drawRoundedRectSdf*( ctx.sdfFactors.data.setVert2(offset + 2, factors) ctx.sdfFactors.data.setVert2(offset + 3, factors) - when defined(emscripten): + when defined(emscripten) or defined(js): let modeVal = mode.int.float32 else: let modeVal = mode.int.uint16 @@ -795,33 +938,38 @@ proc drawRoundedRectSdf*( inc ctx.quadCount -proc line*(ctx: Context, a: Vec2, b: Vec2, weight: float32, color: Color) = - let hash = hash((2345, a, b, (weight * 100).int, hash(color))) - - let - w = ceil(abs(b.x - a.x)).int - h = ceil(abs(a.y - b.y)).int - pos = vec2(min(a.x, b.x), min(a.y, b.y)) - - if w == 0 or h == 0: - return +when not defined(js): + proc line*(ctx: Context, a: Vec2, b: Vec2, weight: float32, color: Color) = + let hash = hash((2345, a, b, (weight * 100).int, hash(color))) - if hash notin ctx.entries: let - image = newImage(w, h) - c = newContext(image) - c.fillStyle = rgba(255, 255, 255, 255) - c.lineWidth = weight - c.strokeSegment(segment(a - pos, b - pos)) - ctx.putImage(hash, image) - let - uvRect = ctx.entries[hash] - wh = vec2(w.float32, h.float32) * ctx.atlasSize.float32 - ctx.drawUvRect( - pos, pos + vec2(w.float32, h.float32), uvRect.xy, uvRect.xy + uvRect.wh, color - ) + w = ceil(abs(b.x - a.x)).int + h = ceil(abs(a.y - b.y)).int + pos = vec2(min(a.x, b.x), min(a.y, b.y)) + + if w == 0 or h == 0: + return + + if hash notin ctx.entries: + let + image = newImage(w, h) + c = newContext(image) + c.fillStyle = rgba(255, 255, 255, 255) + c.lineWidth = weight + c.strokeSegment(segment(a - pos, b - pos)) + ctx.putImage(hash, image) + let + uvRect = ctx.entries[hash] + wh = vec2(w.float32, h.float32) * ctx.atlasSize.float32 + ctx.drawUvRect( + pos, pos + vec2(w.float32, h.float32), uvRect.xy, uvRect.xy + uvRect.wh, color + ) +else: + proc line*(ctx: Context, a: Vec2, b: Vec2, weight: float32, color: Color) = + discard -proc linePolygon*(ctx: Context, poly: seq[Vec2], weight: float32, color: Color) = +proc linePolygon*(ctx: Context, poly: seq[Vec2], weight: float32, + color: Color) = for i in 0 ..< poly.len: ctx.line(poly[i], poly[(i + 1) mod poly.len], weight, color) @@ -836,7 +984,10 @@ proc clearMask*(ctx: Context) = glClearColor(1, 1, 1, 1) glClear(GL_COLOR_BUFFER_BIT) - glBindFramebuffer(GL_FRAMEBUFFER, 0) + when defined(js): + glBindFramebuffer(GL_FRAMEBUFFER, nil) + else: + glBindFramebuffer(GL_FRAMEBUFFER, 0) proc beginMask*(ctx: Context) = ## Starts drawing into a mask. @@ -851,7 +1002,10 @@ proc beginMask*(ctx: Context) = ctx.addMaskTexture(ctx.frameSize) ctx.setUpMaskFramebuffer() - glViewport(0, 0, ctx.frameSize.x.GLint, ctx.frameSize.y.GLint) + when defined(js): + glViewport(0, 0, int32(ctx.frameSize.x), int32(ctx.frameSize.y)) + else: + glViewport(0, 0, ctx.frameSize.x.GLint, ctx.frameSize.y.GLint) glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT) @@ -865,7 +1019,10 @@ proc endMask*(ctx: Context) = ctx.flush(ctx.maskTextureWrite - 1) - glBindFramebuffer(GL_FRAMEBUFFER, 0) + when defined(js): + glBindFramebuffer(GL_FRAMEBUFFER, nil) + else: + glBindFramebuffer(GL_FRAMEBUFFER, 0) ctx.activeShader = ctx.mainShader @@ -892,7 +1049,10 @@ proc beginFrame*(ctx: Context, frameSize: Vec2, proj: Mat4) = # Never resize the 0th mask because its just white. bindTextureData(ctx.maskTextures[i].addr, nil) - glViewport(0, 0, ctx.frameSize.x.GLint, ctx.frameSize.y.GLint) + when defined(js): + glViewport(0, 0, int32(ctx.frameSize.x), int32(ctx.frameSize.y)) + else: + glViewport(0, 0, ctx.frameSize.x.GLint, ctx.frameSize.y.GLint) ctx.clearMask() diff --git a/src/figdraw/opengl/glsl/webgl2/atlas.frag b/src/figdraw/opengl/glsl/webgl2/atlas.frag new file mode 100644 index 0000000..a3703f7 --- /dev/null +++ b/src/figdraw/opengl/glsl/webgl2/atlas.frag @@ -0,0 +1,126 @@ +#version 300 es + +precision highp float; + +in vec2 pos; +in vec2 uv; +in vec4 color; +in vec4 sdfParams; +in vec4 sdfRadii; +in float sdfMode; +in vec2 sdfFactors; + +uniform vec2 windowFrame; +uniform sampler2D atlasTex; +uniform sampler2D maskTex; +uniform float aaFactor; +uniform bool maskTexEnabled; + +out vec4 fragColor; + +const int sdfModeAtlas = 0; +const int sdfModeClipAA = 3; +const int sdfModeDropShadow = 7; +const int sdfModeDropShadowAA = 8; +const int sdfModeInsetShadow = 9; +const int sdfModeInsetShadowAnnular = 10; +const int sdfModeAnnular = 11; +const int sdfModeAnnularAA = 12; + +float sdRoundedBox(vec2 p, vec2 b, vec4 r) { + float rr; + if (p.x > 0.0) { + if (p.y > 0.0) { + rr = r.x; + } else { + rr = r.y; + } + } else { + if (p.y > 0.0) { + rr = r.z; + } else { + rr = r.w; + } + } + + vec2 q = abs(p) - b + vec2(rr, rr); + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +float gaussian(float x, float s) { + return 1.0 / (s * sqrt(6.283185307179586)) * + exp(-1.0 * (x * x) / (2.0 * s * s)); +} + +void main() { + vec2 quadHalfExtents = sdfParams.xy; + vec2 shapeHalfExtents = sdfParams.zw; + + vec2 p = vec2( + (uv.x - 0.5) * 2.0 * quadHalfExtents.x, + (uv.y - 0.5) * 2.0 * quadHalfExtents.y + ); + + float dist = sdRoundedBox(vec2(p.x, -p.y), shapeHalfExtents, sdfRadii); + + float sdfFactor = sdfFactors.x; + float sdfSpread = sdfFactors.y; + int sdfModeInt = int(sdfMode); + + float alpha = 0.0; + vec4 outColor; + if (sdfModeInt == sdfModeAtlas) { + vec4 tex = texture(atlasTex, uv); + outColor = vec4( + tex.x * color.x, + tex.y * color.y, + tex.z * color.z, + tex.w * color.w + ); + } else { + float stdDevFactor = 1.0 / 2.2; + if (sdfModeInt == sdfModeAnnular) { + float f = sdfFactor * 0.5; + float sd = abs(dist + f) - f; + alpha = (sd < 0.0) ? 1.0 : 0.0; + } else if (sdfModeInt == sdfModeAnnularAA) { + float f = sdfFactor * 0.5; + float sd = abs(dist + f) - f; + float cl = clamp(aaFactor * sd + 0.5, 0.0, 1.0); + alpha = 1.0 - cl; + } else if (sdfModeInt == sdfModeDropShadow) { + float sd = dist - sdfSpread + 1.0; + float x = sd / (sdfFactor + 0.5); + float a = 1.1 * gaussian(x, stdDevFactor); + alpha = (sd > 0.0) ? min(a, 1.0) : 1.0; + } else if (sdfModeInt == sdfModeDropShadowAA) { + float cl = clamp(aaFactor * dist + 0.5, 0.0, 1.0); + float insideAlpha = 1.0 - cl; + float sd = dist - sdfSpread + 1.0; + float x = sd / (sdfFactor + 0.5); + float a = 1.1 * gaussian(x, stdDevFactor); + alpha = (sd >= 0.0) ? min(a, 1.0) : insideAlpha; + } else if (sdfModeInt == sdfModeInsetShadow) { + float sd = dist + sdfSpread + 1.0; + float x = sd / (sdfFactor + 0.5); + float a = 1.1 * gaussian(x, stdDevFactor); + alpha = (sd < 0.0) ? min(a, 1.0) : 1.0; + } else if (sdfModeInt == sdfModeInsetShadowAnnular) { + float sd = dist + sdfSpread + 1.0; + float x = sd / (sdfFactor + 0.5); + float a = 1.1 * gaussian(x, stdDevFactor); + alpha = (sd < 0.0) ? min(a, 1.0) : 0.0; + } else { + float cl = clamp(aaFactor * dist + 0.5, 0.0, 1.0); + alpha = 1.0 - cl; + } + + outColor = vec4(color.x, color.y, color.z, color.w * alpha); + } + + vec2 normalizedPos = vec2(pos.x / windowFrame.x, 1.0 - pos.y / windowFrame.y); + if (maskTexEnabled) { + outColor.a *= texture(maskTex, normalizedPos).r; + } + fragColor = outColor; +} diff --git a/src/figdraw/opengl/glsl/webgl2/atlas.vert b/src/figdraw/opengl/glsl/webgl2/atlas.vert new file mode 100644 index 0000000..655e1ca --- /dev/null +++ b/src/figdraw/opengl/glsl/webgl2/atlas.vert @@ -0,0 +1,32 @@ +#version 300 es + +precision highp float; + +in vec2 vertexPos; +in vec2 vertexUv; +in vec4 vertexColor; +in vec4 vertexSdfParams; +in vec4 vertexSdfRadii; +in float vertexSdfMode; +in vec2 vertexSdfFactors; + +uniform mat4 proj; + +out vec2 pos; +out vec2 uv; +out vec4 color; +out vec4 sdfParams; +out vec4 sdfRadii; +out float sdfMode; +out vec2 sdfFactors; + +void main() { + pos = vertexPos; + uv = vertexUv; + color = vertexColor; + sdfParams = vertexSdfParams; + sdfRadii = vertexSdfRadii; + sdfMode = vertexSdfMode; + sdfFactors = vertexSdfFactors; + gl_Position = proj * vec4(vertexPos.x, vertexPos.y, 0.0, 1.0); +} diff --git a/src/figdraw/opengl/glsl/webgl2/mask.frag b/src/figdraw/opengl/glsl/webgl2/mask.frag new file mode 100644 index 0000000..a6971d5 --- /dev/null +++ b/src/figdraw/opengl/glsl/webgl2/mask.frag @@ -0,0 +1,64 @@ +#version 300 es + +precision highp float; + +in vec2 pos; +in vec2 uv; +in vec4 color; +in vec4 sdfParams; +in vec4 sdfRadii; +in float sdfMode; + +uniform vec2 windowFrame; +uniform sampler2D atlasTex; +uniform sampler2D maskTex; +uniform float aaFactor; +uniform bool maskTexEnabled; + +out vec4 fragColor; + +const int sdfModeAtlas = 0; + +float sdRoundedBox(vec2 p, vec2 b, vec4 r) { + float rr; + if (p.x > 0.0) { + if (p.y > 0.0) { + rr = r.x; + } else { + rr = r.y; + } + } else { + if (p.y > 0.0) { + rr = r.z; + } else { + rr = r.w; + } + } + + vec2 q = abs(p) - b + vec2(rr, rr); + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +void main() { + float alpha; + int sdfModeInt = int(sdfMode); + if (sdfModeInt == sdfModeAtlas) { + alpha = texture(atlasTex, uv).a * color.a; + } else { + vec2 quadHalfExtents = sdfParams.xy; + vec2 shapeHalfExtents = sdfParams.zw; + vec2 p = vec2( + (uv.x - 0.5) * 2.0 * quadHalfExtents.x, + (uv.y - 0.5) * 2.0 * quadHalfExtents.y + ); + float dist = sdRoundedBox(vec2(p.x, -p.y), shapeHalfExtents, sdfRadii); + float cl = clamp(aaFactor * dist + 0.5, 0.0, 1.0); + alpha = (1.0 - cl) * color.a; + } + + vec2 normalizedPos = vec2(pos.x / windowFrame.x, 1.0 - pos.y / windowFrame.y); + if (maskTexEnabled) { + alpha *= texture(maskTex, normalizedPos).r; + } + fragColor = vec4(alpha); +} diff --git a/src/figdraw/opengl/renderer.nim b/src/figdraw/opengl/renderer.nim index 8fbd555..2b6906b 100644 --- a/src/figdraw/opengl/renderer.nim +++ b/src/figdraw/opengl/renderer.nim @@ -1,15 +1,19 @@ import std/[hashes, math, tables, unicode] export tables -from pixie import Image, newImage, flipVertical +when not defined(js): + from pixie import Image, newImage, flipVertical +else: + type Image* = object import pkg/chroma -import pkg/chronicles -import pkg/opengl +import ../utils/logging +import glapi import ../commons import ../utils/glutils -import ../utils/drawshadows -import ../utils/drawboxes +when not defined(js): + import ../utils/drawshadows + import ../utils/drawboxes import glcommons, glcontext const FastShadows {.booldefine: "figuro.fastShadows".}: bool = false @@ -17,32 +21,39 @@ const FastShadows {.booldefine: "figuro.fastShadows".}: bool = false type OpenGLRenderer* = ref object ctx*: Context -proc takeScreenshot*(frame: Rect = rect(0, 0, 0, 0), readFront: bool = true): Image = - var viewport: array[4, GLint] - glGetIntegerv(GL_VIEWPORT, viewport[0].addr) - - let - viewportWidth = viewport[2].int - viewportHeight = viewport[3].int - - var x = frame.x.int - var y = frame.y.int - var w = frame.w.int - var h = frame.h.int - - if w <= 0 or h <= 0: - x = 0 - y = 0 - w = viewportWidth - h = viewportHeight - - glReadBuffer(if readFront: GL_FRONT else: GL_BACK) - result = newImage(w, h) - glReadPixels( - x.GLint, y.GLint, w.GLint, h.GLint, GL_RGBA, GL_UNSIGNED_BYTE, result.data[0].addr - ) - result.flipVertical() - glReadBuffer(GL_BACK) +when not defined(js): + proc takeScreenshot*(frame: Rect = rect(0, 0, 0, 0), + readFront: bool = true): Image = + var viewport: array[4, GLint] + glGetIntegerv(GL_VIEWPORT, viewport[0].addr) + + let + viewportWidth = viewport[2].int + viewportHeight = viewport[3].int + + var x = frame.x.int + var y = frame.y.int + var w = frame.w.int + var h = frame.h.int + + if w <= 0 or h <= 0: + x = 0 + y = 0 + w = viewportWidth + h = viewportHeight + + glReadBuffer(if readFront: GL_FRONT else: GL_BACK) + result = newImage(w, h) + glReadPixels( + x.GLint, y.GLint, w.GLint, h.GLint, GL_RGBA, GL_UNSIGNED_BYTE, + result.data[0].addr, + ) + result.flipVertical() + glReadBuffer(GL_BACK) +else: + proc takeScreenshot*(frame: Rect = rect(0, 0, 0, 0), + readFront: bool = true): Image = + Image() proc newOpenGLRenderer*(atlasSize: int, pixelScale = app.pixelScale): OpenGLRenderer = result = OpenGLRenderer() @@ -57,23 +68,27 @@ proc renderDrawable*(ctx: Context, node: Fig) = bx = node.screenBox.atXY(pos.x, pos.y) ctx.drawRect(bx, node.fill) -proc renderText(ctx: Context, node: Fig) {.forbids: [AppMainThreadEff].} = - ## draw characters (glyphs) +when not defined(js): + proc renderText(ctx: Context, node: Fig) {.forbids: [AppMainThreadEff].} = + ## draw characters (glyphs) - for glyph in node.textLayout.glyphs(): - if unicode.isWhiteSpace(glyph.rune): - # Don't draw space, even if font has a char for it. - # FIXME: use unicode 'is whitespace' ? - continue + for glyph in node.textLayout.glyphs(): + if unicode.isWhiteSpace(glyph.rune): + # Don't draw space, even if font has a char for it. + # FIXME: use unicode 'is whitespace' ? + continue - let - glyphId = glyph.hash() - charPos = vec2(glyph.pos.x, glyph.pos.y - glyph.descent * 1.0) - if glyphId notin ctx.entries: - trace "no glyph in context: ", - glyphId = glyphId, glyph = glyph.rune, glyphRepr = repr(glyph.rune) - continue - ctx.drawImage(glyphId, charPos, node.fill) + let + glyphId = glyph.hash() + charPos = vec2(glyph.pos.x, glyph.pos.y - glyph.descent * 1.0) + if glyphId notin ctx.entries: + trace "no glyph in context: ", + glyphId = glyphId, glyph = glyph.rune, glyphRepr = repr(glyph.rune) + continue + ctx.drawImage(glyphId, charPos, node.fill) +else: + proc renderText(ctx: Context, node: Fig) {.forbids: [AppMainThreadEff].} = + discard import macros except `$` @@ -117,7 +132,8 @@ macro postRender() = proc drawMasks(ctx: Context, node: Fig) = ctx.drawRoundedRectSdf( - rect = node.screenBox, color = rgba(255, 0, 0, 255).color, radii = node.corners + rect = node.screenBox, color = rgba(255, 0, 0, 255).color, + radii = node.corners ) proc renderDropShadows(ctx: Context, node: Fig) = @@ -274,14 +290,21 @@ proc renderBoxes(ctx: Context, node: Fig) = doStroke = true, ) -proc renderImage(ctx: Context, node: Fig) = - if node.image.id.int == 0: - return - let size = vec2(node.screenBox.w, node.screenBox.h) - #if ctx.cacheImage($node.image.name, node.image.id.Hash): - ctx.drawImage( - node.image.id.Hash, pos = node.screenBox.xy, color = node.image.color, size = size - ) +when not defined(js): + proc renderImage(ctx: Context, node: Fig) = + if node.image.id.int == 0: + return + let size = vec2(node.screenBox.w, node.screenBox.h) + #if ctx.cacheImage($node.image.name, node.image.id.Hash): + ctx.drawImage( + node.image.id.Hash, + pos = node.screenBox.xy, + color = node.image.color, + size = size, + ) +else: + proc renderImage(ctx: Context, node: Fig) = + discard proc render( ctx: Context, nodes: seq[Fig], nodeIdx, parentIdx: FigIdx @@ -365,18 +388,21 @@ proc render( # finally blocks will be run here, in reverse order postRender() -proc renderRoot*(ctx: Context, nodes: var Renders) {.forbids: [AppMainThreadEff].} = +proc renderRoot*(ctx: Context, nodes: var Renders) {.forbids: [ + AppMainThreadEff].} = ## draw roots for each level - var img: ImgObj - while imageChan.tryRecv(img): - debug "image loaded", id = $img.id.Hash - ctx.putImage(img) + when not defined(js): + var img: ImgObj + while imageChan.tryRecv(img): + debug "image loaded", id = $img.id.Hash + ctx.putImage(img) for zlvl, list in nodes.layers.pairs(): for rootIdx in list.rootIds: ctx.render(list.nodes, rootIdx, -1.FigIdx) -proc renderFrame*(renderer: OpenGLRenderer, nodes: var Renders, frameSize: Vec2) = +proc renderFrame*(renderer: OpenGLRenderer, nodes: var Renders, + frameSize: Vec2) = let ctx: Context = renderer.ctx clearColorBuffer(color(1.0, 1.0, 1.0, 1.0)) ctx.beginFrame(frameSize) @@ -389,7 +415,7 @@ proc renderFrame*(renderer: OpenGLRenderer, nodes: var Renders, frameSize: Vec2) ctx.restoreTransform() ctx.endFrame() - when defined(testOneFrame): + when defined(testOneFrame) and not defined(js): ## This is used for test only ## Take a screen shot of the first frame and exit. var img = takeScreenshot() @@ -409,7 +435,8 @@ proc renderOverlayFrame*( ctx.endFrame() proc renderFrame*( - ctx: Context, nodes: var Renders, frameSize: Vec2, pixelScale = ctx.pixelScale + ctx: Context, nodes: var Renders, frameSize: Vec2, + pixelScale = ctx.pixelScale ) = clearColorBuffer(color(1.0, 1.0, 1.0, 1.0)) ctx.beginFrame(frameSize) @@ -420,7 +447,8 @@ proc renderFrame*( ctx.endFrame() proc renderOverlayFrame*( - ctx: Context, nodes: var Renders, frameSize: Vec2, pixelScale = ctx.pixelScale + ctx: Context, nodes: var Renders, frameSize: Vec2, + pixelScale = ctx.pixelScale ) = ## Render without clearing the color buffer (useful for UI overlays). ctx.beginFrame(frameSize) diff --git a/src/figdraw/opengl/shaders.nim b/src/figdraw/opengl/shaders.nim index 1e6b9a0..77cd697 100644 --- a/src/figdraw/opengl/shaders.nim +++ b/src/figdraw/opengl/shaders.nim @@ -1,5 +1,5 @@ -import buffers, opengl, os, strformat, strutils, vmath, macros -import chronicles +import buffers, glapi, os, strformat, strutils, vmath, macros +import ../utils/logging type ShaderCompilationError* = object of CatchableError @@ -12,181 +12,252 @@ type name: string componentType: GLenum kind: BufferKind - values: array[64, uint8] - location: GLint + when defined(js): + valuesI: array[16, int32] + valuesF: array[16, float32] + else: + values: array[64, uint8] + location: GlUniformLocation changed: bool # Flag for if this uniform has changed since last bound. Shader* = ref object paths: seq[string] - programId*: GLuint + programId*: GlProgramId attribs*: seq[ShaderAttrib] uniforms*: seq[Uniform] -proc getErrorLog*( - id: GLuint, - path: string, - lenProc: typeof(glGetShaderiv), - strProc: typeof(glGetShaderInfoLog), -): string = - ## Gets the error log from compiling or linking shaders. - var length: GLint = 0 - lenProc(id, GL_INFO_LOG_LENGTH, length.addr) - var log = newString(length.int) - strProc(id, length, nil, cstring(log)) - when defined(emscripten): - result = log - else: - if log.startsWith("Compute info"): - log = log[25 ..^ 1] - let clickable = &"{path}({log[2..log.find(')')]}" - result = &"{clickable}: {log}" - -proc compileComputeShader*(compute: (string, string)): GLuint = - ## Compiles the compute shader and returns the program id. - var computeShader: GLuint - - block: - var computeShaderArray = allocCStringArray([compute[1]]) - defer: - dealloc(computeShaderArray) - - var isCompiled: GLint - - computeShader = glCreateShader(GL_COMPUTE_SHADER) - glShaderSource(computeShader, 1, computeShaderArray, nil) - glCompileShader(computeShader) - glGetShaderiv(computeShader, GL_COMPILE_STATUS, isCompiled.addr) - - if isCompiled == 0: - error "Compute shader compilation failed:", - logs = getErrorLog(computeShader, compute[0], glGetShaderiv, glGetShaderInfoLog) - quit(22) - - result = glCreateProgram() - glAttachShader(result, computeShader) - - glLinkProgram(result) - - var isLinked: GLint - glGetProgramiv(result, GL_LINK_STATUS, isLinked.addr) - if isLinked == 0: - let logs = getErrorLog(result, compute[0], glGetProgramiv, glGetProgramInfoLog) - error "Linking compute shader failed:", logs = logs - quit(33) - -proc compileComputeShader*(path: string): GLuint = - ## Compiles the compute shader and returns the program id. - compileComputeShader((path, readFile(path))) - -proc compileShaderFiles*(vert, frag: (string, string)): GLuint = - ## Compiles the shader files and links them into a program, returning that id. - var vertShader, fragShader: GLuint - - # Compile the shaders - block shaders: - var vertShaderArray = allocCStringArray([vert[1]]) - var fragShaderArray = allocCStringArray([frag[1]]) - - defer: - dealloc(vertShaderArray) - dealloc(fragShaderArray) - - var isCompiled: GLint +when not defined(js): + proc getErrorLog*( + id: GlProgramId, + path: string, + lenProc: typeof(glGetShaderiv), + strProc: typeof(glGetShaderInfoLog), + ): string = + ## Gets the error log from compiling or linking shaders. + var length: GLint = 0 + lenProc(id, GL_INFO_LOG_LENGTH, length.addr) + var log = newString(length.int) + strProc(id, length, nil, cstring(log)) + when defined(emscripten): + result = log + else: + if log.startsWith("Compute info"): + log = log[25 ..^ 1] + let clickable = &"{path}({log[2..log.find(')')]}" + result = &"{clickable}: {log}" + +when not defined(js): + proc compileComputeShader*(compute: (string, string)): GlProgramId = + ## Compiles the compute shader and returns the program id. + var computeShader: GlShaderId + + block: + var computeShaderArray = allocCStringArray([compute[1]]) + defer: + dealloc(computeShaderArray) + + var isCompiled: GLint + + computeShader = glCreateShader(GL_COMPUTE_SHADER) + glShaderSource(computeShader, 1, computeShaderArray, nil) + glCompileShader(computeShader) + glGetShaderiv(computeShader, GL_COMPILE_STATUS, isCompiled.addr) + + if isCompiled == 0: + error "Compute shader compilation failed:", + logs = getErrorLog(computeShader, compute[0], glGetShaderiv, + glGetShaderInfoLog) + quit(22) + + result = glCreateProgram() + glAttachShader(result, computeShader) + + glLinkProgram(result) + + var isLinked: GLint + glGetProgramiv(result, GL_LINK_STATUS, isLinked.addr) + if isLinked == 0: + let logs = getErrorLog(result, compute[0], glGetProgramiv, glGetProgramInfoLog) + error "Linking compute shader failed:", logs = logs + quit(33) - vertShader = glCreateShader(GL_VERTEX_SHADER) - glShaderSource(vertShader, 1, vertShaderArray, nil) + proc compileComputeShader*(path: string): GlProgramId = + ## Compiles the compute shader and returns the program id. + compileComputeShader((path, readFile(path))) +else: + proc compileComputeShader*(compute: (string, string)): GlProgramId = + raise newException(ShaderCompilationError, "Compute shaders not supported on JS") + + proc compileComputeShader*(path: string): GlProgramId = + raise newException(ShaderCompilationError, "Compute shaders not supported on JS") + +when defined(js): + proc compileShaderFiles*(vert, frag: (string, string)): GlProgramId = + ## Compiles the shader files and links them into a program, returning that id. + let vertShader = glCreateShader(GL_VERTEX_SHADER) + glShaderSource(vertShader, cstring(vert[1])) glCompileShader(vertShader) - glGetShaderiv(vertShader, GL_COMPILE_STATUS, isCompiled.addr) - - if isCompiled == 0: - let logs = getErrorLog(vertShader, vert[0], glGetShaderiv, glGetShaderInfoLog) - if "GLSL 3.30 is not supported" in logs: - warn "Vertex shader compilation failed", - reason = "GLSL 3.30 is not supported", - advice = "Try compiling with `-d:useOpenGlEs`" - raise newException(ShaderCompilationError, "Vertex shader compilation failed") - else: - error "Vertex shader compilation failed:", logs = logs - quit(33) + if not glGetShaderParameter(vertShader, GL_COMPILE_STATUS): + let logs = glGetShaderInfoLog(vertShader) + error "Vertex shader compilation failed:", logs = logs + quit(33) - fragShader = glCreateShader(GL_FRAGMENT_SHADER) - glShaderSource(fragShader, 1, fragShaderArray, nil) + let fragShader = glCreateShader(GL_FRAGMENT_SHADER) + glShaderSource(fragShader, cstring(frag[1])) glCompileShader(fragShader) - glGetShaderiv(fragShader, GL_COMPILE_STATUS, isCompiled.addr) - - if isCompiled == 0: - let logs = getErrorLog(fragShader, frag[0], glGetShaderiv, glGetShaderInfoLog) + if not glGetShaderParameter(fragShader, GL_COMPILE_STATUS): + let logs = glGetShaderInfoLog(fragShader) error "Fragment shader compilation failed:", logs = logs quit(33) - # Attach shaders to a GL program - result = glCreateProgram() - glAttachShader(result, vertShader) - glAttachShader(result, fragShader) + result = glCreateProgram() + glAttachShader(result, vertShader) + glAttachShader(result, fragShader) + glLinkProgram(result) + + if glGetProgramParameter(result, GL_LINK_STATUS) == 0: + let logs = glGetProgramInfoLog(result) + error "Linking shaders failed:", logs = logs + quit(44) +else: + proc compileShaderFiles*(vert, frag: (string, string)): GlProgramId = + ## Compiles the shader files and links them into a program, returning that id. + var vertShader, fragShader: GlShaderId + + # Compile the shaders + block shaders: + var vertShaderArray = allocCStringArray([vert[1]]) + var fragShaderArray = allocCStringArray([frag[1]]) + + defer: + dealloc(vertShaderArray) + dealloc(fragShaderArray) + + var isCompiled: GLint + + vertShader = glCreateShader(GL_VERTEX_SHADER) + glShaderSource(vertShader, 1, vertShaderArray, nil) + glCompileShader(vertShader) + glGetShaderiv(vertShader, GL_COMPILE_STATUS, isCompiled.addr) + + if isCompiled == 0: + let logs = getErrorLog(vertShader, vert[0], glGetShaderiv, glGetShaderInfoLog) + if "GLSL 3.30 is not supported" in logs: + warn "Vertex shader compilation failed", + reason = "GLSL 3.30 is not supported", + advice = "Try compiling with `-d:useOpenGlEs`" + raise newException(ShaderCompilationError, "Vertex shader compilation failed") + else: + error "Vertex shader compilation failed:", logs = logs + quit(33) + + fragShader = glCreateShader(GL_FRAGMENT_SHADER) + glShaderSource(fragShader, 1, fragShaderArray, nil) + glCompileShader(fragShader) + glGetShaderiv(fragShader, GL_COMPILE_STATUS, isCompiled.addr) + + if isCompiled == 0: + let logs = getErrorLog(fragShader, frag[0], glGetShaderiv, glGetShaderInfoLog) + error "Fragment shader compilation failed:", logs = logs + quit(33) + + # Attach shaders to a GL program + result = glCreateProgram() + glAttachShader(result, vertShader) + glAttachShader(result, fragShader) - glLinkProgram(result) + glLinkProgram(result) - var isLinked: GLint - glGetProgramiv(result, GL_LINK_STATUS, isLinked.addr) - if isLinked == 0: - let logs = getErrorLog(result, "", glGetProgramiv, glGetProgramInfoLog) - error "Linking shaders failed:", logs = logs - quit(44) + var isLinked: GLint + glGetProgramiv(result, GL_LINK_STATUS, isLinked.addr) + if isLinked == 0: + let logs = getErrorLog(result, "", glGetProgramiv, glGetProgramInfoLog) + error "Linking shaders failed:", logs = logs + quit(44) -proc compileShaderFiles*(vertPath, fragPath: string): GLuint = +proc compileShaderFiles*(vertPath, fragPath: string): GlProgramId = ## Compiles the shader files and links them into a program, returning that id. compileShaderFiles((vertPath, readFile(vertPath)), (fragPath, readFile(fragPath))) proc readAttribsAndUniforms(shader: Shader) = - block attributes: - var activeAttribCount: GLint - glGetProgramiv(shader.programId, GL_ACTIVE_ATTRIBUTES, activeAttribCount.addr) - - for i in 0 ..< activeAttribCount: - var - buf = newString(64) - length, size: GLint - kind: GLenum - glGetActiveAttrib( - shader.programId, - i.GLuint, - len(buf).GLint, - length.addr, - size.addr, - kind.addr, - cstring(buf), - ) - buf.setLen(length) - - let location = glGetAttribLocation(shader.programId, cstring(buf)) - shader.attribs.add(ShaderAttrib(name: move(buf), location: location)) - - block uniforms: - var activeUniformCount: GLint - glGetProgramiv(shader.programId, GL_ACTIVE_UNIFORMS, activeUniformCount.addr) - - for i in 0 ..< activeUniformCount: - var - buf = newString(64) - length, size: GLint - kind: GLenum - glGetActiveUniform( - shader.programId, - i.GLuint, - len(buf).GLint, - length.addr, - size.addr, - kind.addr, - cstring(buf), - ) - buf.setLen(length) - - if buf.endsWith("[0]"): - # Skip arrays, these are part of UBOs and done a different way - continue - - let location = glGetUniformLocation(shader.programId, cstring(buf)) - shader.uniforms.add(Uniform(name: move(buf), location: location)) + when defined(js): + block attributes: + let activeAttribCount = + glGetProgramParameter(shader.programId, GL_ACTIVE_ATTRIBUTES) + + for i in 0 ..< activeAttribCount: + let info = glGetActiveAttrib(shader.programId, i.GLuint) + if info.isNil: + continue + let name = $info.name + let location = glGetAttribLocation(shader.programId, info.name) + shader.attribs.add(ShaderAttrib(name: name, location: location)) + + block uniforms: + let activeUniformCount = + glGetProgramParameter(shader.programId, GL_ACTIVE_UNIFORMS) + + for i in 0 ..< activeUniformCount: + let info = glGetActiveUniform(shader.programId, i.GLuint) + if info.isNil: + continue + let name = $info.name + if name.endsWith("[0]"): + continue + let location = glGetUniformLocation(shader.programId, info.name) + shader.uniforms.add(Uniform(name: name, location: location)) + else: + block attributes: + var activeAttribCount: GLint + glGetProgramiv(shader.programId, GL_ACTIVE_ATTRIBUTES, + activeAttribCount.addr) + + for i in 0 ..< activeAttribCount: + var + buf = newString(64) + length, size: GLint + kind: GLenum + glGetActiveAttrib( + shader.programId, + i.GLuint, + len(buf).GLint, + length.addr, + size.addr, + kind.addr, + cstring(buf), + ) + buf.setLen(length) + + let location = glGetAttribLocation(shader.programId, cstring(buf)) + shader.attribs.add(ShaderAttrib(name: move(buf), location: location)) + + block uniforms: + var activeUniformCount: GLint + glGetProgramiv(shader.programId, GL_ACTIVE_UNIFORMS, + activeUniformCount.addr) + + for i in 0 ..< activeUniformCount: + var + buf = newString(64) + length, size: GLint + kind: GLenum + glGetActiveUniform( + shader.programId, + i.GLuint, + len(buf).GLint, + length.addr, + size.addr, + kind.addr, + cstring(buf), + ) + buf.setLen(length) + + if buf.endsWith("[0]"): + # Skip arrays, these are part of UBOs and done a different way + continue + + let location = glGetUniformLocation(shader.programId, cstring(buf)) + shader.uniforms.add(Uniform(name: move(buf), location: location)) proc newShader*(compute: (string, string)): Shader = result = Shader() @@ -242,24 +313,25 @@ proc hasUniform*(shader: Shader, name: string): bool = return true return false -proc setUniform( - shader: Shader, - name: string, - componentType: GLenum, - kind: BufferKind, - values: array[64, uint8], -) = - for uniform in shader.uniforms.mitems: - if uniform.name == name: - if uniform.componentType != componentType or uniform.kind != kind or - uniform.values != values: - uniform.componentType = componentType - uniform.kind = kind - uniform.values = values - uniform.changed = true - return - - warn "Ignoring setUniform, not active", name = $name +when not defined(js): + proc setUniform( + shader: Shader, + name: string, + componentType: GLenum, + kind: BufferKind, + values: array[64, uint8], + ) = + for uniform in shader.uniforms.mitems: + if uniform.name == name: + if uniform.componentType != componentType or uniform.kind != kind or + uniform.values != values: + uniform.componentType = componentType + uniform.kind = kind + uniform.values = values + uniform.changed = true + return + + warn "Ignoring setUniform, not active", name = $name proc setUniform( shader: Shader, @@ -269,7 +341,19 @@ proc setUniform( values: array[16, float32], ) = assert componentType == cGL_FLOAT - setUniform(shader, name, componentType, kind, cast[array[64, uint8]](values)) + when defined(js): + for uniform in shader.uniforms.mitems: + if uniform.name == name: + if uniform.componentType != componentType or uniform.kind != kind or + uniform.valuesF != values: + uniform.componentType = componentType + uniform.kind = kind + uniform.valuesF = values + uniform.changed = true + return + warn "Ignoring setUniform, not active", name = $name + else: + setUniform(shader, name, componentType, kind, cast[array[64, uint8]](values)) proc setUniform( shader: Shader, @@ -279,7 +363,19 @@ proc setUniform( values: array[16, int32], ) = assert componentType == cGL_INT - setUniform(shader, name, componentType, kind, cast[array[64, uint8]](values)) + when defined(js): + for uniform in shader.uniforms.mitems: + if uniform.name == name: + if uniform.componentType != componentType or uniform.kind != kind or + uniform.valuesI != values: + uniform.componentType = componentType + uniform.kind = kind + uniform.valuesI = values + uniform.changed = true + return + warn "Ignoring setUniform, not active", name = $name + else: + setUniform(shader, name, componentType, kind, cast[array[64, uint8]](values)) proc raiseUniformVarargsException(name: string, count: int) = raise newException( @@ -350,7 +446,20 @@ proc setUniform*(shader: Shader, name: string, v: Vec4) = shader.setUniform(name, cGL_FLOAT, bkVEC4, values) proc setUniform*(shader: Shader, name: string, m: Mat4) = - shader.setUniform(name, cGL_FLOAT, bkMAT4, cast[array[16, float32]](m)) + when defined(js): + var values: array[16, float32] + when compiles(m.arr): + for i in 0 .. 15: + values[i] = m.arr[i] + else: + var idx = 0 + for r in 0 .. 3: + for c in 0 .. 3: + values[idx] = m[r, c] + inc idx + shader.setUniform(name, cGL_FLOAT, bkMAT4, values) + else: + shader.setUniform(name, cGL_FLOAT, bkMAT4, cast[array[16, float32]](m)) proc setUniform*(shader: Shader, name: string, b: bool) = var values: array[16, int32] @@ -366,7 +475,10 @@ proc bindUniforms*(shader: Shader) = continue if uniform.componentType == cGL_INT: - let values = cast[array[16, GLint]](uniform.values) + when defined(js): + let values = uniform.valuesI + else: + let values = cast[array[16, GLint]](uniform.values) case uniform.kind of bkSCALAR: glUniform1i(uniform.location, values[0]) @@ -379,7 +491,10 @@ proc bindUniforms*(shader: Shader) = else: raiseUniformKindException(uniform.name, uniform.kind) elif uniform.componentType == cGL_FLOAT: - let values = cast[array[16, float32]](uniform.values) + when defined(js): + let values = uniform.valuesF + else: + let values = cast[array[16, float32]](uniform.values) case uniform.kind of bkSCALAR: glUniform1f(uniform.location, values[0]) @@ -390,7 +505,10 @@ proc bindUniforms*(shader: Shader) = of bkVEC4: glUniform4f(uniform.location, values[0], values[1], values[2], values[3]) of bkMAT4: - glUniformMatrix4fv(uniform.location, 1, GL_FALSE, values[0].unsafeAddr) + when defined(js): + glUniformMatrix4fv(uniform.location, 1, GL_FALSE, values) + else: + glUniformMatrix4fv(uniform.location, 1, GL_FALSE, values[0].unsafeAddr) else: raiseUniformKindException(uniform.name, uniform.kind) else: @@ -398,7 +516,8 @@ proc bindUniforms*(shader: Shader) = uniform.changed = false -proc bindUniformBuffer*(shader: Shader, name: string, buffer: Buffer, binding: GLuint) = +proc bindUniformBuffer*(shader: Shader, name: string, buffer: Buffer, + binding: GLuint) = assert buffer.target == GL_UNIFORM_BUFFER let index = glGetUniformBlockIndex(shader.programId, name) glBindBufferBase(GL_UNIFORM_BUFFER, binding, buffer.bufferId) @@ -411,22 +530,41 @@ proc bindAttrib*(shader: Shader, name: string, buffer: Buffer) = if name == attrib.name: if buffer.componentType == cGL_FLOAT or buffer.normalized or buffer.kind != bkSCALAR: - glVertexAttribPointer( - attrib.location.GLuint, - buffer.kind.componentCount().GLint, - buffer.componentType, - if buffer.normalized: GL_TRUE else: GL_FALSE, - 0, - nil, - ) + when defined(js): + glVertexAttribPointer( + attrib.location.GLuint, + int32(buffer.kind.componentCount()), + buffer.componentType, + if buffer.normalized: GL_TRUE else: GL_FALSE, + 0, + 0.GLintptr, + ) + else: + glVertexAttribPointer( + attrib.location.GLuint, + buffer.kind.componentCount().GLint, + buffer.componentType, + if buffer.normalized: GL_TRUE else: GL_FALSE, + 0, + nil, + ) else: - glVertexAttribIPointer( - attrib.location.GLuint, - buffer.kind.componentCount().GLint, - buffer.componentType, - 0, - nil, - ) + when defined(js): + glVertexAttribIPointer( + attrib.location.GLuint, + int32(buffer.kind.componentCount()), + buffer.componentType, + 0, + 0.GLintptr, + ) + else: + glVertexAttribIPointer( + attrib.location.GLuint, + buffer.kind.componentCount().GLint, + buffer.componentType, + 0, + nil, + ) glEnableVertexAttribArray(attrib.location.GLuint) return diff --git a/src/figdraw/opengl/textures.nim b/src/figdraw/opengl/textures.nim index 2e49360..b9e7da2 100644 --- a/src/figdraw/opengl/textures.nim +++ b/src/figdraw/opengl/textures.nim @@ -1,4 +1,10 @@ -import buffers, pixie, opengl +import buffers, glapi +when not defined(js): + import pixie +else: + type Image* = object +when defined(js): + import std/jsffi type MinFilter* = enum @@ -28,42 +34,80 @@ type magFilter*: MagFilter wrapS*, wrapT*: Wrap genMipmap*: bool - textureId*: GLuint + textureId*: GlTextureId -proc bindTextureBufferData*(texture: ptr Texture, buffer: ptr Buffer, data: pointer) = - bindBufferData(buffer, data) +when not defined(js): + proc bindTextureBufferData*(texture: ptr Texture, buffer: ptr Buffer, + data: pointer) = + bindBufferData(buffer, data) - if texture.textureId == 0: - glGenTextures(1, texture.textureId.addr) + if texture.textureId == 0: + glGenTextures(1, texture.textureId.addr) - glBindTexture(GL_TEXTURE_BUFFER, texture.textureId) - glTexBuffer(GL_TEXTURE_BUFFER, texture.internalFormat, buffer.bufferId) + glBindTexture(GL_TEXTURE_BUFFER, texture.textureId) + glTexBuffer(GL_TEXTURE_BUFFER, texture.internalFormat, buffer.bufferId) +else: + proc bindTextureBufferData*(texture: ptr Texture, buffer: ptr Buffer, + data: pointer) = + discard proc bindTextureData*(texture: ptr Texture, data: pointer) = - if texture.textureId == 0: - glGenTextures(1, texture.textureId.addr) + when defined(js): + if texture.textureId.isNil: + texture.textureId = glCreateTexture() + else: + if texture.textureId == 0: + glGenTextures(1, texture.textureId.addr) glBindTexture(GL_TEXTURE_2D, texture.textureId) - glTexImage2D( - target = GL_TEXTURE_2D, - level = 0, - internalFormat = texture.internalFormat.GLint, - width = texture.width, - height = texture.height, - border = 0, - format = texture.format, - `type` = texture.componentType, - pixels = data, - ) - - if texture.magFilter != magDefault: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture.magFilter.GLint) - if texture.minFilter != minDefault: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture.minFilter.GLint) - if texture.wrapS != wDefault: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texture.wrapS.GLint) - if texture.wrapT != wDefault: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texture.wrapT.GLint) + when defined(js): + let jsData = if data.isNil: jsNull else: cast[JsObject](data) + glTexImage2D( + GL_TEXTURE_2D, + 0, + int32(texture.internalFormat), + texture.width, + texture.height, + 0, + texture.format, + texture.componentType, + jsData, + ) + else: + glTexImage2D( + target = GL_TEXTURE_2D, + level = 0, + internalFormat = texture.internalFormat.GLint, + width = texture.width, + height = texture.height, + border = 0, + format = texture.format, + `type` = texture.componentType, + pixels = data, + ) + + when defined(js): + if texture.magFilter != magDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + int32(texture.magFilter)) + if texture.minFilter != minDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + int32(texture.minFilter)) + if texture.wrapS != wDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, int32(texture.wrapS)) + if texture.wrapT != wDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, int32(texture.wrapT)) + else: + if texture.magFilter != magDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + texture.magFilter.GLint) + if texture.minFilter != minDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + texture.minFilter.GLint) + if texture.wrapS != wDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texture.wrapS.GLint) + if texture.wrapT != wDefault: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texture.wrapT.GLint) if texture.genMipmap: glGenerateMipmap(GL_TEXTURE_2D) @@ -71,49 +115,57 @@ proc bindTextureData*(texture: ptr Texture, data: pointer) = func getFormat(image: Image): GLenum = result = GL_RGBA -proc initTexture*(image: Image): Texture = - result.width = image.width.GLint - result.height = image.height.GLint - result.componentType = GL_UNSIGNED_BYTE - result.format = image.getFormat() - result.internalFormat = GL_RGBA8 - result.genMipmap = true - result.minFilter = minLinearMipmapLinear - result.magFilter = magLinear - var data = newSeq[ColorRGBA](image.width * image.height) - for i in 0 ..< data.len: - data[i] = image.data[i] - bindTextureData(result.addr, data[0].addr) - -proc updateSubImage*(texture: Texture, x, y: int, image: Image, level: int) = - ## Update a small part of a texture image. - var data = newSeq[ColorRGBA](image.width * image.height) - for i in 0 ..< data.len: - data[i] = image.data[i] - glBindTexture(GL_TEXTURE_2D, texture.textureId) - glTexSubImage2D( - GL_TEXTURE_2D, - level = level.GLint, - xoffset = x.GLint, - yoffset = y.GLint, - width = image.width.GLint, - height = image.height.GLint, - format = image.getFormat(), - `type` = GL_UNSIGNED_BYTE, - pixels = data[0].addr, - ) - -proc updateSubImage*(texture: Texture, x, y: int, image: Image) = - ## Update a small part of texture with a new image. - var - x = x - y = y - image = image - level = 0 - - while image.width > 1 and image.height > 1: - texture.updateSubImage(x, y, image, level) - image = image.minifyBy2() - x = x div 2 - y = y div 2 - inc(level) +when not defined(js): + proc initTexture*(image: Image): Texture = + result.width = image.width.GLint + result.height = image.height.GLint + result.componentType = GL_UNSIGNED_BYTE + result.format = image.getFormat() + result.internalFormat = GL_RGBA8 + result.genMipmap = true + result.minFilter = minLinearMipmapLinear + result.magFilter = magLinear + var data = newSeq[ColorRGBA](image.width * image.height) + for i in 0 ..< data.len: + data[i] = image.data[i] + bindTextureData(result.addr, data[0].addr) + +when not defined(js): + proc updateSubImage*(texture: Texture, x, y: int, image: Image, level: int) = + ## Update a small part of a texture image. + var data = newSeq[ColorRGBA](image.width * image.height) + for i in 0 ..< data.len: + data[i] = image.data[i] + glBindTexture(GL_TEXTURE_2D, texture.textureId) + glTexSubImage2D( + GL_TEXTURE_2D, + level = level.GLint, + xoffset = x.GLint, + yoffset = y.GLint, + width = image.width.GLint, + height = image.height.GLint, + format = image.getFormat(), + `type` = GL_UNSIGNED_BYTE, + pixels = data[0].addr, + ) + + proc updateSubImage*(texture: Texture, x, y: int, image: Image) = + ## Update a small part of texture with a new image. + var + x = x + y = y + image = image + level = 0 + + while image.width > 1 and image.height > 1: + texture.updateSubImage(x, y, image, level) + image = image.minifyBy2() + x = x div 2 + y = y div 2 + inc(level) +else: + proc updateSubImage*(texture: Texture, x, y: int, image: Image, level: int) = + discard + + proc updateSubImage*(texture: Texture, x, y: int, image: Image) = + discard diff --git a/src/figdraw/utils/chronicles_stub.nim b/src/figdraw/utils/chronicles_stub.nim new file mode 100644 index 0000000..f650f16 --- /dev/null +++ b/src/figdraw/utils/chronicles_stub.nim @@ -0,0 +1,17 @@ +template logScope*(body: untyped) = + discard + +template trace*(args: varargs[untyped]) = + discard + +template debug*(args: varargs[untyped]) = + discard + +template info*(args: varargs[untyped]) = + discard + +template warn*(args: varargs[untyped]) = + discard + +template error*(args: varargs[untyped]) = + discard diff --git a/src/figdraw/utils/drawextras.nim b/src/figdraw/utils/drawextras.nim index b6073bb..919816a 100644 --- a/src/figdraw/utils/drawextras.nim +++ b/src/figdraw/utils/drawextras.nim @@ -5,7 +5,7 @@ import ../commons import ../fignodes import pkg/chroma -import pkg/chronicles +import logging import pkg/pixie import ./drawutils diff --git a/src/figdraw/utils/drawshadows.nim b/src/figdraw/utils/drawshadows.nim index b4cf62c..afaf420 100644 --- a/src/figdraw/utils/drawshadows.nim +++ b/src/figdraw/utils/drawshadows.nim @@ -3,7 +3,7 @@ import std/hashes import std/fenv import pkg/chroma -import pkg/chronicles +import logging import pkg/pixie import pkg/sdfy @@ -140,11 +140,11 @@ proc fillRoundedRectWithShadowSdf*[R]( dcTopRight: xy + vec2(w - cornerCbs[dcTopRight].inner.float32, 0), dcBottomLeft: xy + vec2(0, h - cornerCbs[dcBottomLeft].inner.float32), dcBottomRight: - xy + - vec2( - w - cornerCbs[dcBottomRight].inner.float32, - h - cornerCbs[dcBottomRight].inner.float32, - ), + xy + + vec2( + w - cornerCbs[dcBottomRight].inner.float32, + h - cornerCbs[dcBottomRight].inner.float32, + ), ] coffset = [ @@ -156,10 +156,12 @@ proc fillRoundedRectWithShadowSdf*[R]( ccenter = [ dcTopLeft: vec2( - cornerCbs[dcTopLeft].sideSize.float32, cornerCbs[dcTopLeft].sideSize.float32 + cornerCbs[dcTopLeft].sideSize.float32, cornerCbs[ + dcTopLeft].sideSize.float32 ), dcTopRight: vec2( - cornerCbs[dcTopRight].sideSize.float32, cornerCbs[dcTopRight].sideSize.float32 + cornerCbs[dcTopRight].sideSize.float32, cornerCbs[ + dcTopRight].sideSize.float32 ), dcBottomLeft: vec2( cornerCbs[dcBottomLeft].sideSize.float32, @@ -191,7 +193,8 @@ proc fillRoundedRectWithShadowSdf*[R]( ] let sides = - [dcTopLeft: dLeft, dcTopRight: dTop, dcBottomLeft: dBottom, dcBottomRight: dRight] + [dcTopLeft: dLeft, dcTopRight: dTop, dcBottomLeft: dBottom, + dcBottomRight: dRight] let prevCorner = [ dcTopLeft: dcBottomLeft, dcTopRight: dcTopLeft, @@ -238,7 +241,8 @@ proc fillRoundedRectWithShadowSdf*[R]( rect(paddingOffset, paddingOffset + inner, inner, sideDelta), shadowColor ) ctx.drawRect( - rect(paddingOffset + inner, paddingOffset, sideDelta, cbs.maxRadius.float32), + rect(paddingOffset + inner, paddingOffset, sideDelta, + cbs.maxRadius.float32), shadowColor, ) @@ -251,7 +255,8 @@ proc fillRoundedRectWithShadowSdf*[R]( ctx.drawImageAdj( sideHashes[sides[corner]], vec2( - paddingOffset, paddingOffset + cornerCbs[prevCorner[corner]].inner.float32 + paddingOffset, paddingOffset + cornerCbs[prevCorner[ + corner]].inner.float32 ), shadowColor, borderSize, @@ -270,7 +275,8 @@ proc fillRoundedRectWithShadowSdf*[R]( rect(maxRadius.float32, 0, w - 2 * maxRadius.float32, h), shadowColor ) ctx.drawRect( - rect(0, 0 + maxRadius.float32, maxRadius.float32, h - 2 * maxRadius.float32), + rect(0, 0 + maxRadius.float32, maxRadius.float32, h - 2 * + maxRadius.float32), shadowColor, ) ctx.drawRect( diff --git a/src/figdraw/utils/drawutils.nim b/src/figdraw/utils/drawutils.nim index 467f519..8d0fd73 100644 --- a/src/figdraw/utils/drawutils.nim +++ b/src/figdraw/utils/drawutils.nim @@ -4,7 +4,7 @@ import ../commons import ../fignodes import pkg/chroma -import pkg/chronicles +import logging proc hash(v: Vec2): Hash = hash((v.x, v.y)) @@ -32,7 +32,8 @@ proc getCircleBoxSizes*( width = float32.high(), height = float32.high(), innerShadow = false, -): tuple[maxRadius, sideSize, totalSize, padding, paddingOffset, inner, weightSize: int] = +): tuple[maxRadius, sideSize, totalSize, padding, paddingOffset, inner, + weightSize: int] = result.maxRadius = 0 for r in radii: result.maxRadius = max(result.maxRadius, r.round().int) @@ -57,11 +58,13 @@ proc getCircleBoxSizes*( proc roundedBoxCornerSizes*( cbs: tuple[ - maxRadius, sideSize, totalSize, padding, paddingOffset, inner, weightSize: int + maxRadius, sideSize, totalSize, padding, paddingOffset, inner, + weightSize: int ], radii: array[DirectionCorners, float32], innerShadow: bool, -): array[DirectionCorners, tuple[radius, sideSize, inner, sideDelta, center: int]] = +): array[DirectionCorners, tuple[radius, sideSize, inner, sideDelta, + center: int]] = let ww = cbs.weightSize for corner in DirectionCorners: diff --git a/src/figdraw/utils/glutils.nim b/src/figdraw/utils/glutils.nim index 8e851c6..e58be08 100644 --- a/src/figdraw/utils/glutils.nim +++ b/src/figdraw/utils/glutils.nim @@ -1,4 +1,4 @@ -import pkg/opengl +import ../opengl/glapi import pkg/chroma const @@ -66,7 +66,7 @@ proc useDepthBuffer*(on: bool) = glDisable(GL_DEPTH_TEST) proc startOpenGL*(openglVersion: (int, int)) = - when not defined(emscripten): + when not defined(emscripten) and not defined(js): loadExtensions() openglDebug() diff --git a/src/figdraw/utils/logging.nim b/src/figdraw/utils/logging.nim new file mode 100644 index 0000000..46cbc07 --- /dev/null +++ b/src/figdraw/utils/logging.nim @@ -0,0 +1,6 @@ +when defined(js): + import chronicles_stub + export chronicles_stub +else: + import pkg/chronicles + export chronicles diff --git a/src/figdraw/webgl/api.nim b/src/figdraw/webgl/api.nim new file mode 100644 index 0000000..43188e6 --- /dev/null +++ b/src/figdraw/webgl/api.nim @@ -0,0 +1,295 @@ +when not defined(js) and not defined(nimsuggest): + {.fatal: "figdraw/webgl/api requires the Nim JS backend.".} + +import std/[dom, jsffi] + +type + # Use signed 32-bit integers in JS to avoid BigInt interop in WebGL calls. + GLenum* = int32 + GLboolean* = bool + GLbitfield* = int32 + GLbyte* = int8 + GLshort* = int16 + GLint* = int32 + GLsizei* = int32 + # WebGL expects numeric byte offsets; keep these 32-bit to avoid JS BigInt. + GLintptr* = int32 + GLsizeiptr* = int32 + GLubyte* = uint8 + GLushort* = uint16 + GLuint* = int32 + GLfloat* = float32 + GLclampf* = float32 + GLint64* = int64 + GLuint64* = uint64 + +type + WebGLRenderingContext* {.importc.} = ref object of JsRoot + WebGL2RenderingContext* {.importc.} = ref object of WebGLRenderingContext + WebGLActiveInfo* {.importc.} = ref object of JsRoot + size*: int + `type`* {.importc: "type".}: GLenum + name*: cstring + WebGLBuffer* {.importc.} = ref object of JsRoot + WebGLContextEvent* {.importc.} = ref object of Event + WebGLFramebuffer* {.importc.} = ref object of JsRoot + WebGLProgram* {.importc.} = ref object of JsRoot + WebGLQuery* {.importc.} = ref object of JsRoot + WebGLRenderbuffer* {.importc.} = ref object of JsRoot + WebGLSampler* {.importc.} = ref object of JsRoot + WebGLShader* {.importc.} = ref object of JsRoot + WebGLShaderPrecisionFormat* {.importc.} = ref object of JsRoot + WebGLSync* {.importc.} = ref object of JsRoot + WebGLTexture* {.importc.} = ref object of JsRoot + WebGLTransformFeedback* {.importc.} = ref object of JsRoot + WebGLUniformLocation* {.importc.} = ref object of JsRoot + WebGLVertexArrayObject* {.importc.} = ref object of JsRoot + + HTMLCanvasElement* {.importc.} = ref object of Element + width*: int + height*: int + + Float32Array* {.importc.} = ref object of JsRoot + Uint16Array* {.importc.} = ref object of JsRoot + Uint32Array* {.importc.} = ref object of JsRoot + Uint8Array* {.importc.} = ref object of JsRoot + +type + WebGLExtension* = ref object of JsRoot + AngleInstancedArrays* = WebGLExtension + ExtBlendMinmax* = WebGLExtension + ExtColorBufferFloat* = WebGLExtension + ExtColorBufferHalfFloat* = WebGLExtension + ExtDisjointTimerQuery* = WebGLExtension + ExtFloatBlend* = WebGLExtension + ExtFragDepth* = WebGLExtension + ExtShaderTextureLod* = WebGLExtension + ExtSRgb* = WebGLExtension + ExtTextureCompressionBptc* = WebGLExtension + ExtTextureCompressionRgtc* = WebGLExtension + ExtTextureFilterAnisotropic* = WebGLExtension + ExtTextureNorm16* = WebGLExtension + KhrParallelShaderCompile* = WebGLExtension + OesElementIndexUint* = WebGLExtension + OesFboRenderMipmap* = WebGLExtension + OesStandardDerivatives* = WebGLExtension + OesTextureFloat* = WebGLExtension + OesTextureFloatLinear* = WebGLExtension + OesTextureHalfFloat* = WebGLExtension + OesTextureHalfFloatLinear* = WebGLExtension + OesVertexArrayObject* = WebGLExtension + OvrMultiview2* = WebGLExtension + WebglColorBufferFloat* = WebGLExtension + WebglCompressedTextureAstc* = WebGLExtension + WebglCompressedTextureEtc* = WebGLExtension + WebglCompressedTextureEtc1* = WebGLExtension + WebglCompressedTexturePvrtc* = WebGLExtension + WebglCompressedTextureS3tc* = WebGLExtension + WebglCompressedTextureS3tcSrgb* = WebGLExtension + WebglDebugRendererInfo* = WebGLExtension + WebglDebugShaders* = WebGLExtension + WebglDepthTexture* = WebGLExtension + WebglDrawBuffers* = WebGLExtension + WebglLoseContext* = WebGLExtension + WebglMultiDraw* = WebGLExtension + +const + webglContextLost* = "webglcontextlost" + webglContextRestored* = "webglcontextrestored" + webglContextCreationError* = "webglcontextcreationerror" + + extAngleInstancedArrays* = "ANGLE_instanced_arrays" + extBlendMinmax* = "EXT_blend_minmax" + extColorBufferFloat* = "EXT_color_buffer_float" + extColorBufferHalfFloat* = "EXT_color_buffer_half_float" + extDisjointTimerQuery* = "EXT_disjoint_timer_query" + extFloatBlend* = "EXT_float_blend" + extFragDepth* = "EXT_frag_depth" + extShaderTextureLod* = "EXT_shader_texture_lod" + extSRgb* = "EXT_sRGB" + extTextureCompressionBptc* = "EXT_texture_compression_bptc" + extTextureCompressionRgtc* = "EXT_texture_compression_rgtc" + extTextureFilterAnisotropic* = "EXT_texture_filter_anisotropic" + extTextureNorm16* = "EXT_texture_norm16" + khrParallelShaderCompile* = "KHR_parallel_shader_compile" + oesElementIndexUint* = "OES_element_index_uint" + oesFboRenderMipmap* = "OES_fbo_render_mipmap" + oesStandardDerivatives* = "OES_standard_derivatives" + oesTextureFloat* = "OES_texture_float" + oesTextureFloatLinear* = "OES_texture_float_linear" + oesTextureHalfFloat* = "OES_texture_half_float" + oesTextureHalfFloatLinear* = "OES_texture_half_float_linear" + oesVertexArrayObject* = "OES_vertex_array_object" + ovrMultiview2* = "OVR_multiview2" + webglColorBufferFloat* = "WEBGL_color_buffer_float" + webglCompressedTextureAstc* = "WEBGL_compressed_texture_astc" + webglCompressedTextureEtc* = "WEBGL_compressed_texture_etc" + webglCompressedTextureEtc1* = "WEBGL_compressed_texture_etc1" + webglCompressedTexturePvrtc* = "WEBGL_compressed_texture_pvrtc" + webglCompressedTextureS3tc* = "WEBGL_compressed_texture_s3tc" + webglCompressedTextureS3tcSrgb* = "WEBGL_compressed_texture_s3tc_srgb" + webglDebugRendererInfo* = "WEBGL_debug_renderer_info" + webglDebugShaders* = "WEBGL_debug_shaders" + webglDepthTexture* = "WEBGL_depth_texture" + webglDrawBuffers* = "WEBGL_draw_buffers" + webglLoseContext* = "WEBGL_lose_context" + webglMultiDraw* = "WEBGL_multi_draw" + + ARRAY_BUFFER* = 0x8892.GLenum + STATIC_DRAW* = 0x88E4.GLenum + FLOAT* = 0x1406.GLenum + TRIANGLES* = 0x0004.GLenum + COLOR_BUFFER_BIT* = 0x4000.GLenum + BLEND* = 0x0BE2.GLenum + SRC_ALPHA* = 0x0302.GLenum + ONE_MINUS_SRC_ALPHA* = 0x0303.GLenum + VERTEX_SHADER* = 0x8B31.GLenum + FRAGMENT_SHADER* = 0x8B30.GLenum + COMPILE_STATUS* = 0x8B81.GLenum + LINK_STATUS* = 0x8B82.GLenum + +proc asCanvas*(el: Element): HTMLCanvasElement = + cast[HTMLCanvasElement](el) + +proc newFloat32Array*(data: openArray[float32]): Float32Array + {.importjs: "new Float32Array(#)".} + +proc newUint16Array*(data: openArray[uint16]): Uint16Array + {.importjs: "new Uint16Array(#)".} + +proc newUint32Array*(data: openArray[uint32]): Uint32Array + {.importjs: "new Uint32Array(#)".} + +proc newUint8Array*(data: openArray[uint8]): Uint8Array + {.importjs: "new Uint8Array(#)".} + +proc getContext*(canvas: HTMLCanvasElement; + contextId: cstring): WebGLRenderingContext + {.importjs: "#.getContext(#)".} + +proc getContext*(canvas: HTMLCanvasElement; contextId: cstring; + options: JsObject): WebGLRenderingContext + {.importjs: "#.getContext(#, #)".} + +proc getExtension*(gl: WebGLRenderingContext; name: cstring): WebGLExtension + {.importjs: "#.getExtension(#)".} + +proc isContextLost*(gl: WebGLRenderingContext): bool + {.importjs: "#.isContextLost()".} + +proc statusMessage*(ev: WebGLContextEvent): cstring + {.importjs: "#.statusMessage".} + +proc canvas*(gl: WebGLRenderingContext): HTMLCanvasElement + {.importjs: "#.canvas".} + +proc drawingBufferWidth*(gl: WebGLRenderingContext): int + {.importjs: "#.drawingBufferWidth".} + +proc drawingBufferHeight*(gl: WebGLRenderingContext): int + {.importjs: "#.drawingBufferHeight".} + +proc createShader*(gl: WebGLRenderingContext; shaderType: GLenum): WebGLShader + {.importjs: "#.createShader(#)".} + +proc shaderSource*(gl: WebGLRenderingContext; shader: WebGLShader; + source: cstring) + {.importjs: "#.shaderSource(#, #)".} + +proc compileShader*(gl: WebGLRenderingContext; shader: WebGLShader) + {.importjs: "#.compileShader(#)".} + +proc getShaderParameter*(gl: WebGLRenderingContext; shader: WebGLShader; + pname: GLenum): bool + {.importjs: "#.getShaderParameter(#, #)".} + +proc getShaderInfoLog*(gl: WebGLRenderingContext; shader: WebGLShader): cstring + {.importjs: "#.getShaderInfoLog(#)".} + +proc deleteShader*(gl: WebGLRenderingContext; shader: WebGLShader) + {.importjs: "#.deleteShader(#)".} + +proc createProgram*(gl: WebGLRenderingContext): WebGLProgram + {.importjs: "#.createProgram()".} + +proc attachShader*(gl: WebGLRenderingContext; program: WebGLProgram; + shader: WebGLShader) + {.importjs: "#.attachShader(#, #)".} + +proc linkProgram*(gl: WebGLRenderingContext; program: WebGLProgram) + {.importjs: "#.linkProgram(#)".} + +proc getProgramParameter*(gl: WebGLRenderingContext; program: WebGLProgram; + pname: GLenum): bool + {.importjs: "#.getProgramParameter(#, #)".} + +proc getProgramInfoLog*(gl: WebGLRenderingContext; + program: WebGLProgram): cstring + {.importjs: "#.getProgramInfoLog(#)".} + +proc deleteProgram*(gl: WebGLRenderingContext; program: WebGLProgram) + {.importjs: "#.deleteProgram(#)".} + +proc useProgram*(gl: WebGLRenderingContext; program: WebGLProgram) + {.importjs: "#.useProgram(#)".} + +proc getAttribLocation*(gl: WebGLRenderingContext; program: WebGLProgram; + name: cstring): GLint + {.importjs: "#.getAttribLocation(#, #)".} + +proc enableVertexAttribArray*(gl: WebGLRenderingContext; index: GLint) + {.importjs: "#.enableVertexAttribArray(#)".} + +proc vertexAttribPointer*( + gl: WebGLRenderingContext; + index: GLint; + size: GLint; + typ: GLenum; + normalized: bool; + stride: GLsizei; + offset: GLintptr; +) {.importjs: "#.vertexAttribPointer(#, #, #, #, #, #)".} + +proc getUniformLocation*( + gl: WebGLRenderingContext; + program: WebGLProgram; + name: cstring; +): WebGLUniformLocation {.importjs: "#.getUniformLocation(#, #)".} + +proc uniform2f*( + gl: WebGLRenderingContext; + location: WebGLUniformLocation; + v0: GLfloat; + v1: GLfloat; +) {.importjs: "#.uniform2f(#, #, #)".} + +proc createBuffer*(gl: WebGLRenderingContext): WebGLBuffer + {.importjs: "#.createBuffer()".} + +proc bindBuffer*(gl: WebGLRenderingContext; target: GLenum; buffer: WebGLBuffer) + {.importjs: "#.bindBuffer(#, #)".} + +proc bufferData*(gl: WebGLRenderingContext; target: GLenum; data: Float32Array; usage: GLenum) + {.importjs: "#.bufferData(#, #, #)".} + +proc deleteBuffer*(gl: WebGLRenderingContext; buffer: WebGLBuffer) + {.importjs: "#.deleteBuffer(#)".} + +proc clearColor*(gl: WebGLRenderingContext; r, g, b, a: GLclampf) + {.importjs: "#.clearColor(#, #, #, #)".} + +proc clear*(gl: WebGLRenderingContext; mask: GLbitfield) + {.importjs: "#.clear(#)".} + +proc enable*(gl: WebGLRenderingContext; cap: GLenum) + {.importjs: "#.enable(#)".} + +proc blendFunc*(gl: WebGLRenderingContext; sfactor: GLenum; dfactor: GLenum) + {.importjs: "#.blendFunc(#, #)".} + +proc viewport*(gl: WebGLRenderingContext; x, y: GLint; width, height: GLsizei) + {.importjs: "#.viewport(#, #, #, #)".} + +proc drawArrays*(gl: WebGLRenderingContext; mode: GLenum; first: GLint; + count: GLsizei) + {.importjs: "#.drawArrays(#, #, #)".}