From a9671d07fd97dd4fe010647c84bc2e6a87d6687c Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 6 Jan 2019 22:50:45 -0500 Subject: render: sprite: Revamp rendering process. Add support for tinting and simplify batch rendering. --- chickadee/render/sprite.scm | 264 ++++++++++++++++++++++++-------------------- doc/api.texi | 13 +-- 2 files changed, 152 insertions(+), 125 deletions(-) diff --git a/chickadee/render/sprite.scm b/chickadee/render/sprite.scm index c9a777f..6b6cf2a 100644 --- a/chickadee/render/sprite.scm +++ b/chickadee/render/sprite.scm @@ -24,6 +24,7 @@ #:use-module (chickadee math rect) #:use-module (chickadee math vector) #:use-module (chickadee render) + #:use-module (chickadee render color) #:use-module (chickadee render shader) #:use-module (chickadee render texture) #:use-module (chickadee render buffer) @@ -33,7 +34,7 @@ draw-nine-patch* draw-nine-patch)) -(define default-shader +(define unbatched-sprite-shader (delay (strings->shader " @@ -54,9 +55,10 @@ void main(void) { in vec2 fragTex; uniform sampler2D colorTexture; +uniform vec4 tint; void main (void) { - gl_FragColor = texture2D(colorTexture, fragTex); + gl_FragColor = texture2D(colorTexture, fragTex) * tint; } "))) @@ -95,7 +97,7 @@ void main (void) { `((0 . ,(force pos)) (1 . ,(force tex)))))) (mvp (make-null-matrix4))) - (lambda (texture region world-matrix blend-mode shader texture-region) + (lambda (texture region world-matrix blend-mode texture-region tint) (with-mapped-typed-buffer (force pos) (let* ((x1 (rect-x region)) (y1 (rect-y region)) @@ -126,7 +128,8 @@ void main (void) { (f32vector-set! bv 15 t1))) (with-blend-mode blend-mode (with-texture 0 texture - (gpu-apply shader (force vertex-array) + (gpu-apply (force unbatched-sprite-shader) (force vertex-array) + #:tint tint #:mvp (if world-matrix (begin (matrix4-mult! mvp world-matrix @@ -140,12 +143,11 @@ void main (void) { ;;; (define-record-type - (%make-sprite-batch texture blend-mode shader size capacity index-buffer + (%make-sprite-batch texture blend-mode size capacity index-buffer position-buffer texture-buffer vertex-array) sprite-batch? (texture sprite-batch-texture set-sprite-batch-texture!) (blend-mode sprite-batch-blend-mode set-sprite-batch-blend-mode!) - (shader sprite-batch-shader set-sprite-batch-shader!) (size sprite-batch-size set-sprite-batch-size!) (capacity sprite-batch-capacity set-sprite-batch-capacity!) (index-buffer sprite-batch-index-buffer set-sprite-batch-index-buffer!) @@ -158,7 +160,7 @@ void main (void) { 'unsigned-int (* capacity 6) #:target 'index)) - (stride 16) ; 4 f32s, 2 for vertex, 2 for texcoord + (stride 32) ; 8 f32s, 2 for vertex, 2 for texcoord, 4 for tint color (buffer (make-buffer #f #:name "sprite batch buffer" #:length (* capacity stride 4) @@ -174,9 +176,17 @@ void main (void) { #:type 'vec2 #:component-type 'float #:length (* capacity 4) - #:offset (/ stride 2))) + #:offset 8)) + (tint (make-typed-buffer #:name "batched-sprite-tint" + #:buffer buffer + #:type 'vec4 + #:component-type 'float + #:length (* capacity 4) + #:offset 16)) (va (make-vertex-array #:indices index - #:attributes `((0 . ,pos) (1 . ,tex))))) + #:attributes `((0 . ,pos) + (1 . ,tex) + (2 . ,tint))))) (set-sprite-batch-capacity! batch capacity) (set-sprite-batch-index-buffer! batch index) (set-sprite-batch-position-buffer! batch pos) @@ -185,7 +195,7 @@ void main (void) { (define (make-sprite-batch capacity) "Make a sprite batch that can hold CAPACITY sprites." - (let ((batch (%make-sprite-batch #f #f #f 0 0 #f #f #f #f))) + (let ((batch (%make-sprite-batch #f #f 0 0 #f #f #f #f))) (init-sprite-batch batch capacity) batch)) @@ -214,13 +224,43 @@ void main (void) { "Reset BATCH to size 0." (set-sprite-batch-texture! batch #f) (set-sprite-batch-blend-mode! batch #f) - (set-sprite-batch-shader! batch #f) (set-sprite-batch-size! batch 0)) (define (sprite-batch-begin! batch) (map-typed-buffer! (sprite-batch-index-buffer batch)) (map-typed-buffer! (sprite-batch-position-buffer batch))) +(define batched-sprite-shader + (delay + (strings->shader + " +#version 130 + +in vec2 position; +in vec2 tex; +in vec4 tint; +out vec2 fragTex; +out vec4 fragTint; +uniform mat4 mvp; + +void main(void) { + fragTex = tex; + fragTint = tint; + gl_Position = mvp * vec4(position.xy, 0.0, 1.0); +} +" + " +#version 130 + +in vec2 fragTex; +in vec4 fragTint; +uniform sampler2D colorTexture; + +void main (void) { + gl_FragColor = texture2D(colorTexture, fragTex) * fragTint; +} +"))) + (define (sprite-batch-flush! batch) "Render the contents of BATCH and clear the cache." (unless (zero? (sprite-batch-size batch)) @@ -228,103 +268,95 @@ void main (void) { (with-texture 0 (sprite-batch-texture batch) (unmap-typed-buffer! (sprite-batch-index-buffer batch)) (unmap-typed-buffer! (sprite-batch-position-buffer batch)) - (gpu-apply* (sprite-batch-shader batch) + (gpu-apply* (force batched-sprite-shader) (sprite-batch-vertex-array batch) (* (sprite-batch-size batch) 6) #:mvp (current-projection)) (sprite-batch-reset! batch))))) -(define sprite-batch-add! - (let ((world1 (vec2 0.0 0.0)) - (world2 (vec2 0.0 0.0)) - (world3 (vec2 0.0 0.0)) - (world4 (vec2 0.0 0.0)) - (offset-bv (make-u32vector 1))) - (define (set-offset offset) - (u32vector-set! offset-bv 0 offset)) - (define (offset) - (u32vector-ref offset-bv 0)) - (lambda (batch texture region world-matrix blend-mode - shader texture-region) - ;; Expand the buffers when necessary. - (when (sprite-batch-full? batch) - (double-sprite-batch-size! batch)) - ;; Flush the batch if any GL state needs changing. - (unless (and (eq? (sprite-batch-texture batch) texture) - (eq? (sprite-batch-blend-mode batch) blend-mode) - (eq? (sprite-batch-shader batch) shader)) - (sprite-batch-flush! batch) - (sprite-batch-begin! batch) - (set-sprite-batch-texture! batch texture) - (set-sprite-batch-blend-mode! batch blend-mode) - (set-sprite-batch-shader! batch shader)) - (let ((size (sprite-batch-size batch))) - (let* ((indices (typed-buffer-data (sprite-batch-index-buffer batch))) - (vertices (typed-buffer-data (sprite-batch-position-buffer batch))) - (texcoords (typed-buffer-data (sprite-batch-texture-buffer batch))) - (local-x1 (rect-x region)) - (local-y1 (rect-y region)) - (local-x2 (+ local-x1 (rect-width region))) - (local-y2 (+ local-y1 (rect-height region))) - (s1 (rect-x texture-region)) - (t1 (rect-y texture-region)) - (s2 (+ (rect-x texture-region) (rect-width texture-region))) - (t2 (+ (rect-y texture-region) (rect-height texture-region)))) - (set-vec2! world1 local-x1 local-y1) - (set-vec2! world2 local-x2 local-y1) - (set-vec2! world3 local-x2 local-y2) - (set-vec2! world4 local-x1 local-y2) - (when world-matrix - (transform! world-matrix world1) - (transform! world-matrix world2) - (transform! world-matrix world3) - (transform! world-matrix world4)) - ;; Add indices. - (set-offset (* size 4)) - (let ((index-vertex-offset (offset))) - (set-offset (* size 6)) - (u32vector-set! indices (offset) index-vertex-offset) - (u32vector-set! indices (+ (offset) 1) (+ index-vertex-offset 3)) - (u32vector-set! indices (+ (offset) 2) (+ index-vertex-offset 2)) - (u32vector-set! indices (+ (offset) 3) index-vertex-offset) - (u32vector-set! indices (+ (offset) 4) (+ index-vertex-offset 2)) - (u32vector-set! indices (+ (offset) 5) (+ index-vertex-offset 1))) - ;; Add vertices. - (set-offset (* size 16)) - ;; Bottom-left - (f32vector-set! vertices (offset) (vec2-x world1)) - (f32vector-set! vertices (+ (offset) 1) (vec2-y world1)) - ;; Bottom-right - (f32vector-set! vertices (+ (offset) 4) (vec2-x world2)) - (f32vector-set! vertices (+ (offset) 5) (vec2-y world2)) - ;; Top-right - (f32vector-set! vertices (+ (offset) 8) (vec2-x world3)) - (f32vector-set! vertices (+ (offset) 9) (vec2-y world3)) - ;; Top-left - (f32vector-set! vertices (+ (offset) 12) (vec2-x world4)) - (f32vector-set! vertices (+ (offset) 13) (vec2-y world4)) - ;; Add texture coordinates. - ;; Bottom-left - (f32vector-set! texcoords (+ (offset) 2) s1) - (f32vector-set! texcoords (+ (offset) 3) t2) - ;; Bottom-right - (f32vector-set! texcoords (+ (offset) 6) s2) - (f32vector-set! texcoords (+ (offset) 7) t2) - ;; Top-right - (f32vector-set! texcoords (+ (offset) 10) s2) - (f32vector-set! texcoords (+ (offset) 11) t1) - ;; Top-left - (f32vector-set! texcoords (+ (offset) 14) s1) - (f32vector-set! texcoords (+ (offset) 15) t1) - (set-sprite-batch-size! batch (1+ size))))))) +(define (sprite-batch-add! batch texture region world-matrix blend-mode + texture-region tint) + ;; Expand the buffers when necessary. + (when (sprite-batch-full? batch) + (double-sprite-batch-size! batch)) + ;; Flush the batch if any GL state needs changing. + (unless (and (eq? (sprite-batch-texture batch) texture) + (eq? (sprite-batch-blend-mode batch) blend-mode)) + (sprite-batch-flush! batch) + (sprite-batch-begin! batch) + (set-sprite-batch-texture! batch texture) + (set-sprite-batch-blend-mode! batch blend-mode)) + (let ((size (sprite-batch-size batch))) + (let* ((indices (typed-buffer-data (sprite-batch-index-buffer batch))) + (vertices (typed-buffer-data (sprite-batch-position-buffer batch))) + (index-offset (* size 6)) + (offset (* size 32)) + (minx (rect-x region)) + (miny (rect-y region)) + (maxx (+ minx (rect-width region))) + (maxy (+ miny (rect-height region))) + (x1 (transform-x world-matrix minx miny)) + (y1 (transform-y world-matrix minx miny)) + (x2 (transform-x world-matrix maxx miny)) + (y2 (transform-y world-matrix maxx miny)) + (x3 (transform-x world-matrix maxx maxy)) + (y3 (transform-y world-matrix maxx maxy)) + (x4 (transform-x world-matrix minx maxy)) + (y4 (transform-y world-matrix minx maxy)) + (s1 (rect-x texture-region)) + (t1 (rect-y texture-region)) + (s2 (+ (rect-x texture-region) (rect-width texture-region))) + (t2 (+ (rect-y texture-region) (rect-height texture-region)))) + ;; Add indices. + (let ((index-vertex-offset (* size 4))) + (u32vector-set! indices index-offset index-vertex-offset) + (u32vector-set! indices (+ index-offset 1) (+ index-vertex-offset 3)) + (u32vector-set! indices (+ index-offset 2) (+ index-vertex-offset 2)) + (u32vector-set! indices (+ index-offset 3) index-vertex-offset) + (u32vector-set! indices (+ index-offset 4) (+ index-vertex-offset 2)) + (u32vector-set! indices (+ index-offset 5) (+ index-vertex-offset 1))) + ;; Add vertices. + ;; Bottom-left + (f32vector-set! vertices offset x1) + (f32vector-set! vertices (+ offset 1) y1) + ;; Bottom-right + (f32vector-set! vertices (+ offset 8) x2) + (f32vector-set! vertices (+ offset 9) y2) + ;; Top-right + (f32vector-set! vertices (+ offset 16) x3) + (f32vector-set! vertices (+ offset 17) y3) + ;; Top-left + (f32vector-set! vertices (+ offset 24) x4) + (f32vector-set! vertices (+ offset 25) y4) + ;; Add texture coordinates. + ;; Bottom-left + (f32vector-set! vertices (+ offset 2) s1) + (f32vector-set! vertices (+ offset 3) t2) + ;; Bottom-right + (f32vector-set! vertices (+ offset 10) s2) + (f32vector-set! vertices (+ offset 11) t2) + ;; Top-right + (f32vector-set! vertices (+ offset 18) s2) + (f32vector-set! vertices (+ offset 19) t1) + ;; Top-left + (f32vector-set! vertices (+ offset 26) s1) + (f32vector-set! vertices (+ offset 27) t1) + ;; Add tint. + (let ((bv ((@@ (chickadee render color) unwrap-color) tint)) + (byte-offset (* offset 4))) + (bytevector-copy! bv 0 vertices (+ byte-offset 16) 16) + (bytevector-copy! bv 0 vertices (+ byte-offset 48) 16) + (bytevector-copy! bv 0 vertices (+ byte-offset 80) 16) + (bytevector-copy! bv 0 vertices (+ byte-offset 112) 16)) + (set-sprite-batch-size! batch (1+ size))))) (define *batch?* #f) (define %batch (delay (make-sprite-batch 256))) -(define (draw-sprite-batched texture region world-matrix blend-mode shader - texture-region) +(define (draw-sprite-batched texture region world-matrix blend-mode + texture-region tint) (sprite-batch-add! (force %batch) texture region world-matrix blend-mode - shader texture-region)) + texture-region tint)) (define-syntax-rule (with-batched-sprites body ...) "Use batched rendering for all draw-sprite calls within BODY." @@ -341,14 +373,14 @@ void main (void) { (set! *batch?* #f))))) (define* (draw-sprite* texture rect matrix #:key + (tint white) (blend-mode 'alpha) - (texcoords (texture-gl-tex-rect texture)) - (shader (force default-shader))) + (texcoords (texture-gl-tex-rect texture))) (if *batch?* (draw-sprite-batched texture rect matrix blend-mode - shader texcoords) + texcoords tint) (draw-sprite-unbatched texture rect matrix blend-mode - shader texcoords))) + texcoords tint))) (define %null-vec2 (vec2 0.0 0.0)) (define %default-scale (vec2 1.0 1.0)) @@ -358,12 +390,12 @@ void main (void) { (lambda* (texture position #:key + (tint white) (origin %null-vec2) (scale %default-scale) (rotation 0.0) (blend-mode 'alpha) - (rect (texture-gl-rect texture)) - (shader (force default-shader))) + (rect (texture-gl-rect texture))) "Draw TEXTURE at POSITION. Optionally, other transformations may be applied to the sprite. @@ -371,19 +403,19 @@ ROTATION specifies the angle to rotate the sprite, in radians. SCALE specifies the scaling factor as a 2D vector. All transformations are applied relative to ORIGIN, a 2D vector. -By default, alpha blending is used but can be changed by specifying -BLEND-MODE. +TINT specifies the color to multiply against all the sprite's pixels. +By default white is used, which does no tinting at all. -Advanced users may pass SHADER to change the way the sprite is -rendered entirely." +By default, alpha blending is used but can be changed by specifying +BLEND-MODE." (matrix4-2d-transform! matrix #:origin origin #:position position #:rotation rotation #:scale scale) (draw-sprite* texture rect matrix - #:blend-mode blend-mode - #:shader shader)))) + #:tint tint + #:blend-mode blend-mode)))) ;;; @@ -402,8 +434,7 @@ rendered entirely." (bottom-margin margin) (left-margin margin) (right-margin margin) - (blend-mode 'alpha) - (shader (force default-shader))) + (blend-mode 'alpha)) (let* ((x (rect-x rect)) (y (rect-y rect)) (w (rect-width rect)) @@ -439,8 +470,7 @@ rendered entirely." (set-rect-height! texcoords (- t2 t1)) (draw-sprite* texture %rect matrix #:texcoords texcoords - #:blend-mode blend-mode - #:shader shader)) + #:blend-mode blend-mode)) (with-batched-sprites ;; bottom-left (draw-piece border-x1 border-y1 fill-x1 fill-y1 @@ -483,8 +513,7 @@ rendered entirely." (origin %null-vec2) (rotation 0.0) (scale %default-scale) - (blend-mode 'alpha) - (shader (force default-shader))) + (blend-mode 'alpha)) "Draw a \"nine patch\" sprite. A nine patch sprite renders TEXTURE on the rectangular area RECT whose stretchable areas are defined by the given margin measurements. The corners are never @@ -513,5 +542,4 @@ LEFT-MARGIN, and RIGHT-MARGIN arguments may be used." #:bottom-margin bottom-margin #:left-margin left-margin #:right-margin right-margin - #:blend-mode blend-mode - #:shader shader)))) + #:blend-mode blend-mode)))) diff --git a/doc/api.texi b/doc/api.texi index b1881bd..fc1f8dd 100644 --- a/doc/api.texi +++ b/doc/api.texi @@ -1334,8 +1334,8 @@ stored in textures (@pxref{Textures}) and can be used to draw sprites via the @code{draw-sprite} procedure. @deffn {Procedure} draw-sprite @var{texture} @var{position} @ - [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] @ - [#:rect] [#:shader] + [#:tint white] [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] @ + [#:rect] Draw @var{texture} at @var{position}. @@ -1345,14 +1345,14 @@ Optionally, other transformations may be applied to the sprite. transformations are applied relative to @var{origin}, a 2D vector, which defaults to the lower-left corner. +@var{tint} specifies the color to multiply against all the sprite's +pixels. By default white is used, which does no tinting at all. + Alpha blending is used by default but the blending method can be changed by specifying @var{blend-mode}. The area drawn to is as big as the texture, by default. To draw to an arbitrary section of the screen, specify @var{rect}. - -Finally, advanced users may specify @var{shader} to change the way the -sprite is rendered entirely. @end deffn It's not uncommon to need to draw hundreds or thousands of sprites @@ -1411,8 +1411,7 @@ artifacts. @deffn {Procedure} draw-nine-patch @var{texture} @var{rect} @ [#:margin 0] [#:top-margin margin] [#:bottom-margin margin] @ [#:left-margin margin] [#:right-margin margin] @ - [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] @ - [#:shader] + [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] Draw a nine patch sprite. A nine patch sprite renders @var{texture} as a @var{width} x @var{height} rectangle whose stretchable areas are -- cgit v1.2.3