summaryrefslogtreecommitdiff
path: root/chickadee/graphics.scm
blob: a6a4bb0b481e8991cbcd748499fcb083b1be21a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
;;; Chickadee Game Toolkit
;;; Copyright © 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.
;;; You may obtain a copy of the License at
;;;
;;;    http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(define-module (chickadee graphics)
  #:use-module ((chickadee graphics backend) #:prefix gpu:)
  #:use-module (chickadee graphics blend)
  #:use-module (chickadee graphics buffer)
  #:use-module (chickadee graphics color)
  #:use-module (chickadee graphics pass)
  #:use-module (chickadee graphics pipeline)
  #:use-module (chickadee graphics primitive)
  #:use-module (chickadee graphics shader)
  #:use-module (chickadee graphics texture)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-9)
  #:use-module (srfi srfi-111)
  #:export (current-projection
            current-viewport
            current-scissor
            current-pass
            with-render-pass
            draw
            stream-draw
            flush-stream
            define-graphics-variable
            graphics-variable-ref)
  #:re-export ((gpu:gpu-limits? . gpu-limits?)
               (gpu:gpu-limits-max-texture-dimension-1d . gpu-limits-max-texture-dimension-1d)
               (gpu:gpu-limits-max-texture-dimension-2d . gpu-limits-max-texture-dimension-2d)
               (gpu:gpu-limits-max-texture-dimension-3d . gpu-limits-max-texture-dimension-3d)
               (gpu:gpu-limits-max-texture-array-layers . gpu-limits-max-texture-array-layers)
               (gpu:gpu-limits-max-sampled-textures-per-shader-stage . gpu-limits-max-sampled-textures-per-shader-stage)
               (gpu:gpu-limits-max-samplers-per-shader-stage . gpu-limits-max-samplers-per-shader-stage)
               (gpu:gpu-limits-max-uniform-buffers-per-shader-stage . gpu-limits-max-uniform-buffers-per-shader-stage)
               (gpu:gpu-limits-max-uniform-buffer-binding-size . gpu-limits-max-uniform-buffer-binding-size)
               (gpu:gpu-limits-max-bindings . gpu-limits-max-bindings)
               (gpu:gpu-limits-max-vertex-buffers . gpu-limits-max-vertex-buffers)
               (gpu:gpu-limits-max-buffer-size . gpu-limits-max-buffer-size)
               (gpu:gpu-limits-max-vertex-attributes . gpu-limits-max-vertex-attributes)
               (gpu:gpu-limits-max-vertex-buffer-array-stride . gpu-limits-max-vertex-buffer-array-stride)
               (gpu:gpu-limits-max-inter-stage-shader-components . gpu-limits-max-inter-stage-shader-components)
               (gpu:gpu-limits-max-inter-stage-shader-variables . gpu-limits-max-inter-stage-shader-variables)
               (gpu:gpu-limits-max-color-attachments . gpu-limits-max-color-attachments)
               (gpu:current-gpu . current-gpu)
               (gpu:gpu? . gpu?)
               (gpu:gpu-name . gpu-name)
               (gpu:gpu-description . gpu-description)
               (gpu:gpu-limits . gpu-limits)))

;; Private API stuff shhhhhhh...
(define buffer-handle (@@ (chickadee graphics buffer) buffer-handle))
(define texture-handle (@@ (chickadee graphics texture) texture-handle))
(define texture-view-handle (@@ (chickadee graphics texture) texture-view-handle))
(define sampler-handle (@@ (chickadee graphics texture) sampler-handle))
(define render-pipeline-handle (@@ (chickadee graphics pipeline) render-pipeline-handle))
(define <render-pipeline> (@@ (chickadee graphics pipeline) <render-pipeline>))

