render: sprite: Rewrite sprite batching API.
[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 color)
28 #:use-module (chickadee render shader)
29 #:use-module (chickadee render texture)
30 #:use-module (chickadee render buffer)
31 #:export (draw-sprite*
32 draw-sprite
33
34 make-sprite-batch
35 sprite-batch?
36 sprite-batch-texture
37 set-sprite-batch-texture!
38 sprite-batch-clear!
39 sprite-batch-add*
40 sprite-batch-add!
41 draw-sprite-batch
42
43 with-batched-sprites
44 draw-nine-patch*
45 draw-nine-patch))
46
47 (define unbatched-sprite-shader
48 (delay
49 (strings->shader
50 "
51 #version 130
52
53 in vec2 position;
54 in vec2 tex;
55 out vec2 fragTex;
56 uniform mat4 mvp;
57
58 void main(void) {
59 fragTex = tex;
60 gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
61 }
62 "
63 "
64 #version 130
65
66 in vec2 fragTex;
67 uniform sampler2D colorTexture;
68 uniform vec4 tint;
69
70 void main (void) {
71 gl_FragColor = texture2D(colorTexture, fragTex) * tint;
72 }
73 ")))
74
75 (define draw-sprite*
76 (let* ((stride 16) ; 4 f32s, 2 for vertex, 2 for texcoord
77 (buffer (delay
78 (make-buffer #f
79 #:name "unbatched sprite buffer"
80 #:length (* stride 4)
81 #:stride stride
82 #:usage 'stream)))
83 (pos (delay
84 (make-typed-buffer #:name "unbatched sprite vertices"
85 #:buffer (force buffer)
86 #:type 'vec2
87 #:component-type 'float
88 #:length 4)))
89 (tex (delay
90 (make-typed-buffer #:name "unbatched sprite texcoords"
91 #:buffer (force buffer)
92 #:type 'vec2
93 #:component-type 'float
94 #:length 4
95 #:offset 8)))
96 (indices
97 (delay
98 (make-typed-buffer #:name "unbatched sprite indices"
99 #:type 'scalar
100 #:component-type 'unsigned-int
101 #:buffer (make-buffer (u32vector 0 3 2 0 2 1)
102 #:target 'index))))
103 (vertex-array
104 (delay
105 (make-vertex-array #:indices (force indices)
106 #:attributes
107 `((0 . ,(force pos))
108 (1 . ,(force tex))))))
109 (mvp (make-null-matrix4)))
110 (lambda* (texture
111 rect
112 matrix
113 #:key
114 (tint white)
115 (blend-mode 'alpha)
116 (texcoords (texture-gl-tex-rect texture)))
117 (with-mapped-typed-buffer (force pos)
118 (let* ((x1 (rect-x rect))
119 (y1 (rect-y rect))
120 (x2 (+ x1 (rect-width rect)))
121 (y2 (+ y1 (rect-height rect)))
122 (s1 (rect-x texcoords))
123 (t1 (rect-y texcoords))
124 (s2 (+ (rect-x texcoords) (rect-width texcoords)))
125 (t2 (+ (rect-y texcoords) (rect-height texcoords)))
126 (bv (typed-buffer-data (force pos))))
127 ;; Texture origin is at the top-left, so we need to flip the Y
128 ;; coordinate relative to the vertices.
129 (f32vector-set! bv 0 x1)
130 (f32vector-set! bv 1 y1)
131 (f32vector-set! bv 2 s1)
132 (f32vector-set! bv 3 t2)
133 (f32vector-set! bv 4 x2)
134 (f32vector-set! bv 5 y1)
135 (f32vector-set! bv 6 s2)
136 (f32vector-set! bv 7 t2)
137 (f32vector-set! bv 8 x2)
138 (f32vector-set! bv 9 y2)
139 (f32vector-set! bv 10 s2)
140 (f32vector-set! bv 11 t1)
141 (f32vector-set! bv 12 x1)
142 (f32vector-set! bv 13 y2)
143 (f32vector-set! bv 14 s1)
144 (f32vector-set! bv 15 t1)))
145 (with-blend-mode blend-mode
146 (with-texture 0 texture
147 (gpu-apply (force unbatched-sprite-shader) (force vertex-array)
148 #:tint tint
149 #:mvp (if matrix
150 (begin
151 (matrix4-mult! mvp matrix
152 (current-projection))
153 mvp)
154 (current-projection))))))))
155
156 (define %null-vec2 (vec2 0.0 0.0))
157 (define %default-scale (vec2 1.0 1.0))
158
159 (define draw-sprite
160 (let ((matrix (make-null-matrix4)))
161 (lambda* (texture
162 position
163 #:key
164 (tint white)
165 (origin %null-vec2)
166 (scale %default-scale)
167 (rotation 0.0)
168 (blend-mode 'alpha)
169 (rect (texture-gl-rect texture)))
170 "Draw TEXTURE at POSITION.
171
172 Optionally, other transformations may be applied to the sprite.
173 ROTATION specifies the angle to rotate the sprite, in radians. SCALE
174 specifies the scaling factor as a 2D vector. All transformations are
175 applied relative to ORIGIN, a 2D vector.
176
177 TINT specifies the color to multiply against all the sprite's pixels.
178 By default white is used, which does no tinting at all.
179
180 By default, alpha blending is used but can be changed by specifying
181 BLEND-MODE."
182 (matrix4-2d-transform! matrix
183 #:origin origin
184 #:position position
185 #:rotation rotation
186 #:scale scale)
187 (draw-sprite* texture rect matrix
188 #:tint tint
189 #:blend-mode blend-mode))))
190
191 \f
192 ;;;
193 ;;; Sprite Batches
194 ;;;
195
196 (define-record-type <sprite-batch>
197 (%make-sprite-batch texture size capacity vertex-buffer vertex-array)
198 sprite-batch?
199 (texture sprite-batch-texture set-sprite-batch-texture!)
200 (size sprite-batch-size set-sprite-batch-size!)
201 (capacity sprite-batch-capacity set-sprite-batch-capacity!)
202 (vertex-buffer sprite-batch-vertex-buffer set-sprite-batch-vertex-buffer!)
203 (vertex-array sprite-batch-vertex-array set-sprite-batch-vertex-array!))
204
205 (define (init-sprite-batch batch capacity)
206 (let* ((index-data (let ((bv (make-u32vector (* capacity 6))))
207 (let loop ((i 0))
208 (when (< i capacity)
209 (let ((index-offset (* i 6))
210 (vertex-offset (* i 4)))
211 (u32vector-set! bv index-offset vertex-offset)
212 (u32vector-set! bv (+ index-offset 1) (+ vertex-offset 3))
213 (u32vector-set! bv (+ index-offset 2) (+ vertex-offset 2))
214 (u32vector-set! bv (+ index-offset 3) vertex-offset)
215 (u32vector-set! bv (+ index-offset 4) (+ vertex-offset 2))
216 (u32vector-set! bv (+ index-offset 5) (+ vertex-offset 1))
217 (loop (+ i 1)))))
218 bv))
219 (index-buffer (make-buffer index-data
220 #:name "indices"
221 #:target 'index))
222 (indices (make-typed-buffer #:name "indices"
223 #:buffer index-buffer
224 #:type 'scalar
225 #:component-type 'unsigned-int))
226 (stride 32) ; 8 f32s, 2 for vertex, 2 for texcoord, 4 for tint color
227 (buffer (make-buffer #f
228 #:name "sprite batch buffer"
229 #:length (* capacity stride 4)
230 #:stride stride
231 #:usage 'stream))
232 (pos (make-typed-buffer #:name "sprite batch vertices"
233 #:buffer buffer
234 #:type 'vec2
235 #:component-type 'float
236 #:length (* capacity 4)))
237 (tex (make-typed-buffer #:name "sprite batch texture coordinates"
238 #:buffer buffer
239 #:type 'vec2
240 #:component-type 'float
241 #:length (* capacity 4)
242 #:offset 8))
243 (tint (make-typed-buffer #:name "sprite batch tint colors"
244 #:buffer buffer
245 #:type 'vec4
246 #:component-type 'float
247 #:length (* capacity 4)
248 #:offset 16))
249 (va (make-vertex-array #:indices indices
250 #:attributes `((0 . ,pos)
251 (1 . ,tex)
252 (2 . ,tint)))))
253 (set-sprite-batch-capacity! batch capacity)
254 (set-sprite-batch-vertex-buffer! batch buffer)
255 (set-sprite-batch-vertex-array! batch va)))
256
257 (define* (make-sprite-batch texture #:key (capacity 256))
258 "Make a sprite batch that can hold CAPACITY sprites."
259 (let ((batch (%make-sprite-batch texture 0 0 #f #f)))
260 (init-sprite-batch batch capacity)
261 batch))
262
263 (define (sprite-batch-full? batch)
264 (= (sprite-batch-capacity batch) (sprite-batch-size batch)))
265
266 (define (double-sprite-batch-size! batch)
267 (let* ((old-verts (sprite-batch-vertex-buffer batch))
268 (old-vertex-data (buffer-data old-verts)))
269 (unmap-buffer! old-verts)
270 (init-sprite-batch batch (* (sprite-batch-capacity batch) 2))
271 (let ((new-verts (sprite-batch-vertex-buffer batch)))
272 (map-buffer! new-verts 'write-only)
273 (bytevector-copy! old-vertex-data 0
274 (buffer-data new-verts) 0
275 (bytevector-length old-vertex-data)))))
276
277 (define (sprite-batch-clear! batch)
278 "Reset BATCH to size 0."
279 (set-sprite-batch-size! batch 0))
280
281 (define (sprite-batch-flush! batch)
282 "Submit the contents of BATCH to the GPU."
283 (unmap-buffer! (sprite-batch-vertex-buffer batch)))
284
285 (define* (sprite-batch-add* batch rect matrix
286 #:key
287 (tint white)
288 texture-region)
289 "Add RECT, transformed by MATRIX, to BATCH. To render a subsection
290 of the batch's texture, a texture object whose parent is the batch
291 texture may be specified via the TEXTURE-REGION argument."
292 ;; Expand the buffers when necessary.
293 (when (sprite-batch-full? batch)
294 (double-sprite-batch-size! batch))
295 (map-buffer! (sprite-batch-vertex-buffer batch) 'write-only)
296 (let* ((size (sprite-batch-size batch))
297 (vertices (buffer-data (sprite-batch-vertex-buffer batch)))
298 (offset (* size 32)) ; each sprite is 32 floats in size
299 (minx (rect-x rect))
300 (miny (rect-y rect))
301 (maxx (+ minx (rect-width rect)))
302 (maxy (+ miny (rect-height rect)))
303 (x1 (transform-x matrix minx miny))
304 (y1 (transform-y matrix minx miny))
305 (x2 (transform-x matrix maxx miny))
306 (y2 (transform-y matrix maxx miny))
307 (x3 (transform-x matrix maxx maxy))
308 (y3 (transform-y matrix maxx maxy))
309 (x4 (transform-x matrix minx maxy))
310 (y4 (transform-y matrix minx maxy))
311 (texcoords (texture-gl-tex-rect
312 (or texture-region
313 (sprite-batch-texture batch))))
314 (s1 (rect-x texcoords))
315 (t1 (rect-y texcoords))
316 (s2 (+ (rect-x texcoords) (rect-width texcoords)))
317 (t2 (+ (rect-y texcoords) (rect-height texcoords))))
318 ;; Add vertices.
319 ;; Bottom-left
320 (f32vector-set! vertices offset x1)
321 (f32vector-set! vertices (+ offset 1) y1)
322 ;; Bottom-right
323 (f32vector-set! vertices (+ offset 8) x2)
324 (f32vector-set! vertices (+ offset 9) y2)
325 ;; Top-right
326 (f32vector-set! vertices (+ offset 16) x3)
327 (f32vector-set! vertices (+ offset 17) y3)
328 ;; Top-left
329 (f32vector-set! vertices (+ offset 24) x4)
330 (f32vector-set! vertices (+ offset 25) y4)
331 ;; Add texture coordinates.
332 ;; Bottom-left
333 (f32vector-set! vertices (+ offset 2) s1)
334 (f32vector-set! vertices (+ offset 3) t2)
335 ;; Bottom-right
336 (f32vector-set! vertices (+ offset 10) s2)
337 (f32vector-set! vertices (+ offset 11) t2)
338 ;; Top-right
339 (f32vector-set! vertices (+ offset 18) s2)
340 (f32vector-set! vertices (+ offset 19) t1)
341 ;; Top-left
342 (f32vector-set! vertices (+ offset 26) s1)
343 (f32vector-set! vertices (+ offset 27) t1)
344 ;; Add tint.
345 (let ((bv ((@@ (chickadee render color) unwrap-color) tint))
346 (byte-offset (* offset 4)))
347 (bytevector-copy! bv 0 vertices (+ byte-offset 16) 16)
348 (bytevector-copy! bv 0 vertices (+ byte-offset 48) 16)
349 (bytevector-copy! bv 0 vertices (+ byte-offset 80) 16)
350 (bytevector-copy! bv 0 vertices (+ byte-offset 112) 16))
351 (set-sprite-batch-size! batch (1+ size))))
352
353 (define sprite-batch-add!
354 (let ((matrix (make-null-matrix4)))
355 (lambda* (batch
356 position
357 #:key
358 (origin %null-vec2)
359 (scale %default-scale)
360 (rotation 0.0)
361 (tint white)
362 texture-region)
363 "Add sprite to BATCH at POSITION. To render a subsection of the
364 batch's texture, a texture object whose parent is the batch texture
365 may be specified via the TEXTURE-REGION argument."
366 (let ((rect (texture-gl-rect
367 (or texture-region (sprite-batch-texture batch)))))
368 (matrix4-2d-transform! matrix
369 #:origin origin
370 #:position position
371 #:rotation rotation
372 #:scale scale)
373 (sprite-batch-add* batch rect matrix
374 #:tint tint
375 #:texture-region texture-region)))))
376
377
378 (define batched-sprite-shader
379 (delay
380 (strings->shader
381 "
382 #version 130
383
384 in vec2 position;
385 in vec2 tex;
386 in vec4 tint;
387 out vec2 fragTex;
388 out vec4 fragTint;
389 uniform mat4 mvp;
390
391 void main(void) {
392 fragTex = tex;
393 fragTint = tint;
394 gl_Position = mvp * vec4(position.xy, 0.0, 1.0);
395 }
396 "
397 "
398 #version 130
399
400 in vec2 fragTex;
401 in vec4 fragTint;
402 uniform sampler2D colorTexture;
403
404 void main (void) {
405 gl_FragColor = texture2D(colorTexture, fragTex) * fragTint;
406 }
407 ")))
408
409 (define* (draw-sprite-batch batch #:key (blend-mode 'alpha))
410 "Render the contents of BATCH."
411 (sprite-batch-flush! batch)
412 (with-blend-mode blend-mode
413 (with-texture 0 (sprite-batch-texture batch)
414 (gpu-apply* (force batched-sprite-shader)
415 (sprite-batch-vertex-array batch)
416 (* (sprite-batch-size batch) 6)
417 #:mvp (current-projection)))))
418
419 \f
420 ;;;
421 ;;; Nine Patches
422 ;;;
423
424 (define draw-nine-patch*
425 (let ((%rect (make-rect 0.0 0.0 0.0 0.0))
426 (texcoords (make-rect 0.0 0.0 0.0 0.0)))
427 (lambda* (texture
428 rect
429 matrix
430 #:key
431 (margin 0.0)
432 (top-margin margin)
433 (bottom-margin margin)
434 (left-margin margin)
435 (right-margin margin)
436 (blend-mode 'alpha)
437 (tint white))
438 (let* ((x (rect-x rect))
439 (y (rect-y rect))
440 (w (rect-width rect))
441 (h (rect-height rect))
442 (border-x1 x)
443 (border-y1 y)
444 (border-x2 (+ x w))
445 (border-y2 (+ y h))
446 (fill-x1 (+ border-x1 left-margin))
447 (fill-y1 (+ border-y1 bottom-margin))
448 (fill-x2 (- border-x2 right-margin))
449 (fill-y2 (- border-y2 top-margin))
450 (prect (texture-gl-rect texture))
451 (trect (texture-gl-tex-rect texture))
452 (tw (rect-width prect))
453 (th (rect-height prect))
454 (border-s1 (rect-x trect))
455 (border-t1 (rect-y trect))
456 (border-s2 (+ (rect-x trect) (rect-width trect)))
457 (border-t2 (+ (rect-y trect) (rect-height trect)))
458 (fill-s1 (+ border-s1 (/ left-margin tw)))
459 (fill-t1 (+ border-t1 (/ top-margin th)))
460 (fill-s2 (- border-s2 (/ right-margin tw)))
461 (fill-t2 (- border-t2 (/ bottom-margin th))))
462 (define (draw-piece x1 y1 x2 y2 s1 t1 s2 t2)
463 (set-rect-x! %rect x1)
464 (set-rect-y! %rect y1)
465 (set-rect-width! %rect (- x2 x1))
466 (set-rect-height! %rect (- y2 y1))
467 (set-rect-x! texcoords s1)
468 (set-rect-y! texcoords t1)
469 (set-rect-width! texcoords (- s2 s1))
470 (set-rect-height! texcoords (- t2 t1))
471 (draw-sprite* texture %rect matrix
472 #:texcoords texcoords
473 #:blend-mode blend-mode
474 #:tint tint))
475 ;; bottom-left
476 (draw-piece border-x1 border-y1 fill-x1 fill-y1
477 border-s1 fill-t2 fill-s1 border-t2)
478 ;; bottom-center
479 (draw-piece fill-x1 border-y1 fill-x2 fill-y1
480 fill-s1 fill-t2 fill-s2 border-t2)
481 ;; bottom-right
482 (draw-piece fill-x2 border-y1 border-x2 fill-y1
483 fill-s2 fill-t2 border-s2 border-t2)
484 ;; center-left
485 (draw-piece border-x1 fill-y1 fill-x1 fill-y2
486 border-s1 fill-t2 fill-s1 fill-t1)
487 ;; center
488 (draw-piece fill-x1 fill-y1 fill-x2 fill-y2
489 fill-s1 fill-t2 fill-s2 fill-t1)
490 ;; center-right
491 (draw-piece fill-x2 fill-y1 border-x2 fill-y2
492 fill-s2 fill-t2 border-s2 fill-t1)
493 ;; top-left
494 (draw-piece border-x1 fill-y2 fill-x1 border-y2
495 border-s1 border-t1 fill-s1 fill-t1)
496 ;; top-center
497 (draw-piece fill-x1 fill-y2 fill-x2 border-y2
498 fill-s1 border-t1 fill-s2 fill-t1)
499 ;; top-right
500 (draw-piece fill-x2 fill-y2 border-x2 border-y2
501 fill-s2 border-t1 border-s2 fill-t1)))))
502
503 (define draw-nine-patch
504 (let ((position (vec2 0.0 0.0))
505 (%rect (make-rect 0.0 0.0 0.0 0.0))
506 (matrix (make-null-matrix4)))
507 (lambda* (texture
508 rect
509 #:key
510 (margin 0.0)
511 (top-margin margin) (bottom-margin margin)
512 (left-margin margin) (right-margin margin)
513 (origin %null-vec2)
514 (rotation 0.0)
515 (scale %default-scale)
516 (blend-mode 'alpha)
517 (tint white))
518 "Draw a \"nine patch\" sprite. A nine patch sprite renders
519 TEXTURE on the rectangular area RECT whose stretchable areas are
520 defined by the given margin measurements. The corners are never
521 stretched, the left and right edges may be stretched vertically, the
522 top and bottom edges may be stretched horizontally, and the center may
523 be stretched in both directions. This rendering technique is
524 particularly well suited for resizable windows and buttons in
525 graphical user interfaces.
526
527 MARGIN specifies the margin size for all sides of the nine patch. To
528 make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN,
529 LEFT-MARGIN, and RIGHT-MARGIN arguments may be used."
530 (set-rect-x! %rect 0.0)
531 (set-rect-y! %rect 0.0)
532 (set-rect-width! %rect (rect-width rect))
533 (set-rect-height! %rect (rect-height rect))
534 (set-vec2-x! position (rect-x rect))
535 (set-vec2-y! position (rect-y rect))
536 (matrix4-2d-transform! matrix
537 #:origin origin
538 #:position position
539 #:rotation rotation
540 #:scale scale)
541 (draw-nine-patch* texture %rect matrix
542 #:top-margin top-margin
543 #:bottom-margin bottom-margin
544 #:left-margin left-margin
545 #:right-margin right-margin
546 #:blend-mode blend-mode
547 #:tint tint))))