render: sprite: batch: Combine vertices and texcoords into one buffer.
[chickadee.git] / chickadee / render / sprite.scm
1 ;;; Chickadee Game Toolkit
2 ;;; Copyright © 2016 David Thompson <davet@gnu.org>
3 ;;;
4 ;;; Chickadee is free software: you can redistribute it and/or modify
5 ;;; it under the terms of the GNU General Public License as published
6 ;;; by the Free Software Foundation, either version 3 of the License,
7 ;;; or (at your option) any later version.
8 ;;;
9 ;;; Chickadee is distributed in the hope that it will be useful, but
10 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ;;; General Public License for more details.
13 ;;;
14 ;;; You should have received a copy of the GNU General Public License
15 ;;; along with this program. If not, see
16 ;;; <http://www.gnu.org/licenses/>.
17
18 (define-module (chickadee render sprite)
19 #:use-module (rnrs bytevectors)
20 #:use-module (srfi srfi-4)
21 #:use-module (srfi srfi-9)
22 #:use-module (srfi srfi-11)
23 #:use-module (chickadee math matrix)
24 #:use-module (chickadee math rect)
25 #:use-module (chickadee math vector)
26 #:use-module (chickadee render)
27 #:use-module (chickadee render shader)
28 #:use-module (chickadee render texture)
29 #:use-module (chickadee render buffer)
30 #:export (draw-sprite*
31 draw-sprite
32 with-batched-sprites
33 draw-nine-patch*
34 draw-nine-patch))
35
36 (define default-shader
37 (delay
38 (strings->shader
39 "
40 #version 130
41
42 in vec2 position;
43 in vec2 tex;
44 out vec2 fragTex;
45 uniform mat4 mvp;
46
47 void main(void) {
48 fragTex = tex;
49 gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
50 }
51 "
52 "
53 #version 130
54
55 in vec2 fragTex;
56 uniform sampler2D colorTexture;
57
58 void main (void) {
59 gl_FragColor = texture2D(colorTexture, fragTex);
60 }
61 ")))
62
63 (define draw-sprite-unbatched
64 (let* ((position-buffer
65 (delay
66 (make-streaming-typed-buffer 'vec2 'float 4
67 #:name "unbatched-sprite-vertices")))
68 (texcoord-buffer
69 (delay
70 (make-streaming-typed-buffer 'vec2 'float 4
71 #:name "unbatched-sprite-texcoords")))
72 (index-buffer
73 (delay
74 (make-typed-buffer #:name "unbatched-sprite-indices"
75 #:type 'scalar
76 #:component-type 'unsigned-int
77 #:buffer (make-buffer (u32vector 0 3 2 0 2 1)
78 #:target 'index))))
79 (vertex-array
80 (delay
81 (make-vertex-array #:indices (force index-buffer)
82 #:attributes
83 `((0 . ,(force position-buffer))
84 (1 . ,(force texcoord-buffer))))))
85 (mvp (make-null-matrix4)))
86 (lambda (texture region world-matrix blend-mode shader texture-region)
87 (with-mapped-typed-buffer (force position-buffer)
88 (let* ((x1 (rect-x region))
89 (y1 (rect-y region))
90 (x2 (+ x1 (rect-width region)))
91 (y2 (+ y1 (rect-height region)))
92 (bv (typed-buffer-data (force position-buffer))))
93 (f32vector-set! bv 0 x1)
94 (f32vector-set! bv 1 y1)
95 (f32vector-set! bv 2 x2)
96 (f32vector-set! bv 3 y1)
97 (f32vector-set! bv 4 x2)
98 (f32vector-set! bv 5 y2)
99 (f32vector-set! bv 6 x1)
100 (f32vector-set! bv 7 y2)))
101 (with-mapped-typed-buffer (force texcoord-buffer)
102 ;; Texture origin is at the top-left, so we need to flip the Y
103 ;; coordinate relative to the vertices.
104 (let ((s1 (rect-x texture-region))
105 (t1 (rect-y texture-region))
106 (s2 (+ (rect-x texture-region) (rect-width texture-region)))
107 (t2 (+ (rect-y texture-region) (rect-height texture-region)))
108 (bv (typed-buffer-data (force texcoord-buffer))))
109 (f32vector-set! bv 0 s1)
110 (f32vector-set! bv 1 t2)
111 (f32vector-set! bv 2 s2)
112 (f32vector-set! bv 3 t2)
113 (f32vector-set! bv 4 s2)
114 (f32vector-set! bv 5 t1)
115 (f32vector-set! bv 6 s1)
116 (f32vector-set! bv 7 t1)))
117 (with-blend-mode blend-mode
118 (with-texture 0 texture
119 (gpu-apply shader (force vertex-array)
120 #:mvp (if world-matrix
121 (begin
122 (matrix4-mult! mvp world-matrix
123 (current-projection))
124 mvp)
125 (current-projection))))))))
126
127 \f
128 ;;;
129 ;;; Sprite Batch
130 ;;;
131
132 (define-record-type <sprite-batch>
133 (%make-sprite-batch texture blend-mode shader size capacity index-buffer
134 position-buffer texture-buffer vertex-array)
135 sprite-batch?
136 (texture sprite-batch-texture set-sprite-batch-texture!)
137 (blend-mode sprite-batch-blend-mode set-sprite-batch-blend-mode!)
138 (shader sprite-batch-shader set-sprite-batch-shader!)
139 (size sprite-batch-size set-sprite-batch-size!)
140 (capacity sprite-batch-capacity set-sprite-batch-capacity!)
141 (index-buffer sprite-batch-index-buffer set-sprite-batch-index-buffer!)
142 (position-buffer sprite-batch-position-buffer set-sprite-batch-position-buffer!)
143 (texture-buffer sprite-batch-texture-buffer set-sprite-batch-texture-buffer!)
144 (vertex-array sprite-batch-vertex-array set-sprite-batch-vertex-array!))
145
146 (define (init-sprite-batch batch capacity)
147 (let* ((index (make-streaming-typed-buffer 'scalar
148 'unsigned-int
149 (* capacity 6)
150 #:target 'index))
151 (stride 16) ; 4 f32s, 2 for vertex, 2 for texcoord
152 (buffer (make-buffer #f
153 #:name "sprite batch buffer"
154 #:length (* capacity stride 4)
155 #:stride stride
156 #:usage 'stream))
157 (pos (make-typed-buffer #:name "sprite batches vertices"
158 #:buffer buffer
159 #:type 'vec2
160 #:component-type 'float
161 #:length (* capacity 4)))
162 (tex (make-typed-buffer #:name "batched-sprite-vertices"
163 #:buffer buffer
164 #:type 'vec2
165 #:component-type 'float
166 #:length (* capacity 4)
167 #:offset (/ stride 2)))
168 (va (make-vertex-array #:indices index
169 #:attributes `((0 . ,pos) (1 . ,tex)))))
170 (set-sprite-batch-capacity! batch capacity)
171 (set-sprite-batch-index-buffer! batch index)
172 (set-sprite-batch-position-buffer! batch pos)
173 (set-sprite-batch-texture-buffer! batch tex)
174 (set-sprite-batch-vertex-array! batch va)))
175
176 (define (make-sprite-batch capacity)
177 "Make a sprite batch that can hold CAPACITY sprites."
178 (let ((batch (%make-sprite-batch #f #f #f 0 0 #f #f #f #f)))
179 (init-sprite-batch batch capacity)
180 batch))
181
182 (define (sprite-batch-full? batch)
183 (= (sprite-batch-capacity batch) (sprite-batch-size batch)))
184
185 (define (double-sprite-batch-size! batch)
186 (let* ((old-index (sprite-batch-index-buffer batch))
187 (old-verts (sprite-batch-position-buffer batch))
188 (old-index-data (typed-buffer-data old-index))
189 (old-vertex-data (typed-buffer-data old-verts)))
190 (unmap-typed-buffer! old-index)
191 (unmap-typed-buffer! old-verts)
192 (init-sprite-batch batch (* (sprite-batch-capacity batch) 2))
193 (sprite-batch-begin! batch)
194 (let ((new-index (sprite-batch-index-buffer batch))
195 (new-verts (sprite-batch-position-buffer batch)))
196 (define (copy from to)
197 (bytevector-copy! from 0
198 (typed-buffer-data to) 0
199 (bytevector-length from)))
200 (copy old-index-data new-index)
201 (copy old-vertex-data new-verts))))
202
203 (define (sprite-batch-reset! batch)
204 "Reset BATCH to size 0."
205 (set-sprite-batch-texture! batch #f)
206 (set-sprite-batch-blend-mode! batch #f)
207 (set-sprite-batch-shader! batch #f)
208 (set-sprite-batch-size! batch 0))
209
210 (define (sprite-batch-begin! batch)
211 (map-typed-buffer! (sprite-batch-index-buffer batch))
212 (map-typed-buffer! (sprite-batch-position-buffer batch)))
213
214 (define (sprite-batch-flush! batch)
215 "Render the contents of BATCH and clear the cache."
216 (unless (zero? (sprite-batch-size batch))
217 (with-blend-mode (sprite-batch-blend-mode batch)
218 (with-texture 0 (sprite-batch-texture batch)
219 (unmap-typed-buffer! (sprite-batch-index-buffer batch))
220 (unmap-typed-buffer! (sprite-batch-position-buffer batch))
221 (gpu-apply* (sprite-batch-shader batch)
222 (sprite-batch-vertex-array batch)
223 (* (sprite-batch-size batch) 6)
224 #:mvp (current-projection))
225 (sprite-batch-reset! batch)))))
226
227 (define sprite-batch-add!
228 (let ((world1 (vec2 0.0 0.0))
229 (world2 (vec2 0.0 0.0))
230 (world3 (vec2 0.0 0.0))
231 (world4 (vec2 0.0 0.0))
232 (offset-bv (make-u32vector 1)))
233 (define (set-offset offset)
234 (u32vector-set! offset-bv 0 offset))
235 (define (offset)
236 (u32vector-ref offset-bv 0))
237 (lambda (batch texture region world-matrix blend-mode
238 shader texture-region)
239 ;; Expand the buffers when necessary.
240 (when (sprite-batch-full? batch)
241 (double-sprite-batch-size! batch))
242 ;; Flush the batch if any GL state needs changing.
243 (unless (and (eq? (sprite-batch-texture batch) texture)
244 (eq? (sprite-batch-blend-mode batch) blend-mode)
245 (eq? (sprite-batch-shader batch) shader))
246 (sprite-batch-flush! batch)
247 (sprite-batch-begin! batch)
248 (set-sprite-batch-texture! batch texture)
249 (set-sprite-batch-blend-mode! batch blend-mode)
250 (set-sprite-batch-shader! batch shader))
251 (let ((size (sprite-batch-size batch)))
252 (let* ((indices (typed-buffer-data (sprite-batch-index-buffer batch)))
253 (vertices (typed-buffer-data (sprite-batch-position-buffer batch)))
254 (texcoords (typed-buffer-data (sprite-batch-texture-buffer batch)))
255 (local-x1 (rect-x region))
256 (local-y1 (rect-y region))
257 (local-x2 (+ local-x1 (rect-width region)))
258 (local-y2 (+ local-y1 (rect-height region)))
259 (s1 (rect-x texture-region))
260 (t1 (rect-y texture-region))
261 (s2 (+ (rect-x texture-region) (rect-width texture-region)))
262 (t2 (+ (rect-y texture-region) (rect-height texture-region))))
263 (set-vec2! world1 local-x1 local-y1)
264 (set-vec2! world2 local-x2 local-y1)
265 (set-vec2! world3 local-x2 local-y2)
266 (set-vec2! world4 local-x1 local-y2)
267 (when world-matrix
268 (transform! world-matrix world1)
269 (transform! world-matrix world2)
270 (transform! world-matrix world3)
271 (transform! world-matrix world4))
272 ;; Add indices.
273 (set-offset (* size 4))
274 (let ((index-vertex-offset (offset)))
275 (set-offset (* size 6))
276 (u32vector-set! indices (offset) index-vertex-offset)
277 (u32vector-set! indices (+ (offset) 1) (+ index-vertex-offset 3))
278 (u32vector-set! indices (+ (offset) 2) (+ index-vertex-offset 2))
279 (u32vector-set! indices (+ (offset) 3) index-vertex-offset)
280 (u32vector-set! indices (+ (offset) 4) (+ index-vertex-offset 2))
281 (u32vector-set! indices (+ (offset) 5) (+ index-vertex-offset 1)))
282 ;; Add vertices.
283 (set-offset (* size 16))
284 ;; Bottom-left
285 (f32vector-set! vertices (offset) (vec2-x world1))
286 (f32vector-set! vertices (+ (offset) 1) (vec2-y world1))
287 ;; Bottom-right
288 (f32vector-set! vertices (+ (offset) 4) (vec2-x world2))
289 (f32vector-set! vertices (+ (offset) 5) (vec2-y world2))
290 ;; Top-right
291 (f32vector-set! vertices (+ (offset) 8) (vec2-x world3))
292 (f32vector-set! vertices (+ (offset) 9) (vec2-y world3))
293 ;; Top-left
294 (f32vector-set! vertices (+ (offset) 12) (vec2-x world4))
295 (f32vector-set! vertices (+ (offset) 13) (vec2-y world4))
296 ;; Add texture coordinates.
297 ;; Bottom-left
298 (f32vector-set! texcoords (+ (offset) 2) s1)
299 (f32vector-set! texcoords (+ (offset) 3) t2)
300 ;; Bottom-right
301 (f32vector-set! texcoords (+ (offset) 6) s2)
302 (f32vector-set! texcoords (+ (offset) 7) t2)
303 ;; Top-right
304 (f32vector-set! texcoords (+ (offset) 10) s2)
305 (f32vector-set! texcoords (+ (offset) 11) t1)
306 ;; Top-left
307 (f32vector-set! texcoords (+ (offset) 14) s1)
308 (f32vector-set! texcoords (+ (offset) 15) t1)
309 (set-sprite-batch-size! batch (1+ size)))))))
310
311 (define *batch?* #f)
312 (define %batch (delay (make-sprite-batch 256)))
313
314 (define (draw-sprite-batched texture region world-matrix blend-mode shader
315 texture-region)
316 (sprite-batch-add! (force %batch) texture region world-matrix blend-mode
317 shader texture-region))
318
319 (define-syntax-rule (with-batched-sprites body ...)
320 "Use batched rendering for all draw-sprite calls within BODY."
321 (if *batch?*
322 (begin body ...)
323 (dynamic-wind
324 (lambda ()
325 (set! *batch?* #t))
326 (lambda ()
327 (sprite-batch-reset! (force %batch))
328 body ...
329 (sprite-batch-flush! (force %batch)))
330 (lambda ()
331 (set! *batch?* #f)))))
332
333 (define* (draw-sprite* texture rect matrix #:key
334 (blend-mode 'alpha)
335 (texcoords (texture-gl-tex-rect texture))
336 (shader (force default-shader)))
337 (if *batch?*
338 (draw-sprite-batched texture rect matrix blend-mode
339 shader texcoords)
340 (draw-sprite-unbatched texture rect matrix blend-mode
341 shader texcoords)))
342
343 (define %null-vec2 (vec2 0.0 0.0))
344 (define %default-scale (vec2 1.0 1.0))
345
346 (define draw-sprite
347 (let ((matrix (make-null-matrix4)))
348 (lambda* (texture
349 position
350 #:key
351 (origin %null-vec2)
352 (scale %default-scale)
353 (rotation 0.0)
354 (blend-mode 'alpha)
355 (rect (texture-gl-rect texture))
356 (shader (force default-shader)))
357 "Draw TEXTURE at POSITION.
358
359 Optionally, other transformations may be applied to the sprite.
360 ROTATION specifies the angle to rotate the sprite, in radians. SCALE
361 specifies the scaling factor as a 2D vector. All transformations are
362 applied relative to ORIGIN, a 2D vector.
363
364 By default, alpha blending is used but can be changed by specifying
365 BLEND-MODE.
366
367 Advanced users may pass SHADER to change the way the sprite is
368 rendered entirely."
369 (matrix4-2d-transform! matrix
370 #:origin origin
371 #:position position
372 #:rotation rotation
373 #:scale scale)
374 (draw-sprite* texture rect matrix
375 #:blend-mode blend-mode
376 #:shader shader))))
377
378 \f
379 ;;;
380 ;;; Nine Patches
381 ;;;
382
383 (define draw-nine-patch*
384 (let ((%rect (make-rect 0.0 0.0 0.0 0.0))
385 (texcoords (make-rect 0.0 0.0 0.0 0.0)))
386 (lambda* (texture
387 rect
388 matrix
389 #:key
390 (margin 0.0)
391 (top-margin margin)
392 (bottom-margin margin)
393 (left-margin margin)
394 (right-margin margin)
395 (blend-mode 'alpha)
396 (shader (force default-shader)))
397 (let* ((x (rect-x rect))
398 (y (rect-y rect))
399 (w (rect-width rect))
400 (h (rect-height rect))
401 (border-x1 x)
402 (border-y1 y)
403 (border-x2 (+ x w))
404 (border-y2 (+ y h))
405 (fill-x1 (+ border-x1 left-margin))
406 (fill-y1 (+ border-y1 bottom-margin))
407 (fill-x2 (- border-x2 right-margin))
408 (fill-y2 (- border-y2 top-margin))
409 (prect (texture-gl-rect texture))
410 (trect (texture-gl-tex-rect texture))
411 (tw (rect-width prect))
412 (th (rect-height prect))
413 (border-s1 (rect-x trect))
414 (border-t1 (rect-y trect))
415 (border-s2 (+ (rect-x trect) (rect-width trect)))
416 (border-t2 (+ (rect-y trect) (rect-height trect)))
417 (fill-s1 (+ border-s1 (/ left-margin tw)))
418 (fill-t1 (+ border-t1 (/ top-margin th)))
419 (fill-s2 (- border-s2 (/ right-margin tw)))
420 (fill-t2 (- border-t2 (/ bottom-margin th))))
421 (define (draw-piece x1 y1 x2 y2 s1 t1 s2 t2)
422 (set-rect-x! %rect x1)
423 (set-rect-y! %rect y1)
424 (set-rect-width! %rect (- x2 x1))
425 (set-rect-height! %rect (- y2 y1))
426 (set-rect-x! texcoords s1)
427 (set-rect-y! texcoords t1)
428 (set-rect-width! texcoords (- s2 s1))
429 (set-rect-height! texcoords (- t2 t1))
430 (draw-sprite* texture %rect matrix
431 #:texcoords texcoords
432 #:blend-mode blend-mode
433 #:shader shader))
434 (with-batched-sprites
435 ;; bottom-left
436 (draw-piece border-x1 border-y1 fill-x1 fill-y1
437 border-s1 fill-t2 fill-s1 border-t2)
438 ;; bottom-center
439 (draw-piece fill-x1 border-y1 fill-x2 fill-y1
440 fill-s1 fill-t2 fill-s2 border-t2)
441 ;; bottom-right
442 (draw-piece fill-x2 border-y1 border-x2 fill-y1
443 fill-s2 fill-t2 border-s2 border-t2)
444 ;; center-left
445 (draw-piece border-x1 fill-y1 fill-x1 fill-y2
446 border-s1 fill-t2 fill-s1 fill-t1)
447 ;; center
448 (draw-piece fill-x1 fill-y1 fill-x2 fill-y2
449 fill-s1 fill-t2 fill-s2 fill-t1)
450 ;; center-right
451 (draw-piece fill-x2 fill-y1 border-x2 fill-y2
452 fill-s2 fill-t2 border-s2 fill-t1)
453 ;; top-left
454 (draw-piece border-x1 fill-y2 fill-x1 border-y2
455 border-s1 border-t1 fill-s1 fill-t1)
456 ;; top-center
457 (draw-piece fill-x1 fill-y2 fill-x2 border-y2
458 fill-s1 border-t1 fill-s2 fill-t1)
459 ;; top-right
460 (draw-piece fill-x2 fill-y2 border-x2 border-y2
461 fill-s2 border-t1 border-s2 fill-t1))))))
462
463 (define draw-nine-patch
464 (let ((position (vec2 0.0 0.0))
465 (%rect (make-rect 0.0 0.0 0.0 0.0))
466 (matrix (make-null-matrix4)))
467 (lambda* (texture
468 rect
469 #:key
470 (margin 0.0)
471 (top-margin margin) (bottom-margin margin)
472 (left-margin margin) (right-margin margin)
473 (origin %null-vec2)
474 (rotation 0.0)
475 (scale %default-scale)
476 (blend-mode 'alpha)
477 (shader (force default-shader)))
478 "Draw a \"nine patch\" sprite. A nine patch sprite renders
479 TEXTURE on the rectangular area RECT whose stretchable areas are
480 defined by the given margin measurements. The corners are never
481 stretched, the left and right edges may be stretched vertically, the
482 top and bottom edges may be stretched horizontally, and the center may
483 be stretched in both directions. This rendering technique is
484 particularly well suited for resizable windows and buttons in
485 graphical user interfaces.
486
487 MARGIN specifies the margin size for all sides of the nine patch. To
488 make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN,
489 LEFT-MARGIN, and RIGHT-MARGIN arguments may be used."
490 (set-rect-x! %rect 0.0)
491 (set-rect-y! %rect 0.0)
492 (set-rect-width! %rect (rect-width rect))
493 (set-rect-height! %rect (rect-height rect))
494 (set-vec2-x! position (rect-x rect))
495 (set-vec2-y! position (rect-y rect))
496 (matrix4-2d-transform! matrix
497 #:origin origin
498 #:position position
499 #:rotation rotation
500 #:scale scale)
501 (draw-nine-patch* texture %rect matrix
502 #:top-margin top-margin
503 #:bottom-margin bottom-margin
504 #:left-margin left-margin
505 #:right-margin right-margin
506 #:blend-mode blend-mode
507 #:shader shader))))