(define-syntax-rule (define-graphics-variable name exp)
  (define name
    (let ((cache '()))
      (define (get-it)
        (let ((gpu (gpu:current-gpu)))
          (or (assq-ref cache gpu)
              (let ((val exp))
                (set! cache (cons (cons gpu val) cache))
                val))))
      get-it)))

(define-syntax-rule (graphics-variable-ref var) (var))

(define current-projection (make-parameter #f))
(define current-viewport (make-parameter #f))
(define current-scissor (make-parameter #f))
(define current-pass (make-parameter #f))

(define-graphics-variable last-pass (box #f))

(define (begin-frame)
  (set-box! (graphics-variable-ref last-pass) #f)
  (begin-stream)
  (gpu:begin-frame (gpu:current-gpu)))

(define (end-frame view)
  (end-stream)
  (gpu:end-frame (gpu:current-gpu) (texture-view-handle view)))

(define (begin-render-pass pass)
  (define (resolve-texture-view view)
    (match view
      (#f #f)
      ((? texture-view?)
       (texture-view-handle view))
      ((? procedure?)
       (texture-view-handle (view)))))
  (match pass
    (($ <render-pass> colors depth+stencil)
     (let* ((gpu (gpu:current-gpu))
            (cmd (gpu:request-begin-render-pass-command gpu)))
       (gpu:set-begin-render-pass-command-pass! cmd pass)
       (do ((i 0 (+ i 1)))
           ((= i (vector-length colors)))
         (match (vector-ref colors i)
           (($ <color-attachment> view resolve-target op)
            (let ((view (resolve-texture-view view))
                  (resolve-target (resolve-texture-view resolve-target)))
              (gpu:begin-render-pass-command-color-attachment-set!
               cmd i view resolve-target op)))))
       (match depth+stencil
         (#f #t)
         (($ <depth+stencil-attachment> view depth-op stencil-op)
          (let ((view (resolve-texture-view view)))
            (gpu:begin-render-pass-command-depth+stencil-attachment-set!
             cmd view depth-op stencil-op))))
       (gpu:submit gpu cmd)))))

(define (end-render-pass pass)
  (let* ((gpu (gpu:current-gpu))
         (cmd (gpu:request-end-render-pass-command gpu)))
    (gpu:set-end-render-pass-command-pass! cmd pass)
    (gpu:submit gpu cmd)))

(define (draw* count instances pipeline pass viewport scissor blend-constant
               stencil-reference index-buffer vertex-buffers bindings)
  (unless (eq? count 0)
    (let* ((gpu (gpu:current-gpu))
           (cmd (gpu:request-draw-command gpu))
           (pass-box (graphics-variable-ref last-pass))
           (pass* (unbox pass-box)))
      (unless (eq? pass pass*)
        (when pass*
          (end-render-pass pass*))
        (begin-render-pass pass)
        (set-box! pass-box pass))
      (gpu:set-draw-command-pass! cmd pass)
      (gpu:set-draw-command-pipeline! cmd (render-pipeline-handle pipeline))
      (gpu:set-draw-command-viewport! cmd viewport)
      (gpu:set-draw-command-scissor! cmd scissor)
      (gpu:set-draw-command-blend-constant! cmd blend-constant)
      (gpu:set-draw-command-stencil-reference! cmd stencil-reference)
      (gpu:set-draw-command-count! cmd count)
      (gpu:set-draw-command-instances! cmd instances)
      (when index-buffer
        (gpu:set-draw-command-index-buffer! cmd (buffer-handle index-buffer)))
      (do ((i 0 (+ i 1)))
          ((= i (vector-length vertex-buffers)))
        (let ((buffer (buffer-handle (vector-ref vertex-buffers i))))
          (gpu:set-draw-command-vertex-buffer! cmd i buffer)))
      (do ((i 0 (+ i 1)))
          ((= i (vector-length bindings)))
        (match (vector-ref bindings i)
          ((? buffer? buffer)
           (gpu:set-draw-command-binding! cmd i (buffer-handle buffer)))
          ((? texture? texture)
           (gpu:set-draw-command-binding! cmd i (texture-handle texture)))
          ((? texture-view? texture)
           (gpu:set-draw-command-binding! cmd i (texture-view-handle texture)))
          ((? sampler? sampler)
           (gpu:set-draw-command-binding! cmd i (sampler-handle sampler)))
          (#f #f)))
      (gpu:submit gpu cmd))))

(define* (draw count #:key
               pipeline
               (pass (current-pass))
               (viewport (current-viewport))
               (scissor (current-scissor))
               (blend-constant black)
               (stencil-reference #xffffFFFF)
               index-buffer
               (vertex-buffers #())
               (bindings #())
               instances)
  (unless (render-pass? pass)
    (error "no render pass specified"))
  (unless (eq? count 0)
    (flush-stream)
    (draw* count instances pipeline pass viewport scissor blend-constant
           stencil-reference index-buffer vertex-buffers bindings)))


;;;
;;; Immediate mode streaming
;;;

;; This streaming interface is inspired by love2d.  The way it works
;; is that the user calls 'stream-draw' and passes along all the
;; desired pipeline and texture/sampler/buffer binding details.  If
;; the settings match what was used in the previous 'stream-draw' call
;; then the new data is simply appended to the current vertex/index
;; accumulation buffers.  If the settings do not match, then the
;; stream is flushed.  Flushing a stream issues a draw call for the
;; batch, clears the accumulation buffers and resets pipeline,
;; bindings, and counters.  Furthermore, if any non-streaming draw
;; calls are made via 'draw' or if any relevant dynamic state is
;; changed between 'stream-draw' calls then the stream is also
;; flushed.
(define-record-type <stream-state>
  (make-stream-state count vertices indices bindings bindings-length
                     pipeline-cache vertex-buffer-vec)
  stream-state?
  (count stream-state-count set-stream-state-count!)
  (vertices stream-state-vertices)
  (indices stream-state-indices)
  (bindings stream-state-bindings)
  (bindings-length stream-state-bindings-length set-stream-state-bindings-length!)
  (pipeline stream-state-pipeline set-stream-state-pipeline!)
  (pipeline-cache stream-state-pipeline-cache set-stream-state-pipeline-cache!)
  (pass stream-state-pass set-stream-state-pass!)
  (projection stream-state-projection set-stream-state-projection!)
  (viewport stream-state-viewport set-stream-state-viewport!)
  (scissor stream-state-scissor set-stream-state-scissor!)
  (vertex-buffer-vec stream-state-vertex-buffer-vec))

(define-graphics-variable stream-state
  (let ((limits (gpu:gpu-limits (gpu:current-gpu))))
    (make-stream-state 0 (make-dbuffer #:name "Stream vertices")
                       (make-dbuffer #:name "Stream indices" #:usage '(index))
                       (make-vector (gpu:gpu-limits-max-vertex-buffers limits))
                       0 '() (vector #f))))

(define %default-primitive-mode (make-primitive-mode))
(define %default-color-target (make-color-target))

(define (begin-stream)
  (match (graphics-variable-ref stream-state)
    ((and state ($ <stream-state> count vertices indices bindings))
     (dbuffer-map! vertices)
     (dbuffer-map! indices)
     (set-stream-state-count! state 0)
     (set-stream-state-pipeline! state #f)
     (set-stream-state-pass! state #f)
     (set-stream-state-projection! state #f)
     (set-stream-state-viewport! state #f)
     (set-stream-state-scissor! state #f)
     (vector-fill! bindings #f))))

(define (end-stream)
  (match (graphics-variable-ref stream-state)
    ((and state ($ <stream-state> count vertices indices bindings _ pipeline _
                                  pass projection viewport scissor vertex-vec))
     (dbuffer-unmap! vertices)
     (dbuffer-unmap! indices)
     (vector-set! vertex-vec 0 (dbuffer-buffer vertices))
     (unless (eq? count 0)
       (draw* (/ (dbuffer-length indices) 4) #f pipeline pass
              viewport scissor black #xffffFFFF (dbuffer-buffer indices)
              vertex-vec bindings)
       (set-stream-state-count! state 0)))))

(define (flush-stream)
  (end-stream)
  (begin-stream))

(define* (stream-draw #:key
                      count
                      shader
                      (primitive %default-primitive-mode)
                      (color-target %default-color-target)
                      depth+stencil
                      (vertex-layout #())
                      (binding-layout #())
                      (bindings #()))
  (match (graphics-variable-ref stream-state)
    ((and state
          ($ <stream-state> _ vertices indices bindings* bindings-length
                            pipeline cache pass projection viewport scissor))
     (define-inlinable (pipeline-equal? pipeline)
       (match pipeline
         (($ <render-pipeline> _ _ _ _ shader* primitive* color-target*
                               depth+stencil* vertex-layout* binding-layout*)
          (and (eq? shader shader*)
               (equal? primitive primitive*)
               (equal? color-target color-target*)
               (equal? depth+stencil depth+stencil*)
               (equal? vertex-layout vertex-layout*)
               (equal? binding-layout binding-layout*)))))
     (let ((pass* (current-pass))
           (projection* (current-projection))
           (viewport* (current-viewport))
           (scissor* (current-scissor)))
       ;; Check if *all* settings are the same as the previous stream
       ;; draw call, including various bits of dynamic state.  If
       ;; anything is different, draw the batch, clear it, and start
       ;; over with the new settings.
       (unless (and pipeline
                    (pipeline-equal? pipeline)
                    (= (vector-length bindings) bindings-length)
                    (let loop ((i 0))
                      (or (= i (vector-length bindings))
                          (and (eq? (vector-ref bindings i)
                                    (vector-ref bindings* i))
                               (loop (+ i 1)))))
                    (eq? pass pass*)
                    (eq? viewport viewport*)
                    (eq? scissor scissor*)
                    (eq? projection projection*))
         (let ((pipeline
                (let loop ((pipelines cache))
                  (match pipelines
                    (()
                     (let ((new (make-render-pipeline
                                 #:name "Stream render pipeline"
                                 #:shader shader
                                 #:primitive primitive
                                 #:color-target color-target
                                 #:depth+stencil depth+stencil
                                 #:vertex-layout vertex-layout
                                 #:binding-layout binding-layout)))
                       (set-stream-state-pipeline-cache! state (cons new cache))
                       new))
                    ((pipeline . rest)
                     (if (pipeline-equal? pipeline)
                         pipeline
                         (loop rest)))))))
           (flush-stream)
           (set-stream-state-pipeline! state pipeline)
           (set-stream-state-pass! state pass*)
           (set-stream-state-projection! state projection*)
           (set-stream-state-viewport! state viewport*)
           (set-stream-state-scissor! state scissor*)
           (set-stream-state-bindings-length! state (vector-length bindings))
           (vector-fill! bindings* #f)
           (do ((i 0 (+ i 1)))
               ((= i (vector-length bindings)))
             (vector-set! bindings* i (vector-ref bindings i))))))
     (let ((count* (stream-state-count state)))
       (set-stream-state-count! state (+ count* count))
       (values vertices indices count*)))))