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