summaryrefslogtreecommitdiff
path: root/chickadee/graphics/9-patch.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/9-patch.scm')
-rw-r--r--chickadee/graphics/9-patch.scm303
1 files changed, 213 insertions, 90 deletions
diff --git a/chickadee/graphics/9-patch.scm b/chickadee/graphics/9-patch.scm
index c023b25..dc5f0e7 100644
--- a/chickadee/graphics/9-patch.scm
+++ b/chickadee/graphics/9-patch.scm
@@ -1,5 +1,5 @@
;;; Chickadee Game Toolkit
-;;; Copyright © 2021 David Thompson <dthompson2@worcester.edu>
+;;; Copyright © 2021, 2024 David Thompson <dthompson2@worcester.edu>
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
@@ -14,74 +14,136 @@
;;; limitations under the License.
(define-module (chickadee graphics 9-patch)
- #:use-module (ice-9 match)
- #:use-module (chickadee math matrix)
- #:use-module (chickadee math rect)
- #:use-module (chickadee math vector)
- #: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 pipeline)
#:use-module (chickadee graphics shader)
+ #:use-module (chickadee graphics sprite)
#:use-module (chickadee graphics texture)
- #:use-module (chickadee graphics buffer)
+ #:use-module (chickadee math matrix)
+ #:use-module (chickadee math rect)
+ #:use-module (chickadee math vector)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-9)
#:export (draw-9-patch*
draw-9-patch))
-(define-geometry-type <9-patch-vertex>
- 9-patch-vertex-ref
- 9-patch-vertex-set!
- 9-patch-vertex-append!
- (position vec2)
- (distance vec2))
-
-(define-graphics-variable 9-patch-geometry
- (make-geometry <9-patch-vertex> 4 #:index-capacity 6))
-(define-graphics-variable 9-patch-model-matrix (make-null-matrix4))
-(define-graphics-variable 9-patch-mvp-matrix (make-null-matrix4))
-(define-graphics-variable 9-patch-margins (make-null-rect))
-(define-graphics-variable 9-patch-shader
- (strings->shader
- "
+(define-bytestruct <9-patch-vertex>
+ (struct (position <vec2>)
+ (distance <vec2>)
+ (color <color>)))
+
+(define-bytestruct <9-patch-uniforms>
+ (struct (matrix <matrix4>)
+ (uv <rect>)
+ (margins <rect>)
+ (width f32)
+ (height f32)
+ (mode s32)))
+
+(define-record-type <9-patch-state>
+ (make-9-patch-state shader uniforms sampler margins bindings
+ color-target-cache)
+ 9-patch-state?
+ (shader 9-patch-state-shader)
+ (uniforms 9-patch-state-uniforms)
+ (sampler 9-patch-state-sampler)
+ (margins 9-patch-state-margins)
+ (bindings 9-patch-state-bindings)
+ (prev-sprite 9-patch-state-prev-sprite)
+ (color-target-cache 9-patch-state-color-target-cache))
+
+;; TODO: This same type of cache is also in the sprite module.
+;; Probably the streaming API should provide a central cache for these
+;; things instead.
+(define (9-patch-color-target state blend-mode)
+ (let ((cache (9-patch-state-color-target-cache state)))
+ (or (hashq-ref cache blend-mode)
+ (let ((color-target (make-color-target #:blend-mode blend-mode)))
+ (hashq-set! cache blend-mode color-target)
+ color-target))))
+
+(define-graphics-variable 9-patch-state
+ (make-9-patch-state
+ (make-shader
+ (lambda (lang)
+ (values "
#ifdef GLSL330
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 distance;
+layout (location = 2) in vec4 tint;
#elif defined(GLSL130)
in vec2 position;
in vec2 distance;
+in vec4 tint;
#elif defined(GLSL120)
attribute vec2 position;
attribute vec2 distance;
+attribtue vec4 tint;
#endif
#ifdef GLSL120
varying vec2 fragDistance;
+varying vec4 fragTint;
#else
out vec2 fragDistance;
+out vec4 fragTint;
+#endif
+#ifdef GLSL120
+uniform mat4 matrix;
+#else
+layout (std140) uniform NinePatch {
+ mat4 matrix;
+ vec4 subtexture;
+ vec4 margins;
+ float width;
+ float height;
+ int mode;
+};
#endif
-uniform mat4 mvp;
void main(void) {
fragDistance = distance;
- gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
+ fragTint = tint;
+ gl_Position = matrix * vec4(position.xy, 0.0, 1.0);
}
-"
- "
+" "
#ifdef GLSL120
varying vec2 fragDistance;
+varying vec4 fragTint;
#else
in vec2 fragDistance;
+in vec4 fragTint;
#endif
#ifdef GLSL330
out vec4 fragColor;
+#else
+#define fragColor gl_FragColor
+#define texture texture2D
#endif
+#ifdef GLSL120
uniform vec4 subtexture;
uniform vec4 margins;
uniform float width;
uniform float height;
-uniform sampler2D colorTexture;
uniform vec4 tint;
uniform int mode;
+uniform sampler2D colorTexture;
+#else
+layout (std140) uniform NinePatch {
+ mat4 matrix;
+ vec4 subtexture;
+ vec4 margins;
+ float width;
+ float height;
+ int mode;
+};
+uniform sampler2D colorTexture;
+#endif
float patch(float d, float m0, float m1, float length, float texLength) {
+ int mode = 0;
if(d <= m0) { // inside the left/bottom margin.
return d * texLength;
} else if(d >= length - m1) { // inside the right/top margin.
@@ -97,18 +159,36 @@ void main (void) {
vec2 texcoord = subtexture.xy;
texcoord.x += patch(fragDistance.x, margins.x, margins.y, width, subtexture.z);
texcoord.y += patch(fragDistance.y, margins.z, margins.w, height, subtexture.w);
-
-#ifdef GLSL330
- fragColor = texture(colorTexture, texcoord) * tint;
-#else
- gl_FragColor = texture2D(colorTexture, texcoord) * tint;
-#endif
+ fragColor = texture(colorTexture, texcoord) * fragTint;
}
-"))
+")))
+ (make-buffer (* 16 4)
+ #:name "9-patch uniform buffer"
+ #:usage '(uniform))
+ (make-sampler #:name "9-patch sampler")
+ (make-null-rect)
+ (make-vector 3 #f)
+ (make-hash-table)))
+
+(define %9-patch-vertex-layout
+ (vector (make-vertex-buffer-layout
+ #:stride (* 8 4)
+ #:attributes (vector
+ (make-vertex-attribute ; position
+ #:format 'float32x2)
+ (make-vertex-attribute ; distance
+ #:format 'float32x2
+ #:offset (* 2 4))
+ (make-vertex-attribute ; color
+ #:format 'float32x4
+ #:offset (* 4 4))))))
+
+(define %9-patch-binding-layout
+ (vector (make-texture-layout)
+ (make-sampler-layout)
+ (make-buffer-layout)))
-(define* (draw-9-patch* texture
- rect
- matrix
+(define* (draw-9-patch* sprite rect matrix
#:key
(margin 0.0)
(top-margin margin)
@@ -117,58 +197,101 @@ void main (void) {
(right-margin margin)
(mode 'stretch)
(tint white)
- (blend-mode blend:alpha)
- (texcoords (texture-gl-tex-rect texture)))
- (let ((shader (graphics-variable-ref 9-patch-shader))
- (geometry (graphics-variable-ref 9-patch-geometry))
- (mvp (graphics-variable-ref 9-patch-mvp-matrix))
- (margins (graphics-variable-ref 9-patch-margins)))
- (let* ((w (rect-width rect))
- (h (rect-height rect))
- (tex-rect (texture-gl-rect texture))
- (tw (rect-width tex-rect))
- (th (rect-height tex-rect))
- ;; Convert pixel coordinates to GL texture coordinates.
- (w* (/ w tw))
- (h* (/ h th)))
- (with-geometry geometry
- (let* ((x1 (rect-x rect))
- (y1 (rect-y rect))
- (x2 (+ x1 w))
- (y2 (+ y1 h))
- (s1 0.0)
- (t1 0.0)
- (s2 w*)
- (t2 h*))
- (9-patch-vertex-append! geometry
- (x1 y1 s1 t1)
- (x2 y1 s2 t1)
- (x2 y2 s2 t2)
- (x1 y2 s1 t2))
- (geometry-index-append! geometry 0 3 2 0 2 1)))
- ;; Convert pixel margin values to GL texture values.
- (set-rect-x! margins (/ left-margin tw))
- (set-rect-y! margins (/ right-margin tw))
- (set-rect-width! margins (/ bottom-margin th))
- (set-rect-height! margins (/ top-margin th))
- (with-graphics-state ((g:blend-mode blend-mode)
- (g:texture-0 texture))
- (shader-apply shader
- (geometry-vertex-array geometry)
- #:width w*
- #:height h*
- #:subtexture texcoords
- #:margins margins
- #:mode (match mode
- ('stretch 0)
- ('tile 1))
- #:tint tint
- #:mvp (if matrix
- (begin
- (matrix4-mult! mvp matrix
- (current-projection))
- mvp)
- (current-projection)))))))
+ (blend-mode blend:alpha))
+ (match (graphics-variable-ref 9-patch-state)
+ ((and state ($ <9-patch-state> shader uniforms sampler margins bindings
+ prev-sprite))
+ (let* ((w (rect-width rect))
+ (h (rect-height rect))
+ (minx (rect-x rect))
+ (miny (rect-y rect))
+ (maxx (+ minx w))
+ (maxy (+ miny h))
+ (x1 (matrix4-transform-x matrix minx miny))
+ (y1 (matrix4-transform-y matrix minx miny))
+ (x2 (matrix4-transform-x matrix maxx miny))
+ (y2 (matrix4-transform-y matrix maxx miny))
+ (x3 (matrix4-transform-x matrix maxx maxy))
+ (y3 (matrix4-transform-y matrix maxx maxy))
+ (x4 (matrix4-transform-x matrix minx maxy))
+ (y4 (matrix4-transform-y matrix minx maxy))
+ (tex-rect (sprite-rect sprite))
+ (tw (rect-width tex-rect))
+ (th (rect-height tex-rect))
+ ;; Convert pixel coordinates to UV coordinates.
+ (w* (/ w tw))
+ (h* (/ h th)))
+ ;; Convert pixel margin values to UV values.
+ (set-rect-x! margins (/ left-margin tw))
+ (set-rect-y! margins (/ right-margin tw))
+ (set-rect-width! margins (/ bottom-margin th))
+ (set-rect-height! margins (/ top-margin th))
+ (vector-set! bindings 0 (sprite-texture-view sprite))
+ (vector-set! bindings 1 sampler)
+ (vector-set! bindings 2 uniforms)
+ ;; Flush stream if sprite is different than the previous call.
+ ;; This could be a no-op if the previous stream draw call was
+ ;; for something other than a 9-patch, or if the previous call
+ ;; was last frame.
+ ;;
+ ;; TODO: Redesign this so we don't flush if the texture view
+ ;; hasn't changed. We should be able to support many different
+ ;; 9-patches packed into the same texture that can be drawn in
+ ;; a batch.
+ (unless (eq? sprite prev-sprite)
+ (flush-stream))
+ (call-with-values
+ (lambda ()
+ (stream-draw #:count 4
+ #:shader shader
+ #:color-target (9-patch-color-target state blend-mode)
+ #:vertex-layout %9-patch-vertex-layout
+ #:binding-layout %9-patch-binding-layout
+ #:bindings bindings))
+ (lambda (vertices indices i)
+ ;; TODO: Figure out how to include uniform buffer init in
+ ;; stream API.
+ (when (eq? i 0)
+ (let ((bv (map-buffer uniforms 'write 0
+ (bytestruct-sizeof <9-patch-uniforms>))))
+ (bytestruct-pack! <9-patch-uniforms>
+ (((matrix) (current-projection))
+ ((uv) (sprite-rect-uv sprite))
+ ((margins) margins)
+ ((width) w*)
+ ((height) h*)
+ ((mode) (match mode
+ ('stretch 0)
+ ('tile 1))))
+ bv 0)))
+ (let* ((u1 0.0)
+ (v1 0.0)
+ (u2 w*)
+ (v2 h*)
+ (r (color-r tint))
+ (g (color-g tint))
+ (b (color-b tint))
+ (a (color-a tint))
+ (vsize (bytestruct-sizeof <9-patch-vertex>))
+ (voffset (dbuffer-reserve! vertices (* vsize 4)))
+ (ioffset (dbuffer-reserve! indices (* 6 4))))
+ (define-syntax-rule (set-vertex! j px py dx dy cr cg cb ca)
+ (dbuffer-pack! <9-patch-vertex>
+ (((position x) px)
+ ((position y) py)
+ ((distance x) dx)
+ ((distance y) dy)
+ ((color r) cr)
+ ((color g) cg)
+ ((color b) cb)
+ ((color a) ca))
+ vertices
+ (+ voffset (* j vsize))))
+ (set-vertex! 0 x1 y1 u1 v1 r g b a)
+ (set-vertex! 1 x2 y2 u2 v1 r g b a)
+ (set-vertex! 2 x3 y3 u2 v2 r g b a)
+ (set-vertex! 3 x4 y4 u1 v2 r g b a)
+ (dbuffer-pack-indices-quad! indices ioffset i))))))))
(define %null-vec2 (vec2 0.0 0.0))
(define %default-scale (vec2 1.0 1.0))