summaryrefslogtreecommitdiff
path: root/chickadee/graphics/path.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/path.scm')
-rw-r--r--chickadee/graphics/path.scm826
1 files changed, 489 insertions, 337 deletions
diff --git a/chickadee/graphics/path.scm b/chickadee/graphics/path.scm
index a6e27e9..435123a 100644
--- a/chickadee/graphics/path.scm
+++ b/chickadee/graphics/path.scm
@@ -22,16 +22,15 @@
(define-module (chickadee graphics path)
#:use-module (chickadee config)
#:use-module (chickadee data array-list)
- #:use-module (chickadee graphics blend)
+ #:use-module (chickadee data bytestruct)
+ #:use-module (chickadee graphics)
#:use-module (chickadee graphics buffer)
#:use-module (chickadee graphics color)
- #:use-module (chickadee graphics engine)
- #:use-module (chickadee graphics framebuffer)
- #:use-module (chickadee graphics gl)
+ #:use-module (chickadee graphics depth-stencil)
#:use-module (chickadee graphics multisample)
- #:use-module (chickadee graphics polygon)
+ #:use-module (chickadee graphics pipeline)
+ #:use-module (chickadee graphics primitive)
#:use-module (chickadee graphics shader)
- #:use-module (chickadee graphics stencil)
#:use-module (chickadee graphics texture)
#:use-module (chickadee image)
#:use-module (chickadee math)
@@ -41,6 +40,7 @@
#:use-module (chickadee math vector)
#:use-module (chickadee utils)
#:use-module (ice-9 match)
+ #:use-module (ice-9 textual-ports)
#:use-module ((rnrs base) #:select (mod))
#:use-module (rnrs bytevectors)
#:use-module (srfi srfi-1)
@@ -892,30 +892,50 @@
;;; Stroked path
;;;
-(define-geometry-type <stroke-vertex>
- stroke-vertex-ref
- stroke-vertex-set!
- stroke-vertex-append!
- (position vec2)
- (texture vec2)
- (length float))
+(define-bytestruct <stroke-vertex>
+ (struct (position <vec2>)
+ (texture <vec2>)
+ (length f32)))
+
+(define-bytestruct <stroke-index>
+ (struct (a s32) (b s32) (c s32) (d s32) (e s32) (f s32)))
+
+(define-bytestruct <stroke-uniforms>
+ (struct (mvp <matrix4>)
+ (color <color>)
+ (feather f32)
+ (stroke-width f32)
+ (stroke-miter-limit f32)
+ (stroke-closed? s32)
+ (stroke-cap s32)
+ (stroke-miter-style s32)))
;; TODO: Allow for multiple path styles to be rendered in a single
;; draw call. This will probably involve abusing textures to store
;; the per-path style info. We can cross that bridge if we ever need
;; the extra performance.
(define-record-type <stroked-path>
- (%make-stroked-path geometry)
+ (%make-stroked-path vertices indices vertex-bindings bindings)
stroked-path?
(blend-mode stroked-path-blend-mode set-stroked-path-blend-mode!)
(color stroked-path-color set-stroked-path-color!)
(width stroked-path-width set-stroked-path-width!)
(feather stroked-path-feather set-stroked-path-feather!)
(cap stroked-path-cap set-stroked-path-cap!)
- (geometry stroked-path-geometry))
+ (vertices stroked-path-vertices)
+ (indices stroked-path-indices)
+ (vertex-bindings stroked-path-vertex-bindings)
+ (bindings stroked-path-bindings))
(define (make-stroked-path)
- (%make-stroked-path (make-geometry <stroke-vertex> 32)))
+ (%make-stroked-path (make-dbuffer #:name "Stroked path vertex buffer")
+ (make-dbuffer #:name "Stroked path index buffer"
+ #:usage '(index))
+ (make-vector 1)
+ (vector
+ (make-buffer (bytestruct-sizeof <stroke-uniforms>)
+ #:name "Stroked path uniform buffer"
+ #:usage '(uniform)))))
;; Tesselation of stroked paths involves building rectangles composed
;; of 2 triangles for each line segment in the path. This
@@ -936,218 +956,260 @@
(counts (compiled-path-counts compiled-path))
(path-count (compiled-path-count compiled-path))
(padding (/ (ceiling (+ width (* feather 2.5))) 2.0))
- (geometry (stroked-path-geometry stroked-path)))
+ (vertices (stroked-path-vertices stroked-path))
+ (indices (stroked-path-indices stroked-path)))
(define (add-points first? lx ly rx ry distance)
- (let ((vert-count (geometry-vertex-count geometry <stroke-vertex>)))
- ;; Each vertex has the following data:
- ;; - x
- ;; - y
- ;; - distance from starting point
- ;; - distance from true line segment (used for antialising)
- ;;
- ;; First vertex is the left hand side, second is the right.
- (stroke-vertex-append! geometry
- (lx ly distance padding 0.0)
- (rx ry distance (- padding) 0.0))
- ;; On the first iteration we only have 2 points which is
- ;; not enough to create line segment geometry which
- ;; requires looking at the newest 2 points + the 2
- ;; previous points.
- (unless first?
- (geometry-index-append! geometry
- (- vert-count 1)
- (- vert-count 2)
- vert-count
- (- vert-count 1)
- vert-count
- (+ vert-count 1)))))
+ ;; Each vertex has the following data:
+ ;; - x
+ ;; - y
+ ;; - distance from starting point
+ ;; - distance from true line segment (used for antialising)
+ ;;
+ ;; First vertex is the left hand side, second is the right.
+ (dbuffer-append! <stroke-vertex>
+ (((position x) lx)
+ ((position y) ly)
+ ((texture x) distance)
+ ((texture y) padding))
+ vertices)
+ (dbuffer-append! <stroke-vertex>
+ (((position x) rx)
+ ((position y) ry)
+ ((texture x) distance)
+ ((texture y) (- padding)))
+ vertices)
+ ;; On the first iteration we only have 2 points which is
+ ;; not enough to create line segment geometry which
+ ;; requires looking at the newest 2 points + the 2
+ ;; previous points.
+ (unless first?
+ (let ((vert-count (/ (dbuffer-length vertices) (bytestruct-sizeof <stroke-vertex>))))
+ (dbuffer-append! <stroke-index>
+ (((a) (- vert-count 2))
+ ((b) (- vert-count 4))
+ ((c) (- vert-count 3))
+ ((d) (- vert-count 1))
+ ((e) (- vert-count 2))
+ ((f) (- vert-count 3)))
+ indices))))
(define (set-length i length)
- (stroke-vertex-set! geometry length (* i 2) length)
- (stroke-vertex-set! geometry length (+ (* i 2) 1) length))
- (with-geometry geometry
- (let path-loop ((i 0))
- (when (< i path-count)
- (let* ((count (u32vector-ref counts i))
- (first (u32vector-ref offsets i))
- (last (+ first count -1))
- (open? (or (not (= (f32vector-ref points (* first 2))
- (f32vector-ref points (* last 2))))
- (not (= (f32vector-ref points (+ (* first 2) 1))
- (f32vector-ref points (+ (* last 2) 1))))))
- (last* (- last 1)))
- (let point-loop ((j first)
- ;; How far along the line segment we are.
- (distance 0.0))
- (when (<= j last)
- (let ((x (f32vector-ref points (* j 2)))
- (y (f32vector-ref points (+ (* j 2) 1))))
- (cond
- ;; Beginning cap.
- ((and open? (= j first))
- (let* ((next-offset (* (+ j 1) 2))
- (next-x (f32vector-ref points next-offset))
- (next-y (f32vector-ref points (+ next-offset 1)))
- (dx (- x next-x))
- (dy (- y next-y))
- (mag (sqrt (+ (* dx dx) (* dy dy))))
- (norm-x (/ dx mag))
- (norm-y (/ dy mag))
- (pad-x (* norm-x padding))
- (pad-y (* norm-y padding))
- (lx (+ x pad-x pad-y))
- (ly (- (+ y pad-y) pad-x))
- (rx (- (+ x pad-x) pad-y))
- (ry (+ y pad-y pad-x)))
- (add-points #t lx ly rx ry (- padding))
- (point-loop (+ j 1) mag)))
- ;; End cap.
- ((and open? (= j last))
- (let* ((prev-offset (* (- j 1) 2))
- (prev-x (f32vector-ref points prev-offset))
- (prev-y (f32vector-ref points (+ prev-offset 1)))
- (dx (- x prev-x))
- (dy (- y prev-y))
- (mag (sqrt (+ (* dx dx) (* dy dy))))
- (norm-x (/ dx mag))
- (norm-y (/ dy mag))
- (pad-x (* norm-x padding))
- (pad-y (* norm-y padding))
- (lx (- (+ x pad-x) pad-y))
- (ly (+ y pad-y pad-x))
- (rx (+ x pad-x pad-y))
- (ry (- (+ y pad-y) pad-x)))
- (add-points #f lx ly rx ry (+ distance padding))))
- ;; Point is somewhere in the middle of the path
- ;; (or the first/last point within a closed loop)
- ;; and needs to be mitered.
- ;;
- ;; The vector math used to make a miter joint for
- ;; a polyline is based on this informative forum
- ;; thread:
- ;;
- ;; https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader
- (else
- ;; For the prev/next offsets, we need to account
- ;; for closed loops. When j = last, we need the
- ;; next index to be 1. Likewise, when j =
- ;; first, we need the previous index to be
- ;; offset + count - 1, in other words: the
- ;; second to last point. This is because the
- ;; first and last points of a closed loop are
- ;; the same, so in order to know how to miter
- ;; the first and last line segments we need to
- ;; know about the second and second to last
- ;; points in the compiled path buffer. The
- ;; modulo operator is our best friend when it
- ;; comes to wrapping values to a range.
- (let* ((prev-offset (* (+ (modulo (- j first 1)
- (- count 1))
- first)
- 2))
- (prev-x (f32vector-ref points prev-offset))
- (prev-y (f32vector-ref points (+ prev-offset 1)))
- (next-offset (* (+ (modulo (- j first)
- (- count 1))
- first 1)
- 2))
- (next-x (f32vector-ref points next-offset))
- (next-y (f32vector-ref points (+ next-offset 1)))
- ;; Vector from (x, y) to (next-x next-y).
- (ndx (- next-x x))
- (ndy (- next-y y))
- ;; Normalized form.
- (nmag (sqrt (+ (* ndx ndx) (* ndy ndy))))
- (nx (if (zero? nmag) 0.0 (/ ndx nmag)))
- (ny (if (zero? nmag) 0.0 (/ ndy nmag)))
- ;; Vector from (prev-x, prev-y) to (x, y).
- (pdx (- x prev-x))
- (pdy (- y prev-y))
- ;; Normalized form.
- (pmag (sqrt (+ (* pdx pdx) (* pdy pdy))))
- (px (if (zero? pmag) 0.0 (/ pdx pmag)))
- (py (if (zero? pmag) 0.0 (/ pdy pmag)))
- ;; Tangent of the 2 vectors.
- (tanx (+ nx px))
- (tany (+ ny py))
- ;; Normalized form.
- (tanmag (sqrt (+ (* tanx tanx) (* tany tany))))
- (ntanx (if (zero? tanmag) 0.0 (/ tanx tanmag)))
- (ntany (if (zero? tanmag) 0.0 (/ tany tanmag)))
- ;; In order to join 2 line segments
- ;; together neatly, they must have
- ;; mitered corners. The miter direction
- ;; is perpendicular to the tangent of the
- ;; 2 line segments.
- (miterx (- ntany))
- (mitery ntanx)
- ;; In order to compute the proper length
- ;; of the miter line, we need to project
- ;; the miter vector (which is in
- ;; normalized form) onto the normalized
- ;; form of a vector for one of the line
- ;; segments. It doesn't matter which
- ;; vector is used, so I chose the vector
- ;; from the current point to the
- ;; previous.
- (miter-dot (+ (* miterx (- py)) (* mitery px)))
- (miter-length (abs (/ padding miter-dot)))
- ;; The vector that will thicken the line.
- (padx (* miterx miter-length))
- (pady (* mitery miter-length))
- ;; Figure out the extra distance +/- that
- ;; the mitering has caused so the
- ;; vertices distance attribute can be
- ;; adjusted accordingly.
- (padmag (sqrt (+ (* padx padx) (* pady pady))))
- ;; (npadx (if (zero? padmag) 0.0 (/ padx padmag)))
- ;; (npady (if (zero? padmag) 0.0 (/ pady padmag)))
- (miter-distance (sqrt (- (* padmag padmag) (* padding padding))))
- ;; Left side
- (lx (+ x padx))
- (ly (+ y pady))
- ;; Right side
- (rx (- x padx))
- (ry (- y pady)))
- (add-points (= j first) lx ly rx ry distance)
- (point-loop (+ j 1) (+ distance nmag)))))))
- (when (= j last)
- ;; Go over the points one more time to set the total
- ;; length in each vertex.
- (let length-loop ((k first))
- (when (<= k last)
- (set-length k distance)
- (length-loop (+ k 1)))))))
- (path-loop (+ i 1)))))))
+ (dbuffer-pack! <stroke-vertex>
+ (((length) length))
+ vertices
+ (* (bytestruct-sizeof <stroke-vertex>) (* i 2)))
+ (dbuffer-pack! <stroke-vertex>
+ (((length) length))
+ vertices
+ (* (bytestruct-sizeof <stroke-vertex>) (1+ (* i 2)))))
+ (dbuffer-map! vertices)
+ (dbuffer-map! indices)
+ (let path-loop ((i 0))
+ (when (< i path-count)
+ (let* ((count (u32vector-ref counts i))
+ (first (u32vector-ref offsets i))
+ (last (+ first count -1))
+ (open? (or (not (= (f32vector-ref points (* first 2))
+ (f32vector-ref points (* last 2))))
+ (not (= (f32vector-ref points (+ (* first 2) 1))
+ (f32vector-ref points (+ (* last 2) 1))))))
+ (last* (- last 1)))
+ (let point-loop ((j first)
+ ;; How far along the line segment we are.
+ (distance 0.0))
+ (when (<= j last)
+ (let ((x (f32vector-ref points (* j 2)))
+ (y (f32vector-ref points (+ (* j 2) 1))))
+ (cond
+ ;; Beginning cap.
+ ((and open? (= j first))
+ (let* ((next-offset (* (+ j 1) 2))
+ (next-x (f32vector-ref points next-offset))
+ (next-y (f32vector-ref points (+ next-offset 1)))
+ (dx (- x next-x))
+ (dy (- y next-y))
+ (mag (sqrt (+ (* dx dx) (* dy dy))))
+ (norm-x (/ dx mag))
+ (norm-y (/ dy mag))
+ (pad-x (* norm-x padding))
+ (pad-y (* norm-y padding))
+ (lx (+ x pad-x pad-y))
+ (ly (- (+ y pad-y) pad-x))
+ (rx (- (+ x pad-x) pad-y))
+ (ry (+ y pad-y pad-x)))
+ (add-points #t lx ly rx ry (- padding))
+ (point-loop (+ j 1) mag)))
+ ;; End cap.
+ ((and open? (= j last))
+ (let* ((prev-offset (* (- j 1) 2))
+ (prev-x (f32vector-ref points prev-offset))
+ (prev-y (f32vector-ref points (+ prev-offset 1)))
+ (dx (- x prev-x))
+ (dy (- y prev-y))
+ (mag (sqrt (+ (* dx dx) (* dy dy))))
+ (norm-x (/ dx mag))
+ (norm-y (/ dy mag))
+ (pad-x (* norm-x padding))
+ (pad-y (* norm-y padding))
+ (lx (- (+ x pad-x) pad-y))
+ (ly (+ y pad-y pad-x))
+ (rx (+ x pad-x pad-y))
+ (ry (- (+ y pad-y) pad-x)))
+ (add-points #f lx ly rx ry (+ distance padding))))
+ ;; Point is somewhere in the middle of the path
+ ;; (or the first/last point within a closed loop)
+ ;; and needs to be mitered.
+ ;;
+ ;; The vector math used to make a miter joint for
+ ;; a polyline is based on this informative forum
+ ;; thread:
+ ;;
+ ;; https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader
+ (else
+ ;; For the prev/next offsets, we need to account
+ ;; for closed loops. When j = last, we need the
+ ;; next index to be 1. Likewise, when j =
+ ;; first, we need the previous index to be
+ ;; offset + count - 1, in other words: the
+ ;; second to last point. This is because the
+ ;; first and last points of a closed loop are
+ ;; the same, so in order to know how to miter
+ ;; the first and last line segments we need to
+ ;; know about the second and second to last
+ ;; points in the compiled path buffer. The
+ ;; modulo operator is our best friend when it
+ ;; comes to wrapping values to a range.
+ (let* ((prev-offset (* (+ (modulo (- j first 1)
+ (- count 1))
+ first)
+ 2))
+ (prev-x (f32vector-ref points prev-offset))
+ (prev-y (f32vector-ref points (+ prev-offset 1)))
+ (next-offset (* (+ (modulo (- j first)
+ (- count 1))
+ first 1)
+ 2))
+ (next-x (f32vector-ref points next-offset))
+ (next-y (f32vector-ref points (+ next-offset 1)))
+ ;; Vector from (x, y) to (next-x next-y).
+ (ndx (- next-x x))
+ (ndy (- next-y y))
+ ;; Normalized form.
+ (nmag (sqrt (+ (* ndx ndx) (* ndy ndy))))
+ (nx (if (zero? nmag) 0.0 (/ ndx nmag)))
+ (ny (if (zero? nmag) 0.0 (/ ndy nmag)))
+ ;; Vector from (prev-x, prev-y) to (x, y).
+ (pdx (- x prev-x))
+ (pdy (- y prev-y))
+ ;; Normalized form.
+ (pmag (sqrt (+ (* pdx pdx) (* pdy pdy))))
+ (px (if (zero? pmag) 0.0 (/ pdx pmag)))
+ (py (if (zero? pmag) 0.0 (/ pdy pmag)))
+ ;; Tangent of the 2 vectors.
+ (tanx (+ nx px))
+ (tany (+ ny py))
+ ;; Normalized form.
+ (tanmag (sqrt (+ (* tanx tanx) (* tany tany))))
+ (ntanx (if (zero? tanmag) 0.0 (/ tanx tanmag)))
+ (ntany (if (zero? tanmag) 0.0 (/ tany tanmag)))
+ ;; In order to join 2 line segments
+ ;; together neatly, they must have
+ ;; mitered corners. The miter direction
+ ;; is perpendicular to the tangent of the
+ ;; 2 line segments.
+ (miterx (- ntany))
+ (mitery ntanx)
+ ;; In order to compute the proper length
+ ;; of the miter line, we need to project
+ ;; the miter vector (which is in
+ ;; normalized form) onto the normalized
+ ;; form of a vector for one of the line
+ ;; segments. It doesn't matter which
+ ;; vector is used, so I chose the vector
+ ;; from the current point to the
+ ;; previous.
+ (miter-dot (+ (* miterx (- py)) (* mitery px)))
+ (miter-length (abs (/ padding miter-dot)))
+ ;; The vector that will thicken the line.
+ (padx (* miterx miter-length))
+ (pady (* mitery miter-length))
+ ;; Figure out the extra distance +/- that
+ ;; the mitering has caused so the
+ ;; vertices distance attribute can be
+ ;; adjusted accordingly.
+ (padmag (sqrt (+ (* padx padx) (* pady pady))))
+ ;; (npadx (if (zero? padmag) 0.0 (/ padx padmag)))
+ ;; (npady (if (zero? padmag) 0.0 (/ pady padmag)))
+ (miter-distance (sqrt (- (* padmag padmag) (* padding padding))))
+ ;; Left side
+ (lx (+ x padx))
+ (ly (+ y pady))
+ ;; Right side
+ (rx (- x padx))
+ (ry (- y pady)))
+ (add-points (= j first) lx ly rx ry distance)
+ (point-loop (+ j 1) (+ distance nmag)))))))
+ (when (= j last)
+ ;; Go over the points one more time to set the total
+ ;; length in each vertex.
+ (let length-loop ((k first))
+ (when (<= k last)
+ (set-length k distance)
+ (length-loop (+ k 1)))))))
+ (path-loop (+ i 1))))
+ (dbuffer-unmap! indices)
+ (dbuffer-unmap! vertices)))
;;;
;;; Filled path
;;;
-(define-geometry-type <fill-vertex>
- fill-vertex-ref
- fill-vertex-set!
- fill-vertex-append!
- (position vec2))
+(define-bytestruct <fill-vertex>
+ (struct (position <vec2>)))
+
+;; TODO: There's something broken with padding here. I want mode to
+;; go *before* color but that seems to mess things up.
+(define-bytestruct <fill-uniforms>
+ (struct (mvp <matrix4>)
+ (color <color>)
+ (mode s32)
+ (end-color <color>)
+ (gradient-matrix <matrix3>)
+ (gradient-range <vec2>)
+ (radial-gradient-ratio f32)))
(define-record-type <filled-path>
- (%make-filled-path count quad-geometry stencil-geometry)
+ (%make-filled-path count quad-vertices quad-indices
+ stencil-vertices stencil-indices
+ vertex-bindings bindings)
filled-path?
(blend-mode filled-path-blend-mode set-filled-path-blend-mode!)
(color filled-path-color set-filled-path-color!)
(counts filled-path-counts set-filled-path-counts!)
(offsets filled-path-offsets set-filled-path-offsets!)
(count filled-path-count set-filled-path-count!)
- (stencil-geometry filled-path-stencil-geometry)
- (quad-geometry filled-path-quad-geometry))
+ (quad-vertices filled-path-quad-vertices)
+ (quad-indices filled-path-quad-indices)
+ (stencil-vertices filled-path-stencil-vertices)
+ (stencil-indices filled-path-stencil-indices)
+ (vertex-bindings filled-path-vertex-bindings)
+ (bindings filled-path-bindings))
(define (make-filled-path)
- (let* ((quad-geometry (make-geometry <fill-vertex> 4
- #:index-capacity 6))
- (stencil-geometry (make-geometry <fill-vertex> 32
- #:index? #f
- #:mode 'triangle-fan)))
- (%make-filled-path 0 quad-geometry stencil-geometry)))
+ (%make-filled-path 0
+ (make-buffer (* (bytestruct-sizeof <fill-vertex>) 4)
+ #:name "Filled path quad vertex buffer")
+ (bytevector->buffer (u32vector 0 2 3 0 1 2)
+ #:name "Filled path quad index buffer"
+ #:usage '(index))
+ (make-dbuffer #:name "Filled path stencil vertex buffer")
+ (make-dbuffer #:name "Filled path stencil index buffer"
+ #:usage '(index))
+ (make-vector 1)
+ (vector
+ (make-buffer (bytestruct-sizeof <fill-uniforms>)
+ #:name "Filled path uniform buffer"
+ #:usage '(uniform)))))
(define* (fill-path filled-path compiled-path #:key blend-mode color)
(let* ((points (compiled-path-points compiled-path))
@@ -1164,8 +1226,9 @@
;; a sensible location.
(ref-x (rect-center-x bbox))
(ref-y (rect-center-y bbox))
- (quad-geometry (filled-path-quad-geometry filled-path))
- (stencil-geometry (filled-path-stencil-geometry filled-path)))
+ (quad-vertices (filled-path-quad-vertices filled-path))
+ (stencil-vertices (filled-path-stencil-vertices filled-path))
+ (stencil-indices (filled-path-stencil-indices filled-path)))
;; Setup style.
(set-filled-path-color! filled-path color)
(set-filled-path-blend-mode! filled-path blend-mode)
@@ -1182,7 +1245,10 @@
(u32vector-set! bv i (+ (u32vector-ref offsets i) i)))
(set-filled-path-offsets! filled-path bv))
;; Create geometry for the stencil buffer.
- (geometry-begin! stencil-geometry)
+ ;;
+ ;; TODO: Convert triangle fan to strip because WebGPU does not
+ ;; support triangle fans.
+ (dbuffer-map! stencil-vertices)
(for-range ((i path-count))
(let* ((count (u32vector-ref counts i))
(first (u32vector-ref offsets i)))
@@ -1194,142 +1260,228 @@
;;
;; Add reference point as the basis for each triangle in
;; the fan.
- (fill-vertex-append! stencil-geometry (ref-x ref-y))
+ (dbuffer-append! <fill-vertex>
+ (((position x) ref-x)
+ ((position y) ref-y))
+ stencil-vertices)
;; Now simply copy all the points in the path into the
;; buffer.
(for-range ((j (+ first count) first))
- (fill-vertex-append! stencil-geometry
- ((f32vector-ref points (* j 2))
- (f32vector-ref points (+ (* j 2) 1)))))))
- (geometry-end! stencil-geometry)
+ (let ((j (* j 2)))
+ (dbuffer-append! <fill-vertex>
+ (((position x) (f32vector-ref points j))
+ ((position y) (f32vector-ref points (1+ j))))
+ stencil-vertices)))))
+ (dbuffer-unmap! stencil-vertices)
;; Create simple quad covering the bounding box to be used for the
;; final render pass with stencil applied.
;;
- ;; TODO: A convex hull would result in less fragments to process.
- (geometry-begin! quad-geometry)
+ ;; TODO: Would computing a convex hull be worth it in order to
+ ;; process fewer fragments on the GPU?
(let ((x1 (rect-x bbox))
(y1 (rect-y bbox))
(x2 (rect-right bbox))
- (y2 (rect-top bbox)))
- (fill-vertex-append! quad-geometry (x1 y1) (x2 y1) (x2 y2) (x1 y2))
- (geometry-index-append! quad-geometry 0 2 3 0 1 2))
- (geometry-end! quad-geometry)))
+ (y2 (rect-top bbox))
+ (size (bytestruct-sizeof <fill-vertex>))
+ (bv (map-buffer quad-vertices 'write 0 (buffer-length quad-vertices))))
+ (bytestruct-pack! <fill-vertex>
+ (((position x) x1)
+ ((position y) y1))
+ bv 0)
+ (bytestruct-pack! <fill-vertex>
+ (((position x) x2)
+ ((position y) y1))
+ bv size)
+ (bytestruct-pack! <fill-vertex>
+ (((position x) x2)
+ ((position y) y2))
+ bv (* size 2))
+ (bytestruct-pack! <fill-vertex>
+ (((position x) x1)
+ ((position y) y2))
+ bv (* size 3))
+ (unmap-buffer quad-vertices))))
;;;
;;; Rendering
;;;
+(define (load-file file-name)
+ (call-with-input-file file-name get-string-all))
+
(define-graphics-variable stroke-shader
- (load-shader (scope-datadir "shaders/path-stroke-vert.glsl")
- (scope-datadir "shaders/path-stroke-frag.glsl")))
+ (make-shader
+ (lambda (lang)
+ (values (load-file (scope-datadir "shaders/path-stroke-vert.glsl"))
+ (load-file (scope-datadir "shaders/path-stroke-frag.glsl"))))))
(define-graphics-variable fill-shader
- (load-shader (scope-datadir "shaders/path-fill-vert.glsl")
- (scope-datadir "shaders/path-fill-frag.glsl")))
+ (make-shader
+ (lambda (lang)
+ (values (load-file (scope-datadir "shaders/path-fill-vert.glsl"))
+ (load-file (scope-datadir "shaders/path-fill-frag.glsl"))))))
(define-graphics-variable mvp-matrix (make-null-matrix4))
-(define stencil-flip
- (make-stencil-test #:on-pass 'invert))
-
-(define stencil-cover-and-clear
- (make-stencil-test #:on-fail 'zero #:on-depth-fail 'zero #:on-pass 'zero
- #:function 'not-equal))
-
-(define *debug?* #f)
+(define-graphics-variable fill-stencil-pipeline
+ (make-render-pipeline
+ #:name "Filled path stencil pipeline"
+ #:shader (graphics-variable-ref fill-shader)
+ #:color-target (make-color-target #:mask (make-color-mask #f #f #f #f))
+ #:primitive (make-primitive-mode #:topology 'triangle-fan
+ #:front-face 'cw)
+ #:depth+stencil (make-depth+stencil
+ #:stencil-face (make-stencil-face #:pass-op 'invert))
+ #:vertex-layout (vector (make-vertex-buffer-layout
+ #:stride (bytestruct-sizeof <fill-vertex>)
+ #:attributes (vector
+ (make-vertex-attribute
+ #:format 'float32x2))))
+ #:binding-layout (vector (make-buffer-layout))))
+
+;; TODO: This needs to be part of the <filled-path> struct so we can
+;; build a pipeline with the desired blend mode!
+(define-graphics-variable fill-quad-pipeline
+ (make-render-pipeline
+ #:name "Filled path quad pipeline"
+ #:shader (graphics-variable-ref fill-shader)
+ ;; Anti-alias the edges of the fill with multisampling.
+ #:multisample (make-multisample #:count 4)
+ #:depth+stencil (make-depth+stencil
+ #:stencil-face (make-stencil-face #:compare 'not-equal
+ #:fail-op 'zero
+ #:depth-fail-op 'zero
+ #:pass-op 'zero))
+ #:vertex-layout (vector (make-vertex-buffer-layout
+ #:stride (bytestruct-sizeof <fill-vertex>)
+ #:attributes (vector
+ (make-vertex-attribute
+ #:format 'float32x2))))
+ #:binding-layout (vector (make-buffer-layout))))
(define* (draw-filled-path filled-path matrix)
- (let ((shader (graphics-variable-ref fill-shader))
- (mvp (graphics-variable-ref mvp-matrix))
- (counts (filled-path-counts filled-path))
- (offsets (filled-path-offsets filled-path))
- (n (filled-path-count filled-path))
- (quad-geometry (filled-path-quad-geometry filled-path))
- (stencil-geometry (filled-path-stencil-geometry filled-path)))
- (matrix4-mult! mvp matrix (current-projection))
- ;; Wireframe debug mode.
- (when *debug?*
- (with-graphics-state ((g:polygon-mode line-polygon-mode))
- (for-range ((i n))
- (shader-apply* shader
- (geometry-vertex-array stencil-geometry)
- (u32vector-ref offsets i)
- (u32vector-ref counts i)
- #:mvp (current-projection)))))
- ;; Anti-alias the edges of the fill.
- (with-graphics-state ((g:multisample? #t))
- ;; Render fan to stencil buffer. Each time a triangle is
- ;; rasterized, it flips the values in the stencil buffer for
- ;; those fragments. So, the first time a triangle is rendered,
- ;; it sets the stencil bits for all fragments to 1. Then, when
- ;; an overlapping triangle is rendered, it sets all the stencil
- ;; bits for the overlapped fragments back to 0. This neat hack
- ;; implements the non-zero rule for determining whether or not a
- ;; point is inside a closed path.
- ;;
- ;; For more information, see:
- ;; http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/opengl/gpupathrender.pdf
- (with-graphics-state ((g:color-mask null-color-mask)
- (g:stencil-test stencil-flip))
- (for-range ((i n))
- (shader-apply* shader
- (geometry-vertex-array stencil-geometry)
- (u32vector-ref offsets i)
- (u32vector-ref counts i)
- #:mvp mvp)))
- ;; Render a quad with the stencil applied. The quad is the size
- ;; of the path's bounding box. The stencil test will make it so
- ;; we only draw fragments that are part of the filled path.
- (with-graphics-state ((g:stencil-test stencil-cover-and-clear)
- (g:blend-mode (filled-path-blend-mode filled-path)))
- (let ((color (filled-path-color filled-path)))
- (if (gradient? color)
- ;; Linear/radial gradient fill.
- (shader-apply shader
- (geometry-vertex-array quad-geometry)
- #:mvp mvp
- #:color (gradient-start-color color)
- #:end-color (gradient-end-color color)
- #:gradient-matrix (gradient-matrix color)
- #:gradient-range (gradient-range color)
- #:radial-gradient-ratio (gradient-radial-ratio color)
- #:mode (case (gradient-type color)
- ((linear) 1)
- ((radial) 2)))
- ;; Solid fill.
- (shader-apply shader
- (geometry-vertex-array quad-geometry)
- #:mvp mvp
- #:color (filled-path-color filled-path)
- #:mode 0)))))))
+ (match filled-path
+ (($ <filled-path> blend-mode color counts offsets n
+ quad-vertices quad-indices
+ stencil-vertices stencil-indices vertex-bindings
+ (and bindings #(uniforms)))
+ (let ((stencil-pipeline (graphics-variable-ref fill-stencil-pipeline))
+ (quad-pipeline (graphics-variable-ref fill-quad-pipeline))
+ (mvp (graphics-variable-ref mvp-matrix)))
+ (matrix4-mult! mvp matrix (current-projection))
+ (let ((bv (map-buffer uniforms 'write 0
+ (bytestruct-sizeof <fill-uniforms>))))
+ (bytestruct-pack! <fill-uniforms>
+ (((mvp) mvp))
+ bv 0)
+ (match color
+ ;; Linear/radial gradient fill.
+ (($ <gradient> type matrix start-color end-color range radial-ratio)
+ (bytestruct-pack! <fill-uniforms>
+ (((mode)
+ (match type
+ ('linear 1)
+ ('radial 2)))
+ ((color) start-color)
+ ((end-color) end-color)
+ ((gradient-matrix) matrix)
+ ((gradient-range) range)
+ ((radial-gradient-ratio) radial-ratio))
+ bv 0))
+ ;; Solid fill.
+ (_
+ (bytestruct-pack! <fill-uniforms>
+ (((mode) 0)
+ ((color) color))
+ bv 0)))
+ (unmap-buffer uniforms))
+ ;; Render fan to stencil buffer. Each time a triangle is
+ ;; rasterized, it flips the values in the stencil buffer for
+ ;; those fragments. So, the first time a triangle is rendered,
+ ;; it sets the stencil bits for all fragments to 1. Then, when
+ ;; an overlapping triangle is rendered, it sets all the stencil
+ ;; bits for the overlapped fragments back to 0. This neat hack
+ ;; implements the non-zero rule for determining whether or not a
+ ;; point is inside a closed path.
+ ;;
+ ;; For more information, see:
+ ;; http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/opengl/gpupathrender.pdf
+ (vector-set! vertex-bindings 0 (dbuffer-buffer stencil-vertices))
+ (for-range ((i n))
+ ;; TODO: Use a different shader for this pass because we
+ ;; don't care about color so using the fill shader is just
+ ;; wasted cycles.
+ (draw (u32vector-ref counts i)
+ #:start (u32vector-ref offsets i)
+ #:pipeline stencil-pipeline
+ #:vertex-buffers vertex-bindings
+ ;; #:index-buffer (dbuffer-buffer stencil-indices)
+ #:bindings bindings))
+ ;; Render a quad with the stencil applied. The quad is the size
+ ;; of the path's bounding box. The stencil test will make it so
+ ;; we only draw fragments that are part of the filled path.
+ (vector-set! vertex-bindings 0 quad-vertices)
+ (draw 6
+ #:pipeline quad-pipeline
+ #:vertex-buffers vertex-bindings
+ #:index-buffer quad-indices
+ #:bindings bindings)))))
+
+;; TODO: Make part of <stroked-path> to accomodate blend mode.
+(define-graphics-variable stroke-pipeline
+ (make-render-pipeline
+ #:name "Stroked path pipeline"
+ #:shader (graphics-variable-ref stroke-shader)
+ #:color-target (make-color-target #:blend-mode blend:alpha)
+ #:vertex-layout (vector (make-vertex-buffer-layout
+ #:stride (bytestruct-sizeof <stroke-vertex>)
+ #:attributes (vector
+ (make-vertex-attribute
+ #:format 'float32x2)
+ (make-vertex-attribute
+ #:format 'float32x2
+ #:offset (* 2 4))
+ (make-vertex-attribute
+ #:format 'float32
+ #:offset (* 4 4)))))
+ #:binding-layout (vector (make-buffer-layout))))
;; TODO: dashed stroke
;; TODO: miter styles and miter limit
(define* (draw-stroked-path stroked-path matrix)
- (let ((shader (graphics-variable-ref stroke-shader))
- (mvp (graphics-variable-ref mvp-matrix)))
- (matrix4-mult! mvp matrix (current-projection))
- (with-graphics-state ((g:blend-mode (stroked-path-blend-mode stroked-path)))
- (let ((geometry (stroked-path-geometry stroked-path)))
- (shader-apply* shader
- (geometry-vertex-array geometry)
- 0
- (geometry-index-count geometry)
- #:mvp mvp
- #:color (stroked-path-color stroked-path)
- #:feather (stroked-path-feather stroked-path)
- #:stroke-cap (case (stroked-path-cap stroked-path)
- ((#f) 0) ; no cap
- ((butt) 1)
- ((square) 2)
- ((round) 3)
- ((triangle-out) 4)
- ((triangle-in) 5)
- (else
- (error "unsupported line cap style"
- (stroked-path-cap stroked-path))))
- #:stroke-width (stroked-path-width stroked-path))))))
+ (match stroked-path
+ (($ <stroked-path> blend-mode color width feather cap vertices indices
+ vertex-bindings (and bindings #(uniforms)))
+ (let ((mvp (graphics-variable-ref mvp-matrix))
+ (pipeline (graphics-variable-ref stroke-pipeline)))
+ (matrix4-mult! mvp matrix (current-projection))
+ (let ((bv (map-buffer uniforms 'write 0
+ (bytestruct-sizeof <stroke-uniforms>))))
+ (bytestruct-pack! <stroke-uniforms>
+ (((mvp) mvp)
+ ((color) color)
+ ((feather) feather)
+ ((stroke-closed?) 0)
+ ((stroke-width) width)
+ ((stroke-cap)
+ (match cap
+ (#f 0) ; no cap
+ ('butt 1)
+ ('square 2)
+ ('round 3)
+ ('triangle-out 4)
+ ('triangle-in 5)))
+ ((stroke-miter-style) 0)
+ ((stroke-miter-limit) 0))
+ bv 0)
+ (unmap-buffer uniforms))
+ (vector-set! vertex-bindings 0 (dbuffer-buffer vertices))
+ (draw (/ (dbuffer-length indices) 4)
+ #:pipeline pipeline
+ #:index-buffer (dbuffer-buffer indices)
+ #:vertex-buffers vertex-bindings
+ #:bindings bindings)))))
;;;
@@ -1638,18 +1790,18 @@
(define (draw-canvas canvas)
(draw-canvas* canvas %identity-matrix))
-(define (canvas->pixbuf canvas)
- "Return a new pixbuf containing the rasterized CANVAS."
- (let* ((bb (painter-bounding-box (canvas-painter canvas)))
- (width (inexact->exact (ceiling (rect-width bb))))
- (height (inexact->exact (ceiling (rect-height bb))))
- (framebuffer (make-framebuffer width height)))
- (with-framebuffer framebuffer
- (draw-canvas* canvas (make-identity-matrix4)))
- (texture->pixbuf (framebuffer-texture framebuffer))))
-
-(define* (write-canvas canvas
- #:optional (file-name (temp-image-file-name 'png))
- #:key (format 'png))
- "Write CANVAS to FILE-NAME using FORMAT ('png' by default.)"
- (write-image (canvas->pixbuf canvas) file-name #:format format))
+;; (define (canvas->pixbuf canvas)
+;; "Return a new pixbuf containing the rasterized CANVAS."
+;; (let* ((bb (painter-bounding-box (canvas-painter canvas)))
+;; (width (inexact->exact (ceiling (rect-width bb))))
+;; (height (inexact->exact (ceiling (rect-height bb))))
+;; (framebuffer (make-framebuffer width height)))
+;; (with-framebuffer framebuffer
+;; (draw-canvas* canvas (make-identity-matrix4)))
+;; (texture->pixbuf (framebuffer-texture framebuffer))))
+
+;; (define* (write-canvas canvas
+;; #:optional (file-name (temp-image-file-name 'png))
+;; #:key (format 'png))
+;; "Write CANVAS to FILE-NAME using FORMAT ('png' by default.)"
+;; (write-image (canvas->pixbuf canvas) file-name #:format format))