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(#, #, #)".}