From f4356b98d2041d8188ae61d1025a98db7becdee0 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 6 Oct 2023 17:41:15 -0400 Subject: display: WIP: Refactor into files; use wishes, not Display:: Expose shader stuff to user programs for the first time. Mostly works, but display thread and main thread run much slower (28fps display, 70fps main). Also introduce dict getwithdefault (helpful for rest options). --- virtual-programs/display/circle.folk | 31 ++++++++ virtual-programs/display/fill.folk | 46 +++++++++++ virtual-programs/display/fns.folk | 0 virtual-programs/display/image.folk | 99 +++++++++++++++++++++++ virtual-programs/display/stroke.folk | 33 ++++++++ virtual-programs/display/text.folk | 150 +++++++++++++++++++++++++++++++++++ 6 files changed, 359 insertions(+) create mode 100644 virtual-programs/display/circle.folk create mode 100644 virtual-programs/display/fill.folk create mode 100644 virtual-programs/display/fns.folk create mode 100644 virtual-programs/display/image.folk create mode 100644 virtual-programs/display/stroke.folk create mode 100644 virtual-programs/display/text.folk (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/circle.folk b/virtual-programs/display/circle.folk new file mode 100644 index 00000000..f50813eb --- /dev/null +++ b/virtual-programs/display/circle.folk @@ -0,0 +1,31 @@ +Wish the GPU compiles pipeline "circle" { + {vec2 center float radius float thickness vec4 color int filled} { + float r = radius + thickness; + vec2 vertices[4] = vec2[4]( + center - r, + vec2(center.x + r, center.y - r), + vec2(center.x - r, center.y + r), + center + r + ); + return vertices[gl_VertexIndex]; + } { + float dist = length(gl_FragCoord.xy - center) - radius; + if (filled == 1) { + return (dist < thickness) ? color : vec4(0, 0, 0, 0); + } else { + return (dist < thickness && dist > 0.0) ? color : vec4(0, 0, 0, 0); + } + } +} + +When /someone/ wishes to draw a circle with /...options/ { + set center [dict getdef $options center ""] + if {center eq ""} { set center [list [dict get $options x] [dict get $options y]] } + set radius [dict get $options radius] + set thickness [dict get $options thickness] + set color [getColor [dict get $options color]] + set filled [dict getdef $options filled false] + + Wish the GPU draws pipeline "circle" with arguments \ + [list $center $radius $thickness $color $filled] +} diff --git a/virtual-programs/display/fill.folk b/virtual-programs/display/fill.folk new file mode 100644 index 00000000..dd9e199d --- /dev/null +++ b/virtual-programs/display/fill.folk @@ -0,0 +1,46 @@ +Wish the GPU compiles pipeline "fillTriangle" { + {vec2 p0 vec2 p1 vec2 p2 vec4 color} { + vec2 vertices[4] = vec2[4](p0, p1, p2, p0); + return vertices[gl_VertexIndex]; + } { + return color; + } +} + +When /someone/ wishes to draw a triangle with /...options/ { + dict with options { + Wish the GPU draws pipeline "fillTriangle" with arguments \ + [list $p0 $p1 $p2 [getColor $color]] + } +} +When /someone/ wishes to draw a quad with /...options/ { + dict with options { + Wish the GPU draws pipeline "fillTriangle" with arguments \ + [list $p0 $p1 $p2 [getColor $color]] + Wish the GPU draws pipeline "fillTriangle" with arguments \ + [list $p0 $p1 $p3 [getColor $color]] + } +} +When /someone/ wishes to draw a polygon with /...options/ { + set points [dict get $options points] + set color [dict get $options color] + + set num_points [llength $points] + if {$num_points < 3} { + error "At least 3 points are required to form a polygon." + } elseif {$num_points == 3} { + eval fillTriangle $points $color + } elseif {$num_points == 4} { + eval fillQuad $points $color + } else { + # Get the first point in the list as the "base" point of the triangles + set p0 [lindex $points 0] + + for {set i 1} {$i < $num_points - 1} {incr i} { + set p1 [lindex $points $i] + set p2 [lindex $points [expr {$i+1}]] + Wish the GPU draws pipeline "fillTriangle" with arguments \ + [list $p0 $p1 $p2 $color] + } + } +} diff --git a/virtual-programs/display/fns.folk b/virtual-programs/display/fns.folk new file mode 100644 index 00000000..e69de29b diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk new file mode 100644 index 00000000..4665a236 --- /dev/null +++ b/virtual-programs/display/image.folk @@ -0,0 +1,99 @@ +On process "display" { + set invBilinear $::invBilinear + set rotate $::rotate + set image [Gpu::pipeline {sampler2D image vec2 imageSize + vec2 pos float radians float scale + fn rotate} { + vec2 a = pos + rotate(-imageSize/2, -radians); + vec2 b = pos + rotate(vec2(imageSize.x, -imageSize.y)/2, -radians); + vec2 c = pos + rotate(imageSize/2, -radians); + vec2 d = pos + rotate(vec2(-imageSize.x, imageSize.y)/2, -radians); + vec2 vertices[4] = vec2[4](a, b, d, c); + return vertices[gl_VertexIndex]; + } {fn invBilinear fn rotate} { + vec2 a = pos + rotate(-imageSize/2, -radians); + vec2 b = pos + rotate(vec2(imageSize.x, -imageSize.y)/2, -radians); + vec2 c = pos + rotate(imageSize/2, -radians); + vec2 d = pos + rotate(vec2(-imageSize.x, imageSize.y)/2, -radians); + vec2 p = gl_FragCoord.xy; + vec2 uv = invBilinear(p, a, b, c, d); + if( max( abs(uv.x-0.5), abs(uv.y-0.5))<0.5 ) { + return texture(image, uv); + } + return vec4(0.0, 0.0, 0.0, 0.0); + }] + + set ::ImCache [dict create] + variable IMCACHE_MAX_IMAGES [- $Gpu::ImageManager::GPU_MAX_IMAGES 1] + proc checkImCacheAndCopyIfNeeded {imDrawSet} { + variable ::ImCache + variable IMCACHE_MAX_IMAGES + + dict for {im v} $imDrawSet { + if {![dict exists $::ImCache $im]} { continue } + # Check for staleness and remove from cache if so. + lassign [dict get $::ImCache $im] gim expectedVersion + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + if {$expectedVersion != $version} { + Gpu::ImageManager::freeGpuImage $gim + dict unset ::ImCache $im + } + } + + set notInCache [dictset difference $imDrawSet $::ImCache] + set notInDrawSet [dictset difference $::ImCache $imDrawSet] + + set numImagesToCopy [dictset size $notInCache] + if {$numImagesToCopy > 0} { + if {[dictset size $::ImCache] + $numImagesToCopy > $IMCACHE_MAX_IMAGES} { + set numImagesToEvict \ + [expr {[dictset size $::ImCache] + $numImagesToCopy - $IMCACHE_MAX_IMAGES}] + + # What can we safely evict? + # - Anything that's stale + # - Anything that's not in use + set numImagesEvicted 0 + dict for {im v} $::ImCache { + if {$numImagesEvicted == $numImagesToEvict} { break } + + lassign $v gim expectedVersion + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + if {$expectedVersion != $version} { + Gpu::ImageManager::freeGpuImage $gim + dict unset ::ImCache $im + incr numImagesEvicted + continue + } + if {![dict exists $imDrawSet $im]} { + Gpu::ImageManager::freeGpuImage $gim + dict unset ::ImCache $im + incr numImagesEvicted + } + } + } + + dict for {im _} $notInCache { + # TODO: This is unsafe (has a race condition) -- we're + # not locking the image, so version and gim may diverge. + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + set gim [Gpu::ImageManager::copyImageToGpu $im] + dict set ::ImCache $im [list $gim $version] + } + } + } + + Wish $::thisProcess receives statements like \ + [list /someone/ wishes to draw an image with /...options/] + When /someone/ wishes to draw an image with /...options/ { + set x [dict get $options x] + set y [dict get $options y] + set im [dict get $options image] + set radians [dict get $options radians] + set scale [dict getdef $options scale 1.0] + + lassign [dict get $::ImCache $im] gim + + Wish the GPU draws pipeline "image" with arguments \ + [list $x $y $im $radians $scale] + } +} diff --git a/virtual-programs/display/stroke.folk b/virtual-programs/display/stroke.folk new file mode 100644 index 00000000..98eec038 --- /dev/null +++ b/virtual-programs/display/stroke.folk @@ -0,0 +1,33 @@ +Wish the GPU compiles pipeline "line" { + {vec2 from vec2 to float thickness vec4 color} { + vec2 vertices[4] = vec2[4]( + min(from, to) - thickness, + vec2(max(from.x, to.x) + thickness, min(from.y, to.y) - thickness), + vec2(min(from.x, to.x) - thickness, max(from.y, to.y) + thickness), + max(from, to) + thickness + ); + return vertices[gl_VertexIndex]; + } { + float l = length(to - from); + vec2 d = (to - from) / l; + vec2 q = (gl_FragCoord.xy - (from + to)*0.5); + q = mat2(d.x, -d.y, d.y, d.x) * q; + q = abs(q) - vec2(l, thickness)*0.5; + float dist = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0); + + return dist < 0.0 ? color : vec4(0, 0, 0, 0); + } +} + +When /someone/ wishes to draw a stroke with /...options/ { + set points [dict get $options points] + set width [dict get $options width] + set color [getColor [dict get $options color]] + + for {set i 0} {$i < [expr {[llength $points] - 1}]} {incr i} { + set from [lindex $points $i] + set to [lindex $points [expr $i+1]] + Wish the GPU draws pipeline "line" with arguments \ + [list $from $to $width $color] + } +} diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk new file mode 100644 index 00000000..3f2b4b5c --- /dev/null +++ b/virtual-programs/display/text.folk @@ -0,0 +1,150 @@ +On process "display" { + namespace eval font { + proc load {name} { + set csvFd [open "vendor/fonts/$name.csv" r]; set csv [read $csvFd]; close $csvFd + set glyphInfos [dict create] + foreach line [split $csv "\n"] { + set info [lassign [split $line ,] glyph] + lassign $info advance \ + planeLeft planeBottom planeRight planeTop \ + atlasLeft atlasBottom atlasRight atlasTop + dict set glyphInfos $glyph \ + [list $advance \ + [list $planeLeft $planeBottom $planeRight $planeTop] \ + [list $atlasLeft $atlasBottom $atlasRight $atlasTop]] + } + + set im [image load "[pwd]/vendor/fonts/$name.png"] + set gim [Gpu::ImageManager::copyImageToGpu $im] + + return [list $glyphInfos $im $gim] + } + proc hasGlyphInfo {font charCode} { dict exists [lindex $font 0] $charCode } + proc glyphInfo {font charCode} { dict get [lindex $font 0] $charCode } + proc atlasImage {font} { lindex $font 1 } + proc gpuAtlasImage {font} { lindex $font 2 } + + namespace export * + namespace ensemble create + } + set font [font load "PTSans-Regular"] + + set rotate $::rotate + set invBilinear $::invBilinear + set glyphMsd [Gpu::fn {sampler2D atlas vec4 atlasGlyphBounds vec2 glyphUv} vec4 { + vec2 atlasUv = mix(atlasGlyphBounds.xw, atlasGlyphBounds.zy, glyphUv); + return texture(atlas, vec2(atlasUv.x, 1.0-atlasUv.y)); + }] + set median [Gpu::fn {float r float g float b} float { + return max(min(r, g), min(max(r, g), b)); + }] + dict set ::pipelines "glyph" [Gpu::pipeline \ + {sampler2D atlas vec2 atlasSize + vec4 atlasGlyphBounds + vec4 planeGlyphBounds + vec2 pos float radians float em + fn rotate} { + float left = planeGlyphBounds[0] * em; + float bottom = planeGlyphBounds[1] * em; + float right = planeGlyphBounds[2] * em; + float top = planeGlyphBounds[3] * em; + vec2 a = pos + rotate(vec2(left, -top), -radians); + vec2 b = pos + rotate(vec2(right, -top), -radians); + vec2 c = pos + rotate(vec2(right, -bottom), -radians); + vec2 d = pos + rotate(vec2(left, -bottom), -radians); + + vec2 vertices[4] = vec2[4](a, b, d, c); + return vertices[gl_VertexIndex]; + } {fn rotate fn invBilinear fn glyphMsd fn median} { + float left = planeGlyphBounds[0] * em; + float bottom = planeGlyphBounds[1] * em; + float right = planeGlyphBounds[2] * em; + float top = planeGlyphBounds[3] * em; + vec2 a = pos + rotate(vec2(left, -top), -radians); + vec2 b = pos + rotate(vec2(right, -top), -radians); + vec2 c = pos + rotate(vec2(right, -bottom), -radians); + vec2 d = pos + rotate(vec2(left, -bottom), -radians); + + vec2 glyphUv = invBilinear(gl_FragCoord.xy, a, b, c, d); + if( max( abs(glyphUv.x-0.5), abs(glyphUv.y-0.5))>=0.5 ) { + return vec4(0, 0, 0, 0); + } + vec3 msd = glyphMsd(atlas, atlasGlyphBounds/atlasSize.xyxy, glyphUv).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = 4.5*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + return mix(vec4(0, 0, 0, 0), vec4(1, 1, 1, 1), opacity); + }] + + fn textExtent {text scale} { + set em [* $scale 25.0] + set x 0; set y 0 + set width 0 + for {set i 0} {$i < [string length $text]} {incr i} { + set char [string index $text $i] + if {$char eq "\n"} { + set y [+ $y $em]; set x 0; continue + } + set charCode [scan $char %c] + if {[font hasGlyphInfo $font $charCode]} { + set glyphInfo [font glyphInfo $font $charCode] + } else { + set glyphInfo [font glyphInfo $font [scan ? %c]] + } + lassign $glyphInfo advance planeBounds atlasBounds + set x [+ $x [* $advance $em]] + if {$x > $width} { set width $x } + } + return [list $width [+ $y $em]] + } + + Wish $::thisProcess receives statements like \ + [list /someone/ wishes to draw text with /...options/] + + When /someone/ wishes to draw text with /...options/ { + set x0 [dict get $options x] + set y0 [dict get $options y] + set scale [dict getdef $options scale 1.0] + set text [dict get $options text] + set radians [dict get $options radians] + + set fontAtlas [font gpuAtlasImage $font] + set fontAtlasSize [list [::image width [font atlasImage $font]] \ + [::image height [font atlasImage $font]]] + + set extent [vec2 rotate [textExtent $text $scale] $radians] + + set em [* $scale 25.0] + + # TODO: Add text alignment/anchor options (right now, this + # setup centers the text). + set x0 [expr {$x0 - [lindex $extent 0]/2}] + set y0 [expr {$y0 - [lindex $extent 1]/2}] + set x $x0; set y $y0 + + set lineNum 0 + for {set i 0} {$i < [string length $text]} {incr i} { + set char [string index $text $i] + if {$char eq "\n"} { + incr lineNum + lassign [vec2 add [list $x0 $y0] \ + [vec2 rotate [list 0 [* $lineNum $em]] $radians]] x y + continue + } + set charCode [scan $char %c] + if {[font hasGlyphInfo $font $charCode]} { + set glyphInfo [font glyphInfo $font $charCode] + } else { + set glyphInfo [font glyphInfo $font [scan ? %c]] + } + lassign $glyphInfo advance planeBounds atlasBounds + if {$char ne " "} { + Wish the GPU draws pipeline "glyph" with arguments \ + [list $fontAtlas $fontAtlasSize \ + $atlasBounds $planeBounds [list $x $y] $radians $em] + } + lassign [vec2 add [list $x $y] \ + [vec2 rotate [list [* $advance $em] 0] $radians]] x y + } + } +} -- cgit v1.2.3 From 7b7a4b895f99a305263e03687b4f7cfa3065dd73 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 6 Oct 2023 17:57:29 -0400 Subject: display/text: Write glyphs straight to displaylist. Hack, but faster --- virtual-programs/display/text.folk | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 3f2b4b5c..412ce266 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -38,12 +38,12 @@ On process "display" { set median [Gpu::fn {float r float g float b} float { return max(min(r, g), min(max(r, g), b)); }] - dict set ::pipelines "glyph" [Gpu::pipeline \ - {sampler2D atlas vec2 atlasSize - vec4 atlasGlyphBounds - vec4 planeGlyphBounds - vec2 pos float radians float em - fn rotate} { + set glyph [Gpu::pipeline \ + {sampler2D atlas vec2 atlasSize + vec4 atlasGlyphBounds + vec4 planeGlyphBounds + vec2 pos float radians float em + fn rotate} { float left = planeGlyphBounds[0] * em; float bottom = planeGlyphBounds[1] * em; float right = planeGlyphBounds[2] * em; @@ -139,8 +139,8 @@ On process "display" { } lassign $glyphInfo advance planeBounds atlasBounds if {$char ne " "} { - Wish the GPU draws pipeline "glyph" with arguments \ - [list $fontAtlas $fontAtlasSize \ + lappend ::displayList \ + [list Gpu::draw $glyph $fontAtlas $fontAtlasSize \ $atlasBounds $planeBounds [list $x $y] $radians $em] } lassign [vec2 add [list $x $y] \ -- cgit v1.2.3 From e91f7c12cb45b7430b08a860993ab5dbbf599b81 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 6 Oct 2023 17:59:03 -0400 Subject: dict getdef -> dict_getdef. Much, much faster --- virtual-programs/display/circle.folk | 4 ++-- virtual-programs/display/image.folk | 2 +- virtual-programs/display/text.folk | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/circle.folk b/virtual-programs/display/circle.folk index f50813eb..2951f3a9 100644 --- a/virtual-programs/display/circle.folk +++ b/virtual-programs/display/circle.folk @@ -19,12 +19,12 @@ Wish the GPU compiles pipeline "circle" { } When /someone/ wishes to draw a circle with /...options/ { - set center [dict getdef $options center ""] + set center [dict_getdef $options center ""] if {center eq ""} { set center [list [dict get $options x] [dict get $options y]] } set radius [dict get $options radius] set thickness [dict get $options thickness] set color [getColor [dict get $options color]] - set filled [dict getdef $options filled false] + set filled [dict_getdef $options filled false] Wish the GPU draws pipeline "circle" with arguments \ [list $center $radius $thickness $color $filled] diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk index 4665a236..8ef1cc30 100644 --- a/virtual-programs/display/image.folk +++ b/virtual-programs/display/image.folk @@ -89,7 +89,7 @@ On process "display" { set y [dict get $options y] set im [dict get $options image] set radians [dict get $options radians] - set scale [dict getdef $options scale 1.0] + set scale [dict_getdef $options scale 1.0] lassign [dict get $::ImCache $im] gim diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 412ce266..1c8f6c45 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -104,7 +104,7 @@ On process "display" { When /someone/ wishes to draw text with /...options/ { set x0 [dict get $options x] set y0 [dict get $options y] - set scale [dict getdef $options scale 1.0] + set scale [dict_getdef $options scale 1.0] set text [dict get $options text] set radians [dict get $options radians] -- cgit v1.2.3 From ce6a82e7db7447e90650df00ccd185e9b258f1ae Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 9 Oct 2023 19:02:02 -0400 Subject: text: Draw with wish; stops the blinking bug. (because now, the text draws every frame, instead of just when it happens to change.) --- virtual-programs/display/text.folk | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 1c8f6c45..992e77b1 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -38,7 +38,8 @@ On process "display" { set median [Gpu::fn {float r float g float b} float { return max(min(r, g), min(max(r, g), b)); }] - set glyph [Gpu::pipeline \ + # TODO: Do this with a wish, instead of hard-coding the global dict. + dict set ::pipelines "glyph" [Gpu::pipeline \ {sampler2D atlas vec2 atlasSize vec4 atlasGlyphBounds vec4 planeGlyphBounds @@ -74,7 +75,7 @@ On process "display" { float screenPxDistance = 4.5*(sd - 0.5); float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); return mix(vec4(0, 0, 0, 0), vec4(1, 1, 1, 1), opacity); - }] + }] fn textExtent {text scale} { set em [* $scale 25.0] @@ -139,8 +140,8 @@ On process "display" { } lassign $glyphInfo advance planeBounds atlasBounds if {$char ne " "} { - lappend ::displayList \ - [list Gpu::draw $glyph $fontAtlas $fontAtlasSize \ + Wish the GPU draws pipeline "glyph" with arguments \ + [list $fontAtlas $fontAtlasSize \ $atlasBounds $planeBounds [list $x $y] $radians $em] } lassign [vec2 add [list $x $y] \ -- cgit v1.2.3 From cc418917fcf9a95873401a3f59b68771c8c4a1e7 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 12 Oct 2023 12:42:56 -0400 Subject: text: Batch glyphs into one wish -- fixes performance. --- virtual-programs/display/text.folk | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 992e77b1..a6c828b9 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -124,6 +124,7 @@ On process "display" { set x $x0; set y $y0 set lineNum 0 + set instances [list] for {set i 0} {$i < [string length $text]} {incr i} { set char [string index $text $i] if {$char eq "\n"} { @@ -140,12 +141,16 @@ On process "display" { } lassign $glyphInfo advance planeBounds atlasBounds if {$char ne " "} { - Wish the GPU draws pipeline "glyph" with arguments \ + lappend instances \ [list $fontAtlas $fontAtlasSize \ $atlasBounds $planeBounds [list $x $y] $radians $em] } lassign [vec2 add [list $x $y] \ [vec2 rotate [list [* $advance $em] 0] $radians]] x y } + + # We need to batch into one wish so we don't deal with n^2 + # checks for existing statements for n glyphs. + Wish the GPU draws pipeline "glyph" with instances $instances } } -- cgit v1.2.3 From 12a58eb55b1afd7dad5b9865b9e86fc85ac1d675 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 12 Oct 2023 12:49:00 -0400 Subject: display: Error on Display:: calls; implement titles with wish --- virtual-programs/display/text.folk | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index a6c828b9..14f0294a 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -103,8 +103,12 @@ On process "display" { [list /someone/ wishes to draw text with /...options/] When /someone/ wishes to draw text with /...options/ { - set x0 [dict get $options x] - set y0 [dict get $options y] + if {[dict exists $options center]} { + lassign [dict get $options center] x0 y0 + } else { + set x0 [dict get $options x] + set y0 [dict get $options y] + } set scale [dict_getdef $options scale 1.0] set text [dict get $options text] set radians [dict get $options radians] -- cgit v1.2.3 From 913f16d12bdf2208c35093350276ca0b6493c81c Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 12 Oct 2023 15:56:42 -0400 Subject: image: WIP: Images are starting to work. --- virtual-programs/display/image.folk | 111 ++++++++++++++++++++---------------- virtual-programs/display/text.folk | 1 + 2 files changed, 64 insertions(+), 48 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk index 8ef1cc30..07cf342a 100644 --- a/virtual-programs/display/image.folk +++ b/virtual-programs/display/image.folk @@ -1,7 +1,8 @@ On process "display" { set invBilinear $::invBilinear set rotate $::rotate - set image [Gpu::pipeline {sampler2D image vec2 imageSize + # TODO: Do this with a wish, instead of hard-coding the global dict. + dict set ::pipelines "image" [Gpu::pipeline {sampler2D image vec2 imageSize vec2 pos float radians float scale fn rotate} { vec2 a = pos + rotate(-imageSize/2, -radians); @@ -23,61 +24,70 @@ On process "display" { return vec4(0.0, 0.0, 0.0, 0.0); }] - set ::ImCache [dict create] - variable IMCACHE_MAX_IMAGES [- $Gpu::ImageManager::GPU_MAX_IMAGES 1] - proc checkImCacheAndCopyIfNeeded {imDrawSet} { - variable ::ImCache - variable IMCACHE_MAX_IMAGES + When the GPU has loaded /nfonts/ fonts { + puts LOADING + namespace eval ::ImageCache { + # Backing store: stores triples of (GPU image handle, heap slot version, refcount). + variable cache [dict create] + variable CACHE_MAX_SIZE [- $Gpu::ImageManager::GPU_MAX_IMAGES [uplevel {set nfonts}]] - dict for {im v} $imDrawSet { - if {![dict exists $::ImCache $im]} { continue } - # Check for staleness and remove from cache if so. - lassign [dict get $::ImCache $im] gim expectedVersion - set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] - if {$expectedVersion != $version} { - Gpu::ImageManager::freeGpuImage $gim - dict unset ::ImCache $im + proc getOrInsertAndIncr {im} { + variable cache + variable CACHE_MAX_SIZE + if {[dict exists $cache $im]} { + lassign [dict get $cache $im] gim cachedVersion refcount + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + if {$version == $cachedVersion} { + dict set cache $im [list $gim $cachedVersion [+ $refcount 1]] + return $gim + } else { + remove $im + } + } + if {[dict size $cache] >= $CACHE_MAX_SIZE} { + evict + } + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + set gim [Gpu::ImageManager::copyImageToGpu $im] + dict set cache $im [list $gim $version 1] + return $gim } - } - - set notInCache [dictset difference $imDrawSet $::ImCache] - set notInDrawSet [dictset difference $::ImCache $imDrawSet] - set numImagesToCopy [dictset size $notInCache] - if {$numImagesToCopy > 0} { - if {[dictset size $::ImCache] + $numImagesToCopy > $IMCACHE_MAX_IMAGES} { - set numImagesToEvict \ - [expr {[dictset size $::ImCache] + $numImagesToCopy - $IMCACHE_MAX_IMAGES}] - - # What can we safely evict? - # - Anything that's stale - # - Anything that's not in use - set numImagesEvicted 0 - dict for {im v} $::ImCache { - if {$numImagesEvicted == $numImagesToEvict} { break } + proc decr {im} { + variable cache + set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] + lassign [dict get $cache $im] gim cachedVersion refcount + incr refcount -1 + if {$refcount == 0} { + remove $im + } else { + dict set cache $im [list $gim $version $refcount] + } + } + proc evict {} { + variable cache + # Evict stale. + dict for {im v} $cache { lassign $v gim expectedVersion set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] if {$expectedVersion != $version} { Gpu::ImageManager::freeGpuImage $gim - dict unset ::ImCache $im - incr numImagesEvicted - continue - } - if {![dict exists $imDrawSet $im]} { - Gpu::ImageManager::freeGpuImage $gim - dict unset ::ImCache $im - incr numImagesEvicted + dict unset cache $im } } + if {[dict size $cache] >= $CACHE_MAX_SIZE} { + error "image: Unable to evict enough from image cache." + } } - dict for {im _} $notInCache { - # TODO: This is unsafe (has a race condition) -- we're - # not locking the image, so version and gim may diverge. - set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] - set gim [Gpu::ImageManager::copyImageToGpu $im] - dict set ::ImCache $im [list $gim $version] + proc remove {im} { + variable cache + if {[dict exists $cache $im]} { + lassign [dict get $cache $im] gim + Gpu::ImageManager::freeGpuImage $gim + } + dict unset cache $im } } } @@ -85,15 +95,20 @@ On process "display" { Wish $::thisProcess receives statements like \ [list /someone/ wishes to draw an image with /...options/] When /someone/ wishes to draw an image with /...options/ { - set x [dict get $options x] - set y [dict get $options y] + if {[dict exists $options center]} { + set center [dict get $options center] + } else { + set center [list [dict get $options x] [dict get $options y]] + } set im [dict get $options image] set radians [dict get $options radians] set scale [dict_getdef $options scale 1.0] - lassign [dict get $::ImCache $im] gim + set gim [ImageCache::getOrInsertAndIncr $im] + On unmatch { ImageCache::decr $im } Wish the GPU draws pipeline "image" with arguments \ - [list $x $y $im $radians $scale] + [list $gim [list [image_t width $im] [image_t height $im]] \ + $center $radians $scale] } } diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 14f0294a..64c96885 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -28,6 +28,7 @@ On process "display" { namespace ensemble create } set font [font load "PTSans-Regular"] + Claim the GPU has loaded 1 fonts set rotate $::rotate set invBilinear $::invBilinear -- cgit v1.2.3 From 0a69f89605a73c87814fda86860099829d6ffa5c Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 15:46:22 -0400 Subject: image: WIP: Replace cache refcount with simpler LRU scheme We don't really want to be constantly throwing out and recopying images when they flip and back, and it's not a big deal to fault an image now. Works well for static Toph image; doesn't work for camera slice yet (validation errors, and it just blinks a lot). --- virtual-programs/display/image.folk | 48 +++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk index 07cf342a..91e571b9 100644 --- a/virtual-programs/display/image.folk +++ b/virtual-programs/display/image.folk @@ -25,60 +25,63 @@ On process "display" { }] When the GPU has loaded /nfonts/ fonts { - puts LOADING namespace eval ::ImageCache { - # Backing store: stores triples of (GPU image handle, heap slot version, refcount). + # Backing store: stores pairs of (GPU image handle, heap slot version). variable cache [dict create] variable CACHE_MAX_SIZE [- $Gpu::ImageManager::GPU_MAX_IMAGES [uplevel {set nfonts}]] - proc getOrInsertAndIncr {im} { + proc getOrInsert {im} { variable cache variable CACHE_MAX_SIZE if {[dict exists $cache $im]} { - lassign [dict get $cache $im] gim cachedVersion refcount + lassign [dict get $cache $im] gim cachedVersion set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] if {$version == $cachedVersion} { - dict set cache $im [list $gim $cachedVersion [+ $refcount 1]] + # Bump this image to end of cache since it's + # most-recently-accessed. + dict unset cache $im + dict set cache $im [list $gim $cachedVersion] return $gim } else { + # This image is stale. Don't retain it. remove $im } } if {[dict size $cache] >= $CACHE_MAX_SIZE} { evict } + if {[dict size $cache] >= $CACHE_MAX_SIZE} { + puts stderr "image: Warning: Out of slots in GPU image cache." + } set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] set gim [Gpu::ImageManager::copyImageToGpu $im] - dict set cache $im [list $gim $version 1] + dict set cache $im [list $gim $version] return $gim } - proc decr {im} { - variable cache - set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] - lassign [dict get $cache $im] gim cachedVersion refcount - incr refcount -1 - if {$refcount == 0} { - remove $im - } else { - dict set cache $im [list $gim $version $refcount] - } - } - proc evict {} { variable cache + variable CACHE_MAX_SIZE + set numToEvict [expr {$CACHE_MAX_SIZE - [dict size $cache]}] + set numEvicted [list] # Evict stale. dict for {im v} $cache { lassign $v gim expectedVersion set version [Heap::folkHeapGetVersion [string map {uint8_t void} [::image_t data_ptr $im]]] if {$expectedVersion != $version} { Gpu::ImageManager::freeGpuImage $gim - dict unset cache $im + lappend numEvicted $im } } - if {[dict size $cache] >= $CACHE_MAX_SIZE} { - error "image: Unable to evict enough from image cache." + # Evict old. + dict for {im v} $cache { + if {$numToEvict - [llength $numEvicted] == 0} { + break + } + Gpu::ImageManager::freeGpuImage $im + lappend numEvicted $im } + foreach im $numEvicted { dict unset cache $im } } proc remove {im} { @@ -104,8 +107,7 @@ On process "display" { set radians [dict get $options radians] set scale [dict_getdef $options scale 1.0] - set gim [ImageCache::getOrInsertAndIncr $im] - On unmatch { ImageCache::decr $im } + set gim [ImageCache::getOrInsert $im] Wish the GPU draws pipeline "image" with arguments \ [list $gim [list [image_t width $im] [image_t height $im]] \ -- cgit v1.2.3 From 5fea5647e7f40c0f5cb99eccd7ab65319a9aa571 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 16:18:49 -0400 Subject: image,Gpu: Fix cache, for the most part (was double-freeing) --- virtual-programs/display/image.folk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk index 91e571b9..28c66a3b 100644 --- a/virtual-programs/display/image.folk +++ b/virtual-programs/display/image.folk @@ -73,12 +73,14 @@ On process "display" { lappend numEvicted $im } } + foreach im $numEvicted { dict unset cache $im } # Evict old. dict for {im v} $cache { if {$numToEvict - [llength $numEvicted] == 0} { break } - Gpu::ImageManager::freeGpuImage $im + lassign $v gim + Gpu::ImageManager::freeGpuImage $gim lappend numEvicted $im } foreach im $numEvicted { dict unset cache $im } -- cgit v1.2.3 From 4e944eb62b0b1be680f6e4aa372de9c2cbb2d195 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 16:52:36 -0400 Subject: image: Actually fix cache math --- virtual-programs/display/image.folk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/image.folk b/virtual-programs/display/image.folk index 28c66a3b..23246d45 100644 --- a/virtual-programs/display/image.folk +++ b/virtual-programs/display/image.folk @@ -62,7 +62,7 @@ On process "display" { proc evict {} { variable cache variable CACHE_MAX_SIZE - set numToEvict [expr {$CACHE_MAX_SIZE - [dict size $cache]}] + set numToEvict [expr {([dict size $cache] + 1) - $CACHE_MAX_SIZE}] set numEvicted [list] # Evict stale. dict for {im v} $cache { @@ -76,7 +76,7 @@ On process "display" { foreach im $numEvicted { dict unset cache $im } # Evict old. dict for {im v} $cache { - if {$numToEvict - [llength $numEvicted] == 0} { + if {$numToEvict - [llength $numEvicted] <= 0} { break } lassign $v gim -- cgit v1.2.3 From 2c2163396b0aec16bc608e2c6c329e1b4f2e0a56 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 17:04:21 -0400 Subject: display,mask-tags,fill: Add layer support Only implemented minimum for mask-tags for now. --- virtual-programs/display/fill.folk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/fill.folk b/virtual-programs/display/fill.folk index dd9e199d..52039661 100644 --- a/virtual-programs/display/fill.folk +++ b/virtual-programs/display/fill.folk @@ -16,9 +16,9 @@ When /someone/ wishes to draw a triangle with /...options/ { When /someone/ wishes to draw a quad with /...options/ { dict with options { Wish the GPU draws pipeline "fillTriangle" with arguments \ - [list $p0 $p1 $p2 [getColor $color]] + [list $p0 $p1 $p2 [getColor $color]] layer $layer Wish the GPU draws pipeline "fillTriangle" with arguments \ - [list $p0 $p1 $p3 [getColor $color]] + [list $p0 $p1 $p3 [getColor $color]] layer $layer } } When /someone/ wishes to draw a polygon with /...options/ { -- cgit v1.2.3 From a285be8d97d5a75fce4a0a9bef9c755b8dc1acc2 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 17:38:02 -0400 Subject: fill: Fix quad fill (& mask-tags) --- virtual-programs/display/fill.folk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/fill.folk b/virtual-programs/display/fill.folk index 52039661..ae3626f4 100644 --- a/virtual-programs/display/fill.folk +++ b/virtual-programs/display/fill.folk @@ -16,7 +16,7 @@ When /someone/ wishes to draw a triangle with /...options/ { When /someone/ wishes to draw a quad with /...options/ { dict with options { Wish the GPU draws pipeline "fillTriangle" with arguments \ - [list $p0 $p1 $p2 [getColor $color]] layer $layer + [list $p1 $p2 $p3 [getColor $color]] layer $layer Wish the GPU draws pipeline "fillTriangle" with arguments \ [list $p0 $p1 $p3 [getColor $color]] layer $layer } -- cgit v1.2.3 From 3f3eece201d0c5dc8087788846f4a30e7bd297df Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 18:15:54 -0400 Subject: text: Restore font support --- virtual-programs/display/text.folk | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 64c96885..95901380 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -27,8 +27,18 @@ On process "display" { namespace export * namespace ensemble create } - set font [font load "PTSans-Regular"] - Claim the GPU has loaded 1 fonts + set ::FontCache [dict create] + # load all fonts into the fontCache + foreach fontPath [list {*}[glob vendor/fonts/*.png]] { + set fontName "" + regexp {vendor/fonts/(.*).png} $fontPath -> fontName + if {!($fontName eq "")} { + puts "Loaded $fontName into font cache" + set fontdata [font load $fontName] + dict set ::FontCache $fontName $fontdata + } + } + Claim the GPU has loaded [dict size $::FontCache] fonts set rotate $::rotate set invBilinear $::invBilinear @@ -78,7 +88,7 @@ On process "display" { return mix(vec4(0, 0, 0, 0), vec4(1, 1, 1, 1), opacity); }] - fn textExtent {text scale} { + fn textExtent {text scale font} { set em [* $scale 25.0] set x 0; set y 0 set width 0 @@ -111,14 +121,20 @@ On process "display" { set y0 [dict get $options y] } set scale [dict_getdef $options scale 1.0] + set font [dict_getdef $options font "PTSans-Regular"] set text [dict get $options text] set radians [dict get $options radians] + if {!([dict exists $::FontCache $font])} { + throw {DISPLAY FONT {font doesn't exist}} "$font doesn't exist" + } + set font [dict get $::FontCache $font] + set fontAtlas [font gpuAtlasImage $font] set fontAtlasSize [list [::image width [font atlasImage $font]] \ [::image height [font atlasImage $font]]] - set extent [vec2 rotate [textExtent $text $scale] $radians] + set extent [vec2 rotate [textExtent $text $scale $font] $radians] set em [* $scale 25.0] -- cgit v1.2.3 From 3e1ed38511ed5bbfc58f86c95eb62ba9761b3099 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 18:24:29 -0400 Subject: arc: Re-add arc support --- virtual-programs/display/arc.folk | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 virtual-programs/display/arc.folk (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/arc.folk b/virtual-programs/display/arc.folk new file mode 100644 index 00000000..e2412bf0 --- /dev/null +++ b/virtual-programs/display/arc.folk @@ -0,0 +1,39 @@ +# Example: +# When $this has region /r/ { +# lassign [region centroid $r] x y +# Wish to draw an arc with x $x y $y start 0 arclen 1 thickness 3 radius 100 color green +# } + +Wish the GPU compiles pipeline "arc" {{vec2 center float start float arclen float radius float thickness vec4 color} { + float r = radius + thickness; + vec2 vertices[4] = vec2[4]( + center - r, + vec2(center.x + r, center.y - r), + vec2(center.x - r, center.y + r), + center + r + ); + return vertices[gl_VertexIndex]; +} { + #define M_TWO_PI 6.283185307179586 + start = clamp(start, 0, M_TWO_PI); + arclen = clamp(arclen, 0, M_TWO_PI); + + float dist = length(gl_FragCoord.xy - center) - radius; + float angle = atan(-(gl_FragCoord.y - center.y), gl_FragCoord.x - center.x); + + // Shift angle from [-pi, pi) to [0, 2*pi] + angle = (angle < 0) ? (angle + M_TWO_PI) : angle; + float end = start + arclen; + + return ((dist < thickness && dist > 0.0) && + ((end < M_TWO_PI && angle > start && angle < end) || + (end >= M_TWO_PI && (angle > start || angle < end-M_TWO_PI)))) ? color : vec4(0, 0, 0, 0); + +}} + +When /someone/ wishes to draw an arc with /...options/ { + dict with options { + Wish the GPU draws pipeline "arc" with arguments \ + [list [list $x $y] $start $arclen $radius $thickness [getColor $color]] + } +} -- cgit v1.2.3 From a798dca8ca19c2601413e85a316f73dd857a2c14 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 19:37:14 -0400 Subject: fill,shapes,circle,connections: Breaking: Fix shapes Shapes all now work by wishes; connections has been ported to use these wishes. --- virtual-programs/display/circle.folk | 4 ++-- virtual-programs/display/fill.folk | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/circle.folk b/virtual-programs/display/circle.folk index 2951f3a9..03e4def7 100644 --- a/virtual-programs/display/circle.folk +++ b/virtual-programs/display/circle.folk @@ -20,12 +20,12 @@ Wish the GPU compiles pipeline "circle" { When /someone/ wishes to draw a circle with /...options/ { set center [dict_getdef $options center ""] - if {center eq ""} { set center [list [dict get $options x] [dict get $options y]] } + if {$center eq ""} { set center [list [dict get $options x] [dict get $options y]] } set radius [dict get $options radius] set thickness [dict get $options thickness] set color [getColor [dict get $options color]] set filled [dict_getdef $options filled false] Wish the GPU draws pipeline "circle" with arguments \ - [list $center $radius $thickness $color $filled] + [list $center $radius $thickness $color [expr {$filled eq false ? 0 : 1}]] } diff --git a/virtual-programs/display/fill.folk b/virtual-programs/display/fill.folk index ae3626f4..f3057068 100644 --- a/virtual-programs/display/fill.folk +++ b/virtual-programs/display/fill.folk @@ -15,6 +15,7 @@ When /someone/ wishes to draw a triangle with /...options/ { } When /someone/ wishes to draw a quad with /...options/ { dict with options { + if {![info exists layer]} { set layer 0 } Wish the GPU draws pipeline "fillTriangle" with arguments \ [list $p1 $p2 $p3 [getColor $color]] layer $layer Wish the GPU draws pipeline "fillTriangle" with arguments \ @@ -29,9 +30,13 @@ When /someone/ wishes to draw a polygon with /...options/ { if {$num_points < 3} { error "At least 3 points are required to form a polygon." } elseif {$num_points == 3} { - eval fillTriangle $points $color + Wish to draw a triangle with \ + p0 [lindex $points 0] p1 [lindex $points 1] p2 [lindex $points 2] \ + color $color } elseif {$num_points == 4} { - eval fillQuad $points $color + Wish to draw a quad with \ + p0 [lindex $points 0] p1 [lindex $points 1] p2 [lindex $points 2] p3 [lindex $points 3] \ + color $color } else { # Get the first point in the list as the "base" point of the triangles set p0 [lindex $points 0] @@ -40,7 +45,7 @@ When /someone/ wishes to draw a polygon with /...options/ { set p1 [lindex $points $i] set p2 [lindex $points [expr {$i+1}]] Wish the GPU draws pipeline "fillTriangle" with arguments \ - [list $p0 $p1 $p2 $color] + [list $p0 $p1 $p2 [getColor $color]] } } } -- cgit v1.2.3 From df52094ecdb6cb3dc6e70e15c3ec7f14909e30fe Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Fri, 13 Oct 2023 20:24:08 -0400 Subject: stroke: Use instances. Idk if this helps much --- virtual-programs/display/stroke.folk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/stroke.folk b/virtual-programs/display/stroke.folk index 98eec038..f212f471 100644 --- a/virtual-programs/display/stroke.folk +++ b/virtual-programs/display/stroke.folk @@ -24,10 +24,11 @@ When /someone/ wishes to draw a stroke with /...options/ { set width [dict get $options width] set color [getColor [dict get $options color]] + set instances [list] for {set i 0} {$i < [expr {[llength $points] - 1}]} {incr i} { set from [lindex $points $i] set to [lindex $points [expr $i+1]] - Wish the GPU draws pipeline "line" with arguments \ - [list $from $to $width $color] + lappend instances [list $from $to $width $color] } + Wish the GPU draws pipeline "line" with instances $instances } -- cgit v1.2.3 From 2cf62a0b248249a529a70e29455a448ebb81e28a Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 23 Oct 2023 18:43:31 -0400 Subject: text: Simplify loop --- virtual-programs/display/text.folk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 95901380..9189e4c1 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -146,8 +146,7 @@ On process "display" { set lineNum 0 set instances [list] - for {set i 0} {$i < [string length $text]} {incr i} { - set char [string index $text $i] + foreach char [split $text ""] { if {$char eq "\n"} { incr lineNum lassign [vec2 add [list $x0 $y0] \ -- cgit v1.2.3 From e4354267a33766d9dacd5731e660407fd60091c9 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 25 Oct 2023 17:35:12 -0400 Subject: text: Do text shaping in C --- virtual-programs/display/text.folk | 184 ++++++++++++++++++++++--------------- 1 file changed, 110 insertions(+), 74 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 9189e4c1..451b9ce0 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -1,28 +1,121 @@ On process "display" { namespace eval font { + set cc [c create] + $cc include + defineImageType $cc + $cc struct GlyphInfo { + float advance; + + float planeLeft; + float planeBottom; + float planeRight; + float planeTop; + + float atlasLeft; + float atlasBottom; + float atlasRight; + float atlasTop; + } + $cc struct Font { + image_t atlasImage; + int gpuAtlasImage; + // TODO: This only handles ASCII, obviously. + GlyphInfo glyphInfos[128]; + } + proc load {name} { set csvFd [open "vendor/fonts/$name.csv" r]; set csv [read $csvFd]; close $csvFd - set glyphInfos [dict create] + set fields [list advance \ + planeLeft planeBottom planeRight planeTop \ + atlasLeft atlasBottom atlasRight atlasTop] + # HACK: Create list of null glyphs to initialize. + set glyphInfos [list] + for {set i 0} {$i < 128} {incr i} { + set glyphInfo [dict create] + foreach field $fields { dict set glyphInfo $field 0 } + lappend glyphInfos $glyphInfo + } + foreach line [split $csv "\n"] { - set info [lassign [split $line ,] glyph] - lassign $info advance \ - planeLeft planeBottom planeRight planeTop \ - atlasLeft atlasBottom atlasRight atlasTop - dict set glyphInfos $glyph \ - [list $advance \ - [list $planeLeft $planeBottom $planeRight $planeTop] \ - [list $atlasLeft $atlasBottom $atlasRight $atlasTop]] + set values [lassign [split $line ,] glyph] + if {![string is integer -strict $glyph]} { continue } + + set glyphInfo [dict create] + foreach field $fields value $values { + dict set glyphInfo $field $value + } + lset glyphInfos $glyph $glyphInfo } set im [image load "[pwd]/vendor/fonts/$name.png"] set gim [Gpu::ImageManager::copyImageToGpu $im] - return [list $glyphInfos $im $gim] + return [dict create atlasImage $im gpuAtlasImage $gim glyphInfos $glyphInfos] + } + $cc struct vec2f { float x; float y; } + $cc proc vec2f_add {vec2f a vec2f b} vec2f { + return (vec2f) { a.x + b.x, a.y + b.y }; } - proc hasGlyphInfo {font charCode} { dict exists [lindex $font 0] $charCode } - proc glyphInfo {font charCode} { dict get [lindex $font 0] $charCode } - proc atlasImage {font} { lindex $font 1 } - proc gpuAtlasImage {font} { lindex $font 2 } + $cc proc vec2f_rotate {vec2f a float radians} vec2f { + return (vec2f) { + a.x*cosf(radians) + a.y*sinf(radians), + -a.x*sinf(radians) + a.y*cosf(radians) + }; + } + $cc proc textExtent {Font* font char* text float scale} vec2f { + float em = scale * 25.0; + float x = 0; float y = 0; + float width = 0; + for (int i = 0; text[i] != 0; i++) { + int ch = text[i]; + if (ch == '\n') { + y = y + em; x = 0; continue; + } + if (ch >= sizeof(font->glyphInfos)/sizeof(font->glyphInfos[0])) { + ch = '?'; + } + GlyphInfo glyphInfo = font->glyphInfos[ch]; + x = x + glyphInfo.advance * em; + if (x > width) { width = x; } + } + return (vec2f) { width, y + em }; + } + $cc proc textShape {Font* font char* text + float x0 float y0 float scale float radians} Tcl_Obj* { + vec2f extent = vec2f_rotate(textExtent(font, text, scale), radians); + float em = scale * 25.0; + + vec2f p0 = { x0 - extent.x/2.0, y0 - extent.y/2.0 }; + vec2f p = p0; + + int lineNum = 0; + Tcl_Obj* instances = Tcl_NewListObj(0, NULL); // List of instances. + for (int i = 0; text[i] != 0; i++) { + int ch = text[i]; + if (ch == '\n') { + lineNum++; + p = vec2f_add(p0, vec2f_rotate((vec2f) {0, lineNum * em}, radians)); + continue; + } + if (ch >= sizeof(font->glyphInfos)/sizeof(font->glyphInfos[0])) { + ch = '?'; + } + GlyphInfo glyphInfo = font->glyphInfos[ch]; + if (ch != ' ') { + // Append to list of instances. + Tcl_Obj* instance = Tcl_ObjPrintf("%d {%d %d} {%f %f %f %f} {%f %f %f %f} {%f %f} %f %f", + font->gpuAtlasImage, + font->atlasImage.width, font->atlasImage.height, + glyphInfo.atlasLeft, glyphInfo.atlasBottom, glyphInfo.atlasRight, glyphInfo.atlasTop, + glyphInfo.planeLeft, glyphInfo.planeBottom, glyphInfo.planeRight, glyphInfo.planeTop, + p.x, p.y, radians, em); + Tcl_ListObjAppendElement(NULL, instances, instance); + } + p = vec2f_add(p, vec2f_rotate((vec2f) {glyphInfo.advance * em, 0}, radians)); + } + return instances; + } + $cc compile namespace export * namespace ensemble create @@ -88,31 +181,11 @@ On process "display" { return mix(vec4(0, 0, 0, 0), vec4(1, 1, 1, 1), opacity); }] - fn textExtent {text scale font} { - set em [* $scale 25.0] - set x 0; set y 0 - set width 0 - for {set i 0} {$i < [string length $text]} {incr i} { - set char [string index $text $i] - if {$char eq "\n"} { - set y [+ $y $em]; set x 0; continue - } - set charCode [scan $char %c] - if {[font hasGlyphInfo $font $charCode]} { - set glyphInfo [font glyphInfo $font $charCode] - } else { - set glyphInfo [font glyphInfo $font [scan ? %c]] - } - lassign $glyphInfo advance planeBounds atlasBounds - set x [+ $x [* $advance $em]] - if {$x > $width} { set width $x } - } - return [list $width [+ $y $em]] - } - Wish $::thisProcess receives statements like \ [list /someone/ wishes to draw text with /...options/] + set cc [c create] + When /someone/ wishes to draw text with /...options/ { if {[dict exists $options center]} { lassign [dict get $options center] x0 y0 @@ -130,44 +203,7 @@ On process "display" { } set font [dict get $::FontCache $font] - set fontAtlas [font gpuAtlasImage $font] - set fontAtlasSize [list [::image width [font atlasImage $font]] \ - [::image height [font atlasImage $font]]] - - set extent [vec2 rotate [textExtent $text $scale $font] $radians] - - set em [* $scale 25.0] - - # TODO: Add text alignment/anchor options (right now, this - # setup centers the text). - set x0 [expr {$x0 - [lindex $extent 0]/2}] - set y0 [expr {$y0 - [lindex $extent 1]/2}] - set x $x0; set y $y0 - - set lineNum 0 - set instances [list] - foreach char [split $text ""] { - if {$char eq "\n"} { - incr lineNum - lassign [vec2 add [list $x0 $y0] \ - [vec2 rotate [list 0 [* $lineNum $em]] $radians]] x y - continue - } - set charCode [scan $char %c] - if {[font hasGlyphInfo $font $charCode]} { - set glyphInfo [font glyphInfo $font $charCode] - } else { - set glyphInfo [font glyphInfo $font [scan ? %c]] - } - lassign $glyphInfo advance planeBounds atlasBounds - if {$char ne " "} { - lappend instances \ - [list $fontAtlas $fontAtlasSize \ - $atlasBounds $planeBounds [list $x $y] $radians $em] - } - lassign [vec2 add [list $x $y] \ - [vec2 rotate [list [* $advance $em] 0] $radians]] x y - } + set instances [font textShape $font $text $x0 $y0 $scale $radians] # We need to batch into one wish so we don't deal with n^2 # checks for existing statements for n glyphs. -- cgit v1.2.3 From f8d2bb768ffbfbf12fdb27df28ea4e1fc707584a Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Wed, 25 Oct 2023 18:31:02 -0400 Subject: text: Try using Tcl objects in text shaping to prevent conversion Still slow (20fps for program list) --- virtual-programs/display/text.folk | 71 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 38 deletions(-) (limited to 'virtual-programs/display') diff --git a/virtual-programs/display/text.folk b/virtual-programs/display/text.folk index 451b9ce0..858a9dc7 100644 --- a/virtual-programs/display/text.folk +++ b/virtual-programs/display/text.folk @@ -3,48 +3,33 @@ On process "display" { set cc [c create] $cc include defineImageType $cc - $cc struct GlyphInfo { - float advance; - - float planeLeft; - float planeBottom; - float planeRight; - float planeTop; - - float atlasLeft; - float atlasBottom; - float atlasRight; - float atlasTop; - } $cc struct Font { image_t atlasImage; int gpuAtlasImage; // TODO: This only handles ASCII, obviously. - GlyphInfo glyphInfos[128]; + Tcl_Obj* glyphInfos[128]; } proc load {name} { set csvFd [open "vendor/fonts/$name.csv" r]; set csv [read $csvFd]; close $csvFd - set fields [list advance \ - planeLeft planeBottom planeRight planeTop \ - atlasLeft atlasBottom atlasRight atlasTop] + set fields [list ] # HACK: Create list of null glyphs to initialize. set glyphInfos [list] for {set i 0} {$i < 128} {incr i} { - set glyphInfo [dict create] - foreach field $fields { dict set glyphInfo $field 0 } - lappend glyphInfos $glyphInfo + lappend glyphInfos {} } foreach line [split $csv "\n"] { set values [lassign [split $line ,] glyph] if {![string is integer -strict $glyph]} { continue } - set glyphInfo [dict create] - foreach field $fields value $values { - dict set glyphInfo $field $value - } - lset glyphInfos $glyph $glyphInfo + lassign $values advance \ + planeLeft planeBottom planeRight planeTop \ + atlasLeft atlasBottom atlasRight atlasTop + lset glyphInfos $glyph \ + [list $advance \ + [list $planeLeft $planeBottom $planeRight $planeTop] \ + [list $atlasLeft $atlasBottom $atlasRight $atlasTop]] } set im [image load "[pwd]/vendor/fonts/$name.png"] @@ -74,14 +59,18 @@ On process "display" { if (ch >= sizeof(font->glyphInfos)/sizeof(font->glyphInfos[0])) { ch = '?'; } - GlyphInfo glyphInfo = font->glyphInfos[ch]; - x = x + glyphInfo.advance * em; + Tcl_Obj* glyphInfo = font->glyphInfos[ch]; + Tcl_Obj* advanceObj; Tcl_ListObjIndex(NULL, glyphInfo, 0, &advanceObj); + double advance; Tcl_GetDoubleFromObj(NULL, advanceObj, &advance); + x = x + advance * em; if (x > width) { width = x; } } return (vec2f) { width, y + em }; } $cc proc textShape {Font* font char* text float x0 float y0 float scale float radians} Tcl_Obj* { + Tcl_Obj* gpuAtlasImageSize = Tcl_ObjPrintf("%d %d", font->atlasImage.width, font->atlasImage.height); + vec2f extent = vec2f_rotate(textExtent(font, text, scale), radians); float em = scale * 25.0; @@ -100,18 +89,26 @@ On process "display" { if (ch >= sizeof(font->glyphInfos)/sizeof(font->glyphInfos[0])) { ch = '?'; } - GlyphInfo glyphInfo = font->glyphInfos[ch]; + Tcl_Obj* glyphInfo = font->glyphInfos[ch]; + Tcl_Obj* advanceObj; Tcl_ListObjIndex(NULL, glyphInfo, 0, &advanceObj); + double advance; Tcl_GetDoubleFromObj(NULL, advanceObj, &advance); if (ch != ' ') { // Append to list of instances. - Tcl_Obj* instance = Tcl_ObjPrintf("%d {%d %d} {%f %f %f %f} {%f %f %f %f} {%f %f} %f %f", - font->gpuAtlasImage, - font->atlasImage.width, font->atlasImage.height, - glyphInfo.atlasLeft, glyphInfo.atlasBottom, glyphInfo.atlasRight, glyphInfo.atlasTop, - glyphInfo.planeLeft, glyphInfo.planeBottom, glyphInfo.planeRight, glyphInfo.planeTop, - p.x, p.y, radians, em); + Tcl_Obj* planeBounds; Tcl_ListObjIndex(NULL, glyphInfo, 1, &planeBounds); + Tcl_Obj* atlasBounds; Tcl_ListObjIndex(NULL, glyphInfo, 2, &atlasBounds); + Tcl_Obj* pv[] = {Tcl_NewDoubleObj(p.x), Tcl_NewDoubleObj(p.y)}; + Tcl_Obj* pObj = Tcl_NewListObj(2, pv); + Tcl_Obj* args[] = { + Tcl_NewIntObj(font->gpuAtlasImage), + gpuAtlasImageSize, + atlasBounds, + planeBounds, + pObj, Tcl_NewDoubleObj(radians), Tcl_NewDoubleObj(em) + }; + Tcl_Obj* instance = Tcl_NewListObj(sizeof(args)/sizeof(args[0]), args); Tcl_ListObjAppendElement(NULL, instances, instance); } - p = vec2f_add(p, vec2f_rotate((vec2f) {glyphInfo.advance * em, 0}, radians)); + p = vec2f_add(p, vec2f_rotate((vec2f) {advance * em, 0}, radians)); } return instances; } @@ -184,9 +181,7 @@ On process "display" { Wish $::thisProcess receives statements like \ [list /someone/ wishes to draw text with /...options/] - set cc [c create] - - When /someone/ wishes to draw text with /...options/ { + When (non-capturing) /someone/ wishes to draw text with /...options/ { if {[dict exists $options center]} { lassign [dict get $options center] x0 y0 } else { -- cgit v1.2.3