1 | // Copyright (C) 2023 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qrhigles2_p.h" |
5 | #include <QOffscreenSurface> |
6 | #include <QOpenGLContext> |
7 | #include <QtCore/qmap.h> |
8 | #include <QtGui/private/qopenglextensions_p.h> |
9 | #include <QtGui/private/qopenglprogrambinarycache_p.h> |
10 | #include <QtGui/private/qwindow_p.h> |
11 | #include <qpa/qplatformopenglcontext.h> |
12 | #include <qmath.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /* |
17 | OpenGL backend. Binding vertex attribute locations and decomposing uniform |
18 | buffers into uniforms are handled transparently to the application via the |
19 | reflection data (QShaderDescription). Real uniform buffers are never used, |
20 | regardless of the GLSL version. Textures and buffers feature no special |
21 | logic, it's all just glTexSubImage2D and glBufferSubData (with "dynamic" |
22 | buffers set to GL_DYNAMIC_DRAW). The swapchain and the associated |
23 | renderbuffer for depth-stencil will be dummies since we have no control over |
24 | the underlying buffers here. While the baseline here is plain GLES 2.0, some |
25 | modern GL(ES) features like multisample renderbuffers, blits, and compute are |
26 | used when available. Also functional with core profile contexts. |
27 | */ |
28 | |
29 | /*! |
30 | \class QRhiGles2InitParams |
31 | \inmodule QtGuiPrivate |
32 | \inheaderfile rhi/qrhi.h |
33 | \since 6.6 |
34 | \brief OpenGL specific initialization parameters. |
35 | |
36 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
37 | for details. |
38 | |
39 | An OpenGL-based QRhi needs an already created QSurface that can be used in |
40 | combination with QOpenGLContext. Most commonly, this is a QOffscreenSurface |
41 | in practice. Additionally, while optional, it is recommended that the QWindow |
42 | the first QRhiSwapChain will target is passed in as well. |
43 | |
44 | \badcode |
45 | QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); |
46 | QRhiGles2InitParams params; |
47 | params.fallbackSurface = fallbackSurface; |
48 | params.window = window; |
49 | rhi = QRhi::create(QRhi::OpenGLES2, ¶ms); |
50 | \endcode |
51 | |
52 | By default QRhi creates a QOpenGLContext on its own. This approach works |
53 | well in most cases, included threaded scenarios, where there is a dedicated |
54 | QRhi for each rendering thread. As there will be a QOpenGLContext for each |
55 | QRhi, the OpenGL context requirements (a context can only be current on one |
56 | thread) are satisfied. The implicitly created context is destroyed |
57 | automatically together with the QRhi. |
58 | |
59 | The QSurfaceFormat for the context is specified in \c format. The |
60 | constructor sets this to QSurfaceFormat::defaultFormat() so applications |
61 | that call QSurfaceFormat::setDefaultFormat() with the appropriate settings |
62 | before the constructor runs will not need to change value of \c format. |
63 | |
64 | \note Remember to set the depth and stencil buffer sizes to 24 and 8 when |
65 | the renderer relies on depth or stencil testing, either in the global |
66 | default QSurfaceFormat, or, alternatively, separately in all the involved |
67 | QSurfaceFormat instances: in \c format, the format argument passed to |
68 | newFallbackSurface(), and on any QWindow that is used with the QRhi. |
69 | |
70 | A QSurface has to be specified in \c fallbackSurface. In order to prevent |
71 | mistakes in threaded situations, this is never created automatically by the |
72 | QRhi because, like QWindow, instances of QSurface subclasses can often be |
73 | created on the gui/main thread only. |
74 | |
75 | As a convenience, applications can use newFallbackSurface() which creates |
76 | and returns a QOffscreenSurface that is compatible with the QOpenGLContext |
77 | that is going to be created by the QRhi afterwards. Note that the ownership |
78 | of the returned QOffscreenSurface is transferred to the caller and the QRhi |
79 | will not destroy it. |
80 | |
81 | \note With the OpenGL backend, QRhiSwapChain can only target QWindow |
82 | instances that have their surface type set to QSurface::OpenGLSurface or |
83 | QSurface::RasterGLSurface. |
84 | |
85 | \note \c window is optional. It is recommended to specify it whenever |
86 | possible, in order to avoid problems on multi-adapter and multi-screen |
87 | systems. When \c window is not set, the very first |
88 | QOpenGLContext::makeCurrent() happens with \c fallbackSurface which may be |
89 | an invisible window on some platforms (for example, Windows) and that may |
90 | trigger unexpected problems in some cases. |
91 | |
92 | In case resource sharing with an existing QOpenGLContext is desired, \c |
93 | shareContext can be set to an existing QOpenGLContext. Alternatively, |
94 | Qt::AA_ShareOpenGLContexts is honored as well, when enabled. |
95 | |
96 | \section2 Working with existing OpenGL contexts |
97 | |
98 | When interoperating with another graphics engine, it may be necessary to |
99 | get a QRhi instance that uses the same OpenGL context. This can be achieved |
100 | by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The |
101 | \c{QRhiGles2NativeHandles::context} must be set to a non-null value then. |
102 | |
103 | An alternative approach is to create a QOpenGLContext that |
104 | \l{QOpenGLContext::setShareContext()}{shares resources} with the other |
105 | engine's context and passing in that context via QRhiGles2NativeHandles. |
106 | |
107 | The QRhi does not take ownership of the QOpenGLContext passed in via |
108 | QRhiGles2NativeHandles. |
109 | */ |
110 | |
111 | /*! |
112 | \variable QRhiGles2InitParams::format |
113 | |
114 | The QSurfaceFormat, initialized to QSurfaceFormat::defaultFormat() by default. |
115 | */ |
116 | |
117 | /*! |
118 | \variable QRhiGles2InitParams::fallbackSurface |
119 | |
120 | A QSurface compatible with \l format. Typically a QOffscreenSurface. |
121 | Providing this is mandatory. Be aware of the threading implications: a |
122 | QOffscreenSurface, like QWindow, must only ever be created and destroyed on |
123 | the main (gui) thread, even if the QRhi is created and operates on another |
124 | thread. |
125 | */ |
126 | |
127 | /*! |
128 | \variable QRhiGles2InitParams::window |
129 | |
130 | Optional, but setting it is recommended when targeting a QWindow with the |
131 | QRhi. |
132 | */ |
133 | |
134 | /*! |
135 | \variable QRhiGles2InitParams::shareContext |
136 | |
137 | Optional, the QOpenGLContext to share resource with. QRhi creates its own |
138 | context, and setting this member to a valid QOpenGLContext leads to calling |
139 | \l{QOpenGLContext::setShareContext()}{setShareContext()} with it. |
140 | */ |
141 | |
142 | /*! |
143 | \class QRhiGles2NativeHandles |
144 | \inmodule QtGuiPrivate |
145 | \inheaderfile rhi/qrhi.h |
146 | \since 6.6 |
147 | \brief Holds the OpenGL context used by the QRhi. |
148 | |
149 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
150 | for details. |
151 | */ |
152 | |
153 | /*! |
154 | \variable QRhiGles2NativeHandles::context |
155 | */ |
156 | |
157 | #ifndef GL_BGRA |
158 | #define GL_BGRA 0x80E1 |
159 | #endif |
160 | |
161 | #ifndef GL_R8 |
162 | #define GL_R8 0x8229 |
163 | #endif |
164 | |
165 | #ifndef GL_RG8 |
166 | #define GL_RG8 0x822B |
167 | #endif |
168 | |
169 | #ifndef GL_RG |
170 | #define GL_RG 0x8227 |
171 | #endif |
172 | |
173 | #ifndef GL_R16 |
174 | #define GL_R16 0x822A |
175 | #endif |
176 | |
177 | #ifndef GL_RG16 |
178 | #define GL_RG16 0x822C |
179 | #endif |
180 | |
181 | #ifndef GL_RED |
182 | #define GL_RED 0x1903 |
183 | #endif |
184 | |
185 | #ifndef GL_RGBA8 |
186 | #define GL_RGBA8 0x8058 |
187 | #endif |
188 | |
189 | #ifndef GL_RGBA32F |
190 | #define GL_RGBA32F 0x8814 |
191 | #endif |
192 | |
193 | #ifndef GL_RGBA16F |
194 | #define GL_RGBA16F 0x881A |
195 | #endif |
196 | |
197 | #ifndef GL_R16F |
198 | #define GL_R16F 0x822D |
199 | #endif |
200 | |
201 | #ifndef GL_R32F |
202 | #define GL_R32F 0x822E |
203 | #endif |
204 | |
205 | #ifndef GL_HALF_FLOAT |
206 | #define GL_HALF_FLOAT 0x140B |
207 | #endif |
208 | |
209 | #ifndef GL_DEPTH_COMPONENT16 |
210 | #define GL_DEPTH_COMPONENT16 0x81A5 |
211 | #endif |
212 | |
213 | #ifndef GL_DEPTH_COMPONENT24 |
214 | #define GL_DEPTH_COMPONENT24 0x81A6 |
215 | #endif |
216 | |
217 | #ifndef GL_DEPTH_COMPONENT32F |
218 | #define GL_DEPTH_COMPONENT32F 0x8CAC |
219 | #endif |
220 | |
221 | #ifndef GL_UNSIGNED_INT_24_8 |
222 | #define GL_UNSIGNED_INT_24_8 0x84FA |
223 | #endif |
224 | |
225 | #ifndef GL_STENCIL_INDEX |
226 | #define GL_STENCIL_INDEX 0x1901 |
227 | #endif |
228 | |
229 | #ifndef GL_STENCIL_INDEX8 |
230 | #define GL_STENCIL_INDEX8 0x8D48 |
231 | #endif |
232 | |
233 | #ifndef GL_DEPTH24_STENCIL8 |
234 | #define GL_DEPTH24_STENCIL8 0x88F0 |
235 | #endif |
236 | |
237 | #ifndef GL_DEPTH_STENCIL_ATTACHMENT |
238 | #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A |
239 | #endif |
240 | |
241 | #ifndef GL_DEPTH_STENCIL |
242 | #define GL_DEPTH_STENCIL 0x84F9 |
243 | #endif |
244 | |
245 | #ifndef GL_PRIMITIVE_RESTART_FIXED_INDEX |
246 | #define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 |
247 | #endif |
248 | |
249 | #ifndef GL_FRAMEBUFFER_SRGB |
250 | #define GL_FRAMEBUFFER_SRGB 0x8DB9 |
251 | #endif |
252 | |
253 | #ifndef GL_READ_FRAMEBUFFER |
254 | #define GL_READ_FRAMEBUFFER 0x8CA8 |
255 | #endif |
256 | |
257 | #ifndef GL_DRAW_FRAMEBUFFER |
258 | #define GL_DRAW_FRAMEBUFFER 0x8CA9 |
259 | #endif |
260 | |
261 | #ifndef GL_MAX_DRAW_BUFFERS |
262 | #define GL_MAX_DRAW_BUFFERS 0x8824 |
263 | #endif |
264 | |
265 | #ifndef GL_TEXTURE_COMPARE_MODE |
266 | #define GL_TEXTURE_COMPARE_MODE 0x884C |
267 | #endif |
268 | |
269 | #ifndef GL_COMPARE_REF_TO_TEXTURE |
270 | #define GL_COMPARE_REF_TO_TEXTURE 0x884E |
271 | #endif |
272 | |
273 | #ifndef GL_TEXTURE_COMPARE_FUNC |
274 | #define GL_TEXTURE_COMPARE_FUNC 0x884D |
275 | #endif |
276 | |
277 | #ifndef GL_MAX_SAMPLES |
278 | #define GL_MAX_SAMPLES 0x8D57 |
279 | #endif |
280 | |
281 | #ifndef GL_SHADER_STORAGE_BUFFER |
282 | #define GL_SHADER_STORAGE_BUFFER 0x90D2 |
283 | #endif |
284 | |
285 | #ifndef GL_READ_ONLY |
286 | #define GL_READ_ONLY 0x88B8 |
287 | #endif |
288 | |
289 | #ifndef GL_WRITE_ONLY |
290 | #define GL_WRITE_ONLY 0x88B9 |
291 | #endif |
292 | |
293 | #ifndef GL_READ_WRITE |
294 | #define GL_READ_WRITE 0x88BA |
295 | #endif |
296 | |
297 | #ifndef GL_COMPUTE_SHADER |
298 | #define GL_COMPUTE_SHADER 0x91B9 |
299 | #endif |
300 | |
301 | #ifndef GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT |
302 | #define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 |
303 | #endif |
304 | |
305 | #ifndef GL_ELEMENT_ARRAY_BARRIER_BIT |
306 | #define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 |
307 | #endif |
308 | |
309 | #ifndef GL_UNIFORM_BARRIER_BIT |
310 | #define GL_UNIFORM_BARRIER_BIT 0x00000004 |
311 | #endif |
312 | |
313 | #ifndef GL_BUFFER_UPDATE_BARRIER_BIT |
314 | #define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 |
315 | #endif |
316 | |
317 | #ifndef GL_SHADER_STORAGE_BARRIER_BIT |
318 | #define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 |
319 | #endif |
320 | |
321 | #ifndef GL_TEXTURE_FETCH_BARRIER_BIT |
322 | #define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 |
323 | #endif |
324 | |
325 | #ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT |
326 | #define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 |
327 | #endif |
328 | |
329 | #ifndef GL_PIXEL_BUFFER_BARRIER_BIT |
330 | #define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 |
331 | #endif |
332 | |
333 | #ifndef GL_TEXTURE_UPDATE_BARRIER_BIT |
334 | #define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 |
335 | #endif |
336 | |
337 | #ifndef GL_FRAMEBUFFER_BARRIER_BIT |
338 | #define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 |
339 | #endif |
340 | |
341 | #ifndef GL_ALL_BARRIER_BITS |
342 | #define GL_ALL_BARRIER_BITS 0xFFFFFFFF |
343 | #endif |
344 | |
345 | #ifndef GL_VERTEX_PROGRAM_POINT_SIZE |
346 | #define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 |
347 | #endif |
348 | |
349 | #ifndef GL_POINT_SPRITE |
350 | #define GL_POINT_SPRITE 0x8861 |
351 | #endif |
352 | |
353 | #ifndef GL_MAP_READ_BIT |
354 | #define GL_MAP_READ_BIT 0x0001 |
355 | #endif |
356 | |
357 | #ifndef GL_MAP_WRITE_BIT |
358 | #define GL_MAP_WRITE_BIT 0x0002 |
359 | #endif |
360 | |
361 | #ifndef GL_MAP_INVALIDATE_BUFFER_BIT |
362 | #define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 |
363 | #endif |
364 | |
365 | #ifndef GL_TEXTURE_2D_MULTISAMPLE |
366 | #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 |
367 | #endif |
368 | |
369 | #ifndef GL_TEXTURE_2D_MULTISAMPLE_ARRAY |
370 | #define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 |
371 | #endif |
372 | |
373 | #ifndef GL_TEXTURE_EXTERNAL_OES |
374 | #define GL_TEXTURE_EXTERNAL_OES 0x8D65 |
375 | #endif |
376 | |
377 | #ifndef GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS |
378 | #define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB |
379 | #endif |
380 | |
381 | #ifndef GL_MAX_COMPUTE_WORK_GROUP_COUNT |
382 | #define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE |
383 | #endif |
384 | |
385 | #ifndef GL_MAX_COMPUTE_WORK_GROUP_SIZE |
386 | #define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF |
387 | #endif |
388 | |
389 | #ifndef GL_TEXTURE_CUBE_MAP_SEAMLESS |
390 | #define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F |
391 | #endif |
392 | |
393 | #ifndef GL_CONTEXT_LOST |
394 | #define GL_CONTEXT_LOST 0x0507 |
395 | #endif |
396 | |
397 | #ifndef GL_PROGRAM_BINARY_LENGTH |
398 | #define GL_PROGRAM_BINARY_LENGTH 0x8741 |
399 | #endif |
400 | |
401 | #ifndef GL_NUM_PROGRAM_BINARY_FORMATS |
402 | #define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE |
403 | #endif |
404 | |
405 | #ifndef GL_UNPACK_ROW_LENGTH |
406 | #define GL_UNPACK_ROW_LENGTH 0x0CF2 |
407 | #endif |
408 | |
409 | #ifndef GL_TEXTURE_3D |
410 | #define GL_TEXTURE_3D 0x806F |
411 | #endif |
412 | |
413 | #ifndef GL_TEXTURE_WRAP_R |
414 | #define GL_TEXTURE_WRAP_R 0x8072 |
415 | #endif |
416 | |
417 | #ifndef GL_TEXTURE_RECTANGLE |
418 | #define GL_TEXTURE_RECTANGLE 0x84F5 |
419 | #endif |
420 | |
421 | #ifndef GL_TEXTURE_2D_ARRAY |
422 | #define GL_TEXTURE_2D_ARRAY 0x8C1A |
423 | #endif |
424 | |
425 | #ifndef GL_MAX_ARRAY_TEXTURE_LAYERS |
426 | #define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF |
427 | #endif |
428 | |
429 | #ifndef GL_MAX_VERTEX_UNIFORM_COMPONENTS |
430 | #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A |
431 | #endif |
432 | |
433 | #ifndef GL_MAX_FRAGMENT_UNIFORM_COMPONENTS |
434 | #define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 |
435 | #endif |
436 | |
437 | #ifndef GL_MAX_VERTEX_UNIFORM_VECTORS |
438 | #define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB |
439 | #endif |
440 | |
441 | #ifndef GL_MAX_FRAGMENT_UNIFORM_VECTORS |
442 | #define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD |
443 | #endif |
444 | |
445 | #ifndef GL_RGB10_A2 |
446 | #define GL_RGB10_A2 0x8059 |
447 | #endif |
448 | |
449 | #ifndef GL_UNSIGNED_INT_2_10_10_10_REV |
450 | #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 |
451 | #endif |
452 | |
453 | #ifndef GL_MAX_VARYING_COMPONENTS |
454 | #define GL_MAX_VARYING_COMPONENTS 0x8B4B |
455 | #endif |
456 | |
457 | #ifndef GL_MAX_VARYING_FLOATS |
458 | #define GL_MAX_VARYING_FLOATS 0x8B4B |
459 | #endif |
460 | |
461 | #ifndef GL_MAX_VARYING_VECTORS |
462 | #define GL_MAX_VARYING_VECTORS 0x8DFC |
463 | #endif |
464 | |
465 | #ifndef GL_TESS_CONTROL_SHADER |
466 | #define GL_TESS_CONTROL_SHADER 0x8E88 |
467 | #endif |
468 | |
469 | #ifndef GL_TESS_EVALUATION_SHADER |
470 | #define GL_TESS_EVALUATION_SHADER 0x8E87 |
471 | #endif |
472 | |
473 | #ifndef GL_PATCH_VERTICES |
474 | #define GL_PATCH_VERTICES 0x8E72 |
475 | #endif |
476 | |
477 | #ifndef GL_LINE |
478 | #define GL_LINE 0x1B01 |
479 | #endif |
480 | |
481 | #ifndef GL_FILL |
482 | #define GL_FILL 0x1B02 |
483 | #endif |
484 | |
485 | #ifndef GL_PATCHES |
486 | #define GL_PATCHES 0x000E |
487 | #endif |
488 | |
489 | #ifndef GL_GEOMETRY_SHADER |
490 | #define GL_GEOMETRY_SHADER 0x8DD9 |
491 | #endif |
492 | |
493 | #ifndef GL_BACK_LEFT |
494 | #define GL_BACK_LEFT 0x0402 |
495 | #endif |
496 | |
497 | #ifndef GL_BACK_RIGHT |
498 | #define GL_BACK_RIGHT 0x0403 |
499 | #endif |
500 | |
501 | #ifndef GL_TEXTURE_1D |
502 | # define GL_TEXTURE_1D 0x0DE0 |
503 | #endif |
504 | |
505 | #ifndef GL_TEXTURE_1D_ARRAY |
506 | # define GL_TEXTURE_1D_ARRAY 0x8C18 |
507 | #endif |
508 | |
509 | #ifndef GL_HALF_FLOAT |
510 | #define GL_HALF_FLOAT 0x140B |
511 | #endif |
512 | |
513 | #ifndef GL_MAX_VERTEX_OUTPUT_COMPONENTS |
514 | #define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 |
515 | #endif |
516 | |
517 | #ifndef GL_TIMESTAMP |
518 | #define GL_TIMESTAMP 0x8E28 |
519 | #endif |
520 | |
521 | #ifndef GL_QUERY_RESULT |
522 | #define GL_QUERY_RESULT 0x8866 |
523 | #endif |
524 | |
525 | #ifndef GL_QUERY_RESULT_AVAILABLE |
526 | #define GL_QUERY_RESULT_AVAILABLE 0x8867 |
527 | #endif |
528 | |
529 | #ifndef GL_BUFFER |
530 | #define GL_BUFFER 0x82E0 |
531 | #endif |
532 | |
533 | #ifndef GL_PROGRAM |
534 | #define GL_PROGRAM 0x82E2 |
535 | #endif |
536 | |
537 | /*! |
538 | Constructs a new QRhiGles2InitParams. |
539 | |
540 | \l format is set to QSurfaceFormat::defaultFormat(). |
541 | */ |
542 | QRhiGles2InitParams::QRhiGles2InitParams() |
543 | { |
544 | format = QSurfaceFormat::defaultFormat(); |
545 | } |
546 | |
547 | /*! |
548 | \return a new QOffscreenSurface that can be used with a QRhi by passing it |
549 | via a QRhiGles2InitParams. |
550 | |
551 | When \a format is not specified, its default value is the global default |
552 | format settable via QSurfaceFormat::setDefaultFormat(). |
553 | |
554 | \a format is adjusted as appropriate in order to avoid having problems |
555 | afterwards due to an incompatible context and surface. |
556 | |
557 | \note This function must only be called on the gui/main thread. |
558 | |
559 | \note It is the application's responsibility to destroy the returned |
560 | QOffscreenSurface on the gui/main thread once the associated QRhi has been |
561 | destroyed. The QRhi will not destroy the QOffscreenSurface. |
562 | */ |
563 | QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format) |
564 | { |
565 | QSurfaceFormat fmt = format; |
566 | |
567 | // To resolve all fields in the format as much as possible, create a context. |
568 | // This may be heavy, but allows avoiding BAD_MATCH on some systems. |
569 | QOpenGLContext tempContext; |
570 | tempContext.setFormat(fmt); |
571 | if (tempContext.create()) |
572 | fmt = tempContext.format(); |
573 | else |
574 | qWarning(msg: "QRhiGles2: Failed to create temporary context"); |
575 | |
576 | QOffscreenSurface *s = new QOffscreenSurface; |
577 | s->setFormat(fmt); |
578 | s->create(); |
579 | |
580 | return s; |
581 | } |
582 | |
583 | QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice) |
584 | : ofr(this) |
585 | { |
586 | requestedFormat = params->format; |
587 | fallbackSurface = params->fallbackSurface; |
588 | maybeWindow = params->window; // may be null |
589 | maybeShareContext = params->shareContext; // may be null |
590 | |
591 | importedContext = importDevice != nullptr; |
592 | if (importedContext) { |
593 | ctx = importDevice->context; |
594 | if (!ctx) { |
595 | qWarning(msg: "No OpenGL context given, cannot import"); |
596 | importedContext = false; |
597 | } |
598 | } |
599 | } |
600 | |
601 | static inline QSurface *currentSurfaceForCurrentContext(QOpenGLContext *ctx) |
602 | { |
603 | if (QOpenGLContext::currentContext() != ctx) |
604 | return nullptr; |
605 | |
606 | QSurface *currentSurface = ctx->surface(); |
607 | if (!currentSurface) |
608 | return nullptr; |
609 | |
610 | if (currentSurface->surfaceClass() == QSurface::Window && !currentSurface->surfaceHandle()) |
611 | return nullptr; |
612 | |
613 | return currentSurface; |
614 | } |
615 | |
616 | QSurface *QRhiGles2::evaluateFallbackSurface() const |
617 | { |
618 | // With Apple's deprecated OpenGL support we need to minimize the usage of |
619 | // QOffscreenSurface since delicate problems can pop up with |
620 | // NSOpenGLContext and drawables. |
621 | #if defined(Q_OS_MACOS) |
622 | return maybeWindow && maybeWindow->handle() ? static_cast<QSurface *>(maybeWindow) : fallbackSurface; |
623 | #else |
624 | return fallbackSurface; |
625 | #endif |
626 | } |
627 | |
628 | bool QRhiGles2::ensureContext(QSurface *surface) const |
629 | { |
630 | if (!surface) { |
631 | // null means any surface is good because not going to render |
632 | if (currentSurfaceForCurrentContext(ctx)) |
633 | return true; |
634 | // if the context is not already current with a valid surface, use our |
635 | // fallback surface, but platform specific quirks may apply |
636 | surface = evaluateFallbackSurface(); |
637 | } else if (surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) { |
638 | // the window is not usable anymore (no native window underneath), behave as if offscreen |
639 | surface = evaluateFallbackSurface(); |
640 | } else if (!needsMakeCurrentDueToSwap && currentSurfaceForCurrentContext(ctx) == surface) { |
641 | // bail out if the makeCurrent is not necessary |
642 | return true; |
643 | } |
644 | needsMakeCurrentDueToSwap = false; |
645 | |
646 | if (!ctx->makeCurrent(surface)) { |
647 | if (ctx->isValid()) { |
648 | qWarning(msg: "QRhiGles2: Failed to make context current. Expect bad things to happen."); |
649 | } else { |
650 | qWarning(msg: "QRhiGles2: Context is lost."); |
651 | contextLost = true; |
652 | } |
653 | return false; |
654 | } |
655 | |
656 | return true; |
657 | } |
658 | |
659 | static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) |
660 | { |
661 | const bool srgb = flags.testFlag(flag: QRhiTexture::sRGB); |
662 | switch (format) { |
663 | case QRhiTexture::BC1: |
664 | return srgb ? 0x8C4C : 0x83F0; |
665 | case QRhiTexture::BC2: |
666 | return srgb ? 0x8C4E : 0x83F2; |
667 | case QRhiTexture::BC3: |
668 | return srgb ? 0x8C4F : 0x83F3; |
669 | |
670 | case QRhiTexture::ETC2_RGB8: |
671 | return srgb ? 0x9275 : 0x9274; |
672 | case QRhiTexture::ETC2_RGB8A1: |
673 | return srgb ? 0x9277 : 0x9276; |
674 | case QRhiTexture::ETC2_RGBA8: |
675 | return srgb ? 0x9279 : 0x9278; |
676 | |
677 | case QRhiTexture::ASTC_4x4: |
678 | return srgb ? 0x93D0 : 0x93B0; |
679 | case QRhiTexture::ASTC_5x4: |
680 | return srgb ? 0x93D1 : 0x93B1; |
681 | case QRhiTexture::ASTC_5x5: |
682 | return srgb ? 0x93D2 : 0x93B2; |
683 | case QRhiTexture::ASTC_6x5: |
684 | return srgb ? 0x93D3 : 0x93B3; |
685 | case QRhiTexture::ASTC_6x6: |
686 | return srgb ? 0x93D4 : 0x93B4; |
687 | case QRhiTexture::ASTC_8x5: |
688 | return srgb ? 0x93D5 : 0x93B5; |
689 | case QRhiTexture::ASTC_8x6: |
690 | return srgb ? 0x93D6 : 0x93B6; |
691 | case QRhiTexture::ASTC_8x8: |
692 | return srgb ? 0x93D7 : 0x93B7; |
693 | case QRhiTexture::ASTC_10x5: |
694 | return srgb ? 0x93D8 : 0x93B8; |
695 | case QRhiTexture::ASTC_10x6: |
696 | return srgb ? 0x93D9 : 0x93B9; |
697 | case QRhiTexture::ASTC_10x8: |
698 | return srgb ? 0x93DA : 0x93BA; |
699 | case QRhiTexture::ASTC_10x10: |
700 | return srgb ? 0x93DB : 0x93BB; |
701 | case QRhiTexture::ASTC_12x10: |
702 | return srgb ? 0x93DC : 0x93BC; |
703 | case QRhiTexture::ASTC_12x12: |
704 | return srgb ? 0x93DD : 0x93BD; |
705 | |
706 | default: |
707 | return 0; // this is reachable, just return an invalid format |
708 | } |
709 | } |
710 | |
711 | bool QRhiGles2::create(QRhi::Flags flags) |
712 | { |
713 | Q_ASSERT(fallbackSurface); |
714 | rhiFlags = flags; |
715 | |
716 | if (!importedContext) { |
717 | ctx = new QOpenGLContext; |
718 | ctx->setFormat(requestedFormat); |
719 | if (maybeShareContext) { |
720 | ctx->setShareContext(maybeShareContext); |
721 | ctx->setScreen(maybeShareContext->screen()); |
722 | } else if (QOpenGLContext *shareContext = qt_gl_global_share_context()) { |
723 | ctx->setShareContext(shareContext); |
724 | ctx->setScreen(shareContext->screen()); |
725 | } else if (maybeWindow) { |
726 | ctx->setScreen(maybeWindow->screen()); |
727 | } |
728 | if (!ctx->create()) { |
729 | qWarning(msg: "QRhiGles2: Failed to create context"); |
730 | delete ctx; |
731 | ctx = nullptr; |
732 | return false; |
733 | } |
734 | qCDebug(QRHI_LOG_INFO) << "Created OpenGL context"<< ctx->format(); |
735 | } |
736 | |
737 | if (!ensureContext(surface: maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments |
738 | return false; |
739 | |
740 | f = static_cast<QOpenGLExtensions *>(ctx->extraFunctions()); |
741 | const QSurfaceFormat actualFormat = ctx->format(); |
742 | caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES; |
743 | |
744 | if (!caps.gles) { |
745 | glPolygonMode = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum)>( |
746 | ctx->getProcAddress(QByteArrayLiteral("glPolygonMode"))); |
747 | |
748 | glTexImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( |
749 | GLenum, GLint, GLint, GLsizei, GLint, GLenum, GLenum, const void *)>( |
750 | ctx->getProcAddress(QByteArrayLiteral("glTexImage1D"))); |
751 | |
752 | glTexStorage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLint, GLenum, GLsizei)>( |
753 | ctx->getProcAddress(QByteArrayLiteral("glTexStorage1D"))); |
754 | |
755 | glTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( |
756 | GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *)>( |
757 | ctx->getProcAddress(QByteArrayLiteral("glTexSubImage1D"))); |
758 | |
759 | glCopyTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLint, GLint, GLint, |
760 | GLint, GLsizei)>( |
761 | ctx->getProcAddress(QByteArrayLiteral("glCopyTexSubImage1D"))); |
762 | |
763 | glCompressedTexImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( |
764 | GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *)>( |
765 | ctx->getProcAddress(QByteArrayLiteral("glCompressedTexImage1D"))); |
766 | |
767 | glCompressedTexSubImage1D = reinterpret_cast<void(QOPENGLF_APIENTRYP)( |
768 | GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *)>( |
769 | ctx->getProcAddress(QByteArrayLiteral("glCompressedTexSubImage1D"))); |
770 | |
771 | glFramebufferTexture1D = |
772 | reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLenum, GLuint, GLint)>( |
773 | ctx->getProcAddress(QByteArrayLiteral("glFramebufferTexture1D"))); |
774 | } |
775 | |
776 | const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR)); |
777 | const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER)); |
778 | const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION)); |
779 | if (vendor && renderer && version) |
780 | qCDebug(QRHI_LOG_INFO, "OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version); |
781 | |
782 | if (vendor) { |
783 | driverInfoStruct.deviceName += QByteArray(vendor); |
784 | driverInfoStruct.deviceName += ' '; |
785 | } |
786 | if (renderer) { |
787 | driverInfoStruct.deviceName += QByteArray(renderer); |
788 | driverInfoStruct.deviceName += ' '; |
789 | } |
790 | if (version) |
791 | driverInfoStruct.deviceName += QByteArray(version); |
792 | |
793 | caps.ctxMajor = actualFormat.majorVersion(); |
794 | caps.ctxMinor = actualFormat.minorVersion(); |
795 | |
796 | GLint n = 0; |
797 | f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, params: &n); |
798 | if (n > 0) { |
799 | QVarLengthArray<GLint, 16> compressedTextureFormats(n); |
800 | f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, params: compressedTextureFormats.data()); |
801 | for (GLint format : compressedTextureFormats) |
802 | supportedCompressedFormats.insert(value: format); |
803 | |
804 | } |
805 | // The above looks nice, if only it worked always. With GLES the list we |
806 | // query is likely the full list of compressed formats (mostly anything |
807 | // that can be decoded). With OpenGL however the list is not required to |
808 | // include all formats due to the way the spec is worded. For instance, we |
809 | // cannot rely on ASTC formats being present in the list on non-ES. Some |
810 | // drivers do include them (Intel, NVIDIA), some don't (Mesa). On the other |
811 | // hand, relying on extension strings only is not ok: for example, Intel |
812 | // reports GL_KHR_texture_compression_astc_ldr whereas NVIDIA doesn't. So |
813 | // the only reasonable thing to do is to query the list always and then see |
814 | // if there is something we can add - if not already in there. |
815 | std::array<QRhiTexture::Flags, 2> textureVariantFlags; |
816 | textureVariantFlags[0] = {}; |
817 | textureVariantFlags[1] = QRhiTexture::sRGB; |
818 | if (f->hasOpenGLExtension(extension: QOpenGLExtensions::DDSTextureCompression)) { |
819 | for (QRhiTexture::Flags f : textureVariantFlags) { |
820 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::BC1, flags: f)); |
821 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::BC2, flags: f)); |
822 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::BC3, flags: f)); |
823 | } |
824 | } |
825 | if (f->hasOpenGLExtension(extension: QOpenGLExtensions::ETC2TextureCompression)) { |
826 | for (QRhiTexture::Flags f : textureVariantFlags) { |
827 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ETC2_RGB8, flags: f)); |
828 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ETC2_RGB8A1, flags: f)); |
829 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ETC2_RGBA8, flags: f)); |
830 | } |
831 | } |
832 | if (f->hasOpenGLExtension(extension: QOpenGLExtensions::ASTCTextureCompression)) { |
833 | for (QRhiTexture::Flags f : textureVariantFlags) { |
834 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_4x4, flags: f)); |
835 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_5x4, flags: f)); |
836 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_5x5, flags: f)); |
837 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_6x5, flags: f)); |
838 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_6x6, flags: f)); |
839 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_8x5, flags: f)); |
840 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_8x6, flags: f)); |
841 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_8x8, flags: f)); |
842 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_10x5, flags: f)); |
843 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_10x8, flags: f)); |
844 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_10x10, flags: f)); |
845 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_12x10, flags: f)); |
846 | supportedCompressedFormats.insert(value: toGlCompressedTextureFormat(format: QRhiTexture::ASTC_12x12, flags: f)); |
847 | } |
848 | } |
849 | |
850 | f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &caps.maxTextureSize); |
851 | |
852 | if (!caps.gles || caps.ctxMajor >= 3) { |
853 | // non-ES or ES 3.0+ |
854 | f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, params: &caps.maxDrawBuffers); |
855 | caps.hasDrawBuffersFunc = true; |
856 | f->glGetIntegerv(GL_MAX_SAMPLES, params: &caps.maxSamples); |
857 | caps.maxSamples = qMax(a: 1, b: caps.maxSamples); |
858 | } else { |
859 | // ES 2.0 / WebGL 1 |
860 | caps.maxDrawBuffers = 1; |
861 | caps.hasDrawBuffersFunc = false; |
862 | // This does not mean MSAA is not supported, just that we cannot query |
863 | // the supported sample counts. Assume that 4x is always supported. |
864 | caps.maxSamples = 4; |
865 | } |
866 | |
867 | caps.msaaRenderBuffer = f->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferMultisample) |
868 | && f->hasOpenGLExtension(extension: QOpenGLExtensions::FramebufferBlit); |
869 | |
870 | caps.npotTextureFull = f->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextures) |
871 | && f->hasOpenGLFeature(feature: QOpenGLFunctions::NPOTTextureRepeat); |
872 | |
873 | if (caps.gles) |
874 | caps.fixedIndexPrimitiveRestart = caps.ctxMajor >= 3; // ES 3.0 |
875 | else |
876 | caps.fixedIndexPrimitiveRestart = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3 |
877 | |
878 | if (caps.fixedIndexPrimitiveRestart) { |
879 | #ifdef Q_OS_WASM |
880 | // WebGL 2 behaves as if GL_PRIMITIVE_RESTART_FIXED_INDEX was always |
881 | // enabled (i.e. matching D3D/Metal), and the value cannot be passed to |
882 | // glEnable, so skip the call. |
883 | #else |
884 | f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); |
885 | #endif |
886 | } |
887 | |
888 | caps.bgraExternalFormat = f->hasOpenGLExtension(extension: QOpenGLExtensions::BGRATextureFormat); |
889 | caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles; |
890 | caps.r8Format = f->hasOpenGLFeature(feature: QOpenGLFunctions::TextureRGFormats); |
891 | caps.r16Format = f->hasOpenGLExtension(extension: QOpenGLExtensions::Sized16Formats); |
892 | caps.floatFormats = caps.ctxMajor >= 3; // 3.0 or ES 3.0 |
893 | caps.rgb10Formats = caps.ctxMajor >= 3; // 3.0 or ES 3.0 |
894 | caps.depthTexture = caps.ctxMajor >= 3; // 3.0 or ES 3.0 |
895 | caps.packedDepthStencil = f->hasOpenGLExtension(extension: QOpenGLExtensions::PackedDepthStencil); |
896 | #ifdef Q_OS_WASM |
897 | caps.needsDepthStencilCombinedAttach = true; |
898 | #else |
899 | caps.needsDepthStencilCombinedAttach = false; |
900 | #endif |
901 | |
902 | // QOpenGLExtensions::SRGBFrameBuffer is not useful here. We need to know if |
903 | // controlling the sRGB-on-shader-write state is supported, not that if the |
904 | // default framebuffer is sRGB-capable. And there are two different |
905 | // extensions for desktop and ES. |
906 | caps.srgbWriteControl = ctx->hasExtension(extension: "GL_EXT_framebuffer_sRGB") || ctx->hasExtension(extension: "GL_EXT_sRGB_write_control"); |
907 | |
908 | caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile; |
909 | |
910 | if (caps.gles) |
911 | caps.uniformBuffers = caps.ctxMajor >= 3; // ES 3.0 |
912 | else |
913 | caps.uniformBuffers = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // 3.1 |
914 | |
915 | caps.elementIndexUint = f->hasOpenGLExtension(extension: QOpenGLExtensions::ElementIndexUint); |
916 | caps.depth24 = f->hasOpenGLExtension(extension: QOpenGLExtensions::Depth24); |
917 | caps.rgba8Format = f->hasOpenGLExtension(extension: QOpenGLExtensions::Sized8Formats); |
918 | |
919 | if (caps.gles) |
920 | caps.instancing = caps.ctxMajor >= 3; // ES 3.0 |
921 | else |
922 | caps.instancing = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3); // 3.3 |
923 | |
924 | caps.baseVertex = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // 3.2 or ES 3.2 |
925 | |
926 | if (caps.gles) |
927 | caps.compute = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1 |
928 | else |
929 | caps.compute = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); // 4.3 |
930 | |
931 | if (caps.compute) { |
932 | f->glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, params: &caps.maxThreadsPerThreadGroup); |
933 | GLint tgPerDim[3]; |
934 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, index: 0, data: &tgPerDim[0]); |
935 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, index: 1, data: &tgPerDim[1]); |
936 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, index: 2, data: &tgPerDim[2]); |
937 | caps.maxThreadGroupsPerDimension = qMin(a: tgPerDim[0], b: qMin(a: tgPerDim[1], b: tgPerDim[2])); |
938 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index: 0, data: &caps.maxThreadGroupsX); |
939 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index: 1, data: &caps.maxThreadGroupsY); |
940 | f->glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index: 2, data: &caps.maxThreadGroupsZ); |
941 | } |
942 | |
943 | if (caps.gles) |
944 | caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0 |
945 | else |
946 | caps.textureCompareMode = true; |
947 | |
948 | // proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer |
949 | // extension(s) (which is not in ES 3.0...messy) |
950 | caps.properMapBuffer = f->hasOpenGLExtension(extension: QOpenGLExtensions::MapBufferRange); |
951 | |
952 | if (caps.gles) |
953 | caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0 |
954 | else |
955 | caps.nonBaseLevelFramebufferTexture = true; |
956 | |
957 | caps.texelFetch = caps.ctxMajor >= 3; // 3.0 or ES 3.0 |
958 | caps.intAttributes = caps.ctxMajor >= 3; // 3.0 or ES 3.0 |
959 | caps.screenSpaceDerivatives = f->hasOpenGLExtension(extension: QOpenGLExtensions::StandardDerivatives); |
960 | |
961 | if (caps.gles) |
962 | caps.multisampledTexture = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 1); // ES 3.1 |
963 | else |
964 | caps.multisampledTexture = caps.ctxMajor >= 3; // 3.0 |
965 | |
966 | // Program binary support: only the core stuff, do not bother with the old |
967 | // extensions like GL_OES_get_program_binary |
968 | if (caps.gles) |
969 | caps.programBinary = caps.ctxMajor >= 3; // ES 3.0 |
970 | else |
971 | caps.programBinary = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 1); // 4.1 |
972 | |
973 | if (caps.programBinary) { |
974 | GLint fmtCount = 0; |
975 | f->glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, params: &fmtCount); |
976 | if (fmtCount < 1) |
977 | caps.programBinary = false; |
978 | } |
979 | |
980 | caps.texture3D = caps.ctxMajor >= 3; // 3.0 |
981 | |
982 | if (caps.gles) |
983 | caps.texture1D = false; // ES |
984 | else |
985 | caps.texture1D = glTexImage1D && (caps.ctxMajor >= 2); // 2.0 |
986 | |
987 | if (caps.gles) |
988 | caps.tessellation = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // ES 3.2 |
989 | else |
990 | caps.tessellation = caps.ctxMajor >= 4; // 4.0 |
991 | |
992 | if (caps.gles) |
993 | caps.geometryShader = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // ES 3.2 |
994 | else |
995 | caps.geometryShader = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // 3.2 |
996 | |
997 | if (caps.ctxMajor >= 3) { // 3.0 or ES 3.0 |
998 | GLint maxArraySize = 0; |
999 | f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, params: &maxArraySize); |
1000 | caps.maxTextureArraySize = maxArraySize; |
1001 | } else { |
1002 | caps.maxTextureArraySize = 0; |
1003 | } |
1004 | |
1005 | // The ES 2.0 spec only has MAX_xxxx_VECTORS. ES 3.0 and up has both |
1006 | // *VECTORS and *COMPONENTS. OpenGL 2.0-4.0 only has MAX_xxxx_COMPONENTS. |
1007 | // 4.1 and above has both. What a mess. |
1008 | if (caps.gles) { |
1009 | GLint maxVertexUniformVectors = 0; |
1010 | f->glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, params: &maxVertexUniformVectors); |
1011 | GLint maxFragmentUniformVectors = 0; |
1012 | f->glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, params: &maxFragmentUniformVectors); |
1013 | caps.maxUniformVectors = qMin(a: maxVertexUniformVectors, b: maxFragmentUniformVectors); |
1014 | } else { |
1015 | GLint maxVertexUniformComponents = 0; |
1016 | f->glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, params: &maxVertexUniformComponents); |
1017 | GLint maxFragmentUniformComponents = 0; |
1018 | f->glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, params: &maxFragmentUniformComponents); |
1019 | caps.maxUniformVectors = qMin(a: maxVertexUniformComponents, b: maxFragmentUniformComponents) / 4; |
1020 | } |
1021 | |
1022 | f->glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, params: &caps.maxVertexInputs); |
1023 | |
1024 | if (caps.gles) { |
1025 | f->glGetIntegerv(GL_MAX_VARYING_VECTORS, params: &caps.maxVertexOutputs); |
1026 | } else if (caps.ctxMajor >= 3) { |
1027 | GLint components = 0; |
1028 | f->glGetIntegerv(pname: caps.coreProfile ? GL_MAX_VERTEX_OUTPUT_COMPONENTS : GL_MAX_VARYING_COMPONENTS, params: &components); |
1029 | caps.maxVertexOutputs = components / 4; |
1030 | } else { |
1031 | // OpenGL before 3.0 only has this, and not the same as |
1032 | // MAX_VARYING_COMPONENTS strictly speaking, but will do. |
1033 | GLint components = 0; |
1034 | f->glGetIntegerv(GL_MAX_VARYING_FLOATS, params: &components); |
1035 | if (components > 0) |
1036 | caps.maxVertexOutputs = components / 4; |
1037 | } |
1038 | |
1039 | if (!caps.gles) { |
1040 | f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); |
1041 | if (!caps.coreProfile) |
1042 | f->glEnable(GL_POINT_SPRITE); |
1043 | } // else (with gles) these are always on |
1044 | |
1045 | // Match D3D and others when it comes to seamless cubemap filtering. |
1046 | // ES 3.0+ has this always enabled. (hopefully) |
1047 | // ES 2.0 and GL < 3.2 will not have it. |
1048 | if (!caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2))) |
1049 | f->glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); |
1050 | |
1051 | caps.halfAttributes = f->hasOpenGLExtension(extension: QOpenGLExtensions::HalfFloatVertex); |
1052 | |
1053 | // We always require GL_OVR_multiview2 for symmetry with other backends. |
1054 | caps.multiView = f->hasOpenGLExtension(extension: QOpenGLExtensions::MultiView) |
1055 | && f->hasOpenGLExtension(extension: QOpenGLExtensions::MultiViewExtended); |
1056 | if (caps.multiView) { |
1057 | glFramebufferTextureMultiviewOVR = |
1058 | reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei)>( |
1059 | ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultiviewOVR"))); |
1060 | } |
1061 | |
1062 | // Only do timestamp queries on OpenGL 3.3+. |
1063 | caps.timestamps = !caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3)); |
1064 | if (caps.timestamps) { |
1065 | glQueryCounter = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum)>( |
1066 | ctx->getProcAddress(QByteArrayLiteral("glQueryCounter"))); |
1067 | glGetQueryObjectui64v = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum, quint64 *)>( |
1068 | ctx->getProcAddress(QByteArrayLiteral("glGetQueryObjectui64v"))); |
1069 | if (!glQueryCounter || !glGetQueryObjectui64v) |
1070 | caps.timestamps = false; |
1071 | } |
1072 | |
1073 | // glObjectLabel is available on OpenGL ES 3.2+ and OpenGL 4.3+ |
1074 | if (caps.gles) |
1075 | caps.objectLabel = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); |
1076 | else |
1077 | caps.objectLabel = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3); |
1078 | if (caps.objectLabel) { |
1079 | glObjectLabel = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLuint, GLsizei, const GLchar *)>( |
1080 | ctx->getProcAddress(QByteArrayLiteral("glObjectLabel"))); |
1081 | } |
1082 | |
1083 | if (caps.gles) { |
1084 | // This is the third way to get multisample rendering with GLES. (1. is |
1085 | // multisample render buffer -> resolve to texture; 2. is multisample |
1086 | // texture with GLES 3.1; 3. is this, avoiding the explicit multisample |
1087 | // buffer and should be more efficient with tiled architectures. |
1088 | // Interesting also because 2. does not seem to work in practice on |
1089 | // devices such as the Quest 3) |
1090 | caps.glesMultisampleRenderToTexture = ctx->hasExtension(extension: "GL_EXT_multisampled_render_to_texture"); |
1091 | if (caps.glesMultisampleRenderToTexture) { |
1092 | glFramebufferTexture2DMultisampleEXT = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei)>( |
1093 | ctx->getProcAddress(QByteArrayLiteral("glFramebufferTexture2DMultisampleEXT"))); |
1094 | } |
1095 | caps.glesMultiviewMultisampleRenderToTexture = ctx->hasExtension(extension: "GL_OVR_multiview_multisampled_render_to_texture"); |
1096 | if (caps.glesMultiviewMultisampleRenderToTexture) { |
1097 | glFramebufferTextureMultisampleMultiviewOVR = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLsizei, GLint, GLsizei)>( |
1098 | ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultisampleMultiviewOVR"))); |
1099 | } |
1100 | } else { |
1101 | caps.glesMultisampleRenderToTexture = false; |
1102 | caps.glesMultiviewMultisampleRenderToTexture = false; |
1103 | } |
1104 | |
1105 | caps.unpackRowLength = !caps.gles || caps.ctxMajor >= 3; |
1106 | |
1107 | nativeHandlesStruct.context = ctx; |
1108 | |
1109 | contextLost = false; |
1110 | |
1111 | return true; |
1112 | } |
1113 | |
1114 | void QRhiGles2::destroy() |
1115 | { |
1116 | if (!f) |
1117 | return; |
1118 | |
1119 | ensureContext(); |
1120 | executeDeferredReleases(); |
1121 | |
1122 | if (ofr.tsQueries[0]) { |
1123 | f->glDeleteQueries(n: 2, ids: ofr.tsQueries); |
1124 | ofr.tsQueries[0] = ofr.tsQueries[1] = 0; |
1125 | } |
1126 | |
1127 | if (vao) { |
1128 | f->glDeleteVertexArrays(n: 1, arrays: &vao); |
1129 | vao = 0; |
1130 | } |
1131 | |
1132 | for (uint shader : m_shaderCache) |
1133 | f->glDeleteShader(shader); |
1134 | m_shaderCache.clear(); |
1135 | |
1136 | if (!importedContext) { |
1137 | delete ctx; |
1138 | ctx = nullptr; |
1139 | } |
1140 | |
1141 | f = nullptr; |
1142 | } |
1143 | |
1144 | void QRhiGles2::executeDeferredReleases() |
1145 | { |
1146 | for (int i = releaseQueue.size() - 1; i >= 0; --i) { |
1147 | const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]); |
1148 | switch (e.type) { |
1149 | case QRhiGles2::DeferredReleaseEntry::Buffer: |
1150 | f->glDeleteBuffers(n: 1, buffers: &e.buffer.buffer); |
1151 | break; |
1152 | case QRhiGles2::DeferredReleaseEntry::Pipeline: |
1153 | f->glDeleteProgram(program: e.pipeline.program); |
1154 | break; |
1155 | case QRhiGles2::DeferredReleaseEntry::Texture: |
1156 | f->glDeleteTextures(n: 1, textures: &e.texture.texture); |
1157 | break; |
1158 | case QRhiGles2::DeferredReleaseEntry::RenderBuffer: |
1159 | f->glDeleteRenderbuffers(n: 1, renderbuffers: &e.renderbuffer.renderbuffer); |
1160 | f->glDeleteRenderbuffers(n: 1, renderbuffers: &e.renderbuffer.renderbuffer2); |
1161 | break; |
1162 | case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget: |
1163 | f->glDeleteFramebuffers(n: 1, framebuffers: &e.textureRenderTarget.framebuffer); |
1164 | f->glDeleteTextures(n: 1, textures: &e.textureRenderTarget.nonMsaaThrowawayDepthTexture); |
1165 | break; |
1166 | default: |
1167 | Q_UNREACHABLE(); |
1168 | break; |
1169 | } |
1170 | releaseQueue.removeAt(i); |
1171 | } |
1172 | } |
1173 | |
1174 | QList<int> QRhiGles2::supportedSampleCounts() const |
1175 | { |
1176 | if (supportedSampleCountList.isEmpty()) { |
1177 | // 1, 2, 4, 8, ... |
1178 | for (int i = 1; i <= caps.maxSamples; i *= 2) |
1179 | supportedSampleCountList.append(t: i); |
1180 | } |
1181 | return supportedSampleCountList; |
1182 | } |
1183 | |
1184 | QRhiSwapChain *QRhiGles2::createSwapChain() |
1185 | { |
1186 | return new QGles2SwapChain(this); |
1187 | } |
1188 | |
1189 | QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) |
1190 | { |
1191 | return new QGles2Buffer(this, type, usage, size); |
1192 | } |
1193 | |
1194 | int QRhiGles2::ubufAlignment() const |
1195 | { |
1196 | // No real uniform buffers are used so no need to pretend there is any |
1197 | // alignment requirement. |
1198 | return 1; |
1199 | } |
1200 | |
1201 | bool QRhiGles2::isYUpInFramebuffer() const |
1202 | { |
1203 | return true; |
1204 | } |
1205 | |
1206 | bool QRhiGles2::isYUpInNDC() const |
1207 | { |
1208 | return true; |
1209 | } |
1210 | |
1211 | bool QRhiGles2::isClipDepthZeroToOne() const |
1212 | { |
1213 | return false; |
1214 | } |
1215 | |
1216 | QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const |
1217 | { |
1218 | return QMatrix4x4(); // identity |
1219 | } |
1220 | |
1221 | static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2::Caps &caps, |
1222 | GLenum *glintformat, GLenum *glsizedintformat, |
1223 | GLenum *glformat, GLenum *gltype) |
1224 | { |
1225 | switch (format) { |
1226 | case QRhiTexture::RGBA8: |
1227 | *glintformat = GL_RGBA; |
1228 | *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; |
1229 | *glformat = GL_RGBA; |
1230 | *gltype = GL_UNSIGNED_BYTE; |
1231 | break; |
1232 | case QRhiTexture::BGRA8: |
1233 | *glintformat = caps.bgraInternalFormat ? GL_BGRA : GL_RGBA; |
1234 | *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; |
1235 | *glformat = GL_BGRA; |
1236 | *gltype = GL_UNSIGNED_BYTE; |
1237 | break; |
1238 | case QRhiTexture::R16: |
1239 | *glintformat = GL_R16; |
1240 | *glsizedintformat = *glintformat; |
1241 | *glformat = GL_RED; |
1242 | *gltype = GL_UNSIGNED_SHORT; |
1243 | break; |
1244 | case QRhiTexture::RG16: |
1245 | *glintformat = GL_RG16; |
1246 | *glsizedintformat = *glintformat; |
1247 | *glformat = GL_RG; |
1248 | *gltype = GL_UNSIGNED_SHORT; |
1249 | break; |
1250 | case QRhiTexture::R8: |
1251 | *glintformat = GL_R8; |
1252 | *glsizedintformat = *glintformat; |
1253 | *glformat = GL_RED; |
1254 | *gltype = GL_UNSIGNED_BYTE; |
1255 | break; |
1256 | case QRhiTexture::RG8: |
1257 | *glintformat = GL_RG8; |
1258 | *glsizedintformat = *glintformat; |
1259 | *glformat = GL_RG; |
1260 | *gltype = GL_UNSIGNED_BYTE; |
1261 | break; |
1262 | case QRhiTexture::RED_OR_ALPHA8: |
1263 | *glintformat = caps.coreProfile ? GL_R8 : GL_ALPHA; |
1264 | *glsizedintformat = *glintformat; |
1265 | *glformat = caps.coreProfile ? GL_RED : GL_ALPHA; |
1266 | *gltype = GL_UNSIGNED_BYTE; |
1267 | break; |
1268 | case QRhiTexture::RGBA16F: |
1269 | *glintformat = GL_RGBA16F; |
1270 | *glsizedintformat = *glintformat; |
1271 | *glformat = GL_RGBA; |
1272 | *gltype = GL_HALF_FLOAT; |
1273 | break; |
1274 | case QRhiTexture::RGBA32F: |
1275 | *glintformat = GL_RGBA32F; |
1276 | *glsizedintformat = *glintformat; |
1277 | *glformat = GL_RGBA; |
1278 | *gltype = GL_FLOAT; |
1279 | break; |
1280 | case QRhiTexture::R16F: |
1281 | *glintformat = GL_R16F; |
1282 | *glsizedintformat = *glintformat; |
1283 | *glformat = GL_RED; |
1284 | *gltype = GL_HALF_FLOAT; |
1285 | break; |
1286 | case QRhiTexture::R32F: |
1287 | *glintformat = GL_R32F; |
1288 | *glsizedintformat = *glintformat; |
1289 | *glformat = GL_RED; |
1290 | *gltype = GL_FLOAT; |
1291 | break; |
1292 | case QRhiTexture::RGB10A2: |
1293 | *glintformat = GL_RGB10_A2; |
1294 | *glsizedintformat = *glintformat; |
1295 | *glformat = GL_RGBA; |
1296 | *gltype = GL_UNSIGNED_INT_2_10_10_10_REV; |
1297 | break; |
1298 | case QRhiTexture::D16: |
1299 | *glintformat = GL_DEPTH_COMPONENT16; |
1300 | *glsizedintformat = *glintformat; |
1301 | *glformat = GL_DEPTH_COMPONENT; |
1302 | *gltype = GL_UNSIGNED_SHORT; |
1303 | break; |
1304 | case QRhiTexture::D24: |
1305 | *glintformat = GL_DEPTH_COMPONENT24; |
1306 | *glsizedintformat = *glintformat; |
1307 | *glformat = GL_DEPTH_COMPONENT; |
1308 | *gltype = GL_UNSIGNED_INT; |
1309 | break; |
1310 | case QRhiTexture::D24S8: |
1311 | *glintformat = GL_DEPTH24_STENCIL8; |
1312 | *glsizedintformat = *glintformat; |
1313 | *glformat = GL_DEPTH_STENCIL; |
1314 | *gltype = GL_UNSIGNED_INT_24_8; |
1315 | break; |
1316 | case QRhiTexture::D32F: |
1317 | *glintformat = GL_DEPTH_COMPONENT32F; |
1318 | *glsizedintformat = *glintformat; |
1319 | *glformat = GL_DEPTH_COMPONENT; |
1320 | *gltype = GL_FLOAT; |
1321 | break; |
1322 | default: |
1323 | Q_UNREACHABLE(); |
1324 | *glintformat = GL_RGBA; |
1325 | *glsizedintformat = caps.rgba8Format ? GL_RGBA8 : GL_RGBA; |
1326 | *glformat = GL_RGBA; |
1327 | *gltype = GL_UNSIGNED_BYTE; |
1328 | break; |
1329 | } |
1330 | } |
1331 | |
1332 | bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const |
1333 | { |
1334 | if (isCompressedFormat(format)) |
1335 | return supportedCompressedFormats.contains(value: GLint(toGlCompressedTextureFormat(format, flags))); |
1336 | |
1337 | switch (format) { |
1338 | case QRhiTexture::D16: |
1339 | case QRhiTexture::D32F: |
1340 | return caps.depthTexture; |
1341 | |
1342 | case QRhiTexture::D24: |
1343 | return caps.depth24; |
1344 | |
1345 | case QRhiTexture::D24S8: |
1346 | return caps.depth24 && caps.packedDepthStencil; |
1347 | |
1348 | case QRhiTexture::BGRA8: |
1349 | return caps.bgraExternalFormat; |
1350 | |
1351 | case QRhiTexture::R8: |
1352 | return caps.r8Format; |
1353 | |
1354 | case QRhiTexture::RG8: |
1355 | return caps.r8Format; |
1356 | |
1357 | case QRhiTexture::R16: |
1358 | return caps.r16Format; |
1359 | |
1360 | case QRhiTexture::RG16: |
1361 | return caps.r16Format; |
1362 | |
1363 | case QRhiTexture::RGBA16F: |
1364 | case QRhiTexture::RGBA32F: |
1365 | return caps.floatFormats; |
1366 | |
1367 | case QRhiTexture::R16F: |
1368 | case QRhiTexture::R32F: |
1369 | return caps.floatFormats; |
1370 | |
1371 | case QRhiTexture::RGB10A2: |
1372 | return caps.rgb10Formats; |
1373 | |
1374 | default: |
1375 | break; |
1376 | } |
1377 | |
1378 | return true; |
1379 | } |
1380 | |
1381 | bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const |
1382 | { |
1383 | switch (feature) { |
1384 | case QRhi::MultisampleTexture: |
1385 | return caps.multisampledTexture; |
1386 | case QRhi::MultisampleRenderBuffer: |
1387 | return caps.msaaRenderBuffer; |
1388 | case QRhi::DebugMarkers: |
1389 | return false; |
1390 | case QRhi::Timestamps: |
1391 | return caps.timestamps; |
1392 | case QRhi::Instancing: |
1393 | return caps.instancing; |
1394 | case QRhi::CustomInstanceStepRate: |
1395 | return false; |
1396 | case QRhi::PrimitiveRestart: |
1397 | return caps.fixedIndexPrimitiveRestart; |
1398 | case QRhi::NonDynamicUniformBuffers: |
1399 | return true; |
1400 | case QRhi::NonFourAlignedEffectiveIndexBufferOffset: |
1401 | return true; |
1402 | case QRhi::NPOTTextureRepeat: |
1403 | return caps.npotTextureFull; |
1404 | case QRhi::RedOrAlpha8IsRed: |
1405 | return caps.coreProfile; |
1406 | case QRhi::ElementIndexUint: |
1407 | return caps.elementIndexUint; |
1408 | case QRhi::Compute: |
1409 | return caps.compute; |
1410 | case QRhi::WideLines: |
1411 | return !caps.coreProfile; |
1412 | case QRhi::VertexShaderPointSize: |
1413 | return true; |
1414 | case QRhi::BaseVertex: |
1415 | return caps.baseVertex; |
1416 | case QRhi::BaseInstance: |
1417 | return false; // not in ES 3.2, so won't bother |
1418 | case QRhi::TriangleFanTopology: |
1419 | return true; |
1420 | case QRhi::ReadBackNonUniformBuffer: |
1421 | return !caps.gles || caps.properMapBuffer; |
1422 | case QRhi::ReadBackNonBaseMipLevel: |
1423 | return caps.nonBaseLevelFramebufferTexture; |
1424 | case QRhi::TexelFetch: |
1425 | return caps.texelFetch; |
1426 | case QRhi::RenderToNonBaseMipLevel: |
1427 | return caps.nonBaseLevelFramebufferTexture; |
1428 | case QRhi::IntAttributes: |
1429 | return caps.intAttributes; |
1430 | case QRhi::ScreenSpaceDerivatives: |
1431 | return caps.screenSpaceDerivatives; |
1432 | case QRhi::ReadBackAnyTextureFormat: |
1433 | return false; |
1434 | case QRhi::PipelineCacheDataLoadSave: |
1435 | return caps.programBinary; |
1436 | case QRhi::ImageDataStride: |
1437 | return caps.unpackRowLength; |
1438 | case QRhi::RenderBufferImport: |
1439 | return true; |
1440 | case QRhi::ThreeDimensionalTextures: |
1441 | return caps.texture3D; |
1442 | case QRhi::RenderTo3DTextureSlice: |
1443 | return caps.texture3D; |
1444 | case QRhi::TextureArrays: |
1445 | return caps.maxTextureArraySize > 0; |
1446 | case QRhi::Tessellation: |
1447 | return caps.tessellation; |
1448 | case QRhi::GeometryShader: |
1449 | return caps.geometryShader; |
1450 | case QRhi::TextureArrayRange: |
1451 | return false; |
1452 | case QRhi::NonFillPolygonMode: |
1453 | return !caps.gles; |
1454 | case QRhi::OneDimensionalTextures: |
1455 | return caps.texture1D; |
1456 | case QRhi::OneDimensionalTextureMipmaps: |
1457 | return caps.texture1D; |
1458 | case QRhi::HalfAttributes: |
1459 | return caps.halfAttributes; |
1460 | case QRhi::RenderToOneDimensionalTexture: |
1461 | return caps.texture1D; |
1462 | case QRhi::ThreeDimensionalTextureMipmaps: |
1463 | return caps.texture3D; |
1464 | case QRhi::MultiView: |
1465 | return caps.multiView && caps.maxTextureArraySize > 0; |
1466 | case QRhi::TextureViewFormat: |
1467 | return false; |
1468 | case QRhi::ResolveDepthStencil: |
1469 | return true; |
1470 | default: |
1471 | Q_UNREACHABLE_RETURN(false); |
1472 | } |
1473 | } |
1474 | |
1475 | int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const |
1476 | { |
1477 | switch (limit) { |
1478 | case QRhi::TextureSizeMin: |
1479 | return 1; |
1480 | case QRhi::TextureSizeMax: |
1481 | return caps.maxTextureSize; |
1482 | case QRhi::MaxColorAttachments: |
1483 | return caps.maxDrawBuffers; |
1484 | case QRhi::FramesInFlight: |
1485 | // From our perspective. What the GL impl does internally is another |
1486 | // question, but that's out of our hands and does not concern us here. |
1487 | return 1; |
1488 | case QRhi::MaxAsyncReadbackFrames: |
1489 | return 1; |
1490 | case QRhi::MaxThreadGroupsPerDimension: |
1491 | return caps.maxThreadGroupsPerDimension; |
1492 | case QRhi::MaxThreadsPerThreadGroup: |
1493 | return caps.maxThreadsPerThreadGroup; |
1494 | case QRhi::MaxThreadGroupX: |
1495 | return caps.maxThreadGroupsX; |
1496 | case QRhi::MaxThreadGroupY: |
1497 | return caps.maxThreadGroupsY; |
1498 | case QRhi::MaxThreadGroupZ: |
1499 | return caps.maxThreadGroupsZ; |
1500 | case QRhi::TextureArraySizeMax: |
1501 | return 2048; |
1502 | case QRhi::MaxUniformBufferRange: |
1503 | return int(qMin<qint64>(INT_MAX, b: caps.maxUniformVectors * qint64(16))); |
1504 | case QRhi::MaxVertexInputs: |
1505 | return caps.maxVertexInputs; |
1506 | case QRhi::MaxVertexOutputs: |
1507 | return caps.maxVertexOutputs; |
1508 | default: |
1509 | Q_UNREACHABLE_RETURN(0); |
1510 | } |
1511 | } |
1512 | |
1513 | const QRhiNativeHandles *QRhiGles2::nativeHandles() |
1514 | { |
1515 | return &nativeHandlesStruct; |
1516 | } |
1517 | |
1518 | QRhiDriverInfo QRhiGles2::driverInfo() const |
1519 | { |
1520 | return driverInfoStruct; |
1521 | } |
1522 | |
1523 | QRhiStats QRhiGles2::statistics() |
1524 | { |
1525 | QRhiStats result; |
1526 | result.totalPipelineCreationTime = totalPipelineCreationTime(); |
1527 | return result; |
1528 | } |
1529 | |
1530 | bool QRhiGles2::makeThreadLocalNativeContextCurrent() |
1531 | { |
1532 | if (inFrame && !ofr.active) |
1533 | return ensureContext(surface: currentSwapChain->surface); |
1534 | else |
1535 | return ensureContext(); |
1536 | } |
1537 | |
1538 | void QRhiGles2::releaseCachedResources() |
1539 | { |
1540 | if (!ensureContext()) |
1541 | return; |
1542 | |
1543 | for (uint shader : m_shaderCache) |
1544 | f->glDeleteShader(shader); |
1545 | |
1546 | m_shaderCache.clear(); |
1547 | |
1548 | m_pipelineCache.clear(); |
1549 | } |
1550 | |
1551 | bool QRhiGles2::isDeviceLost() const |
1552 | { |
1553 | return contextLost; |
1554 | } |
1555 | |
1556 | struct QGles2PipelineCacheDataHeader |
1557 | { |
1558 | quint32 rhiId; |
1559 | quint32 arch; |
1560 | quint32 programBinaryCount; |
1561 | quint32 dataSize; |
1562 | char driver[240]; |
1563 | }; |
1564 | |
1565 | QByteArray QRhiGles2::pipelineCacheData() |
1566 | { |
1567 | Q_STATIC_ASSERT(sizeof(QGles2PipelineCacheDataHeader) == 256); |
1568 | |
1569 | if (m_pipelineCache.isEmpty()) |
1570 | return QByteArray(); |
1571 | |
1572 | QGles2PipelineCacheDataHeader header; |
1573 | memset(s: &header, c: 0, n: sizeof(header)); |
1574 | header.rhiId = pipelineCacheRhiId(); |
1575 | header.arch = quint32(sizeof(void*)); |
1576 | header.programBinaryCount = m_pipelineCache.size(); |
1577 | const size_t driverStrLen = qMin(a: sizeof(header.driver) - 1, b: size_t(driverInfoStruct.deviceName.size())); |
1578 | if (driverStrLen) |
1579 | memcpy(dest: header.driver, src: driverInfoStruct.deviceName.constData(), n: driverStrLen); |
1580 | header.driver[driverStrLen] = '\0'; |
1581 | |
1582 | const size_t dataOffset = sizeof(header); |
1583 | size_t dataSize = 0; |
1584 | for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) { |
1585 | dataSize += sizeof(quint32) + it.key().size() |
1586 | + sizeof(quint32) + it->data.size() |
1587 | + sizeof(quint32); |
1588 | } |
1589 | |
1590 | QByteArray buf(dataOffset + dataSize, Qt::Uninitialized); |
1591 | char *p = buf.data() + dataOffset; |
1592 | for (auto it = m_pipelineCache.cbegin(), end = m_pipelineCache.cend(); it != end; ++it) { |
1593 | const QByteArray key = it.key(); |
1594 | const QByteArray data = it->data; |
1595 | const quint32 format = it->format; |
1596 | |
1597 | quint32 i = key.size(); |
1598 | memcpy(dest: p, src: &i, n: 4); |
1599 | p += 4; |
1600 | memcpy(dest: p, src: key.constData(), n: key.size()); |
1601 | p += key.size(); |
1602 | |
1603 | i = data.size(); |
1604 | memcpy(dest: p, src: &i, n: 4); |
1605 | p += 4; |
1606 | memcpy(dest: p, src: data.constData(), n: data.size()); |
1607 | p += data.size(); |
1608 | |
1609 | memcpy(dest: p, src: &format, n: 4); |
1610 | p += 4; |
1611 | } |
1612 | Q_ASSERT(p == buf.data() + dataOffset + dataSize); |
1613 | |
1614 | header.dataSize = quint32(dataSize); |
1615 | memcpy(dest: buf.data(), src: &header, n: sizeof(header)); |
1616 | |
1617 | return buf; |
1618 | } |
1619 | |
1620 | void QRhiGles2::setPipelineCacheData(const QByteArray &data) |
1621 | { |
1622 | if (data.isEmpty()) |
1623 | return; |
1624 | |
1625 | const size_t headerSize = sizeof(QGles2PipelineCacheDataHeader); |
1626 | if (data.size() < qsizetype(headerSize)) { |
1627 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)"); |
1628 | return; |
1629 | } |
1630 | const size_t dataOffset = headerSize; |
1631 | QGles2PipelineCacheDataHeader header; |
1632 | memcpy(dest: &header, src: data.constData(), n: headerSize); |
1633 | |
1634 | const quint32 rhiId = pipelineCacheRhiId(); |
1635 | if (header.rhiId != rhiId) { |
1636 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)", |
1637 | rhiId, header.rhiId); |
1638 | return; |
1639 | } |
1640 | const quint32 arch = quint32(sizeof(void*)); |
1641 | if (header.arch != arch) { |
1642 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)", |
1643 | arch, header.arch); |
1644 | return; |
1645 | } |
1646 | if (header.programBinaryCount == 0) |
1647 | return; |
1648 | |
1649 | const size_t driverStrLen = qMin(a: sizeof(header.driver) - 1, b: size_t(driverInfoStruct.deviceName.size())); |
1650 | if (strncmp(s1: header.driver, s2: driverInfoStruct.deviceName.constData(), n: driverStrLen)) { |
1651 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: OpenGL vendor/renderer/version does not match"); |
1652 | return; |
1653 | } |
1654 | |
1655 | if (data.size() < qsizetype(dataOffset + header.dataSize)) { |
1656 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)"); |
1657 | return; |
1658 | } |
1659 | |
1660 | m_pipelineCache.clear(); |
1661 | |
1662 | const char *p = data.constData() + dataOffset; |
1663 | for (quint32 i = 0; i < header.programBinaryCount; ++i) { |
1664 | quint32 len = 0; |
1665 | memcpy(dest: &len, src: p, n: 4); |
1666 | p += 4; |
1667 | QByteArray key(len, Qt::Uninitialized); |
1668 | memcpy(dest: key.data(), src: p, n: len); |
1669 | p += len; |
1670 | |
1671 | memcpy(dest: &len, src: p, n: 4); |
1672 | p += 4; |
1673 | QByteArray data(len, Qt::Uninitialized); |
1674 | memcpy(dest: data.data(), src: p, n: len); |
1675 | p += len; |
1676 | |
1677 | quint32 format; |
1678 | memcpy(dest: &format, src: p, n: 4); |
1679 | p += 4; |
1680 | |
1681 | m_pipelineCache.insert(key, value: { .format: format, .data: data }); |
1682 | } |
1683 | |
1684 | qCDebug(QRHI_LOG_INFO, "Seeded pipeline cache with %d program binaries", int(m_pipelineCache.size())); |
1685 | } |
1686 | |
1687 | QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, |
1688 | int sampleCount, QRhiRenderBuffer::Flags flags, |
1689 | QRhiTexture::Format backingFormatHint) |
1690 | { |
1691 | return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint); |
1692 | } |
1693 | |
1694 | QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, |
1695 | const QSize &pixelSize, int depth, int arraySize, |
1696 | int sampleCount, QRhiTexture::Flags flags) |
1697 | { |
1698 | return new QGles2Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags); |
1699 | } |
1700 | |
1701 | QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, |
1702 | QRhiSampler::Filter mipmapMode, |
1703 | QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) |
1704 | { |
1705 | return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v, w); |
1706 | } |
1707 | |
1708 | QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, |
1709 | QRhiTextureRenderTarget::Flags flags) |
1710 | { |
1711 | return new QGles2TextureRenderTarget(this, desc, flags); |
1712 | } |
1713 | |
1714 | QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline() |
1715 | { |
1716 | return new QGles2GraphicsPipeline(this); |
1717 | } |
1718 | |
1719 | QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings() |
1720 | { |
1721 | return new QGles2ShaderResourceBindings(this); |
1722 | } |
1723 | |
1724 | QRhiComputePipeline *QRhiGles2::createComputePipeline() |
1725 | { |
1726 | return new QGles2ComputePipeline(this); |
1727 | } |
1728 | |
1729 | void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) |
1730 | { |
1731 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1732 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1733 | QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps); |
1734 | const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation; |
1735 | |
1736 | if (pipelineChanged) { |
1737 | cbD->currentGraphicsPipeline = ps; |
1738 | cbD->currentComputePipeline = nullptr; |
1739 | cbD->currentPipelineGeneration = psD->generation; |
1740 | |
1741 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1742 | cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline; |
1743 | cmd.args.bindGraphicsPipeline.ps = ps; |
1744 | } |
1745 | } |
1746 | |
1747 | void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, |
1748 | int dynamicOffsetCount, |
1749 | const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) |
1750 | { |
1751 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1752 | Q_ASSERT(cbD->recordingPass != QGles2CommandBuffer::NoPass); |
1753 | QGles2GraphicsPipeline *gfxPsD = QRHI_RES(QGles2GraphicsPipeline, cbD->currentGraphicsPipeline); |
1754 | QGles2ComputePipeline *compPsD = QRHI_RES(QGles2ComputePipeline, cbD->currentComputePipeline); |
1755 | |
1756 | if (!srb) { |
1757 | if (gfxPsD) |
1758 | srb = gfxPsD->m_shaderResourceBindings; |
1759 | else |
1760 | srb = compPsD->m_shaderResourceBindings; |
1761 | } |
1762 | |
1763 | QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); |
1764 | if (cbD->passNeedsResourceTracking) { |
1765 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
1766 | for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { |
1767 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i)); |
1768 | switch (b->type) { |
1769 | case QRhiShaderResourceBinding::UniformBuffer: |
1770 | // no BufUniformRead / AccessUniform because no real uniform buffers are used |
1771 | break; |
1772 | case QRhiShaderResourceBinding::SampledTexture: |
1773 | case QRhiShaderResourceBinding::Texture: |
1774 | for (int elem = 0; elem < b->u.stex.count; ++elem) { |
1775 | trackedRegisterTexture(passResTracker: &passResTracker, |
1776 | QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex), |
1777 | access: QRhiPassResourceTracker::TexSample, |
1778 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
1779 | } |
1780 | break; |
1781 | case QRhiShaderResourceBinding::ImageLoad: |
1782 | case QRhiShaderResourceBinding::ImageStore: |
1783 | case QRhiShaderResourceBinding::ImageLoadStore: |
1784 | { |
1785 | QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex); |
1786 | QRhiPassResourceTracker::TextureAccess access; |
1787 | if (b->type == QRhiShaderResourceBinding::ImageLoad) |
1788 | access = QRhiPassResourceTracker::TexStorageLoad; |
1789 | else if (b->type == QRhiShaderResourceBinding::ImageStore) |
1790 | access = QRhiPassResourceTracker::TexStorageStore; |
1791 | else |
1792 | access = QRhiPassResourceTracker::TexStorageLoadStore; |
1793 | trackedRegisterTexture(passResTracker: &passResTracker, texD, access, |
1794 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
1795 | } |
1796 | break; |
1797 | case QRhiShaderResourceBinding::BufferLoad: |
1798 | case QRhiShaderResourceBinding::BufferStore: |
1799 | case QRhiShaderResourceBinding::BufferLoadStore: |
1800 | { |
1801 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf); |
1802 | QRhiPassResourceTracker::BufferAccess access; |
1803 | if (b->type == QRhiShaderResourceBinding::BufferLoad) |
1804 | access = QRhiPassResourceTracker::BufStorageLoad; |
1805 | else if (b->type == QRhiShaderResourceBinding::BufferStore) |
1806 | access = QRhiPassResourceTracker::BufStorageStore; |
1807 | else |
1808 | access = QRhiPassResourceTracker::BufStorageLoadStore; |
1809 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, access, |
1810 | stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage)); |
1811 | } |
1812 | break; |
1813 | default: |
1814 | break; |
1815 | } |
1816 | } |
1817 | } |
1818 | |
1819 | bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); |
1820 | |
1821 | // The Command::BindShaderResources command generated below is what will |
1822 | // cause uniforms to be set (glUniformNxx). This needs some special |
1823 | // handling here in this backend without real uniform buffers, because, |
1824 | // like in other backends, we optimize out the setShaderResources when the |
1825 | // srb that was set before is attempted to be set again on the command |
1826 | // buffer, but that is incorrect if the same srb is now used with another |
1827 | // pipeline. (because that could mean a glUseProgram not followed by |
1828 | // up-to-date glUniform calls, i.e. with GL we have a strong dependency |
1829 | // between the pipeline (== program) and the srb, unlike other APIs) This |
1830 | // is the reason there is a second level of srb(+generation) tracking in |
1831 | // the pipeline objects. |
1832 | if (gfxPsD && (gfxPsD->currentSrb != srb || gfxPsD->currentSrbGeneration != srbD->generation)) { |
1833 | srbChanged = true; |
1834 | gfxPsD->currentSrb = srb; |
1835 | gfxPsD->currentSrbGeneration = srbD->generation; |
1836 | } else if (compPsD && (compPsD->currentSrb != srb || compPsD->currentSrbGeneration != srbD->generation)) { |
1837 | srbChanged = true; |
1838 | compPsD->currentSrb = srb; |
1839 | compPsD->currentSrbGeneration = srbD->generation; |
1840 | } |
1841 | |
1842 | if (srbChanged || cbD->currentSrbGeneration != srbD->generation || srbD->hasDynamicOffset) { |
1843 | if (gfxPsD) { |
1844 | cbD->currentGraphicsSrb = srb; |
1845 | cbD->currentComputeSrb = nullptr; |
1846 | } else { |
1847 | cbD->currentGraphicsSrb = nullptr; |
1848 | cbD->currentComputeSrb = srb; |
1849 | } |
1850 | cbD->currentSrbGeneration = srbD->generation; |
1851 | |
1852 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1853 | cmd.cmd = QGles2CommandBuffer::Command::BindShaderResources; |
1854 | cmd.args.bindShaderResources.maybeGraphicsPs = gfxPsD; |
1855 | cmd.args.bindShaderResources.maybeComputePs = compPsD; |
1856 | cmd.args.bindShaderResources.srb = srb; |
1857 | cmd.args.bindShaderResources.dynamicOffsetCount = 0; |
1858 | if (srbD->hasDynamicOffset) { |
1859 | if (dynamicOffsetCount < QGles2CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT) { |
1860 | cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount; |
1861 | uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs; |
1862 | for (int i = 0; i < dynamicOffsetCount; ++i) { |
1863 | const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]); |
1864 | *p++ = uint(dynOfs.first); |
1865 | *p++ = dynOfs.second; |
1866 | } |
1867 | } else { |
1868 | qWarning(msg: "Too many dynamic offsets (%d, max is %d)", |
1869 | dynamicOffsetCount, QGles2CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT); |
1870 | } |
1871 | } |
1872 | } |
1873 | } |
1874 | |
1875 | void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb, |
1876 | int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, |
1877 | QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) |
1878 | { |
1879 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1880 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1881 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
1882 | |
1883 | for (int i = 0; i < bindingCount; ++i) { |
1884 | QRhiBuffer *buf = bindings[i].first; |
1885 | quint32 ofs = bindings[i].second; |
1886 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf); |
1887 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); |
1888 | |
1889 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1890 | cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer; |
1891 | cmd.args.bindVertexBuffer.ps = cbD->currentGraphicsPipeline; |
1892 | cmd.args.bindVertexBuffer.buffer = bufD->buffer; |
1893 | cmd.args.bindVertexBuffer.offset = ofs; |
1894 | cmd.args.bindVertexBuffer.binding = startBinding + i; |
1895 | |
1896 | if (cbD->passNeedsResourceTracking) { |
1897 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, access: QRhiPassResourceTracker::BufVertexInput, |
1898 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
1899 | } |
1900 | } |
1901 | |
1902 | if (indexBuf) { |
1903 | QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf); |
1904 | Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); |
1905 | |
1906 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1907 | cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer; |
1908 | cmd.args.bindIndexBuffer.buffer = ibufD->buffer; |
1909 | cmd.args.bindIndexBuffer.offset = indexOffset; |
1910 | cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; |
1911 | |
1912 | if (cbD->passNeedsResourceTracking) { |
1913 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD: ibufD, access: QRhiPassResourceTracker::BufIndexRead, |
1914 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
1915 | } |
1916 | } |
1917 | } |
1918 | |
1919 | void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) |
1920 | { |
1921 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1922 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1923 | |
1924 | const std::array<float, 4> r = viewport.viewport(); |
1925 | // A negative width or height is an error. A negative x or y is not. |
1926 | if (r[2] < 0.0f || r[3] < 0.0f) |
1927 | return; |
1928 | |
1929 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1930 | cmd.cmd = QGles2CommandBuffer::Command::Viewport; |
1931 | cmd.args.viewport.x = r[0]; |
1932 | cmd.args.viewport.y = r[1]; |
1933 | cmd.args.viewport.w = r[2]; |
1934 | cmd.args.viewport.h = r[3]; |
1935 | cmd.args.viewport.d0 = viewport.minDepth(); |
1936 | cmd.args.viewport.d1 = viewport.maxDepth(); |
1937 | } |
1938 | |
1939 | void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) |
1940 | { |
1941 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1942 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1943 | |
1944 | const std::array<int, 4> r = scissor.scissor(); |
1945 | // A negative width or height is an error. A negative x or y is not. |
1946 | if (r[2] < 0 || r[3] < 0) |
1947 | return; |
1948 | |
1949 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1950 | cmd.cmd = QGles2CommandBuffer::Command::Scissor; |
1951 | cmd.args.scissor.x = r[0]; |
1952 | cmd.args.scissor.y = r[1]; |
1953 | cmd.args.scissor.w = r[2]; |
1954 | cmd.args.scissor.h = r[3]; |
1955 | } |
1956 | |
1957 | void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) |
1958 | { |
1959 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1960 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1961 | |
1962 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1963 | cmd.cmd = QGles2CommandBuffer::Command::BlendConstants; |
1964 | cmd.args.blendConstants.r = float(c.redF()); |
1965 | cmd.args.blendConstants.g = float(c.greenF()); |
1966 | cmd.args.blendConstants.b = float(c.blueF()); |
1967 | cmd.args.blendConstants.a = float(c.alphaF()); |
1968 | } |
1969 | |
1970 | void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) |
1971 | { |
1972 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1973 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1974 | |
1975 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1976 | cmd.cmd = QGles2CommandBuffer::Command::StencilRef; |
1977 | cmd.args.stencilRef.ref = refValue; |
1978 | cmd.args.stencilRef.ps = cbD->currentGraphicsPipeline; |
1979 | } |
1980 | |
1981 | void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount, |
1982 | quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) |
1983 | { |
1984 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
1985 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
1986 | |
1987 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
1988 | cmd.cmd = QGles2CommandBuffer::Command::Draw; |
1989 | cmd.args.draw.ps = cbD->currentGraphicsPipeline; |
1990 | cmd.args.draw.vertexCount = vertexCount; |
1991 | cmd.args.draw.firstVertex = firstVertex; |
1992 | cmd.args.draw.instanceCount = instanceCount; |
1993 | cmd.args.draw.baseInstance = firstInstance; |
1994 | } |
1995 | |
1996 | void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, |
1997 | quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) |
1998 | { |
1999 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2000 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
2001 | |
2002 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2003 | cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed; |
2004 | cmd.args.drawIndexed.ps = cbD->currentGraphicsPipeline; |
2005 | cmd.args.drawIndexed.indexCount = indexCount; |
2006 | cmd.args.drawIndexed.firstIndex = firstIndex; |
2007 | cmd.args.drawIndexed.instanceCount = instanceCount; |
2008 | cmd.args.drawIndexed.baseInstance = firstInstance; |
2009 | cmd.args.drawIndexed.baseVertex = vertexOffset; |
2010 | } |
2011 | |
2012 | void QRhiGles2::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) |
2013 | { |
2014 | if (!debugMarkers) |
2015 | return; |
2016 | |
2017 | Q_UNUSED(cb); |
2018 | Q_UNUSED(name); |
2019 | } |
2020 | |
2021 | void QRhiGles2::debugMarkEnd(QRhiCommandBuffer *cb) |
2022 | { |
2023 | if (!debugMarkers) |
2024 | return; |
2025 | |
2026 | Q_UNUSED(cb); |
2027 | } |
2028 | |
2029 | void QRhiGles2::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) |
2030 | { |
2031 | if (!debugMarkers) |
2032 | return; |
2033 | |
2034 | Q_UNUSED(cb); |
2035 | Q_UNUSED(msg); |
2036 | } |
2037 | |
2038 | const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb) |
2039 | { |
2040 | Q_UNUSED(cb); |
2041 | return nullptr; |
2042 | } |
2043 | |
2044 | static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type, GLuint tsQuery = 0) |
2045 | { |
2046 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2047 | cmd.cmd = type; |
2048 | if (type == QGles2CommandBuffer::Command::BeginFrame) |
2049 | cmd.args.beginFrame.timestampQuery = tsQuery; |
2050 | else if (type == QGles2CommandBuffer::Command::EndFrame) |
2051 | cmd.args.endFrame.timestampQuery = tsQuery; |
2052 | } |
2053 | |
2054 | void QRhiGles2::beginExternal(QRhiCommandBuffer *cb) |
2055 | { |
2056 | if (ofr.active) { |
2057 | Q_ASSERT(!currentSwapChain); |
2058 | if (!ensureContext()) |
2059 | return; |
2060 | } else { |
2061 | Q_ASSERT(currentSwapChain); |
2062 | if (!ensureContext(surface: currentSwapChain->surface)) |
2063 | return; |
2064 | } |
2065 | |
2066 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2067 | |
2068 | if (cbD->recordingPass == QGles2CommandBuffer::ComputePass |
2069 | && !cbD->computePassState.writtenResources.isEmpty()) |
2070 | { |
2071 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2072 | cmd.cmd = QGles2CommandBuffer::Command::Barrier; |
2073 | cmd.args.barrier.barriers = GL_ALL_BARRIER_BITS; |
2074 | } |
2075 | |
2076 | executeCommandBuffer(cb: cbD); |
2077 | |
2078 | cbD->resetCommands(); |
2079 | |
2080 | if (vao) { |
2081 | f->glBindVertexArray(array: 0); |
2082 | } else { |
2083 | f->glBindBuffer(GL_ARRAY_BUFFER, buffer: 0); |
2084 | f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0); |
2085 | if (caps.compute) |
2086 | f->glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer: 0); |
2087 | } |
2088 | } |
2089 | |
2090 | void QRhiGles2::endExternal(QRhiCommandBuffer *cb) |
2091 | { |
2092 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2093 | Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1); |
2094 | |
2095 | cbD->resetCachedState(); |
2096 | |
2097 | if (cbD->recordingPass != QGles2CommandBuffer::NoPass) { |
2098 | // Commands that come after this point need a resource tracker and also |
2099 | // a BarriersForPass command enqueued. (the ones we had from |
2100 | // beginPass() are now gone since beginExternal() processed all that |
2101 | // due to calling executeCommandBuffer()). |
2102 | enqueueBarriersForPass(cbD); |
2103 | } |
2104 | |
2105 | addBoundaryCommand(cbD, type: QGles2CommandBuffer::Command::ResetFrame); |
2106 | |
2107 | if (cbD->currentTarget) |
2108 | enqueueBindFramebuffer(rt: cbD->currentTarget, cbD); |
2109 | } |
2110 | |
2111 | double QRhiGles2::lastCompletedGpuTime(QRhiCommandBuffer *cb) |
2112 | { |
2113 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2114 | return cbD->lastGpuTime; |
2115 | } |
2116 | |
2117 | QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) |
2118 | { |
2119 | QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); |
2120 | if (!ensureContext(surface: swapChainD->surface)) |
2121 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2122 | |
2123 | ctx->handle()->beginFrame(); |
2124 | |
2125 | currentSwapChain = swapChainD; |
2126 | |
2127 | executeDeferredReleases(); |
2128 | swapChainD->cb.resetState(); |
2129 | |
2130 | if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) { |
2131 | double elapsedSec = 0; |
2132 | if (swapChainD->timestamps.tryQueryTimestamps(pairIndex: swapChainD->currentTimestampPairIndex, rhiD: this, elapsedSec: &elapsedSec)) |
2133 | swapChainD->cb.lastGpuTime = elapsedSec; |
2134 | } |
2135 | |
2136 | GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2]; |
2137 | GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1]; |
2138 | const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]; |
2139 | |
2140 | addBoundaryCommand(cbD: &swapChainD->cb, type: QGles2CommandBuffer::Command::BeginFrame, tsQuery: recordTimestamps ? tsStart : 0); |
2141 | |
2142 | return QRhi::FrameOpSuccess; |
2143 | } |
2144 | |
2145 | QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) |
2146 | { |
2147 | QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain); |
2148 | Q_ASSERT(currentSwapChain == swapChainD); |
2149 | |
2150 | GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2]; |
2151 | GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1]; |
2152 | const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]; |
2153 | if (recordTimestamps) { |
2154 | swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true; |
2155 | swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QGles2SwapChainTimestamps::TIMESTAMP_PAIRS; |
2156 | } |
2157 | |
2158 | addBoundaryCommand(cbD: &swapChainD->cb, type: QGles2CommandBuffer::Command::EndFrame, tsQuery: recordTimestamps ? tsEnd : 0); |
2159 | |
2160 | if (!ensureContext(surface: swapChainD->surface)) |
2161 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2162 | |
2163 | executeCommandBuffer(cb: &swapChainD->cb); |
2164 | |
2165 | if (swapChainD->surface && !flags.testFlag(flag: QRhi::SkipPresent)) { |
2166 | ctx->swapBuffers(surface: swapChainD->surface); |
2167 | needsMakeCurrentDueToSwap = true; |
2168 | } else { |
2169 | f->glFlush(); |
2170 | } |
2171 | |
2172 | swapChainD->frameCount += 1; |
2173 | currentSwapChain = nullptr; |
2174 | |
2175 | ctx->handle()->endFrame(); |
2176 | |
2177 | return QRhi::FrameOpSuccess; |
2178 | } |
2179 | |
2180 | QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags) |
2181 | { |
2182 | if (!ensureContext()) |
2183 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2184 | |
2185 | ofr.active = true; |
2186 | |
2187 | executeDeferredReleases(); |
2188 | ofr.cbWrapper.resetState(); |
2189 | |
2190 | if (rhiFlags.testFlag(flag: QRhi::EnableTimestamps) && caps.timestamps) { |
2191 | if (!ofr.tsQueries[0]) |
2192 | f->glGenQueries(n: 2, ids: ofr.tsQueries); |
2193 | } |
2194 | |
2195 | addBoundaryCommand(cbD: &ofr.cbWrapper, type: QGles2CommandBuffer::Command::BeginFrame, tsQuery: ofr.tsQueries[0]); |
2196 | *cb = &ofr.cbWrapper; |
2197 | |
2198 | return QRhi::FrameOpSuccess; |
2199 | } |
2200 | |
2201 | QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags) |
2202 | { |
2203 | Q_UNUSED(flags); |
2204 | Q_ASSERT(ofr.active); |
2205 | ofr.active = false; |
2206 | |
2207 | addBoundaryCommand(cbD: &ofr.cbWrapper, type: QGles2CommandBuffer::Command::EndFrame, tsQuery: ofr.tsQueries[1]); |
2208 | |
2209 | if (!ensureContext()) |
2210 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2211 | |
2212 | executeCommandBuffer(cb: &ofr.cbWrapper); |
2213 | |
2214 | // Just as endFrame() does a flush when skipping the swapBuffers(), do it |
2215 | // here as well. This has the added benefit of playing nice when rendering |
2216 | // to a texture from a context and then consuming that texture from |
2217 | // another, sharing context. |
2218 | f->glFlush(); |
2219 | |
2220 | if (ofr.tsQueries[0]) { |
2221 | quint64 timestamps[2]; |
2222 | glGetQueryObjectui64v(ofr.tsQueries[1], GL_QUERY_RESULT, ×tamps[1]); |
2223 | glGetQueryObjectui64v(ofr.tsQueries[0], GL_QUERY_RESULT, ×tamps[0]); |
2224 | if (timestamps[1] >= timestamps[0]) { |
2225 | const quint64 nanoseconds = timestamps[1] - timestamps[0]; |
2226 | ofr.cbWrapper.lastGpuTime = nanoseconds / 1000000000.0; // seconds |
2227 | } |
2228 | } |
2229 | |
2230 | return QRhi::FrameOpSuccess; |
2231 | } |
2232 | |
2233 | QRhi::FrameOpResult QRhiGles2::finish() |
2234 | { |
2235 | if (inFrame) { |
2236 | if (ofr.active) { |
2237 | Q_ASSERT(!currentSwapChain); |
2238 | Q_ASSERT(ofr.cbWrapper.recordingPass == QGles2CommandBuffer::NoPass); |
2239 | if (!ensureContext()) |
2240 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2241 | executeCommandBuffer(cb: &ofr.cbWrapper); |
2242 | ofr.cbWrapper.resetCommands(); |
2243 | } else { |
2244 | Q_ASSERT(currentSwapChain); |
2245 | Q_ASSERT(currentSwapChain->cb.recordingPass == QGles2CommandBuffer::NoPass); |
2246 | if (!ensureContext(surface: currentSwapChain->surface)) |
2247 | return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError; |
2248 | executeCommandBuffer(cb: ¤tSwapChain->cb); |
2249 | currentSwapChain->cb.resetCommands(); |
2250 | } |
2251 | // Do an actual glFinish(). May seem superfluous, but this is what |
2252 | // matches most other backends e.g. Vulkan/Metal that do a heavyweight |
2253 | // wait-for-idle blocking in their finish(). More importantly, this |
2254 | // allows clients simply call finish() in threaded or shared context |
2255 | // situations where one explicitly needs to do a glFlush or Finish. |
2256 | f->glFinish(); |
2257 | } |
2258 | return QRhi::FrameOpSuccess; |
2259 | } |
2260 | |
2261 | static bool bufferAccessIsWrite(QGles2Buffer::Access access) |
2262 | { |
2263 | return access == QGles2Buffer::AccessStorageWrite |
2264 | || access == QGles2Buffer::AccessStorageReadWrite |
2265 | || access == QGles2Buffer::AccessUpdate; |
2266 | } |
2267 | |
2268 | static bool textureAccessIsWrite(QGles2Texture::Access access) |
2269 | { |
2270 | return access == QGles2Texture::AccessStorageWrite |
2271 | || access == QGles2Texture::AccessStorageReadWrite |
2272 | || access == QGles2Texture::AccessUpdate |
2273 | || access == QGles2Texture::AccessFramebuffer; |
2274 | } |
2275 | |
2276 | static inline GLbitfield barriersForBuffer() |
2277 | { |
2278 | return GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT |
2279 | | GL_ELEMENT_ARRAY_BARRIER_BIT |
2280 | | GL_UNIFORM_BARRIER_BIT |
2281 | | GL_BUFFER_UPDATE_BARRIER_BIT |
2282 | | GL_SHADER_STORAGE_BARRIER_BIT; |
2283 | } |
2284 | |
2285 | static inline GLbitfield barriersForTexture() |
2286 | { |
2287 | return GL_TEXTURE_FETCH_BARRIER_BIT |
2288 | | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT |
2289 | | GL_PIXEL_BUFFER_BARRIER_BIT |
2290 | | GL_TEXTURE_UPDATE_BARRIER_BIT |
2291 | | GL_FRAMEBUFFER_BARRIER_BIT; |
2292 | } |
2293 | |
2294 | void QRhiGles2::trackedBufferBarrier(QGles2CommandBuffer *cbD, QGles2Buffer *bufD, QGles2Buffer::Access access) |
2295 | { |
2296 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only |
2297 | if (!bufD->m_usage.testFlag(flag: QRhiBuffer::StorageBuffer)) |
2298 | return; |
2299 | |
2300 | const QGles2Buffer::Access prevAccess = bufD->usageState.access; |
2301 | if (access == prevAccess) |
2302 | return; |
2303 | |
2304 | if (bufferAccessIsWrite(access: prevAccess)) { |
2305 | // Generating the minimal barrier set is way too complicated to do |
2306 | // correctly (prevAccess is overwritten so we won't have proper |
2307 | // tracking across multiple passes) so setting all barrier bits will do |
2308 | // for now. |
2309 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2310 | cmd.cmd = QGles2CommandBuffer::Command::Barrier; |
2311 | cmd.args.barrier.barriers = barriersForBuffer(); |
2312 | } |
2313 | |
2314 | bufD->usageState.access = access; |
2315 | } |
2316 | |
2317 | void QRhiGles2::trackedImageBarrier(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Texture::Access access) |
2318 | { |
2319 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); // this is for resource updates only |
2320 | if (!texD->m_flags.testFlag(flag: QRhiTexture::UsedWithLoadStore)) |
2321 | return; |
2322 | |
2323 | const QGles2Texture::Access prevAccess = texD->usageState.access; |
2324 | if (access == prevAccess) |
2325 | return; |
2326 | |
2327 | if (textureAccessIsWrite(access: prevAccess)) { |
2328 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2329 | cmd.cmd = QGles2CommandBuffer::Command::Barrier; |
2330 | cmd.args.barrier.barriers = barriersForTexture(); |
2331 | } |
2332 | |
2333 | texD->usageState.access = access; |
2334 | } |
2335 | |
2336 | void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD, |
2337 | int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc) |
2338 | { |
2339 | trackedImageBarrier(cbD, texD, access: QGles2Texture::AccessUpdate); |
2340 | const bool isCompressed = isCompressedFormat(format: texD->m_format); |
2341 | const bool isCubeMap = texD->m_flags.testFlag(flag: QRhiTexture::CubeMap); |
2342 | const bool is3D = texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
2343 | const bool is1D = texD->m_flags.testFlag(flag: QRhiTexture::OneDimensional); |
2344 | const bool isArray = texD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
2345 | const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; |
2346 | const GLenum effectiveTarget = faceTargetBase + (isCubeMap ? uint(layer) : 0u); |
2347 | const QPoint dp = subresDesc.destinationTopLeft(); |
2348 | const QByteArray rawData = subresDesc.data(); |
2349 | |
2350 | auto setCmdByNotCompressedData = [&](const void* data, QSize size, quint32 dataStride) |
2351 | { |
2352 | quint32 bytesPerLine = 0; |
2353 | quint32 bytesPerPixel = 0; |
2354 | textureFormatInfo(format: texD->m_format, size, bpl: &bytesPerLine, byteSize: nullptr, bytesPerPixel: &bytesPerPixel); |
2355 | |
2356 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2357 | cmd.cmd = QGles2CommandBuffer::Command::SubImage; |
2358 | cmd.args.subImage.target = texD->target; |
2359 | cmd.args.subImage.texture = texD->texture; |
2360 | cmd.args.subImage.faceTarget = effectiveTarget; |
2361 | cmd.args.subImage.level = level; |
2362 | cmd.args.subImage.dx = dp.x(); |
2363 | cmd.args.subImage.dy = is1D && isArray ? layer : dp.y(); |
2364 | cmd.args.subImage.dz = is3D || isArray ? layer : 0; |
2365 | cmd.args.subImage.w = size.width(); |
2366 | cmd.args.subImage.h = size.height(); |
2367 | cmd.args.subImage.glformat = texD->glformat; |
2368 | cmd.args.subImage.gltype = texD->gltype; |
2369 | |
2370 | if (dataStride == 0) |
2371 | dataStride = bytesPerLine; |
2372 | |
2373 | cmd.args.subImage.rowStartAlign = (dataStride & 3) ? 1 : 4; |
2374 | cmd.args.subImage.rowLength = caps.unpackRowLength ? (bytesPerPixel ? dataStride / bytesPerPixel : 0) : 0; |
2375 | |
2376 | cmd.args.subImage.data = data; |
2377 | }; |
2378 | |
2379 | if (!subresDesc.image().isNull()) { |
2380 | QImage img = subresDesc.image(); |
2381 | QSize size = img.size(); |
2382 | if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { |
2383 | const QPoint sp = subresDesc.sourceTopLeft(); |
2384 | if (!subresDesc.sourceSize().isEmpty()) |
2385 | size = subresDesc.sourceSize(); |
2386 | |
2387 | if (caps.unpackRowLength) { |
2388 | cbD->retainImage(image: img); |
2389 | // create a non-owning wrapper for the subimage |
2390 | const uchar *data = img.constBits() + sp.y() * img.bytesPerLine() + sp.x() * (qMax(a: 1, b: img.depth() / 8)); |
2391 | img = QImage(data, size.width(), size.height(), img.bytesPerLine(), img.format()); |
2392 | } else { |
2393 | img = img.copy(x: sp.x(), y: sp.y(), w: size.width(), h: size.height()); |
2394 | } |
2395 | } |
2396 | |
2397 | setCmdByNotCompressedData(cbD->retainImage(image: img), size, img.bytesPerLine()); |
2398 | } else if (!rawData.isEmpty() && isCompressed) { |
2399 | const int depth = qMax(a: 1, b: texD->m_depth); |
2400 | const int arraySize = qMax(a: 0, b: texD->m_arraySize); |
2401 | if ((texD->flags().testFlag(flag: QRhiTexture::UsedAsCompressedAtlas) || is3D || isArray) |
2402 | && !texD->zeroInitialized) |
2403 | { |
2404 | // Create on first upload since glCompressedTexImage2D cannot take |
2405 | // nullptr data. We have a rule in the QRhi docs that the first |
2406 | // upload for a compressed texture must cover the entire image, but |
2407 | // that is clearly not ideal when building a texture atlas, or when |
2408 | // having a 3D texture with per-slice data. |
2409 | quint32 byteSize = 0; |
2410 | compressedFormatInfo(format: texD->m_format, size: texD->m_pixelSize, bpl: nullptr, byteSize: &byteSize, blockDim: nullptr); |
2411 | if (is3D) |
2412 | byteSize *= depth; |
2413 | if (isArray) |
2414 | byteSize *= arraySize; |
2415 | QByteArray zeroBuf(byteSize, 0); |
2416 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2417 | cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; |
2418 | cmd.args.compressedImage.target = texD->target; |
2419 | cmd.args.compressedImage.texture = texD->texture; |
2420 | cmd.args.compressedImage.faceTarget = effectiveTarget; |
2421 | cmd.args.compressedImage.level = level; |
2422 | cmd.args.compressedImage.glintformat = texD->glintformat; |
2423 | cmd.args.compressedImage.w = texD->m_pixelSize.width(); |
2424 | cmd.args.compressedImage.h = is1D && isArray ? arraySize : texD->m_pixelSize.height(); |
2425 | cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); |
2426 | cmd.args.compressedImage.size = byteSize; |
2427 | cmd.args.compressedImage.data = cbD->retainData(data: zeroBuf); |
2428 | texD->zeroInitialized = true; |
2429 | } |
2430 | |
2431 | const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize) |
2432 | : subresDesc.sourceSize(); |
2433 | if (texD->specified || texD->zeroInitialized) { |
2434 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2435 | cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage; |
2436 | cmd.args.compressedSubImage.target = texD->target; |
2437 | cmd.args.compressedSubImage.texture = texD->texture; |
2438 | cmd.args.compressedSubImage.faceTarget = effectiveTarget; |
2439 | cmd.args.compressedSubImage.level = level; |
2440 | cmd.args.compressedSubImage.dx = dp.x(); |
2441 | cmd.args.compressedSubImage.dy = is1D && isArray ? layer : dp.y(); |
2442 | cmd.args.compressedSubImage.dz = is3D || isArray ? layer : 0; |
2443 | cmd.args.compressedSubImage.w = size.width(); |
2444 | cmd.args.compressedSubImage.h = size.height(); |
2445 | cmd.args.compressedSubImage.glintformat = texD->glintformat; |
2446 | cmd.args.compressedSubImage.size = rawData.size(); |
2447 | cmd.args.compressedSubImage.data = cbD->retainData(data: rawData); |
2448 | } else { |
2449 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2450 | cmd.cmd = QGles2CommandBuffer::Command::CompressedImage; |
2451 | cmd.args.compressedImage.target = texD->target; |
2452 | cmd.args.compressedImage.texture = texD->texture; |
2453 | cmd.args.compressedImage.faceTarget = effectiveTarget; |
2454 | cmd.args.compressedImage.level = level; |
2455 | cmd.args.compressedImage.glintformat = texD->glintformat; |
2456 | cmd.args.compressedImage.w = size.width(); |
2457 | cmd.args.compressedImage.h = is1D && isArray ? arraySize : size.height(); |
2458 | cmd.args.compressedImage.depth = is3D ? depth : (isArray ? arraySize : 0); |
2459 | cmd.args.compressedImage.size = rawData.size(); |
2460 | cmd.args.compressedImage.data = cbD->retainData(data: rawData); |
2461 | } |
2462 | } else if (!rawData.isEmpty()) { |
2463 | const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize) |
2464 | : subresDesc.sourceSize(); |
2465 | |
2466 | setCmdByNotCompressedData(cbD->retainData(data: rawData), size, subresDesc.dataStride()); |
2467 | } else { |
2468 | qWarning(msg: "Invalid texture upload for %p layer=%d mip=%d", texD, layer, level); |
2469 | } |
2470 | } |
2471 | |
2472 | void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2473 | { |
2474 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2475 | QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(b: resourceUpdates); |
2476 | |
2477 | for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { |
2478 | const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); |
2479 | if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { |
2480 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); |
2481 | Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); |
2482 | if (bufD->m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) { |
2483 | memcpy(dest: bufD->data.data() + u.offset, src: u.data.constData(), n: size_t(u.data.size())); |
2484 | } else { |
2485 | trackedBufferBarrier(cbD, bufD, access: QGles2Buffer::AccessUpdate); |
2486 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2487 | cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; |
2488 | cmd.args.bufferSubData.target = bufD->targetForDataOps; |
2489 | cmd.args.bufferSubData.buffer = bufD->buffer; |
2490 | cmd.args.bufferSubData.offset = u.offset; |
2491 | cmd.args.bufferSubData.size = u.data.size(); |
2492 | cmd.args.bufferSubData.data = cbD->retainBufferData(data: u.data); |
2493 | } |
2494 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { |
2495 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); |
2496 | Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); |
2497 | Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); |
2498 | if (bufD->m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) { |
2499 | memcpy(dest: bufD->data.data() + u.offset, src: u.data.constData(), n: size_t(u.data.size())); |
2500 | } else { |
2501 | trackedBufferBarrier(cbD, bufD, access: QGles2Buffer::AccessUpdate); |
2502 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2503 | cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; |
2504 | cmd.args.bufferSubData.target = bufD->targetForDataOps; |
2505 | cmd.args.bufferSubData.buffer = bufD->buffer; |
2506 | cmd.args.bufferSubData.offset = u.offset; |
2507 | cmd.args.bufferSubData.size = u.data.size(); |
2508 | cmd.args.bufferSubData.data = cbD->retainBufferData(data: u.data); |
2509 | } |
2510 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { |
2511 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); |
2512 | if (bufD->m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) { |
2513 | u.result->data.resize(size: u.readSize); |
2514 | memcpy(dest: u.result->data.data(), src: bufD->data.constData() + u.offset, n: size_t(u.readSize)); |
2515 | if (u.result->completed) |
2516 | u.result->completed(); |
2517 | } else { |
2518 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2519 | cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData; |
2520 | cmd.args.getBufferSubData.result = u.result; |
2521 | cmd.args.getBufferSubData.target = bufD->targetForDataOps; |
2522 | cmd.args.getBufferSubData.buffer = bufD->buffer; |
2523 | cmd.args.getBufferSubData.offset = u.offset; |
2524 | cmd.args.getBufferSubData.size = u.readSize; |
2525 | } |
2526 | } |
2527 | } |
2528 | |
2529 | for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { |
2530 | const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]); |
2531 | if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { |
2532 | QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); |
2533 | for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { |
2534 | for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { |
2535 | for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(t: u.subresDesc[layer][level])) |
2536 | enqueueSubresUpload(texD, cbD, layer, level, subresDesc); |
2537 | } |
2538 | } |
2539 | texD->specified = true; |
2540 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { |
2541 | Q_ASSERT(u.src && u.dst); |
2542 | QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src); |
2543 | QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst); |
2544 | |
2545 | trackedImageBarrier(cbD, texD: srcD, access: QGles2Texture::AccessRead); |
2546 | trackedImageBarrier(cbD, texD: dstD, access: QGles2Texture::AccessUpdate); |
2547 | |
2548 | const QSize mipSize = q->sizeForMipLevel(mipLevel: u.desc.sourceLevel(), baseLevelSize: srcD->m_pixelSize); |
2549 | const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); |
2550 | // do not translate coordinates, even if sp is bottom-left from gl's pov |
2551 | const QPoint sp = u.desc.sourceTopLeft(); |
2552 | const QPoint dp = u.desc.destinationTopLeft(); |
2553 | |
2554 | const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(flag: QRhiTexture::CubeMap) |
2555 | ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target; |
2556 | const GLenum dstFaceTargetBase = dstD->m_flags.testFlag(flag: QRhiTexture::CubeMap) |
2557 | ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target; |
2558 | |
2559 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2560 | cmd.cmd = QGles2CommandBuffer::Command::CopyTex; |
2561 | |
2562 | const bool srcHasZ = srcD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || srcD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
2563 | const bool dstHasZ = dstD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || dstD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
2564 | const bool dstIs1dArray = dstD->m_flags.testFlag(flag: QRhiTexture::OneDimensional) |
2565 | && dstD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
2566 | |
2567 | cmd.args.copyTex.srcTarget = srcD->target; |
2568 | cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + (srcHasZ ? 0u : uint(u.desc.sourceLayer())); |
2569 | cmd.args.copyTex.srcTexture = srcD->texture; |
2570 | cmd.args.copyTex.srcLevel = u.desc.sourceLevel(); |
2571 | cmd.args.copyTex.srcX = sp.x(); |
2572 | cmd.args.copyTex.srcY = sp.y(); |
2573 | cmd.args.copyTex.srcZ = srcHasZ ? u.desc.sourceLayer() : 0; |
2574 | |
2575 | cmd.args.copyTex.dstTarget = dstD->target; |
2576 | cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + (dstHasZ ? 0u : uint(u.desc.destinationLayer())); |
2577 | cmd.args.copyTex.dstTexture = dstD->texture; |
2578 | cmd.args.copyTex.dstLevel = u.desc.destinationLevel(); |
2579 | cmd.args.copyTex.dstX = dp.x(); |
2580 | cmd.args.copyTex.dstY = dstIs1dArray ? u.desc.destinationLayer() : dp.y(); |
2581 | cmd.args.copyTex.dstZ = dstHasZ ? u.desc.destinationLayer() : 0; |
2582 | |
2583 | cmd.args.copyTex.w = copySize.width(); |
2584 | cmd.args.copyTex.h = copySize.height(); |
2585 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { |
2586 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2587 | cmd.cmd = QGles2CommandBuffer::Command::ReadPixels; |
2588 | cmd.args.readPixels.result = u.result; |
2589 | QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture()); |
2590 | if (texD) |
2591 | trackedImageBarrier(cbD, texD, access: QGles2Texture::AccessRead); |
2592 | cmd.args.readPixels.texture = texD ? texD->texture : 0; |
2593 | cmd.args.readPixels.slice3D = -1; |
2594 | if (texD) { |
2595 | const QSize readImageSize = q->sizeForMipLevel(mipLevel: u.rb.level(), baseLevelSize: texD->m_pixelSize); |
2596 | cmd.args.readPixels.w = readImageSize.width(); |
2597 | cmd.args.readPixels.h = readImageSize.height(); |
2598 | cmd.args.readPixels.format = texD->m_format; |
2599 | if (texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) |
2600 | || texD->m_flags.testFlag(flag: QRhiTexture::TextureArray)) |
2601 | { |
2602 | cmd.args.readPixels.readTarget = texD->target; |
2603 | cmd.args.readPixels.slice3D = u.rb.layer(); |
2604 | } else { |
2605 | const GLenum faceTargetBase = texD->m_flags.testFlag(flag: QRhiTexture::CubeMap) |
2606 | ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; |
2607 | cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer()); |
2608 | } |
2609 | cmd.args.readPixels.level = u.rb.level(); |
2610 | } |
2611 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { |
2612 | QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); |
2613 | trackedImageBarrier(cbD, texD, access: QGles2Texture::AccessFramebuffer); |
2614 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
2615 | cmd.cmd = QGles2CommandBuffer::Command::GenMip; |
2616 | cmd.args.genMip.target = texD->target; |
2617 | cmd.args.genMip.texture = texD->texture; |
2618 | } |
2619 | } |
2620 | |
2621 | ud->free(); |
2622 | } |
2623 | |
2624 | static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t) |
2625 | { |
2626 | switch (t) { |
2627 | case QRhiGraphicsPipeline::Triangles: |
2628 | return GL_TRIANGLES; |
2629 | case QRhiGraphicsPipeline::TriangleStrip: |
2630 | return GL_TRIANGLE_STRIP; |
2631 | case QRhiGraphicsPipeline::TriangleFan: |
2632 | return GL_TRIANGLE_FAN; |
2633 | case QRhiGraphicsPipeline::Lines: |
2634 | return GL_LINES; |
2635 | case QRhiGraphicsPipeline::LineStrip: |
2636 | return GL_LINE_STRIP; |
2637 | case QRhiGraphicsPipeline::Points: |
2638 | return GL_POINTS; |
2639 | case QRhiGraphicsPipeline::Patches: |
2640 | return GL_PATCHES; |
2641 | default: |
2642 | Q_UNREACHABLE_RETURN(GL_TRIANGLES); |
2643 | } |
2644 | } |
2645 | |
2646 | static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c) |
2647 | { |
2648 | switch (c) { |
2649 | case QRhiGraphicsPipeline::Front: |
2650 | return GL_FRONT; |
2651 | case QRhiGraphicsPipeline::Back: |
2652 | return GL_BACK; |
2653 | default: |
2654 | Q_UNREACHABLE_RETURN(GL_BACK); |
2655 | } |
2656 | } |
2657 | |
2658 | static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f) |
2659 | { |
2660 | switch (f) { |
2661 | case QRhiGraphicsPipeline::CCW: |
2662 | return GL_CCW; |
2663 | case QRhiGraphicsPipeline::CW: |
2664 | return GL_CW; |
2665 | default: |
2666 | Q_UNREACHABLE_RETURN(GL_CCW); |
2667 | } |
2668 | } |
2669 | |
2670 | static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f) |
2671 | { |
2672 | switch (f) { |
2673 | case QRhiGraphicsPipeline::Zero: |
2674 | return GL_ZERO; |
2675 | case QRhiGraphicsPipeline::One: |
2676 | return GL_ONE; |
2677 | case QRhiGraphicsPipeline::SrcColor: |
2678 | return GL_SRC_COLOR; |
2679 | case QRhiGraphicsPipeline::OneMinusSrcColor: |
2680 | return GL_ONE_MINUS_SRC_COLOR; |
2681 | case QRhiGraphicsPipeline::DstColor: |
2682 | return GL_DST_COLOR; |
2683 | case QRhiGraphicsPipeline::OneMinusDstColor: |
2684 | return GL_ONE_MINUS_DST_COLOR; |
2685 | case QRhiGraphicsPipeline::SrcAlpha: |
2686 | return GL_SRC_ALPHA; |
2687 | case QRhiGraphicsPipeline::OneMinusSrcAlpha: |
2688 | return GL_ONE_MINUS_SRC_ALPHA; |
2689 | case QRhiGraphicsPipeline::DstAlpha: |
2690 | return GL_DST_ALPHA; |
2691 | case QRhiGraphicsPipeline::OneMinusDstAlpha: |
2692 | return GL_ONE_MINUS_DST_ALPHA; |
2693 | case QRhiGraphicsPipeline::ConstantColor: |
2694 | return GL_CONSTANT_COLOR; |
2695 | case QRhiGraphicsPipeline::OneMinusConstantColor: |
2696 | return GL_ONE_MINUS_CONSTANT_COLOR; |
2697 | case QRhiGraphicsPipeline::ConstantAlpha: |
2698 | return GL_CONSTANT_ALPHA; |
2699 | case QRhiGraphicsPipeline::OneMinusConstantAlpha: |
2700 | return GL_ONE_MINUS_CONSTANT_ALPHA; |
2701 | case QRhiGraphicsPipeline::SrcAlphaSaturate: |
2702 | return GL_SRC_ALPHA_SATURATE; |
2703 | case QRhiGraphicsPipeline::Src1Color: |
2704 | case QRhiGraphicsPipeline::OneMinusSrc1Color: |
2705 | case QRhiGraphicsPipeline::Src1Alpha: |
2706 | case QRhiGraphicsPipeline::OneMinusSrc1Alpha: |
2707 | qWarning(msg: "Unsupported blend factor %d", f); |
2708 | return GL_ZERO; |
2709 | default: |
2710 | Q_UNREACHABLE_RETURN(GL_ZERO); |
2711 | } |
2712 | } |
2713 | |
2714 | static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op) |
2715 | { |
2716 | switch (op) { |
2717 | case QRhiGraphicsPipeline::Add: |
2718 | return GL_FUNC_ADD; |
2719 | case QRhiGraphicsPipeline::Subtract: |
2720 | return GL_FUNC_SUBTRACT; |
2721 | case QRhiGraphicsPipeline::ReverseSubtract: |
2722 | return GL_FUNC_REVERSE_SUBTRACT; |
2723 | case QRhiGraphicsPipeline::Min: |
2724 | return GL_MIN; |
2725 | case QRhiGraphicsPipeline::Max: |
2726 | return GL_MAX; |
2727 | default: |
2728 | Q_UNREACHABLE_RETURN(GL_FUNC_ADD); |
2729 | } |
2730 | } |
2731 | |
2732 | static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op) |
2733 | { |
2734 | switch (op) { |
2735 | case QRhiGraphicsPipeline::Never: |
2736 | return GL_NEVER; |
2737 | case QRhiGraphicsPipeline::Less: |
2738 | return GL_LESS; |
2739 | case QRhiGraphicsPipeline::Equal: |
2740 | return GL_EQUAL; |
2741 | case QRhiGraphicsPipeline::LessOrEqual: |
2742 | return GL_LEQUAL; |
2743 | case QRhiGraphicsPipeline::Greater: |
2744 | return GL_GREATER; |
2745 | case QRhiGraphicsPipeline::NotEqual: |
2746 | return GL_NOTEQUAL; |
2747 | case QRhiGraphicsPipeline::GreaterOrEqual: |
2748 | return GL_GEQUAL; |
2749 | case QRhiGraphicsPipeline::Always: |
2750 | return GL_ALWAYS; |
2751 | default: |
2752 | Q_UNREACHABLE_RETURN(GL_ALWAYS); |
2753 | } |
2754 | } |
2755 | |
2756 | static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op) |
2757 | { |
2758 | switch (op) { |
2759 | case QRhiGraphicsPipeline::StencilZero: |
2760 | return GL_ZERO; |
2761 | case QRhiGraphicsPipeline::Keep: |
2762 | return GL_KEEP; |
2763 | case QRhiGraphicsPipeline::Replace: |
2764 | return GL_REPLACE; |
2765 | case QRhiGraphicsPipeline::IncrementAndClamp: |
2766 | return GL_INCR; |
2767 | case QRhiGraphicsPipeline::DecrementAndClamp: |
2768 | return GL_DECR; |
2769 | case QRhiGraphicsPipeline::Invert: |
2770 | return GL_INVERT; |
2771 | case QRhiGraphicsPipeline::IncrementAndWrap: |
2772 | return GL_INCR_WRAP; |
2773 | case QRhiGraphicsPipeline::DecrementAndWrap: |
2774 | return GL_DECR_WRAP; |
2775 | default: |
2776 | Q_UNREACHABLE_RETURN(GL_KEEP); |
2777 | } |
2778 | } |
2779 | |
2780 | static inline GLenum toGlPolygonMode(QRhiGraphicsPipeline::PolygonMode mode) |
2781 | { |
2782 | switch (mode) { |
2783 | case QRhiGraphicsPipeline::PolygonMode::Fill: |
2784 | return GL_FILL; |
2785 | case QRhiGraphicsPipeline::PolygonMode::Line: |
2786 | return GL_LINE; |
2787 | default: |
2788 | Q_UNREACHABLE_RETURN(GL_FILL); |
2789 | } |
2790 | } |
2791 | |
2792 | static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m) |
2793 | { |
2794 | switch (f) { |
2795 | case QRhiSampler::Nearest: |
2796 | if (m == QRhiSampler::None) |
2797 | return GL_NEAREST; |
2798 | else |
2799 | return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR; |
2800 | case QRhiSampler::Linear: |
2801 | if (m == QRhiSampler::None) |
2802 | return GL_LINEAR; |
2803 | else |
2804 | return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR; |
2805 | default: |
2806 | Q_UNREACHABLE_RETURN(GL_LINEAR); |
2807 | } |
2808 | } |
2809 | |
2810 | static inline GLenum toGlMagFilter(QRhiSampler::Filter f) |
2811 | { |
2812 | switch (f) { |
2813 | case QRhiSampler::Nearest: |
2814 | return GL_NEAREST; |
2815 | case QRhiSampler::Linear: |
2816 | return GL_LINEAR; |
2817 | default: |
2818 | Q_UNREACHABLE_RETURN(GL_LINEAR); |
2819 | } |
2820 | } |
2821 | |
2822 | static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m) |
2823 | { |
2824 | switch (m) { |
2825 | case QRhiSampler::Repeat: |
2826 | return GL_REPEAT; |
2827 | case QRhiSampler::ClampToEdge: |
2828 | return GL_CLAMP_TO_EDGE; |
2829 | case QRhiSampler::Mirror: |
2830 | return GL_MIRRORED_REPEAT; |
2831 | default: |
2832 | Q_UNREACHABLE_RETURN(GL_CLAMP_TO_EDGE); |
2833 | } |
2834 | } |
2835 | |
2836 | static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op) |
2837 | { |
2838 | switch (op) { |
2839 | case QRhiSampler::Never: |
2840 | return GL_NEVER; |
2841 | case QRhiSampler::Less: |
2842 | return GL_LESS; |
2843 | case QRhiSampler::Equal: |
2844 | return GL_EQUAL; |
2845 | case QRhiSampler::LessOrEqual: |
2846 | return GL_LEQUAL; |
2847 | case QRhiSampler::Greater: |
2848 | return GL_GREATER; |
2849 | case QRhiSampler::NotEqual: |
2850 | return GL_NOTEQUAL; |
2851 | case QRhiSampler::GreaterOrEqual: |
2852 | return GL_GEQUAL; |
2853 | case QRhiSampler::Always: |
2854 | return GL_ALWAYS; |
2855 | default: |
2856 | Q_UNREACHABLE_RETURN(GL_NEVER); |
2857 | } |
2858 | } |
2859 | |
2860 | static inline QGles2Buffer::Access toGlAccess(QRhiPassResourceTracker::BufferAccess access) |
2861 | { |
2862 | switch (access) { |
2863 | case QRhiPassResourceTracker::BufVertexInput: |
2864 | return QGles2Buffer::AccessVertex; |
2865 | case QRhiPassResourceTracker::BufIndexRead: |
2866 | return QGles2Buffer::AccessIndex; |
2867 | case QRhiPassResourceTracker::BufUniformRead: |
2868 | return QGles2Buffer::AccessUniform; |
2869 | case QRhiPassResourceTracker::BufStorageLoad: |
2870 | return QGles2Buffer::AccessStorageRead; |
2871 | case QRhiPassResourceTracker::BufStorageStore: |
2872 | return QGles2Buffer::AccessStorageWrite; |
2873 | case QRhiPassResourceTracker::BufStorageLoadStore: |
2874 | return QGles2Buffer::AccessStorageReadWrite; |
2875 | default: |
2876 | Q_UNREACHABLE(); |
2877 | break; |
2878 | } |
2879 | return QGles2Buffer::AccessNone; |
2880 | } |
2881 | |
2882 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Buffer::UsageState &bufUsage) |
2883 | { |
2884 | QRhiPassResourceTracker::UsageState u; |
2885 | u.layout = 0; // N/A |
2886 | u.access = bufUsage.access; |
2887 | u.stage = 0; // N/A |
2888 | return u; |
2889 | } |
2890 | |
2891 | static inline QGles2Texture::Access toGlAccess(QRhiPassResourceTracker::TextureAccess access) |
2892 | { |
2893 | switch (access) { |
2894 | case QRhiPassResourceTracker::TexSample: |
2895 | return QGles2Texture::AccessSample; |
2896 | case QRhiPassResourceTracker::TexColorOutput: |
2897 | return QGles2Texture::AccessFramebuffer; |
2898 | case QRhiPassResourceTracker::TexDepthOutput: |
2899 | return QGles2Texture::AccessFramebuffer; |
2900 | case QRhiPassResourceTracker::TexStorageLoad: |
2901 | return QGles2Texture::AccessStorageRead; |
2902 | case QRhiPassResourceTracker::TexStorageStore: |
2903 | return QGles2Texture::AccessStorageWrite; |
2904 | case QRhiPassResourceTracker::TexStorageLoadStore: |
2905 | return QGles2Texture::AccessStorageReadWrite; |
2906 | default: |
2907 | Q_UNREACHABLE(); |
2908 | break; |
2909 | } |
2910 | return QGles2Texture::AccessNone; |
2911 | } |
2912 | |
2913 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QGles2Texture::UsageState &texUsage) |
2914 | { |
2915 | QRhiPassResourceTracker::UsageState u; |
2916 | u.layout = 0; // N/A |
2917 | u.access = texUsage.access; |
2918 | u.stage = 0; // N/A |
2919 | return u; |
2920 | } |
2921 | |
2922 | void QRhiGles2::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, |
2923 | QGles2Buffer *bufD, |
2924 | QRhiPassResourceTracker::BufferAccess access, |
2925 | QRhiPassResourceTracker::BufferStage stage) |
2926 | { |
2927 | QGles2Buffer::UsageState &u(bufD->usageState); |
2928 | passResTracker->registerBuffer(buf: bufD, slot: 0, access: &access, stage: &stage, state: toPassTrackerUsageState(bufUsage: u)); |
2929 | u.access = toGlAccess(access); |
2930 | } |
2931 | |
2932 | void QRhiGles2::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, |
2933 | QGles2Texture *texD, |
2934 | QRhiPassResourceTracker::TextureAccess access, |
2935 | QRhiPassResourceTracker::TextureStage stage) |
2936 | { |
2937 | QGles2Texture::UsageState &u(texD->usageState); |
2938 | passResTracker->registerTexture(tex: texD, access: &access, stage: &stage, state: toPassTrackerUsageState(texUsage: u)); |
2939 | u.access = toGlAccess(access); |
2940 | } |
2941 | |
2942 | struct CommandBufferExecTrackedState |
2943 | { |
2944 | GLenum indexType = GL_UNSIGNED_SHORT; |
2945 | quint32 indexStride = sizeof(quint16); |
2946 | quint32 indexOffset = 0; |
2947 | GLuint currentArrayBuffer = 0; |
2948 | GLuint currentElementArrayBuffer = 0; |
2949 | struct { |
2950 | QRhiGraphicsPipeline *ps = nullptr; |
2951 | GLuint buffer = 0; |
2952 | quint32 offset = 0; |
2953 | int binding = 0; |
2954 | } lastBindVertexBuffer; |
2955 | static const int TRACKED_ATTRIB_COUNT = 16; |
2956 | bool enabledAttribArrays[TRACKED_ATTRIB_COUNT] = {}; |
2957 | bool nonzeroAttribDivisor[TRACKED_ATTRIB_COUNT] = {}; |
2958 | bool instancedAttributesUsed = false; |
2959 | int maxUntrackedInstancedAttribute = 0; |
2960 | }; |
2961 | |
2962 | // Helper that must be used in executeCommandBuffer() whenever changing the |
2963 | // ARRAY or ELEMENT_ARRAY buffer binding outside of Command::BindVertexBuffer |
2964 | // and Command::BindIndexBuffer. |
2965 | static inline void bindVertexIndexBufferWithStateReset(CommandBufferExecTrackedState *state, |
2966 | QOpenGLExtensions *f, |
2967 | GLenum target, |
2968 | GLuint buffer) |
2969 | { |
2970 | state->currentArrayBuffer = 0; |
2971 | state->currentElementArrayBuffer = 0; |
2972 | state->lastBindVertexBuffer.buffer = 0; |
2973 | f->glBindBuffer(target, buffer); |
2974 | } |
2975 | |
2976 | void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) |
2977 | { |
2978 | CommandBufferExecTrackedState state; |
2979 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
2980 | |
2981 | for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) { |
2982 | const QGles2CommandBuffer::Command &cmd(*it); |
2983 | switch (cmd.cmd) { |
2984 | case QGles2CommandBuffer::Command::BeginFrame: |
2985 | if (cmd.args.beginFrame.timestampQuery) |
2986 | glQueryCounter(cmd.args.beginFrame.timestampQuery, GL_TIMESTAMP); |
2987 | if (caps.coreProfile) { |
2988 | if (!vao) |
2989 | f->glGenVertexArrays(n: 1, arrays: &vao); |
2990 | f->glBindVertexArray(array: vao); |
2991 | } |
2992 | break; |
2993 | case QGles2CommandBuffer::Command::EndFrame: |
2994 | if (state.instancedAttributesUsed) { |
2995 | for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { |
2996 | if (state.nonzeroAttribDivisor[i]) |
2997 | f->glVertexAttribDivisor(index: GLuint(i), divisor: 0); |
2998 | } |
2999 | for (int i = CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; i <= state.maxUntrackedInstancedAttribute; ++i) |
3000 | f->glVertexAttribDivisor(index: GLuint(i), divisor: 0); |
3001 | state.instancedAttributesUsed = false; |
3002 | } |
3003 | #ifdef Q_OS_WASM |
3004 | for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { |
3005 | if (state.enabledAttribArrays[i]) { |
3006 | f->glDisableVertexAttribArray(GLuint(i)); |
3007 | state.enabledAttribArrays[i] = false; |
3008 | } |
3009 | } |
3010 | #endif |
3011 | if (vao) |
3012 | f->glBindVertexArray(array: 0); |
3013 | if (cmd.args.endFrame.timestampQuery) |
3014 | glQueryCounter(cmd.args.endFrame.timestampQuery, GL_TIMESTAMP); |
3015 | break; |
3016 | case QGles2CommandBuffer::Command::ResetFrame: |
3017 | if (vao) |
3018 | f->glBindVertexArray(array: vao); |
3019 | break; |
3020 | case QGles2CommandBuffer::Command::Viewport: |
3021 | f->glViewport(x: GLint(cmd.args.viewport.x), y: GLint(cmd.args.viewport.y), width: GLsizei(cmd.args.viewport.w), height: GLsizei(cmd.args.viewport.h)); |
3022 | f->glDepthRangef(zNear: cmd.args.viewport.d0, zFar: cmd.args.viewport.d1); |
3023 | break; |
3024 | case QGles2CommandBuffer::Command::Scissor: |
3025 | f->glScissor(x: cmd.args.scissor.x, y: cmd.args.scissor.y, width: cmd.args.scissor.w, height: cmd.args.scissor.h); |
3026 | break; |
3027 | case QGles2CommandBuffer::Command::BlendConstants: |
3028 | f->glBlendColor(red: cmd.args.blendConstants.r, green: cmd.args.blendConstants.g, blue: cmd.args.blendConstants.b, alpha: cmd.args.blendConstants.a); |
3029 | break; |
3030 | case QGles2CommandBuffer::Command::StencilRef: |
3031 | { |
3032 | QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps); |
3033 | if (psD) { |
3034 | const GLint ref = GLint(cmd.args.stencilRef.ref); |
3035 | f->glStencilFuncSeparate(GL_FRONT, func: toGlCompareOp(op: psD->m_stencilFront.compareOp), ref, mask: psD->m_stencilReadMask); |
3036 | f->glStencilFuncSeparate(GL_BACK, func: toGlCompareOp(op: psD->m_stencilBack.compareOp), ref, mask: psD->m_stencilReadMask); |
3037 | cbD->graphicsPassState.dynamic.stencilRef = ref; |
3038 | } else { |
3039 | qWarning(msg: "No graphics pipeline active for setStencilRef; ignored"); |
3040 | } |
3041 | } |
3042 | break; |
3043 | case QGles2CommandBuffer::Command::BindVertexBuffer: |
3044 | { |
3045 | QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps); |
3046 | if (psD) { |
3047 | if (state.lastBindVertexBuffer.ps == psD |
3048 | && state.lastBindVertexBuffer.buffer == cmd.args.bindVertexBuffer.buffer |
3049 | && state.lastBindVertexBuffer.offset == cmd.args.bindVertexBuffer.offset |
3050 | && state.lastBindVertexBuffer.binding == cmd.args.bindVertexBuffer.binding) |
3051 | { |
3052 | // The pipeline and so the vertex input layout is |
3053 | // immutable, no point in issuing the exact same set of |
3054 | // glVertexAttribPointer again and again for the same buffer. |
3055 | break; |
3056 | } |
3057 | state.lastBindVertexBuffer.ps = psD; |
3058 | state.lastBindVertexBuffer.buffer = cmd.args.bindVertexBuffer.buffer; |
3059 | state.lastBindVertexBuffer.offset = cmd.args.bindVertexBuffer.offset; |
3060 | state.lastBindVertexBuffer.binding = cmd.args.bindVertexBuffer.binding; |
3061 | |
3062 | if (cmd.args.bindVertexBuffer.buffer != state.currentArrayBuffer) { |
3063 | state.currentArrayBuffer = cmd.args.bindVertexBuffer.buffer; |
3064 | // we do not support more than one vertex buffer |
3065 | f->glBindBuffer(GL_ARRAY_BUFFER, buffer: state.currentArrayBuffer); |
3066 | } |
3067 | for (auto it = psD->m_vertexInputLayout.cbeginAttributes(), itEnd = psD->m_vertexInputLayout.cendAttributes(); |
3068 | it != itEnd; ++it) |
3069 | { |
3070 | const int bindingIdx = it->binding(); |
3071 | if (bindingIdx != cmd.args.bindVertexBuffer.binding) |
3072 | continue; |
3073 | |
3074 | const QRhiVertexInputBinding *inputBinding = psD->m_vertexInputLayout.bindingAt(index: bindingIdx); |
3075 | const int stride = int(inputBinding->stride()); |
3076 | int size = 1; |
3077 | GLenum type = GL_FLOAT; |
3078 | bool normalize = false; |
3079 | switch (it->format()) { |
3080 | case QRhiVertexInputAttribute::Float4: |
3081 | type = GL_FLOAT; |
3082 | size = 4; |
3083 | break; |
3084 | case QRhiVertexInputAttribute::Float3: |
3085 | type = GL_FLOAT; |
3086 | size = 3; |
3087 | break; |
3088 | case QRhiVertexInputAttribute::Float2: |
3089 | type = GL_FLOAT; |
3090 | size = 2; |
3091 | break; |
3092 | case QRhiVertexInputAttribute::Float: |
3093 | type = GL_FLOAT; |
3094 | size = 1; |
3095 | break; |
3096 | case QRhiVertexInputAttribute::UNormByte4: |
3097 | type = GL_UNSIGNED_BYTE; |
3098 | normalize = true; |
3099 | size = 4; |
3100 | break; |
3101 | case QRhiVertexInputAttribute::UNormByte2: |
3102 | type = GL_UNSIGNED_BYTE; |
3103 | normalize = true; |
3104 | size = 2; |
3105 | break; |
3106 | case QRhiVertexInputAttribute::UNormByte: |
3107 | type = GL_UNSIGNED_BYTE; |
3108 | normalize = true; |
3109 | size = 1; |
3110 | break; |
3111 | case QRhiVertexInputAttribute::UInt4: |
3112 | type = GL_UNSIGNED_INT; |
3113 | size = 4; |
3114 | break; |
3115 | case QRhiVertexInputAttribute::UInt3: |
3116 | type = GL_UNSIGNED_INT; |
3117 | size = 3; |
3118 | break; |
3119 | case QRhiVertexInputAttribute::UInt2: |
3120 | type = GL_UNSIGNED_INT; |
3121 | size = 2; |
3122 | break; |
3123 | case QRhiVertexInputAttribute::UInt: |
3124 | type = GL_UNSIGNED_INT; |
3125 | size = 1; |
3126 | break; |
3127 | case QRhiVertexInputAttribute::SInt4: |
3128 | type = GL_INT; |
3129 | size = 4; |
3130 | break; |
3131 | case QRhiVertexInputAttribute::SInt3: |
3132 | type = GL_INT; |
3133 | size = 3; |
3134 | break; |
3135 | case QRhiVertexInputAttribute::SInt2: |
3136 | type = GL_INT; |
3137 | size = 2; |
3138 | break; |
3139 | case QRhiVertexInputAttribute::SInt: |
3140 | type = GL_INT; |
3141 | size = 1; |
3142 | break; |
3143 | case QRhiVertexInputAttribute::Half4: |
3144 | type = GL_HALF_FLOAT; |
3145 | size = 4; |
3146 | break; |
3147 | case QRhiVertexInputAttribute::Half3: |
3148 | type = GL_HALF_FLOAT; |
3149 | size = 3; |
3150 | break; |
3151 | case QRhiVertexInputAttribute::Half2: |
3152 | type = GL_HALF_FLOAT; |
3153 | size = 2; |
3154 | break; |
3155 | case QRhiVertexInputAttribute::Half: |
3156 | type = GL_HALF_FLOAT; |
3157 | size = 1; |
3158 | break; |
3159 | case QRhiVertexInputAttribute::UShort4: |
3160 | type = GL_UNSIGNED_SHORT; |
3161 | size = 4; |
3162 | break; |
3163 | case QRhiVertexInputAttribute::UShort3: |
3164 | type = GL_UNSIGNED_SHORT; |
3165 | size = 3; |
3166 | break; |
3167 | case QRhiVertexInputAttribute::UShort2: |
3168 | type = GL_UNSIGNED_SHORT; |
3169 | size = 2; |
3170 | break; |
3171 | case QRhiVertexInputAttribute::UShort: |
3172 | type = GL_UNSIGNED_SHORT; |
3173 | size = 1; |
3174 | break; |
3175 | case QRhiVertexInputAttribute::SShort4: |
3176 | type = GL_SHORT; |
3177 | size = 4; |
3178 | break; |
3179 | case QRhiVertexInputAttribute::SShort3: |
3180 | type = GL_SHORT; |
3181 | size = 3; |
3182 | break; |
3183 | case QRhiVertexInputAttribute::SShort2: |
3184 | type = GL_SHORT; |
3185 | size = 2; |
3186 | break; |
3187 | case QRhiVertexInputAttribute::SShort: |
3188 | type = GL_SHORT; |
3189 | size = 1; |
3190 | break; |
3191 | default: |
3192 | break; |
3193 | } |
3194 | |
3195 | const int locationIdx = it->location(); |
3196 | quint32 ofs = it->offset() + cmd.args.bindVertexBuffer.offset; |
3197 | if (type == GL_UNSIGNED_INT || type == GL_INT) { |
3198 | if (caps.intAttributes) { |
3199 | f->glVertexAttribIPointer(index: GLuint(locationIdx), size, type, stride, |
3200 | pointer: reinterpret_cast<const GLvoid *>(quintptr(ofs))); |
3201 | } else { |
3202 | qWarning(msg: "Current RHI backend does not support IntAttributes. Check supported features."); |
3203 | // This is a trick to disable this attribute |
3204 | if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) |
3205 | state.enabledAttribArrays[locationIdx] = true; |
3206 | } |
3207 | } else { |
3208 | f->glVertexAttribPointer(indx: GLuint(locationIdx), size, type, normalized: normalize, stride, |
3209 | ptr: reinterpret_cast<const GLvoid *>(quintptr(ofs))); |
3210 | } |
3211 | if (locationIdx >= CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT || !state.enabledAttribArrays[locationIdx]) { |
3212 | if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) |
3213 | state.enabledAttribArrays[locationIdx] = true; |
3214 | f->glEnableVertexAttribArray(index: GLuint(locationIdx)); |
3215 | } |
3216 | if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance && caps.instancing) { |
3217 | f->glVertexAttribDivisor(index: GLuint(locationIdx), divisor: inputBinding->instanceStepRate()); |
3218 | if (Q_LIKELY(locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT)) |
3219 | state.nonzeroAttribDivisor[locationIdx] = true; |
3220 | else |
3221 | state.maxUntrackedInstancedAttribute = qMax(a: state.maxUntrackedInstancedAttribute, b: locationIdx); |
3222 | state.instancedAttributesUsed = true; |
3223 | } else if ((locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT |
3224 | && state.nonzeroAttribDivisor[locationIdx]) |
3225 | || Q_UNLIKELY(locationIdx >= CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT |
3226 | && locationIdx <= state.maxUntrackedInstancedAttribute)) |
3227 | { |
3228 | f->glVertexAttribDivisor(index: GLuint(locationIdx), divisor: 0); |
3229 | if (locationIdx < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT) |
3230 | state.nonzeroAttribDivisor[locationIdx] = false; |
3231 | } |
3232 | } |
3233 | } else { |
3234 | qWarning(msg: "No graphics pipeline active for setVertexInput; ignored"); |
3235 | } |
3236 | } |
3237 | break; |
3238 | case QGles2CommandBuffer::Command::BindIndexBuffer: |
3239 | state.indexType = cmd.args.bindIndexBuffer.type; |
3240 | state.indexStride = state.indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32); |
3241 | state.indexOffset = cmd.args.bindIndexBuffer.offset; |
3242 | if (state.currentElementArrayBuffer != cmd.args.bindIndexBuffer.buffer) { |
3243 | state.currentElementArrayBuffer = cmd.args.bindIndexBuffer.buffer; |
3244 | f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: state.currentElementArrayBuffer); |
3245 | } |
3246 | break; |
3247 | case QGles2CommandBuffer::Command::Draw: |
3248 | { |
3249 | QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps); |
3250 | if (psD) { |
3251 | if (cmd.args.draw.instanceCount == 1 || !caps.instancing) { |
3252 | f->glDrawArrays(mode: psD->drawMode, first: GLint(cmd.args.draw.firstVertex), count: GLsizei(cmd.args.draw.vertexCount)); |
3253 | } else { |
3254 | f->glDrawArraysInstanced(mode: psD->drawMode, first: GLint(cmd.args.draw.firstVertex), count: GLsizei(cmd.args.draw.vertexCount), |
3255 | instancecount: GLsizei(cmd.args.draw.instanceCount)); |
3256 | } |
3257 | } else { |
3258 | qWarning(msg: "No graphics pipeline active for draw; ignored"); |
3259 | } |
3260 | } |
3261 | break; |
3262 | case QGles2CommandBuffer::Command::DrawIndexed: |
3263 | { |
3264 | QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps); |
3265 | if (psD) { |
3266 | const GLvoid *ofs = reinterpret_cast<const GLvoid *>( |
3267 | quintptr(cmd.args.drawIndexed.firstIndex * state.indexStride + state.indexOffset)); |
3268 | if (cmd.args.drawIndexed.instanceCount == 1 || !caps.instancing) { |
3269 | if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) { |
3270 | f->glDrawElementsBaseVertex(mode: psD->drawMode, |
3271 | count: GLsizei(cmd.args.drawIndexed.indexCount), |
3272 | type: state.indexType, |
3273 | indices: ofs, |
3274 | basevertex: cmd.args.drawIndexed.baseVertex); |
3275 | } else { |
3276 | f->glDrawElements(mode: psD->drawMode, |
3277 | count: GLsizei(cmd.args.drawIndexed.indexCount), |
3278 | type: state.indexType, |
3279 | indices: ofs); |
3280 | } |
3281 | } else { |
3282 | if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) { |
3283 | f->glDrawElementsInstancedBaseVertex(mode: psD->drawMode, |
3284 | count: GLsizei(cmd.args.drawIndexed.indexCount), |
3285 | type: state.indexType, |
3286 | indices: ofs, |
3287 | instancecount: GLsizei(cmd.args.drawIndexed.instanceCount), |
3288 | basevertex: cmd.args.drawIndexed.baseVertex); |
3289 | } else { |
3290 | f->glDrawElementsInstanced(mode: psD->drawMode, |
3291 | count: GLsizei(cmd.args.drawIndexed.indexCount), |
3292 | type: state.indexType, |
3293 | indices: ofs, |
3294 | instancecount: GLsizei(cmd.args.drawIndexed.instanceCount)); |
3295 | } |
3296 | } |
3297 | } else { |
3298 | qWarning(msg: "No graphics pipeline active for drawIndexed; ignored"); |
3299 | } |
3300 | } |
3301 | break; |
3302 | case QGles2CommandBuffer::Command::BindGraphicsPipeline: |
3303 | executeBindGraphicsPipeline(cbD, QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindGraphicsPipeline.ps)); |
3304 | break; |
3305 | case QGles2CommandBuffer::Command::BindShaderResources: |
3306 | bindShaderResources(cbD, |
3307 | maybeGraphicsPs: cmd.args.bindShaderResources.maybeGraphicsPs, |
3308 | maybeComputePs: cmd.args.bindShaderResources.maybeComputePs, |
3309 | srb: cmd.args.bindShaderResources.srb, |
3310 | dynOfsPairs: cmd.args.bindShaderResources.dynamicOffsetPairs, |
3311 | dynOfsCount: cmd.args.bindShaderResources.dynamicOffsetCount); |
3312 | break; |
3313 | case QGles2CommandBuffer::Command::BindFramebuffer: |
3314 | { |
3315 | QVarLengthArray<GLenum, 8> bufs; |
3316 | if (cmd.args.bindFramebuffer.fbo) { |
3317 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: cmd.args.bindFramebuffer.fbo); |
3318 | const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount; |
3319 | bufs.append(t: colorAttCount > 0 ? GL_COLOR_ATTACHMENT0 : GL_NONE); |
3320 | if (caps.maxDrawBuffers > 1) { |
3321 | for (int i = 1; i < colorAttCount; ++i) |
3322 | bufs.append(GL_COLOR_ATTACHMENT0 + uint(i)); |
3323 | } |
3324 | } else { |
3325 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
3326 | if (cmd.args.bindFramebuffer.stereo && cmd.args.bindFramebuffer.stereoTarget == QRhiSwapChain::RightBuffer) |
3327 | bufs.append(GL_BACK_RIGHT); |
3328 | else |
3329 | bufs.append(t: caps.gles ? GL_BACK : GL_BACK_LEFT); |
3330 | } |
3331 | if (caps.hasDrawBuffersFunc) |
3332 | f->glDrawBuffers(n: bufs.count(), bufs: bufs.constData()); |
3333 | if (caps.srgbWriteControl) { |
3334 | if (cmd.args.bindFramebuffer.srgb) |
3335 | f->glEnable(GL_FRAMEBUFFER_SRGB); |
3336 | else |
3337 | f->glDisable(GL_FRAMEBUFFER_SRGB); |
3338 | } |
3339 | } |
3340 | break; |
3341 | case QGles2CommandBuffer::Command::Clear: |
3342 | f->glDisable(GL_SCISSOR_TEST); |
3343 | if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) { |
3344 | f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
3345 | f->glClearColor(red: cmd.args.clear.c[0], green: cmd.args.clear.c[1], blue: cmd.args.clear.c[2], alpha: cmd.args.clear.c[3]); |
3346 | } |
3347 | if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) { |
3348 | f->glDepthMask(GL_TRUE); |
3349 | f->glClearDepthf(depth: cmd.args.clear.d); |
3350 | } |
3351 | if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT) { |
3352 | f->glStencilMask(mask: 0xFF); |
3353 | f->glClearStencil(s: GLint(cmd.args.clear.s)); |
3354 | } |
3355 | f->glClear(mask: cmd.args.clear.mask); |
3356 | cbD->graphicsPassState.reset(); // altered depth/color write, invalidate in order to avoid confusing the state tracking |
3357 | break; |
3358 | case QGles2CommandBuffer::Command::BufferSubData: |
3359 | bindVertexIndexBufferWithStateReset(state: &state, f, target: cmd.args.bufferSubData.target, buffer: cmd.args.bufferSubData.buffer); |
3360 | f->glBufferSubData(target: cmd.args.bufferSubData.target, offset: cmd.args.bufferSubData.offset, size: cmd.args.bufferSubData.size, |
3361 | data: cmd.args.bufferSubData.data); |
3362 | break; |
3363 | case QGles2CommandBuffer::Command::GetBufferSubData: |
3364 | { |
3365 | QRhiReadbackResult *result = cmd.args.getBufferSubData.result; |
3366 | bindVertexIndexBufferWithStateReset(state: &state, f, target: cmd.args.getBufferSubData.target, buffer: cmd.args.getBufferSubData.buffer); |
3367 | if (caps.gles) { |
3368 | if (caps.properMapBuffer) { |
3369 | void *p = f->glMapBufferRange(target: cmd.args.getBufferSubData.target, |
3370 | offset: cmd.args.getBufferSubData.offset, |
3371 | length: cmd.args.getBufferSubData.size, |
3372 | GL_MAP_READ_BIT); |
3373 | if (p) { |
3374 | result->data.resize(size: cmd.args.getBufferSubData.size); |
3375 | memcpy(dest: result->data.data(), src: p, n: size_t(cmd.args.getBufferSubData.size)); |
3376 | f->glUnmapBuffer(target: cmd.args.getBufferSubData.target); |
3377 | } |
3378 | } |
3379 | } else { |
3380 | result->data.resize(size: cmd.args.getBufferSubData.size); |
3381 | f->glGetBufferSubData(target: cmd.args.getBufferSubData.target, |
3382 | offset: cmd.args.getBufferSubData.offset, |
3383 | size: cmd.args.getBufferSubData.size, |
3384 | data: result->data.data()); |
3385 | } |
3386 | if (result->completed) |
3387 | result->completed(); |
3388 | } |
3389 | break; |
3390 | case QGles2CommandBuffer::Command::CopyTex: |
3391 | { |
3392 | GLuint fbo; |
3393 | f->glGenFramebuffers(n: 1, framebuffers: &fbo); |
3394 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo); |
3395 | if (cmd.args.copyTex.srcTarget == GL_TEXTURE_3D |
3396 | || cmd.args.copyTex.srcTarget == GL_TEXTURE_2D_ARRAY |
3397 | || cmd.args.copyTex.srcTarget == GL_TEXTURE_1D_ARRAY) { |
3398 | f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture: cmd.args.copyTex.srcTexture, |
3399 | level: cmd.args.copyTex.srcLevel, layer: cmd.args.copyTex.srcZ); |
3400 | } else if (cmd.args.copyTex.srcTarget == GL_TEXTURE_1D) { |
3401 | glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3402 | cmd.args.copyTex.srcTarget, cmd.args.copyTex.srcTexture, |
3403 | cmd.args.copyTex.srcLevel); |
3404 | } else { |
3405 | f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3406 | textarget: cmd.args.copyTex.srcFaceTarget, texture: cmd.args.copyTex.srcTexture, level: cmd.args.copyTex.srcLevel); |
3407 | } |
3408 | f->glBindTexture(target: cmd.args.copyTex.dstTarget, texture: cmd.args.copyTex.dstTexture); |
3409 | if (cmd.args.copyTex.dstTarget == GL_TEXTURE_3D || cmd.args.copyTex.dstTarget == GL_TEXTURE_2D_ARRAY) { |
3410 | f->glCopyTexSubImage3D(target: cmd.args.copyTex.dstTarget, level: cmd.args.copyTex.dstLevel, |
3411 | xoffset: cmd.args.copyTex.dstX, yoffset: cmd.args.copyTex.dstY, zoffset: cmd.args.copyTex.dstZ, |
3412 | x: cmd.args.copyTex.srcX, y: cmd.args.copyTex.srcY, |
3413 | width: cmd.args.copyTex.w, height: cmd.args.copyTex.h); |
3414 | } else if (cmd.args.copyTex.dstTarget == GL_TEXTURE_1D) { |
3415 | glCopyTexSubImage1D(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstLevel, |
3416 | cmd.args.copyTex.dstX, cmd.args.copyTex.srcX, |
3417 | cmd.args.copyTex.srcY, cmd.args.copyTex.w); |
3418 | } else { |
3419 | f->glCopyTexSubImage2D(target: cmd.args.copyTex.dstFaceTarget, level: cmd.args.copyTex.dstLevel, |
3420 | xoffset: cmd.args.copyTex.dstX, yoffset: cmd.args.copyTex.dstY, |
3421 | x: cmd.args.copyTex.srcX, y: cmd.args.copyTex.srcY, |
3422 | width: cmd.args.copyTex.w, height: cmd.args.copyTex.h); |
3423 | } |
3424 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
3425 | f->glDeleteFramebuffers(n: 1, framebuffers: &fbo); |
3426 | } |
3427 | break; |
3428 | case QGles2CommandBuffer::Command::ReadPixels: |
3429 | { |
3430 | QRhiReadbackResult *result = cmd.args.readPixels.result; |
3431 | GLuint tex = cmd.args.readPixels.texture; |
3432 | GLuint fbo = 0; |
3433 | int mipLevel = 0; |
3434 | if (tex) { |
3435 | result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h); |
3436 | result->format = cmd.args.readPixels.format; |
3437 | mipLevel = cmd.args.readPixels.level; |
3438 | if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { |
3439 | f->glGenFramebuffers(n: 1, framebuffers: &fbo); |
3440 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: fbo); |
3441 | if (cmd.args.readPixels.slice3D >= 0) { |
3442 | f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3443 | texture: tex, level: mipLevel, layer: cmd.args.readPixels.slice3D); |
3444 | } else if (cmd.args.readPixels.readTarget == GL_TEXTURE_1D) { |
3445 | glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3446 | cmd.args.readPixels.readTarget, tex, mipLevel); |
3447 | } else { |
3448 | f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3449 | textarget: cmd.args.readPixels.readTarget, texture: tex, level: mipLevel); |
3450 | } |
3451 | } |
3452 | } else { |
3453 | result->pixelSize = currentSwapChain->pixelSize; |
3454 | result->format = QRhiTexture::RGBA8; |
3455 | // readPixels handles multisample resolving implicitly |
3456 | } |
3457 | const int w = result->pixelSize.width(); |
3458 | const int h = result->pixelSize.height(); |
3459 | if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { |
3460 | // With GLES, GL_RGBA is the only mandated readback format, so stick with it. |
3461 | // (and that's why we return false for the ReadBackAnyTextureFormat feature) |
3462 | if (result->format == QRhiTexture::R8 || result->format == QRhiTexture::RED_OR_ALPHA8) { |
3463 | result->data.resize(size: w * h); |
3464 | QByteArray tmpBuf; |
3465 | tmpBuf.resize(size: w * h * 4); |
3466 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_BYTE, pixels: tmpBuf.data()); |
3467 | const quint8 *srcBase = reinterpret_cast<const quint8 *>(tmpBuf.constData()); |
3468 | quint8 *dstBase = reinterpret_cast<quint8 *>(result->data.data()); |
3469 | const int componentIndex = isFeatureSupported(feature: QRhi::RedOrAlpha8IsRed) ? 0 : 3; |
3470 | for (int y = 0; y < h; ++y) { |
3471 | const quint8 *src = srcBase + y * w * 4; |
3472 | quint8 *dst = dstBase + y * w; |
3473 | int count = w; |
3474 | while (count-- > 0) { |
3475 | *dst++ = src[componentIndex]; |
3476 | src += 4; |
3477 | } |
3478 | } |
3479 | } else { |
3480 | switch (result->format) { |
3481 | // For floating point formats try it because this can be |
3482 | // relevant for some use cases; if it works, then fine, if |
3483 | // not, there's nothing we can do. |
3484 | case QRhiTexture::RGBA16F: |
3485 | result->data.resize(size: w * h * 8); |
3486 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_HALF_FLOAT, pixels: result->data.data()); |
3487 | break; |
3488 | case QRhiTexture::R16F: |
3489 | result->data.resize(size: w * h * 2); |
3490 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RED, GL_HALF_FLOAT, pixels: result->data.data()); |
3491 | break; |
3492 | case QRhiTexture::R32F: |
3493 | result->data.resize(size: w * h * 4); |
3494 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RED, GL_FLOAT, pixels: result->data.data()); |
3495 | break; |
3496 | case QRhiTexture::RGBA32F: |
3497 | result->data.resize(size: w * h * 16); |
3498 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_FLOAT, pixels: result->data.data()); |
3499 | break; |
3500 | case QRhiTexture::RGB10A2: |
3501 | result->data.resize(size: w * h * 4); |
3502 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, pixels: result->data.data()); |
3503 | break; |
3504 | default: |
3505 | result->data.resize(size: w * h * 4); |
3506 | f->glReadPixels(x: 0, y: 0, width: w, height: h, GL_RGBA, GL_UNSIGNED_BYTE, pixels: result->data.data()); |
3507 | break; |
3508 | } |
3509 | } |
3510 | } else { |
3511 | result->data.resize(size: w * h * 4); |
3512 | result->data.fill(c: '\0'); |
3513 | } |
3514 | if (fbo) { |
3515 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
3516 | f->glDeleteFramebuffers(n: 1, framebuffers: &fbo); |
3517 | } |
3518 | if (result->completed) |
3519 | result->completed(); |
3520 | } |
3521 | break; |
3522 | case QGles2CommandBuffer::Command::SubImage: |
3523 | f->glBindTexture(target: cmd.args.subImage.target, texture: cmd.args.subImage.texture); |
3524 | if (cmd.args.subImage.rowStartAlign != 4) |
3525 | f->glPixelStorei(GL_UNPACK_ALIGNMENT, param: cmd.args.subImage.rowStartAlign); |
3526 | if (cmd.args.subImage.rowLength != 0) |
3527 | f->glPixelStorei(GL_UNPACK_ROW_LENGTH, param: cmd.args.subImage.rowLength); |
3528 | if (cmd.args.subImage.target == GL_TEXTURE_3D || cmd.args.subImage.target == GL_TEXTURE_2D_ARRAY) { |
3529 | f->glTexSubImage3D(target: cmd.args.subImage.target, level: cmd.args.subImage.level, |
3530 | xoffset: cmd.args.subImage.dx, yoffset: cmd.args.subImage.dy, zoffset: cmd.args.subImage.dz, |
3531 | width: cmd.args.subImage.w, height: cmd.args.subImage.h, depth: 1, |
3532 | format: cmd.args.subImage.glformat, type: cmd.args.subImage.gltype, |
3533 | pixels: cmd.args.subImage.data); |
3534 | } else if (cmd.args.subImage.target == GL_TEXTURE_1D) { |
3535 | glTexSubImage1D(cmd.args.subImage.target, cmd.args.subImage.level, |
3536 | cmd.args.subImage.dx, cmd.args.subImage.w, |
3537 | cmd.args.subImage.glformat, cmd.args.subImage.gltype, |
3538 | cmd.args.subImage.data); |
3539 | } else { |
3540 | f->glTexSubImage2D(target: cmd.args.subImage.faceTarget, level: cmd.args.subImage.level, |
3541 | xoffset: cmd.args.subImage.dx, yoffset: cmd.args.subImage.dy, |
3542 | width: cmd.args.subImage.w, height: cmd.args.subImage.h, |
3543 | format: cmd.args.subImage.glformat, type: cmd.args.subImage.gltype, |
3544 | pixels: cmd.args.subImage.data); |
3545 | } |
3546 | if (cmd.args.subImage.rowStartAlign != 4) |
3547 | f->glPixelStorei(GL_UNPACK_ALIGNMENT, param: 4); |
3548 | if (cmd.args.subImage.rowLength != 0) |
3549 | f->glPixelStorei(GL_UNPACK_ROW_LENGTH, param: 0); |
3550 | break; |
3551 | case QGles2CommandBuffer::Command::CompressedImage: |
3552 | f->glBindTexture(target: cmd.args.compressedImage.target, texture: cmd.args.compressedImage.texture); |
3553 | if (cmd.args.compressedImage.target == GL_TEXTURE_3D || cmd.args.compressedImage.target == GL_TEXTURE_2D_ARRAY) { |
3554 | f->glCompressedTexImage3D(target: cmd.args.compressedImage.target, level: cmd.args.compressedImage.level, |
3555 | internalformat: cmd.args.compressedImage.glintformat, |
3556 | width: cmd.args.compressedImage.w, height: cmd.args.compressedImage.h, depth: cmd.args.compressedImage.depth, |
3557 | border: 0, imageSize: cmd.args.compressedImage.size, data: cmd.args.compressedImage.data); |
3558 | } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) { |
3559 | glCompressedTexImage1D( |
3560 | cmd.args.compressedImage.target, cmd.args.compressedImage.level, |
3561 | cmd.args.compressedImage.glintformat, cmd.args.compressedImage.w, 0, |
3562 | cmd.args.compressedImage.size, cmd.args.compressedImage.data); |
3563 | } else { |
3564 | f->glCompressedTexImage2D(target: cmd.args.compressedImage.faceTarget, level: cmd.args.compressedImage.level, |
3565 | internalformat: cmd.args.compressedImage.glintformat, |
3566 | width: cmd.args.compressedImage.w, height: cmd.args.compressedImage.h, |
3567 | border: 0, imageSize: cmd.args.compressedImage.size, data: cmd.args.compressedImage.data); |
3568 | } |
3569 | break; |
3570 | case QGles2CommandBuffer::Command::CompressedSubImage: |
3571 | f->glBindTexture(target: cmd.args.compressedSubImage.target, texture: cmd.args.compressedSubImage.texture); |
3572 | if (cmd.args.compressedSubImage.target == GL_TEXTURE_3D || cmd.args.compressedSubImage.target == GL_TEXTURE_2D_ARRAY) { |
3573 | f->glCompressedTexSubImage3D(target: cmd.args.compressedSubImage.target, level: cmd.args.compressedSubImage.level, |
3574 | xoffset: cmd.args.compressedSubImage.dx, yoffset: cmd.args.compressedSubImage.dy, zoffset: cmd.args.compressedSubImage.dz, |
3575 | width: cmd.args.compressedSubImage.w, height: cmd.args.compressedSubImage.h, depth: 1, |
3576 | format: cmd.args.compressedSubImage.glintformat, |
3577 | imageSize: cmd.args.compressedSubImage.size, data: cmd.args.compressedSubImage.data); |
3578 | } else if (cmd.args.compressedImage.target == GL_TEXTURE_1D) { |
3579 | glCompressedTexSubImage1D( |
3580 | cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.level, |
3581 | cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.w, |
3582 | cmd.args.compressedSubImage.glintformat, cmd.args.compressedSubImage.size, |
3583 | cmd.args.compressedSubImage.data); |
3584 | } else { |
3585 | f->glCompressedTexSubImage2D(target: cmd.args.compressedSubImage.faceTarget, level: cmd.args.compressedSubImage.level, |
3586 | xoffset: cmd.args.compressedSubImage.dx, yoffset: cmd.args.compressedSubImage.dy, |
3587 | width: cmd.args.compressedSubImage.w, height: cmd.args.compressedSubImage.h, |
3588 | format: cmd.args.compressedSubImage.glintformat, |
3589 | imageSize: cmd.args.compressedSubImage.size, data: cmd.args.compressedSubImage.data); |
3590 | } |
3591 | break; |
3592 | case QGles2CommandBuffer::Command::BlitFromRenderbuffer: |
3593 | { |
3594 | // Altering the scissor state, so reset the stored state, although |
3595 | // not strictly required as long as blit is done in endPass() only. |
3596 | cbD->graphicsPassState.reset(); |
3597 | f->glDisable(GL_SCISSOR_TEST); |
3598 | GLuint fbo[2]; |
3599 | f->glGenFramebuffers(n: 2, framebuffers: fbo); |
3600 | f->glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: fbo[0]); |
3601 | const bool ds = cmd.args.blitFromRenderbuffer.isDepthStencil; |
3602 | if (ds) { |
3603 | f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
3604 | GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer); |
3605 | f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
3606 | GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer); |
3607 | } else { |
3608 | f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3609 | GL_RENDERBUFFER, renderbuffer: cmd.args.blitFromRenderbuffer.renderbuffer); |
3610 | } |
3611 | f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: fbo[1]); |
3612 | if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_2D_ARRAY) { |
3613 | if (ds) { |
3614 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
3615 | texture: cmd.args.blitFromRenderbuffer.dstTexture, |
3616 | level: cmd.args.blitFromRenderbuffer.dstLevel, |
3617 | layer: cmd.args.blitFromRenderbuffer.dstLayer); |
3618 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
3619 | texture: cmd.args.blitFromRenderbuffer.dstTexture, |
3620 | level: cmd.args.blitFromRenderbuffer.dstLevel, |
3621 | layer: cmd.args.blitFromRenderbuffer.dstLayer); |
3622 | } else { |
3623 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3624 | texture: cmd.args.blitFromRenderbuffer.dstTexture, |
3625 | level: cmd.args.blitFromRenderbuffer.dstLevel, |
3626 | layer: cmd.args.blitFromRenderbuffer.dstLayer); |
3627 | } |
3628 | } else { |
3629 | if (ds) { |
3630 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromRenderbuffer.target, |
3631 | texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel); |
3632 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromRenderbuffer.target, |
3633 | texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel); |
3634 | } else { |
3635 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromRenderbuffer.target, |
3636 | texture: cmd.args.blitFromRenderbuffer.dstTexture, level: cmd.args.blitFromRenderbuffer.dstLevel); |
3637 | } |
3638 | } |
3639 | f->glBlitFramebuffer(srcX0: 0, srcY0: 0, srcX1: cmd.args.blitFromRenderbuffer.w, srcY1: cmd.args.blitFromRenderbuffer.h, |
3640 | dstX0: 0, dstY0: 0, dstX1: cmd.args.blitFromRenderbuffer.w, dstY1: cmd.args.blitFromRenderbuffer.h, |
3641 | mask: ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, |
3642 | GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that |
3643 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
3644 | f->glDeleteFramebuffers(n: 2, framebuffers: fbo); |
3645 | } |
3646 | break; |
3647 | case QGles2CommandBuffer::Command::BlitFromTexture: |
3648 | { |
3649 | // Altering the scissor state, so reset the stored state, although |
3650 | // not strictly required as long as blit is done in endPass() only. |
3651 | cbD->graphicsPassState.reset(); |
3652 | f->glDisable(GL_SCISSOR_TEST); |
3653 | GLuint fbo[2]; |
3654 | f->glGenFramebuffers(n: 2, framebuffers: fbo); |
3655 | f->glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer: fbo[0]); |
3656 | const bool ds = cmd.args.blitFromTexture.isDepthStencil; |
3657 | if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) { |
3658 | if (ds) { |
3659 | f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
3660 | texture: cmd.args.blitFromTexture.srcTexture, |
3661 | level: cmd.args.blitFromTexture.srcLevel, |
3662 | layer: cmd.args.blitFromTexture.srcLayer); |
3663 | f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
3664 | texture: cmd.args.blitFromTexture.srcTexture, |
3665 | level: cmd.args.blitFromTexture.srcLevel, |
3666 | layer: cmd.args.blitFromTexture.srcLayer); |
3667 | } else { |
3668 | f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3669 | texture: cmd.args.blitFromTexture.srcTexture, |
3670 | level: cmd.args.blitFromTexture.srcLevel, |
3671 | layer: cmd.args.blitFromTexture.srcLayer); |
3672 | } |
3673 | } else { |
3674 | if (ds) { |
3675 | f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromTexture.srcTarget, |
3676 | texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel); |
3677 | f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromTexture.srcTarget, |
3678 | texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel); |
3679 | } else { |
3680 | f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromTexture.srcTarget, |
3681 | texture: cmd.args.blitFromTexture.srcTexture, level: cmd.args.blitFromTexture.srcLevel); |
3682 | } |
3683 | } |
3684 | f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer: fbo[1]); |
3685 | if (cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) { |
3686 | if (ds) { |
3687 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
3688 | texture: cmd.args.blitFromTexture.dstTexture, |
3689 | level: cmd.args.blitFromTexture.dstLevel, |
3690 | layer: cmd.args.blitFromTexture.dstLayer); |
3691 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
3692 | texture: cmd.args.blitFromTexture.dstTexture, |
3693 | level: cmd.args.blitFromTexture.dstLevel, |
3694 | layer: cmd.args.blitFromTexture.dstLayer); |
3695 | } else { |
3696 | f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
3697 | texture: cmd.args.blitFromTexture.dstTexture, |
3698 | level: cmd.args.blitFromTexture.dstLevel, |
3699 | layer: cmd.args.blitFromTexture.dstLayer); |
3700 | } |
3701 | } else { |
3702 | if (ds) { |
3703 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: cmd.args.blitFromTexture.dstTarget, |
3704 | texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel); |
3705 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: cmd.args.blitFromTexture.dstTarget, |
3706 | texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel); |
3707 | } else { |
3708 | f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget: cmd.args.blitFromTexture.dstTarget, |
3709 | texture: cmd.args.blitFromTexture.dstTexture, level: cmd.args.blitFromTexture.dstLevel); |
3710 | } |
3711 | } |
3712 | f->glBlitFramebuffer(srcX0: 0, srcY0: 0, srcX1: cmd.args.blitFromTexture.w, srcY1: cmd.args.blitFromTexture.h, |
3713 | dstX0: 0, dstY0: 0, dstX1: cmd.args.blitFromTexture.w, dstY1: cmd.args.blitFromTexture.h, |
3714 | mask: ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT, |
3715 | GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that |
3716 | f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: ctx->defaultFramebufferObject()); |
3717 | f->glDeleteFramebuffers(n: 2, framebuffers: fbo); |
3718 | } |
3719 | break; |
3720 | case QGles2CommandBuffer::Command::GenMip: |
3721 | f->glBindTexture(target: cmd.args.genMip.target, texture: cmd.args.genMip.texture); |
3722 | f->glGenerateMipmap(target: cmd.args.genMip.target); |
3723 | break; |
3724 | case QGles2CommandBuffer::Command::BindComputePipeline: |
3725 | { |
3726 | QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, cmd.args.bindComputePipeline.ps); |
3727 | f->glUseProgram(program: psD->program); |
3728 | } |
3729 | break; |
3730 | case QGles2CommandBuffer::Command::Dispatch: |
3731 | f->glDispatchCompute(num_groups_x: cmd.args.dispatch.x, num_groups_y: cmd.args.dispatch.y, num_groups_z: cmd.args.dispatch.z); |
3732 | break; |
3733 | case QGles2CommandBuffer::Command::BarriersForPass: |
3734 | { |
3735 | if (!caps.compute) |
3736 | break; |
3737 | GLbitfield barriers = 0; |
3738 | QRhiPassResourceTracker &tracker(cbD->passResTrackers[cmd.args.barriersForPass.trackerIndex]); |
3739 | // we only care about after-write, not any other accesses, and |
3740 | // cannot tell if something was written in a shader several passes |
3741 | // ago: now the previously written resource may be used with an |
3742 | // access that was not in the previous passes, result in a missing |
3743 | // barrier in theory. Hence setting all barrier bits whenever |
3744 | // something previously written is used for the first time in a |
3745 | // subsequent pass. |
3746 | for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) { |
3747 | QGles2Buffer::Access accessBeforePass = QGles2Buffer::Access(it->stateAtPassBegin.access); |
3748 | if (bufferAccessIsWrite(access: accessBeforePass)) |
3749 | barriers |= barriersForBuffer(); |
3750 | } |
3751 | for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) { |
3752 | QGles2Texture::Access accessBeforePass = QGles2Texture::Access(it->stateAtPassBegin.access); |
3753 | if (textureAccessIsWrite(access: accessBeforePass)) |
3754 | barriers |= barriersForTexture(); |
3755 | } |
3756 | if (barriers) |
3757 | f->glMemoryBarrier(barriers); |
3758 | } |
3759 | break; |
3760 | case QGles2CommandBuffer::Command::Barrier: |
3761 | if (caps.compute) |
3762 | f->glMemoryBarrier(barriers: cmd.args.barrier.barriers); |
3763 | break; |
3764 | case QGles2CommandBuffer::Command::InvalidateFramebuffer: |
3765 | if (caps.gles && caps.ctxMajor >= 3) { |
3766 | f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, |
3767 | numAttachments: cmd.args.invalidateFramebuffer.attCount, |
3768 | attachments: cmd.args.invalidateFramebuffer.att); |
3769 | } |
3770 | break; |
3771 | default: |
3772 | break; |
3773 | } |
3774 | } |
3775 | if (state.instancedAttributesUsed) { |
3776 | for (int i = 0; i < CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; ++i) { |
3777 | if (state.nonzeroAttribDivisor[i]) |
3778 | f->glVertexAttribDivisor(index: GLuint(i), divisor: 0); |
3779 | } |
3780 | for (int i = CommandBufferExecTrackedState::TRACKED_ATTRIB_COUNT; i <= state.maxUntrackedInstancedAttribute; ++i) |
3781 | f->glVertexAttribDivisor(index: GLuint(i), divisor: 0); |
3782 | } |
3783 | } |
3784 | |
3785 | void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2GraphicsPipeline *psD) |
3786 | { |
3787 | QGles2CommandBuffer::GraphicsPassState &state(cbD->graphicsPassState); |
3788 | const bool forceUpdate = !state.valid; |
3789 | state.valid = true; |
3790 | |
3791 | const bool scissor = psD->m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesScissor); |
3792 | if (forceUpdate || scissor != state.scissor) { |
3793 | state.scissor = scissor; |
3794 | if (scissor) |
3795 | f->glEnable(GL_SCISSOR_TEST); |
3796 | else |
3797 | f->glDisable(GL_SCISSOR_TEST); |
3798 | } |
3799 | |
3800 | const bool cullFace = psD->m_cullMode != QRhiGraphicsPipeline::None; |
3801 | const GLenum cullMode = cullFace ? toGlCullMode(c: psD->m_cullMode) : GL_NONE; |
3802 | if (forceUpdate || cullFace != state.cullFace || cullMode != state.cullMode) { |
3803 | state.cullFace = cullFace; |
3804 | state.cullMode = cullMode; |
3805 | if (cullFace) { |
3806 | f->glEnable(GL_CULL_FACE); |
3807 | f->glCullFace(mode: cullMode); |
3808 | } else { |
3809 | f->glDisable(GL_CULL_FACE); |
3810 | } |
3811 | } |
3812 | |
3813 | const GLenum frontFace = toGlFrontFace(f: psD->m_frontFace); |
3814 | if (forceUpdate || frontFace != state.frontFace) { |
3815 | state.frontFace = frontFace; |
3816 | f->glFrontFace(mode: frontFace); |
3817 | } |
3818 | |
3819 | const GLenum polygonMode = toGlPolygonMode(mode: psD->m_polygonMode); |
3820 | if (glPolygonMode && (forceUpdate || polygonMode != state.polygonMode)) { |
3821 | state.polygonMode = polygonMode; |
3822 | glPolygonMode(GL_FRONT_AND_BACK, polygonMode); |
3823 | } |
3824 | |
3825 | if (!psD->m_targetBlends.isEmpty()) { |
3826 | // We do not have MRT support here, meaning all targets use the blend |
3827 | // params from the first one. This is technically incorrect, even if |
3828 | // nothing in Qt relies on it. However, considering that |
3829 | // glBlendFuncSeparatei is only available in GL 4.0+ and GLES 3.2+, we |
3830 | // may just live with this for now because no point in bothering if it |
3831 | // won't be usable on many GLES (3.1 or 3.0) systems. |
3832 | const QRhiGraphicsPipeline::TargetBlend &targetBlend(psD->m_targetBlends.first()); |
3833 | |
3834 | const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { |
3835 | .r: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::R), |
3836 | .g: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::G), |
3837 | .b: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::B), |
3838 | .a: targetBlend.colorWrite.testFlag(flag: QRhiGraphicsPipeline::A) |
3839 | }; |
3840 | if (forceUpdate || colorMask != state.colorMask) { |
3841 | state.colorMask = colorMask; |
3842 | f->glColorMask(red: colorMask.r, green: colorMask.g, blue: colorMask.b, alpha: colorMask.a); |
3843 | } |
3844 | |
3845 | const bool blendEnabled = targetBlend.enable; |
3846 | const QGles2CommandBuffer::GraphicsPassState::Blend blend = { |
3847 | .srcColor: toGlBlendFactor(f: targetBlend.srcColor), |
3848 | .dstColor: toGlBlendFactor(f: targetBlend.dstColor), |
3849 | .srcAlpha: toGlBlendFactor(f: targetBlend.srcAlpha), |
3850 | .dstAlpha: toGlBlendFactor(f: targetBlend.dstAlpha), |
3851 | .opColor: toGlBlendOp(op: targetBlend.opColor), |
3852 | .opAlpha: toGlBlendOp(op: targetBlend.opAlpha) |
3853 | }; |
3854 | if (forceUpdate || blendEnabled != state.blendEnabled || (blendEnabled && blend != state.blend)) { |
3855 | state.blendEnabled = blendEnabled; |
3856 | if (blendEnabled) { |
3857 | state.blend = blend; |
3858 | f->glEnable(GL_BLEND); |
3859 | f->glBlendFuncSeparate(srcRGB: blend.srcColor, dstRGB: blend.dstColor, srcAlpha: blend.srcAlpha, dstAlpha: blend.dstAlpha); |
3860 | f->glBlendEquationSeparate(modeRGB: blend.opColor, modeAlpha: blend.opAlpha); |
3861 | } else { |
3862 | f->glDisable(GL_BLEND); |
3863 | } |
3864 | } |
3865 | } else { |
3866 | const QGles2CommandBuffer::GraphicsPassState::ColorMask colorMask = { .r: true, .g: true, .b: true, .a: true }; |
3867 | if (forceUpdate || colorMask != state.colorMask) { |
3868 | state.colorMask = colorMask; |
3869 | f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
3870 | } |
3871 | const bool blendEnabled = false; |
3872 | if (forceUpdate || blendEnabled != state.blendEnabled) { |
3873 | state.blendEnabled = blendEnabled; |
3874 | f->glDisable(GL_BLEND); |
3875 | } |
3876 | } |
3877 | |
3878 | const bool depthTest = psD->m_depthTest; |
3879 | if (forceUpdate || depthTest != state.depthTest) { |
3880 | state.depthTest = depthTest; |
3881 | if (depthTest) |
3882 | f->glEnable(GL_DEPTH_TEST); |
3883 | else |
3884 | f->glDisable(GL_DEPTH_TEST); |
3885 | } |
3886 | |
3887 | const bool depthWrite = psD->m_depthWrite; |
3888 | if (forceUpdate || depthWrite != state.depthWrite) { |
3889 | state.depthWrite = depthWrite; |
3890 | f->glDepthMask(flag: depthWrite); |
3891 | } |
3892 | |
3893 | const GLenum depthFunc = toGlCompareOp(op: psD->m_depthOp); |
3894 | if (forceUpdate || depthFunc != state.depthFunc) { |
3895 | state.depthFunc = depthFunc; |
3896 | f->glDepthFunc(func: depthFunc); |
3897 | } |
3898 | |
3899 | const bool stencilTest = psD->m_stencilTest; |
3900 | const GLuint stencilReadMask = psD->m_stencilReadMask; |
3901 | const GLuint stencilWriteMask = psD->m_stencilWriteMask; |
3902 | const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilFront = { |
3903 | .func: toGlCompareOp(op: psD->m_stencilFront.compareOp), |
3904 | .failOp: toGlStencilOp(op: psD->m_stencilFront.failOp), |
3905 | .zfailOp: toGlStencilOp(op: psD->m_stencilFront.depthFailOp), |
3906 | .zpassOp: toGlStencilOp(op: psD->m_stencilFront.passOp) |
3907 | }; |
3908 | const QGles2CommandBuffer::GraphicsPassState::StencilFace stencilBack = { |
3909 | .func: toGlCompareOp(op: psD->m_stencilBack.compareOp), |
3910 | .failOp: toGlStencilOp(op: psD->m_stencilBack.failOp), |
3911 | .zfailOp: toGlStencilOp(op: psD->m_stencilBack.depthFailOp), |
3912 | .zpassOp: toGlStencilOp(op: psD->m_stencilBack.passOp) |
3913 | }; |
3914 | if (forceUpdate || stencilTest != state.stencilTest |
3915 | || (stencilTest |
3916 | && (stencilReadMask != state.stencilReadMask || stencilWriteMask != state.stencilWriteMask |
3917 | || stencilFront != state.stencil[0] || stencilBack != state.stencil[1]))) |
3918 | { |
3919 | state.stencilTest = stencilTest; |
3920 | if (stencilTest) { |
3921 | state.stencilReadMask = stencilReadMask; |
3922 | state.stencilWriteMask = stencilWriteMask; |
3923 | state.stencil[0] = stencilFront; |
3924 | state.stencil[1] = stencilBack; |
3925 | |
3926 | f->glEnable(GL_STENCIL_TEST); |
3927 | |
3928 | f->glStencilFuncSeparate(GL_FRONT, func: stencilFront.func, ref: state.dynamic.stencilRef, mask: stencilReadMask); |
3929 | f->glStencilOpSeparate(GL_FRONT, fail: stencilFront.failOp, zfail: stencilFront.zfailOp, zpass: stencilFront.zpassOp); |
3930 | f->glStencilMaskSeparate(GL_FRONT, mask: stencilWriteMask); |
3931 | |
3932 | f->glStencilFuncSeparate(GL_BACK, func: stencilBack.func, ref: state.dynamic.stencilRef, mask: stencilReadMask); |
3933 | f->glStencilOpSeparate(GL_BACK, fail: stencilBack.failOp, zfail: stencilBack.zfailOp, zpass: stencilBack.zpassOp); |
3934 | f->glStencilMaskSeparate(GL_BACK, mask: stencilWriteMask); |
3935 | } else { |
3936 | f->glDisable(GL_STENCIL_TEST); |
3937 | } |
3938 | } |
3939 | |
3940 | const bool polyOffsetFill = psD->m_depthBias != 0 || !qFuzzyIsNull(f: psD->m_slopeScaledDepthBias); |
3941 | const float polyOffsetFactor = psD->m_slopeScaledDepthBias; |
3942 | const float polyOffsetUnits = psD->m_depthBias; |
3943 | if (forceUpdate || state.polyOffsetFill != polyOffsetFill |
3944 | || polyOffsetFactor != state.polyOffsetFactor || polyOffsetUnits != state.polyOffsetUnits) |
3945 | { |
3946 | state.polyOffsetFill = polyOffsetFill; |
3947 | state.polyOffsetFactor = polyOffsetFactor; |
3948 | state.polyOffsetUnits = polyOffsetUnits; |
3949 | if (polyOffsetFill) { |
3950 | f->glPolygonOffset(factor: polyOffsetFactor, units: polyOffsetUnits); |
3951 | f->glEnable(GL_POLYGON_OFFSET_FILL); |
3952 | } else { |
3953 | f->glDisable(GL_POLYGON_OFFSET_FILL); |
3954 | } |
3955 | } |
3956 | |
3957 | if (psD->m_topology == QRhiGraphicsPipeline::Lines || psD->m_topology == QRhiGraphicsPipeline::LineStrip) { |
3958 | const float lineWidth = psD->m_lineWidth; |
3959 | if (forceUpdate || lineWidth != state.lineWidth) { |
3960 | state.lineWidth = lineWidth; |
3961 | f->glLineWidth(width: lineWidth); |
3962 | } |
3963 | } |
3964 | |
3965 | if (psD->m_topology == QRhiGraphicsPipeline::Patches) { |
3966 | const int cpCount = psD->m_patchControlPointCount; |
3967 | if (forceUpdate || cpCount != state.cpCount) { |
3968 | state.cpCount = cpCount; |
3969 | f->glPatchParameteri(GL_PATCH_VERTICES, value: qMax(a: 1, b: cpCount)); |
3970 | } |
3971 | } |
3972 | |
3973 | f->glUseProgram(program: psD->program); |
3974 | } |
3975 | |
3976 | template <typename T> |
3977 | static inline void qrhi_std140_to_packed(T *dst, int vecSize, int elemCount, const void *src) |
3978 | { |
3979 | const T *p = reinterpret_cast<const T *>(src); |
3980 | for (int i = 0; i < elemCount; ++i) { |
3981 | for (int j = 0; j < vecSize; ++j) |
3982 | dst[vecSize * i + j] = *p++; |
3983 | p += 4 - vecSize; |
3984 | } |
3985 | } |
3986 | |
3987 | void QRhiGles2::bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *texD, QGles2Sampler *samplerD, |
3988 | void *ps, uint psGeneration, int glslLocation, |
3989 | int *texUnit, bool *activeTexUnitAltered) |
3990 | { |
3991 | const bool samplerStateValid = texD->samplerState == samplerD->d; |
3992 | const bool cachedStateInRange = *texUnit < 16; |
3993 | bool updateTextureBinding = true; |
3994 | if (samplerStateValid && cachedStateInRange) { |
3995 | // If we already encountered the same texture with |
3996 | // the same pipeline for this texture unit in the |
3997 | // current pass, then the shader program already |
3998 | // has the uniform set. As in a 3D scene one model |
3999 | // often has more than one associated texture map, |
4000 | // the savings here can become significant, |
4001 | // depending on the scene. |
4002 | if (cbD->textureUnitState[*texUnit].ps == ps |
4003 | && cbD->textureUnitState[*texUnit].psGeneration == psGeneration |
4004 | && cbD->textureUnitState[*texUnit].texture == texD->texture) |
4005 | { |
4006 | updateTextureBinding = false; |
4007 | } |
4008 | } |
4009 | if (updateTextureBinding) { |
4010 | f->glActiveTexture(GL_TEXTURE0 + uint(*texUnit)); |
4011 | *activeTexUnitAltered = true; |
4012 | f->glBindTexture(target: texD->target, texture: texD->texture); |
4013 | f->glUniform1i(location: glslLocation, x: *texUnit); |
4014 | if (cachedStateInRange) { |
4015 | cbD->textureUnitState[*texUnit].ps = ps; |
4016 | cbD->textureUnitState[*texUnit].psGeneration = psGeneration; |
4017 | cbD->textureUnitState[*texUnit].texture = texD->texture; |
4018 | } |
4019 | } |
4020 | ++(*texUnit); |
4021 | if (!samplerStateValid) { |
4022 | f->glTexParameteri(target: texD->target, GL_TEXTURE_MIN_FILTER, param: GLint(samplerD->d.glminfilter)); |
4023 | f->glTexParameteri(target: texD->target, GL_TEXTURE_MAG_FILTER, param: GLint(samplerD->d.glmagfilter)); |
4024 | f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_S, param: GLint(samplerD->d.glwraps)); |
4025 | f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_T, param: GLint(samplerD->d.glwrapt)); |
4026 | if (caps.texture3D && texD->target == GL_TEXTURE_3D) |
4027 | f->glTexParameteri(target: texD->target, GL_TEXTURE_WRAP_R, param: GLint(samplerD->d.glwrapr)); |
4028 | if (caps.textureCompareMode) { |
4029 | if (samplerD->d.gltexcomparefunc != GL_NEVER) { |
4030 | f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); |
4031 | f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_FUNC, param: GLint(samplerD->d.gltexcomparefunc)); |
4032 | } else { |
4033 | f->glTexParameteri(target: texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE); |
4034 | } |
4035 | } |
4036 | texD->samplerState = samplerD->d; |
4037 | } |
4038 | } |
4039 | |
4040 | void QRhiGles2::bindShaderResources(QGles2CommandBuffer *cbD, |
4041 | QRhiGraphicsPipeline *maybeGraphicsPs, QRhiComputePipeline *maybeComputePs, |
4042 | QRhiShaderResourceBindings *srb, |
4043 | const uint *dynOfsPairs, int dynOfsCount) |
4044 | { |
4045 | QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb); |
4046 | int texUnit = 1; // start from unit 1, keep 0 for resource mgmt stuff to avoid clashes |
4047 | bool activeTexUnitAltered = false; |
4048 | QGles2UniformDescriptionVector &uniforms(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniforms |
4049 | : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniforms); |
4050 | QGles2UniformState *uniformState = maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->uniformState |
4051 | : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->uniformState; |
4052 | m_scratch.separateTextureBindings.clear(); |
4053 | m_scratch.separateSamplerBindings.clear(); |
4054 | |
4055 | for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) { |
4056 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i)); |
4057 | |
4058 | switch (b->type) { |
4059 | case QRhiShaderResourceBinding::UniformBuffer: |
4060 | { |
4061 | int viewOffset = b->u.ubuf.offset; |
4062 | for (int j = 0; j < dynOfsCount; ++j) { |
4063 | if (dynOfsPairs[2 * j] == uint(b->binding)) { |
4064 | viewOffset = int(dynOfsPairs[2 * j + 1]); |
4065 | break; |
4066 | } |
4067 | } |
4068 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf); |
4069 | const char *bufView = bufD->data.constData() + viewOffset; |
4070 | for (const QGles2UniformDescription &uniform : std::as_const(t&: uniforms)) { |
4071 | if (uniform.binding == b->binding) { |
4072 | // in a uniform buffer everything is at least 4 byte aligned |
4073 | // so this should not cause unaligned reads |
4074 | const void *src = bufView + uniform.offset; |
4075 | |
4076 | #ifndef QT_NO_DEBUG |
4077 | if (uniform.arrayDim > 0 |
4078 | && uniform.type != QShaderDescription::Float |
4079 | && uniform.type != QShaderDescription::Vec2 |
4080 | && uniform.type != QShaderDescription::Vec3 |
4081 | && uniform.type != QShaderDescription::Vec4 |
4082 | && uniform.type != QShaderDescription::Int |
4083 | && uniform.type != QShaderDescription::Int2 |
4084 | && uniform.type != QShaderDescription::Int3 |
4085 | && uniform.type != QShaderDescription::Int4 |
4086 | && uniform.type != QShaderDescription::Mat3 |
4087 | && uniform.type != QShaderDescription::Mat4) |
4088 | { |
4089 | qWarning(msg: "Uniform with buffer binding %d, buffer offset %d, type %d is an array, " |
4090 | "but arrays are only supported for float, vec2, vec3, vec4, int, " |
4091 | "ivec2, ivec3, ivec4, mat3 and mat4. " |
4092 | "Only the first element will be set.", |
4093 | uniform.binding, uniform.offset, uniform.type); |
4094 | } |
4095 | #endif |
4096 | |
4097 | // Our input is an std140 layout uniform block. See |
4098 | // "Standard Uniform Block Layout" in section 7.6.2.2 of |
4099 | // the OpenGL spec. This has some peculiar alignment |
4100 | // requirements, which is not what glUniform* wants. Hence |
4101 | // the unpacking/repacking for arrays and certain types. |
4102 | |
4103 | switch (uniform.type) { |
4104 | case QShaderDescription::Float: |
4105 | { |
4106 | const int elemCount = uniform.arrayDim; |
4107 | if (elemCount < 1) { |
4108 | const float v = *reinterpret_cast<const float *>(src); |
4109 | if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { |
4110 | QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); |
4111 | if (thisUniformState.componentCount != 1 || thisUniformState.v[0] != v) { |
4112 | thisUniformState.componentCount = 1; |
4113 | thisUniformState.v[0] = v; |
4114 | f->glUniform1f(location: uniform.glslLocation, x: v); |
4115 | } |
4116 | } else { |
4117 | f->glUniform1f(location: uniform.glslLocation, x: v); |
4118 | } |
4119 | } else { |
4120 | // input is 16 bytes per element as per std140, have to convert to packed |
4121 | m_scratch.packedArray.resize(sz: elemCount); |
4122 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 1, elemCount, src); |
4123 | f->glUniform1fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f); |
4124 | } |
4125 | } |
4126 | break; |
4127 | case QShaderDescription::Vec2: |
4128 | { |
4129 | const int elemCount = uniform.arrayDim; |
4130 | if (elemCount < 1) { |
4131 | const float *v = reinterpret_cast<const float *>(src); |
4132 | if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { |
4133 | QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); |
4134 | if (thisUniformState.componentCount != 2 |
4135 | || thisUniformState.v[0] != v[0] |
4136 | || thisUniformState.v[1] != v[1]) |
4137 | { |
4138 | thisUniformState.componentCount = 2; |
4139 | thisUniformState.v[0] = v[0]; |
4140 | thisUniformState.v[1] = v[1]; |
4141 | f->glUniform2fv(location: uniform.glslLocation, count: 1, v); |
4142 | } |
4143 | } else { |
4144 | f->glUniform2fv(location: uniform.glslLocation, count: 1, v); |
4145 | } |
4146 | } else { |
4147 | m_scratch.packedArray.resize(sz: elemCount * 2); |
4148 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 2, elemCount, src); |
4149 | f->glUniform2fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f); |
4150 | } |
4151 | } |
4152 | break; |
4153 | case QShaderDescription::Vec3: |
4154 | { |
4155 | const int elemCount = uniform.arrayDim; |
4156 | if (elemCount < 1) { |
4157 | const float *v = reinterpret_cast<const float *>(src); |
4158 | if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { |
4159 | QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); |
4160 | if (thisUniformState.componentCount != 3 |
4161 | || thisUniformState.v[0] != v[0] |
4162 | || thisUniformState.v[1] != v[1] |
4163 | || thisUniformState.v[2] != v[2]) |
4164 | { |
4165 | thisUniformState.componentCount = 3; |
4166 | thisUniformState.v[0] = v[0]; |
4167 | thisUniformState.v[1] = v[1]; |
4168 | thisUniformState.v[2] = v[2]; |
4169 | f->glUniform3fv(location: uniform.glslLocation, count: 1, v); |
4170 | } |
4171 | } else { |
4172 | f->glUniform3fv(location: uniform.glslLocation, count: 1, v); |
4173 | } |
4174 | } else { |
4175 | m_scratch.packedArray.resize(sz: elemCount * 3); |
4176 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 3, elemCount, src); |
4177 | f->glUniform3fv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->f); |
4178 | } |
4179 | } |
4180 | break; |
4181 | case QShaderDescription::Vec4: |
4182 | { |
4183 | const int elemCount = uniform.arrayDim; |
4184 | if (elemCount < 1) { |
4185 | const float *v = reinterpret_cast<const float *>(src); |
4186 | if (uniform.glslLocation <= QGles2UniformState::MAX_TRACKED_LOCATION) { |
4187 | QGles2UniformState &thisUniformState(uniformState[uniform.glslLocation]); |
4188 | if (thisUniformState.componentCount != 4 |
4189 | || thisUniformState.v[0] != v[0] |
4190 | || thisUniformState.v[1] != v[1] |
4191 | || thisUniformState.v[2] != v[2] |
4192 | || thisUniformState.v[3] != v[3]) |
4193 | { |
4194 | thisUniformState.componentCount = 4; |
4195 | thisUniformState.v[0] = v[0]; |
4196 | thisUniformState.v[1] = v[1]; |
4197 | thisUniformState.v[2] = v[2]; |
4198 | thisUniformState.v[3] = v[3]; |
4199 | f->glUniform4fv(location: uniform.glslLocation, count: 1, v); |
4200 | } |
4201 | } else { |
4202 | f->glUniform4fv(location: uniform.glslLocation, count: 1, v); |
4203 | } |
4204 | } else { |
4205 | f->glUniform4fv(location: uniform.glslLocation, count: elemCount, v: reinterpret_cast<const float *>(src)); |
4206 | } |
4207 | } |
4208 | break; |
4209 | case QShaderDescription::Mat2: |
4210 | f->glUniformMatrix2fv(location: uniform.glslLocation, count: 1, GL_FALSE, value: reinterpret_cast<const float *>(src)); |
4211 | break; |
4212 | case QShaderDescription::Mat3: |
4213 | { |
4214 | const int elemCount = uniform.arrayDim; |
4215 | if (elemCount < 1) { |
4216 | // 4 floats per column (or row, if row-major) |
4217 | float mat[9]; |
4218 | const float *srcMat = reinterpret_cast<const float *>(src); |
4219 | memcpy(dest: mat, src: srcMat, n: 3 * sizeof(float)); |
4220 | memcpy(dest: mat + 3, src: srcMat + 4, n: 3 * sizeof(float)); |
4221 | memcpy(dest: mat + 6, src: srcMat + 8, n: 3 * sizeof(float)); |
4222 | f->glUniformMatrix3fv(location: uniform.glslLocation, count: 1, GL_FALSE, value: mat); |
4223 | } else { |
4224 | m_scratch.packedArray.resize(sz: elemCount * 9); |
4225 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->f, vecSize: 3, elemCount: elemCount * 3, src); |
4226 | f->glUniformMatrix3fv(location: uniform.glslLocation, count: elemCount, GL_FALSE, value: &m_scratch.packedArray.constData()->f); |
4227 | } |
4228 | } |
4229 | break; |
4230 | case QShaderDescription::Mat4: |
4231 | f->glUniformMatrix4fv(location: uniform.glslLocation, count: qMax(a: 1, b: uniform.arrayDim), GL_FALSE, value: reinterpret_cast<const float *>(src)); |
4232 | break; |
4233 | case QShaderDescription::Int: |
4234 | { |
4235 | const int elemCount = uniform.arrayDim; |
4236 | if (elemCount < 1) { |
4237 | f->glUniform1i(location: uniform.glslLocation, x: *reinterpret_cast<const qint32 *>(src)); |
4238 | } else { |
4239 | m_scratch.packedArray.resize(sz: elemCount); |
4240 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 1, elemCount, src); |
4241 | f->glUniform1iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i); |
4242 | } |
4243 | } |
4244 | break; |
4245 | case QShaderDescription::Int2: |
4246 | { |
4247 | const int elemCount = uniform.arrayDim; |
4248 | if (elemCount < 1) { |
4249 | f->glUniform2iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src)); |
4250 | } else { |
4251 | m_scratch.packedArray.resize(sz: elemCount * 2); |
4252 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 2, elemCount, src); |
4253 | f->glUniform2iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i); |
4254 | } |
4255 | } |
4256 | break; |
4257 | case QShaderDescription::Int3: |
4258 | { |
4259 | const int elemCount = uniform.arrayDim; |
4260 | if (elemCount < 1) { |
4261 | f->glUniform3iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src)); |
4262 | } else { |
4263 | m_scratch.packedArray.resize(sz: elemCount * 3); |
4264 | qrhi_std140_to_packed(dst: &m_scratch.packedArray.data()->i, vecSize: 3, elemCount, src); |
4265 | f->glUniform3iv(location: uniform.glslLocation, count: elemCount, v: &m_scratch.packedArray.constData()->i); |
4266 | } |
4267 | } |
4268 | break; |
4269 | case QShaderDescription::Int4: |
4270 | f->glUniform4iv(location: uniform.glslLocation, count: qMax(a: 1, b: uniform.arrayDim), v: reinterpret_cast<const qint32 *>(src)); |
4271 | break; |
4272 | case QShaderDescription::Uint: |
4273 | f->glUniform1ui(location: uniform.glslLocation, v0: *reinterpret_cast<const quint32 *>(src)); |
4274 | break; |
4275 | case QShaderDescription::Uint2: |
4276 | f->glUniform2uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src)); |
4277 | break; |
4278 | case QShaderDescription::Uint3: |
4279 | f->glUniform3uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src)); |
4280 | break; |
4281 | case QShaderDescription::Uint4: |
4282 | f->glUniform4uiv(location: uniform.glslLocation, count: 1, value: reinterpret_cast<const quint32 *>(src)); |
4283 | break; |
4284 | case QShaderDescription::Bool: // a glsl bool is 4 bytes, like (u)int |
4285 | f->glUniform1i(location: uniform.glslLocation, x: *reinterpret_cast<const qint32 *>(src)); |
4286 | break; |
4287 | case QShaderDescription::Bool2: |
4288 | f->glUniform2iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src)); |
4289 | break; |
4290 | case QShaderDescription::Bool3: |
4291 | f->glUniform3iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src)); |
4292 | break; |
4293 | case QShaderDescription::Bool4: |
4294 | f->glUniform4iv(location: uniform.glslLocation, count: 1, v: reinterpret_cast<const qint32 *>(src)); |
4295 | break; |
4296 | default: |
4297 | qWarning(msg: "Uniform with buffer binding %d, buffer offset %d has unsupported type %d", |
4298 | uniform.binding, uniform.offset, uniform.type); |
4299 | break; |
4300 | } |
4301 | } |
4302 | } |
4303 | } |
4304 | break; |
4305 | case QRhiShaderResourceBinding::SampledTexture: |
4306 | { |
4307 | const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers |
4308 | : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers); |
4309 | void *ps; |
4310 | uint psGeneration; |
4311 | if (maybeGraphicsPs) { |
4312 | ps = maybeGraphicsPs; |
4313 | psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation; |
4314 | } else { |
4315 | ps = maybeComputePs; |
4316 | psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation; |
4317 | } |
4318 | for (int elem = 0; elem < b->u.stex.count; ++elem) { |
4319 | QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex); |
4320 | QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[elem].sampler); |
4321 | for (const QGles2SamplerDescription &shaderSampler : samplers) { |
4322 | if (shaderSampler.combinedBinding == b->binding) { |
4323 | const int loc = shaderSampler.glslLocation + elem; |
4324 | bindCombinedSampler(cbD, texD, samplerD, ps, psGeneration, glslLocation: loc, texUnit: &texUnit, activeTexUnitAltered: &activeTexUnitAltered); |
4325 | break; |
4326 | } |
4327 | } |
4328 | } |
4329 | } |
4330 | break; |
4331 | case QRhiShaderResourceBinding::Texture: |
4332 | for (int elem = 0; elem < b->u.stex.count; ++elem) { |
4333 | QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.texSamplers[elem].tex); |
4334 | m_scratch.separateTextureBindings.append(t: { .texture: texD, .binding: b->binding, .elem: elem }); |
4335 | } |
4336 | break; |
4337 | case QRhiShaderResourceBinding::Sampler: |
4338 | { |
4339 | QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.texSamplers[0].sampler); |
4340 | m_scratch.separateSamplerBindings.append(t: { .sampler: samplerD, .binding: b->binding }); |
4341 | } |
4342 | break; |
4343 | case QRhiShaderResourceBinding::ImageLoad: |
4344 | case QRhiShaderResourceBinding::ImageStore: |
4345 | case QRhiShaderResourceBinding::ImageLoadStore: |
4346 | { |
4347 | QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.simage.tex); |
4348 | Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore)); |
4349 | const bool layered = texD->m_flags.testFlag(flag: QRhiTexture::CubeMap); |
4350 | GLenum access = GL_READ_WRITE; |
4351 | if (b->type == QRhiShaderResourceBinding::ImageLoad) |
4352 | access = GL_READ_ONLY; |
4353 | else if (b->type == QRhiShaderResourceBinding::ImageStore) |
4354 | access = GL_WRITE_ONLY; |
4355 | f->glBindImageTexture(unit: GLuint(b->binding), texture: texD->texture, |
4356 | level: b->u.simage.level, layered, layer: 0, |
4357 | access, format: texD->glsizedintformat); |
4358 | } |
4359 | break; |
4360 | case QRhiShaderResourceBinding::BufferLoad: |
4361 | case QRhiShaderResourceBinding::BufferStore: |
4362 | case QRhiShaderResourceBinding::BufferLoadStore: |
4363 | { |
4364 | QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.sbuf.buf); |
4365 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); |
4366 | if (b->u.sbuf.offset == 0 && b->u.sbuf.maybeSize == 0) |
4367 | f->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index: GLuint(b->binding), buffer: bufD->buffer); |
4368 | else |
4369 | f->glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index: GLuint(b->binding), buffer: bufD->buffer, |
4370 | offset: b->u.sbuf.offset, size: b->u.sbuf.maybeSize ? b->u.sbuf.maybeSize : bufD->m_size); |
4371 | } |
4372 | break; |
4373 | default: |
4374 | Q_UNREACHABLE(); |
4375 | break; |
4376 | } |
4377 | } |
4378 | |
4379 | if (!m_scratch.separateTextureBindings.isEmpty() || !m_scratch.separateSamplerBindings.isEmpty()) { |
4380 | const QGles2SamplerDescriptionVector &samplers(maybeGraphicsPs ? QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->samplers |
4381 | : QRHI_RES(QGles2ComputePipeline, maybeComputePs)->samplers); |
4382 | void *ps; |
4383 | uint psGeneration; |
4384 | if (maybeGraphicsPs) { |
4385 | ps = maybeGraphicsPs; |
4386 | psGeneration = QRHI_RES(QGles2GraphicsPipeline, maybeGraphicsPs)->generation; |
4387 | } else { |
4388 | ps = maybeComputePs; |
4389 | psGeneration = QRHI_RES(QGles2ComputePipeline, maybeComputePs)->generation; |
4390 | } |
4391 | for (const QGles2SamplerDescription &shaderSampler : samplers) { |
4392 | if (shaderSampler.combinedBinding >= 0) |
4393 | continue; |
4394 | for (const Scratch::SeparateSampler &sepSampler : std::as_const(t&: m_scratch.separateSamplerBindings)) { |
4395 | if (sepSampler.binding != shaderSampler.sbinding) |
4396 | continue; |
4397 | for (const Scratch::SeparateTexture &sepTex : std::as_const(t&: m_scratch.separateTextureBindings)) { |
4398 | if (sepTex.binding != shaderSampler.tbinding) |
4399 | continue; |
4400 | const int loc = shaderSampler.glslLocation + sepTex.elem; |
4401 | bindCombinedSampler(cbD, texD: sepTex.texture, samplerD: sepSampler.sampler, ps, psGeneration, |
4402 | glslLocation: loc, texUnit: &texUnit, activeTexUnitAltered: &activeTexUnitAltered); |
4403 | } |
4404 | } |
4405 | } |
4406 | } |
4407 | |
4408 | if (activeTexUnitAltered) |
4409 | f->glActiveTexture(GL_TEXTURE0); |
4410 | } |
4411 | |
4412 | void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
4413 | { |
4414 | Q_ASSERT(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass); |
4415 | |
4416 | enqueueResourceUpdates(cb, resourceUpdates); |
4417 | } |
4418 | |
4419 | QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD, |
4420 | bool *wantsColorClear, bool *wantsDsClear) |
4421 | { |
4422 | QGles2RenderTargetData *rtD = nullptr; |
4423 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
4424 | |
4425 | QGles2CommandBuffer::Command &fbCmd(cbD->commands.get()); |
4426 | fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer; |
4427 | |
4428 | static const bool doClearBuffers = qEnvironmentVariableIntValue(varName: "QT_GL_NO_CLEAR_BUFFERS") == 0; |
4429 | static const bool doClearColorBuffer = qEnvironmentVariableIntValue(varName: "QT_GL_NO_CLEAR_COLOR_BUFFER") == 0; |
4430 | |
4431 | switch (rt->resourceType()) { |
4432 | case QRhiResource::SwapChainRenderTarget: |
4433 | rtD = &QRHI_RES(QGles2SwapChainRenderTarget, rt)->d; |
4434 | if (wantsColorClear) |
4435 | *wantsColorClear = doClearBuffers && doClearColorBuffer; |
4436 | if (wantsDsClear) |
4437 | *wantsDsClear = doClearBuffers; |
4438 | fbCmd.args.bindFramebuffer.fbo = 0; |
4439 | fbCmd.args.bindFramebuffer.colorAttCount = 1; |
4440 | fbCmd.args.bindFramebuffer.stereo = rtD->stereoTarget.has_value(); |
4441 | if (fbCmd.args.bindFramebuffer.stereo) |
4442 | fbCmd.args.bindFramebuffer.stereoTarget = rtD->stereoTarget.value(); |
4443 | break; |
4444 | case QRhiResource::TextureRenderTarget: |
4445 | { |
4446 | QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt); |
4447 | rtD = &rtTex->d; |
4448 | if (wantsColorClear) |
4449 | *wantsColorClear = !rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveColorContents); |
4450 | if (wantsDsClear) |
4451 | *wantsDsClear = !rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveDepthStencilContents); |
4452 | fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer; |
4453 | fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount; |
4454 | fbCmd.args.bindFramebuffer.stereo = false; |
4455 | |
4456 | for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); |
4457 | it != itEnd; ++it) |
4458 | { |
4459 | const QRhiColorAttachment &colorAtt(*it); |
4460 | QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture()); |
4461 | QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); |
4462 | if (texD && cbD->passNeedsResourceTracking) { |
4463 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
4464 | access: QRhiPassResourceTracker::TexColorOutput, |
4465 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
4466 | } |
4467 | if (resolveTexD && cbD->passNeedsResourceTracking) { |
4468 | trackedRegisterTexture(passResTracker: &passResTracker, texD: resolveTexD, |
4469 | access: QRhiPassResourceTracker::TexColorOutput, |
4470 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
4471 | } |
4472 | // renderbuffers cannot be written in shaders (no image store) so |
4473 | // they do not matter here |
4474 | } |
4475 | if (rtTex->m_desc.depthTexture() && cbD->passNeedsResourceTracking) { |
4476 | trackedRegisterTexture(passResTracker: &passResTracker, QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()), |
4477 | access: QRhiPassResourceTracker::TexDepthOutput, |
4478 | stage: QRhiPassResourceTracker::TexDepthOutputStage); |
4479 | } |
4480 | } |
4481 | break; |
4482 | default: |
4483 | Q_UNREACHABLE(); |
4484 | break; |
4485 | } |
4486 | |
4487 | fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend; |
4488 | |
4489 | return rtD; |
4490 | } |
4491 | |
4492 | void QRhiGles2::enqueueBarriersForPass(QGles2CommandBuffer *cbD) |
4493 | { |
4494 | cbD->passResTrackers.append(t: QRhiPassResourceTracker()); |
4495 | cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1; |
4496 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4497 | cmd.cmd = QGles2CommandBuffer::Command::BarriersForPass; |
4498 | cmd.args.barriersForPass.trackerIndex = cbD->currentPassResTrackerIndex; |
4499 | } |
4500 | |
4501 | void QRhiGles2::beginPass(QRhiCommandBuffer *cb, |
4502 | QRhiRenderTarget *rt, |
4503 | const QColor &colorClearValue, |
4504 | const QRhiDepthStencilClearValue &depthStencilClearValue, |
4505 | QRhiResourceUpdateBatch *resourceUpdates, |
4506 | QRhiCommandBuffer::BeginPassFlags flags) |
4507 | { |
4508 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4509 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); |
4510 | |
4511 | if (resourceUpdates) |
4512 | enqueueResourceUpdates(cb, resourceUpdates); |
4513 | |
4514 | // Get a new resource tracker. Then add a command that will generate |
4515 | // glMemoryBarrier() calls based on that tracker when submitted. |
4516 | enqueueBarriersForPass(cbD); |
4517 | |
4518 | if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) { |
4519 | QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt); |
4520 | if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(desc: rtTex->description(), currentResIdList: rtTex->d.currentResIdList)) |
4521 | rtTex->create(); |
4522 | } |
4523 | |
4524 | bool wantsColorClear, wantsDsClear; |
4525 | QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, wantsColorClear: &wantsColorClear, wantsDsClear: &wantsDsClear); |
4526 | |
4527 | QGles2CommandBuffer::Command &clearCmd(cbD->commands.get()); |
4528 | clearCmd.cmd = QGles2CommandBuffer::Command::Clear; |
4529 | clearCmd.args.clear.mask = 0; |
4530 | if (rtD->colorAttCount && wantsColorClear) |
4531 | clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT; |
4532 | if (rtD->dsAttCount && wantsDsClear) |
4533 | clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; |
4534 | clearCmd.args.clear.c[0] = float(colorClearValue.redF()); |
4535 | clearCmd.args.clear.c[1] = float(colorClearValue.greenF()); |
4536 | clearCmd.args.clear.c[2] = float(colorClearValue.blueF()); |
4537 | clearCmd.args.clear.c[3] = float(colorClearValue.alphaF()); |
4538 | clearCmd.args.clear.d = depthStencilClearValue.depthClearValue(); |
4539 | clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue(); |
4540 | |
4541 | cbD->recordingPass = QGles2CommandBuffer::RenderPass; |
4542 | cbD->passNeedsResourceTracking = !flags.testFlag(flag: QRhiCommandBuffer::DoNotTrackResourcesForCompute); |
4543 | cbD->currentTarget = rt; |
4544 | |
4545 | cbD->resetCachedState(); |
4546 | } |
4547 | |
4548 | void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
4549 | { |
4550 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4551 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass); |
4552 | |
4553 | if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) { |
4554 | QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget); |
4555 | for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); |
4556 | it != itEnd; ++it) |
4557 | { |
4558 | const QRhiColorAttachment &colorAtt(*it); |
4559 | if (!colorAtt.resolveTexture()) |
4560 | continue; |
4561 | |
4562 | QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); |
4563 | const QSize size = resolveTexD->pixelSize(); |
4564 | if (colorAtt.renderBuffer()) { |
4565 | QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer()); |
4566 | if (rbD->pixelSize() != size) { |
4567 | qWarning(msg: "Resolve source (%dx%d) and target (%dx%d) size does not match", |
4568 | rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height()); |
4569 | } |
4570 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4571 | cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer; |
4572 | cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer; |
4573 | cmd.args.blitFromRenderbuffer.w = size.width(); |
4574 | cmd.args.blitFromRenderbuffer.h = size.height(); |
4575 | if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::CubeMap)) |
4576 | cmd.args.blitFromRenderbuffer.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer()); |
4577 | else |
4578 | cmd.args.blitFromRenderbuffer.target = resolveTexD->target; |
4579 | cmd.args.blitFromRenderbuffer.dstTexture = resolveTexD->texture; |
4580 | cmd.args.blitFromRenderbuffer.dstLevel = colorAtt.resolveLevel(); |
4581 | const bool hasZ = resolveTexD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) |
4582 | || resolveTexD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
4583 | cmd.args.blitFromRenderbuffer.dstLayer = hasZ ? colorAtt.resolveLayer() : 0; |
4584 | cmd.args.blitFromRenderbuffer.isDepthStencil = false; |
4585 | } else if (caps.glesMultisampleRenderToTexture) { |
4586 | // Nothing to do, resolving into colorAtt.resolveTexture() is automatic, |
4587 | // colorAtt.texture() is in fact not used for anything. |
4588 | } else { |
4589 | Q_ASSERT(colorAtt.texture()); |
4590 | QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture()); |
4591 | if (texD->pixelSize() != size) { |
4592 | qWarning(msg: "Resolve source (%dx%d) and target (%dx%d) size does not match", |
4593 | texD->pixelSize().width(), texD->pixelSize().height(), size.width(), size.height()); |
4594 | } |
4595 | const int resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1; |
4596 | for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) { |
4597 | const int srcLayer = colorAtt.layer() + resolveIdx; |
4598 | const int dstLayer = colorAtt.resolveLayer() + resolveIdx; |
4599 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4600 | cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture; |
4601 | if (texD->m_flags.testFlag(flag: QRhiTexture::CubeMap)) |
4602 | cmd.args.blitFromTexture.srcTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(srcLayer); |
4603 | else |
4604 | cmd.args.blitFromTexture.srcTarget = texD->target; |
4605 | cmd.args.blitFromTexture.srcTexture = texD->texture; |
4606 | cmd.args.blitFromTexture.srcLevel = colorAtt.level(); |
4607 | cmd.args.blitFromTexture.srcLayer = 0; |
4608 | if (texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || texD->m_flags.testFlag(flag: QRhiTexture::TextureArray)) |
4609 | cmd.args.blitFromTexture.srcLayer = srcLayer; |
4610 | cmd.args.blitFromTexture.w = size.width(); |
4611 | cmd.args.blitFromTexture.h = size.height(); |
4612 | if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::CubeMap)) |
4613 | cmd.args.blitFromTexture.dstTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(dstLayer); |
4614 | else |
4615 | cmd.args.blitFromTexture.dstTarget = resolveTexD->target; |
4616 | cmd.args.blitFromTexture.dstTexture = resolveTexD->texture; |
4617 | cmd.args.blitFromTexture.dstLevel = colorAtt.resolveLevel(); |
4618 | cmd.args.blitFromTexture.dstLayer = 0; |
4619 | if (resolveTexD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional) || resolveTexD->m_flags.testFlag(flag: QRhiTexture::TextureArray)) |
4620 | cmd.args.blitFromTexture.dstLayer = dstLayer; |
4621 | cmd.args.blitFromTexture.isDepthStencil = false; |
4622 | } |
4623 | } |
4624 | } |
4625 | |
4626 | if (rtTex->m_desc.depthResolveTexture()) { |
4627 | QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthResolveTexture()); |
4628 | const QSize size = depthResolveTexD->pixelSize(); |
4629 | if (rtTex->m_desc.depthStencilBuffer()) { |
4630 | QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, rtTex->m_desc.depthStencilBuffer()); |
4631 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4632 | cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer; |
4633 | cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer; |
4634 | cmd.args.blitFromRenderbuffer.w = size.width(); |
4635 | cmd.args.blitFromRenderbuffer.h = size.height(); |
4636 | cmd.args.blitFromRenderbuffer.target = depthResolveTexD->target; |
4637 | cmd.args.blitFromRenderbuffer.dstTexture = depthResolveTexD->texture; |
4638 | cmd.args.blitFromRenderbuffer.dstLevel = 0; |
4639 | cmd.args.blitFromRenderbuffer.dstLayer = 0; |
4640 | cmd.args.blitFromRenderbuffer.isDepthStencil = true; |
4641 | } else if (caps.glesMultisampleRenderToTexture) { |
4642 | // Nothing to do, resolving into depthResolveTexture() is automatic. |
4643 | } else { |
4644 | QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture()); |
4645 | const int resolveCount = depthTexD->arraySize() >= 2 ? depthTexD->arraySize() : 1; |
4646 | for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) { |
4647 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4648 | cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture; |
4649 | cmd.args.blitFromTexture.srcTarget = depthTexD->target; |
4650 | cmd.args.blitFromTexture.srcTexture = depthTexD->texture; |
4651 | cmd.args.blitFromTexture.srcLevel = 0; |
4652 | cmd.args.blitFromTexture.srcLayer = resolveIdx; |
4653 | cmd.args.blitFromTexture.w = size.width(); |
4654 | cmd.args.blitFromTexture.h = size.height(); |
4655 | cmd.args.blitFromTexture.dstTarget = depthResolveTexD->target; |
4656 | cmd.args.blitFromTexture.dstTexture = depthResolveTexD->texture; |
4657 | cmd.args.blitFromTexture.dstLevel = 0; |
4658 | cmd.args.blitFromTexture.dstLayer = resolveIdx; |
4659 | cmd.args.blitFromTexture.isDepthStencil = true; |
4660 | } |
4661 | } |
4662 | } |
4663 | |
4664 | const bool mayDiscardDepthStencil = |
4665 | (rtTex->m_desc.depthStencilBuffer() |
4666 | || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(flag: QRhiTextureRenderTarget::DoNotStoreDepthStencilContents))) |
4667 | && !rtTex->m_desc.depthResolveTexture(); |
4668 | if (mayDiscardDepthStencil) { |
4669 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4670 | cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer; |
4671 | if (caps.needsDepthStencilCombinedAttach) { |
4672 | cmd.args.invalidateFramebuffer.attCount = 1; |
4673 | cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT; |
4674 | } else { |
4675 | cmd.args.invalidateFramebuffer.attCount = 2; |
4676 | cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_ATTACHMENT; |
4677 | cmd.args.invalidateFramebuffer.att[1] = GL_STENCIL_ATTACHMENT; |
4678 | } |
4679 | } |
4680 | } |
4681 | |
4682 | cbD->recordingPass = QGles2CommandBuffer::NoPass; |
4683 | cbD->currentTarget = nullptr; |
4684 | |
4685 | if (resourceUpdates) |
4686 | enqueueResourceUpdates(cb, resourceUpdates); |
4687 | } |
4688 | |
4689 | void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb, |
4690 | QRhiResourceUpdateBatch *resourceUpdates, |
4691 | QRhiCommandBuffer::BeginPassFlags) |
4692 | { |
4693 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4694 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass); |
4695 | |
4696 | if (resourceUpdates) |
4697 | enqueueResourceUpdates(cb, resourceUpdates); |
4698 | |
4699 | enqueueBarriersForPass(cbD); |
4700 | |
4701 | cbD->recordingPass = QGles2CommandBuffer::ComputePass; |
4702 | |
4703 | cbD->resetCachedState(); |
4704 | } |
4705 | |
4706 | void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
4707 | { |
4708 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4709 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); |
4710 | |
4711 | cbD->recordingPass = QGles2CommandBuffer::NoPass; |
4712 | |
4713 | if (resourceUpdates) |
4714 | enqueueResourceUpdates(cb, resourceUpdates); |
4715 | } |
4716 | |
4717 | void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) |
4718 | { |
4719 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4720 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); |
4721 | QGles2ComputePipeline *psD = QRHI_RES(QGles2ComputePipeline, ps); |
4722 | const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation; |
4723 | |
4724 | if (pipelineChanged) { |
4725 | cbD->currentGraphicsPipeline = nullptr; |
4726 | cbD->currentComputePipeline = ps; |
4727 | cbD->currentPipelineGeneration = psD->generation; |
4728 | |
4729 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4730 | cmd.cmd = QGles2CommandBuffer::Command::BindComputePipeline; |
4731 | cmd.args.bindComputePipeline.ps = ps; |
4732 | } |
4733 | } |
4734 | |
4735 | template<typename T> |
4736 | inline void qrhigl_accumulateComputeResource(T *writtenResources, QRhiResource *resource, |
4737 | QRhiShaderResourceBinding::Type bindingType, |
4738 | int loadTypeVal, int storeTypeVal, int loadStoreTypeVal) |
4739 | { |
4740 | int access = 0; |
4741 | if (bindingType == loadTypeVal) { |
4742 | access = QGles2CommandBuffer::ComputePassState::Read; |
4743 | } else { |
4744 | access = QGles2CommandBuffer::ComputePassState::Write; |
4745 | if (bindingType == loadStoreTypeVal) |
4746 | access |= QGles2CommandBuffer::ComputePassState::Read; |
4747 | } |
4748 | auto it = writtenResources->find(resource); |
4749 | if (it != writtenResources->end()) |
4750 | it->first |= access; |
4751 | else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal) |
4752 | writtenResources->insert(resource, { access, true }); |
4753 | } |
4754 | |
4755 | void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) |
4756 | { |
4757 | QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); |
4758 | Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass); |
4759 | |
4760 | if (cbD->currentComputeSrb) { |
4761 | GLbitfield barriers = 0; |
4762 | |
4763 | // The key in the writtenResources map indicates that the resource was |
4764 | // written in a previous dispatch, whereas the value accumulates the |
4765 | // access mask in the current one. |
4766 | for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources) |
4767 | accessAndIsNewFlag = { 0, false }; |
4768 | |
4769 | QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, cbD->currentComputeSrb); |
4770 | const int bindingCount = srbD->m_bindings.size(); |
4771 | for (int i = 0; i < bindingCount; ++i) { |
4772 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i)); |
4773 | switch (b->type) { |
4774 | case QRhiShaderResourceBinding::ImageLoad: |
4775 | case QRhiShaderResourceBinding::ImageStore: |
4776 | case QRhiShaderResourceBinding::ImageLoadStore: |
4777 | qrhigl_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
4778 | resource: b->u.simage.tex, |
4779 | bindingType: b->type, |
4780 | loadTypeVal: QRhiShaderResourceBinding::ImageLoad, |
4781 | storeTypeVal: QRhiShaderResourceBinding::ImageStore, |
4782 | loadStoreTypeVal: QRhiShaderResourceBinding::ImageLoadStore); |
4783 | break; |
4784 | case QRhiShaderResourceBinding::BufferLoad: |
4785 | case QRhiShaderResourceBinding::BufferStore: |
4786 | case QRhiShaderResourceBinding::BufferLoadStore: |
4787 | qrhigl_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
4788 | resource: b->u.sbuf.buf, |
4789 | bindingType: b->type, |
4790 | loadTypeVal: QRhiShaderResourceBinding::BufferLoad, |
4791 | storeTypeVal: QRhiShaderResourceBinding::BufferStore, |
4792 | loadStoreTypeVal: QRhiShaderResourceBinding::BufferLoadStore); |
4793 | break; |
4794 | default: |
4795 | break; |
4796 | } |
4797 | } |
4798 | |
4799 | for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) { |
4800 | const int accessInThisDispatch = it->first; |
4801 | const bool isNewInThisDispatch = it->second; |
4802 | if (accessInThisDispatch && !isNewInThisDispatch) { |
4803 | if (it.key()->resourceType() == QRhiResource::Texture) |
4804 | barriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; |
4805 | else |
4806 | barriers |= GL_SHADER_STORAGE_BARRIER_BIT; |
4807 | } |
4808 | // Anything that was previously written, but is only read now, can be |
4809 | // removed from the written list (because that previous write got a |
4810 | // corresponding barrier now). |
4811 | if (accessInThisDispatch == QGles2CommandBuffer::ComputePassState::Read) |
4812 | it = cbD->computePassState.writtenResources.erase(it); |
4813 | else |
4814 | ++it; |
4815 | } |
4816 | |
4817 | if (barriers) { |
4818 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4819 | cmd.cmd = QGles2CommandBuffer::Command::Barrier; |
4820 | cmd.args.barrier.barriers = barriers; |
4821 | } |
4822 | } |
4823 | |
4824 | QGles2CommandBuffer::Command &cmd(cbD->commands.get()); |
4825 | cmd.cmd = QGles2CommandBuffer::Command::Dispatch; |
4826 | cmd.args.dispatch.x = GLuint(x); |
4827 | cmd.args.dispatch.y = GLuint(y); |
4828 | cmd.args.dispatch.z = GLuint(z); |
4829 | } |
4830 | |
4831 | static inline GLenum toGlShaderType(QRhiShaderStage::Type type) |
4832 | { |
4833 | switch (type) { |
4834 | case QRhiShaderStage::Vertex: |
4835 | return GL_VERTEX_SHADER; |
4836 | case QRhiShaderStage::TessellationControl: |
4837 | return GL_TESS_CONTROL_SHADER; |
4838 | case QRhiShaderStage::TessellationEvaluation: |
4839 | return GL_TESS_EVALUATION_SHADER; |
4840 | case QRhiShaderStage::Geometry: |
4841 | return GL_GEOMETRY_SHADER; |
4842 | case QRhiShaderStage::Fragment: |
4843 | return GL_FRAGMENT_SHADER; |
4844 | case QRhiShaderStage::Compute: |
4845 | return GL_COMPUTE_SHADER; |
4846 | default: |
4847 | Q_UNREACHABLE_RETURN(GL_VERTEX_SHADER); |
4848 | } |
4849 | } |
4850 | |
4851 | QByteArray QRhiGles2::shaderSource(const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion) |
4852 | { |
4853 | const QShader bakedShader = shaderStage.shader(); |
4854 | QList<int> versionsToTry; |
4855 | QByteArray source; |
4856 | if (caps.gles) { |
4857 | if (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2)) { |
4858 | versionsToTry << 320 << 310 << 300 << 100; |
4859 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) { |
4860 | versionsToTry << 310 << 300 << 100; |
4861 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) { |
4862 | versionsToTry << 300 << 100; |
4863 | } else { |
4864 | versionsToTry << 100; |
4865 | } |
4866 | for (int v : versionsToTry) { |
4867 | QShaderVersion ver(v, QShaderVersion::GlslEs); |
4868 | source = bakedShader.shader(key: { QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader(); |
4869 | if (!source.isEmpty()) { |
4870 | if (shaderVersion) |
4871 | *shaderVersion = ver; |
4872 | break; |
4873 | } |
4874 | } |
4875 | } else { |
4876 | if (caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 6)) { |
4877 | versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; |
4878 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 5) { |
4879 | versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; |
4880 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 4) { |
4881 | versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; |
4882 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 3) { |
4883 | versionsToTry << 430 << 420 << 410 << 400 << 330 << 150 << 140 << 130; |
4884 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 2) { |
4885 | versionsToTry << 420 << 410 << 400 << 330 << 150 << 140 << 130; |
4886 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 1) { |
4887 | versionsToTry << 410 << 400 << 330 << 150 << 140 << 130; |
4888 | } else if (caps.ctxMajor == 4 && caps.ctxMinor == 0) { |
4889 | versionsToTry << 400 << 330 << 150 << 140 << 130; |
4890 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 3) { |
4891 | versionsToTry << 330 << 150 << 140 << 130; |
4892 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 2) { |
4893 | versionsToTry << 150 << 140 << 130; |
4894 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 1) { |
4895 | versionsToTry << 140 << 130; |
4896 | } else if (caps.ctxMajor == 3 && caps.ctxMinor == 0) { |
4897 | versionsToTry << 130; |
4898 | } |
4899 | if (!caps.coreProfile) |
4900 | versionsToTry << 120; |
4901 | for (int v : versionsToTry) { |
4902 | source = bakedShader.shader(key: { QShader::GlslShader, v, shaderStage.shaderVariant() }).shader(); |
4903 | if (!source.isEmpty()) { |
4904 | if (shaderVersion) |
4905 | *shaderVersion = v; |
4906 | break; |
4907 | } |
4908 | } |
4909 | } |
4910 | if (source.isEmpty()) { |
4911 | qWarning() << "No GLSL shader code found (versions tried: "<< versionsToTry |
4912 | << ") in baked shader"<< bakedShader; |
4913 | } |
4914 | return source; |
4915 | } |
4916 | |
4917 | bool QRhiGles2::compileShader(GLuint program, const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion) |
4918 | { |
4919 | const QByteArray source = shaderSource(shaderStage, shaderVersion); |
4920 | if (source.isEmpty()) |
4921 | return false; |
4922 | |
4923 | GLuint shader; |
4924 | auto cacheIt = m_shaderCache.constFind(key: shaderStage); |
4925 | if (cacheIt != m_shaderCache.constEnd()) { |
4926 | shader = *cacheIt; |
4927 | } else { |
4928 | shader = f->glCreateShader(type: toGlShaderType(type: shaderStage.type())); |
4929 | const char *srcStr = source.constData(); |
4930 | const GLint srcLength = source.size(); |
4931 | f->glShaderSource(shader, count: 1, string: &srcStr, length: &srcLength); |
4932 | f->glCompileShader(shader); |
4933 | GLint compiled = 0; |
4934 | f->glGetShaderiv(shader, GL_COMPILE_STATUS, params: &compiled); |
4935 | if (!compiled) { |
4936 | GLint infoLogLength = 0; |
4937 | f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, params: &infoLogLength); |
4938 | QByteArray log; |
4939 | if (infoLogLength > 1) { |
4940 | GLsizei length = 0; |
4941 | log.resize(size: infoLogLength); |
4942 | f->glGetShaderInfoLog(shader, bufsize: infoLogLength, length: &length, infolog: log.data()); |
4943 | } |
4944 | qWarning(msg: "Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData()); |
4945 | return false; |
4946 | } |
4947 | if (m_shaderCache.size() >= MAX_SHADER_CACHE_ENTRIES) { |
4948 | // Use the simplest strategy: too many cached shaders -> drop them all. |
4949 | for (uint shader : m_shaderCache) |
4950 | f->glDeleteShader(shader); // does not actually get released yet when attached to a not-yet-released program |
4951 | m_shaderCache.clear(); |
4952 | } |
4953 | m_shaderCache.insert(key: shaderStage, value: shader); |
4954 | } |
4955 | |
4956 | f->glAttachShader(program, shader); |
4957 | |
4958 | return true; |
4959 | } |
4960 | |
4961 | bool QRhiGles2::linkProgram(GLuint program) |
4962 | { |
4963 | f->glLinkProgram(program); |
4964 | GLint linked = 0; |
4965 | f->glGetProgramiv(program, GL_LINK_STATUS, params: &linked); |
4966 | if (!linked) { |
4967 | GLint infoLogLength = 0; |
4968 | f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, params: &infoLogLength); |
4969 | QByteArray log; |
4970 | if (infoLogLength > 1) { |
4971 | GLsizei length = 0; |
4972 | log.resize(size: infoLogLength); |
4973 | f->glGetProgramInfoLog(program, bufsize: infoLogLength, length: &length, infolog: log.data()); |
4974 | } |
4975 | qWarning(msg: "Failed to link shader program: %s", log.constData()); |
4976 | return false; |
4977 | } |
4978 | return true; |
4979 | } |
4980 | |
4981 | void QRhiGles2::registerUniformIfActive(const QShaderDescription::BlockVariable &var, |
4982 | const QByteArray &namePrefix, |
4983 | int binding, |
4984 | int baseOffset, |
4985 | GLuint program, |
4986 | ActiveUniformLocationTracker *activeUniformLocations, |
4987 | QGles2UniformDescriptionVector *dst) |
4988 | { |
4989 | if (var.type == QShaderDescription::Struct) { |
4990 | qWarning(msg: "Nested structs are not supported at the moment. '%s' ignored.", |
4991 | var.name.constData()); |
4992 | return; |
4993 | } |
4994 | QGles2UniformDescription uniform; |
4995 | uniform.type = var.type; |
4996 | const QByteArray name = namePrefix + var.name; |
4997 | // Here we expect that the OpenGL implementation has proper active uniform |
4998 | // handling, meaning that a uniform that is declared but not accessed |
4999 | // elsewhere in the code is reported as -1 when querying the location. If |
5000 | // that is not the case, it won't break anything, but we'll generate |
5001 | // unnecessary glUniform* calls then. |
5002 | uniform.glslLocation = f->glGetUniformLocation(program, name: name.constData()); |
5003 | if (uniform.glslLocation >= 0 && !activeUniformLocations->hasSeen(s: uniform.glslLocation)) { |
5004 | if (var.arrayDims.size() > 1) { |
5005 | qWarning(msg: "Array '%s' has more than one dimension. This is not supported.", |
5006 | var.name.constData()); |
5007 | return; |
5008 | } |
5009 | uniform.binding = binding; |
5010 | uniform.offset = uint(baseOffset + var.offset); |
5011 | uniform.size = var.size; |
5012 | uniform.arrayDim = var.arrayDims.isEmpty() ? 0 : var.arrayDims.first(); |
5013 | dst->append(t: uniform); |
5014 | } |
5015 | } |
5016 | |
5017 | void QRhiGles2::gatherUniforms(GLuint program, |
5018 | const QShaderDescription::UniformBlock &ub, |
5019 | ActiveUniformLocationTracker *activeUniformLocations, |
5020 | QGles2UniformDescriptionVector *dst) |
5021 | { |
5022 | QByteArray prefix = ub.structName + '.'; |
5023 | for (const QShaderDescription::BlockVariable &blockMember : ub.members) { |
5024 | if (blockMember.type == QShaderDescription::Struct) { |
5025 | QByteArray structPrefix = prefix + blockMember.name; |
5026 | |
5027 | const int baseOffset = blockMember.offset; |
5028 | if (blockMember.arrayDims.isEmpty()) { |
5029 | for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers) |
5030 | registerUniformIfActive(var: structMember, namePrefix: structPrefix + ".", binding: ub.binding, |
5031 | baseOffset, program, activeUniformLocations, dst); |
5032 | } else { |
5033 | if (blockMember.arrayDims.size() > 1) { |
5034 | qWarning(msg: "Array of struct '%s' has more than one dimension. Only the first " |
5035 | "dimension is used.", |
5036 | blockMember.name.constData()); |
5037 | } |
5038 | const int dim = blockMember.arrayDims.first(); |
5039 | const int elemSize = blockMember.size / dim; |
5040 | int elemOffset = baseOffset; |
5041 | for (int di = 0; di < dim; ++di) { |
5042 | const QByteArray arrayPrefix = structPrefix + '[' + QByteArray::number(di) + ']' + '.'; |
5043 | for (const QShaderDescription::BlockVariable &structMember : blockMember.structMembers) |
5044 | registerUniformIfActive(var: structMember, namePrefix: arrayPrefix, binding: ub.binding, baseOffset: elemOffset, program, activeUniformLocations, dst); |
5045 | elemOffset += elemSize; |
5046 | } |
5047 | } |
5048 | } else { |
5049 | registerUniformIfActive(var: blockMember, namePrefix: prefix, binding: ub.binding, baseOffset: 0, program, activeUniformLocations, dst); |
5050 | } |
5051 | } |
5052 | } |
5053 | |
5054 | void QRhiGles2::gatherSamplers(GLuint program, |
5055 | const QShaderDescription::InOutVariable &v, |
5056 | QGles2SamplerDescriptionVector *dst) |
5057 | { |
5058 | QGles2SamplerDescription sampler; |
5059 | sampler.glslLocation = f->glGetUniformLocation(program, name: v.name.constData()); |
5060 | if (sampler.glslLocation >= 0) { |
5061 | sampler.combinedBinding = v.binding; |
5062 | sampler.tbinding = -1; |
5063 | sampler.sbinding = -1; |
5064 | dst->append(t: sampler); |
5065 | } |
5066 | } |
5067 | |
5068 | void QRhiGles2::gatherGeneratedSamplers(GLuint program, |
5069 | const QShader::SeparateToCombinedImageSamplerMapping &mapping, |
5070 | QGles2SamplerDescriptionVector *dst) |
5071 | { |
5072 | QGles2SamplerDescription sampler; |
5073 | sampler.glslLocation = f->glGetUniformLocation(program, name: mapping.combinedSamplerName.constData()); |
5074 | if (sampler.glslLocation >= 0) { |
5075 | sampler.combinedBinding = -1; |
5076 | sampler.tbinding = mapping.textureBinding; |
5077 | sampler.sbinding = mapping.samplerBinding; |
5078 | dst->append(t: sampler); |
5079 | } |
5080 | } |
5081 | |
5082 | void QRhiGles2::sanityCheckVertexFragmentInterface(const QShaderDescription &vsDesc, const QShaderDescription &fsDesc) |
5083 | { |
5084 | if (!vsDesc.isValid() || !fsDesc.isValid()) |
5085 | return; |
5086 | |
5087 | // Print a warning if the fragment shader input for a given location uses a |
5088 | // name that does not match the vertex shader output at the same location. |
5089 | // This is not an error with any other API and not with GLSL >= 330 either, |
5090 | // but matters for older GLSL code that has no location qualifiers. |
5091 | for (const QShaderDescription::InOutVariable &outVar : vsDesc.outputVariables()) { |
5092 | for (const QShaderDescription::InOutVariable &inVar : fsDesc.inputVariables()) { |
5093 | if (inVar.location == outVar.location) { |
5094 | if (inVar.name != outVar.name) { |
5095 | qWarning(msg: "Vertex output name '%s' does not match fragment input '%s'. " |
5096 | "This should be avoided because it causes problems with older GLSL versions.", |
5097 | outVar.name.constData(), inVar.name.constData()); |
5098 | } |
5099 | break; |
5100 | } |
5101 | } |
5102 | } |
5103 | } |
5104 | |
5105 | bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const |
5106 | { |
5107 | static QOpenGLProgramBinarySupportCheckWrapper checker; |
5108 | return checker.get(context: ctx)->isSupported(); |
5109 | } |
5110 | |
5111 | Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache); |
5112 | |
5113 | static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type) |
5114 | { |
5115 | switch (type) { |
5116 | case QRhiShaderStage::Vertex: |
5117 | return QShader::VertexStage; |
5118 | case QRhiShaderStage::TessellationControl: |
5119 | return QShader::TessellationControlStage; |
5120 | case QRhiShaderStage::TessellationEvaluation: |
5121 | return QShader::TessellationEvaluationStage; |
5122 | case QRhiShaderStage::Geometry: |
5123 | return QShader::GeometryStage; |
5124 | case QRhiShaderStage::Fragment: |
5125 | return QShader::FragmentStage; |
5126 | case QRhiShaderStage::Compute: |
5127 | return QShader::ComputeStage; |
5128 | default: |
5129 | Q_UNREACHABLE_RETURN(QShader::VertexStage); |
5130 | } |
5131 | } |
5132 | |
5133 | QRhiGles2::ProgramCacheResult QRhiGles2::tryLoadFromDiskOrPipelineCache(const QRhiShaderStage *stages, |
5134 | int stageCount, |
5135 | GLuint program, |
5136 | const QVector<QShaderDescription::InOutVariable> &inputVars, |
5137 | QByteArray *cacheKey) |
5138 | { |
5139 | Q_ASSERT(cacheKey); |
5140 | |
5141 | // the traditional QOpenGL disk cache since Qt 5.9 |
5142 | const bool legacyDiskCacheEnabled = isProgramBinaryDiskCacheEnabled(); |
5143 | |
5144 | // QRhi's own (set)PipelineCacheData() |
5145 | const bool pipelineCacheEnabled = caps.programBinary && !m_pipelineCache.isEmpty(); |
5146 | |
5147 | // calculating the cache key based on the source code is common for both types of caches |
5148 | if (legacyDiskCacheEnabled || pipelineCacheEnabled) { |
5149 | QOpenGLProgramBinaryCache::ProgramDesc binaryProgram; |
5150 | for (int i = 0; i < stageCount; ++i) { |
5151 | const QRhiShaderStage &stage(stages[i]); |
5152 | QByteArray source = shaderSource(shaderStage: stage, shaderVersion: nullptr); |
5153 | if (source.isEmpty()) |
5154 | return QRhiGles2::ProgramCacheError; |
5155 | |
5156 | if (stage.type() == QRhiShaderStage::Vertex) { |
5157 | // Now add something to the key that indicates the vertex input locations. |
5158 | // A GLSL shader lower than 330 (150, 140, ...) will not have location |
5159 | // qualifiers. This means that the shader source code is the same |
5160 | // regardless of what locations inputVars contains. This becomes a problem |
5161 | // because we'll glBindAttribLocation the shader based on inputVars, but |
5162 | // that's only when compiling/linking when there was no cache hit. Picking |
5163 | // from the cache afterwards should take the input locations into account |
5164 | // since if inputVars has now different locations for the attributes, then |
5165 | // it is not ok to reuse a program binary that used different attribute |
5166 | // locations. For a lot of clients this would not be an issue since they |
5167 | // typically hardcode and use the same vertex locations on every run. Some |
5168 | // systems that dynamically generate shaders may end up with a non-stable |
5169 | // order (and so location numbers), however. This is sub-optimal because |
5170 | // it makes caching inefficient, and said clients should be fixed, but in |
5171 | // any case this should not break rendering. Hence including the locations |
5172 | // in the cache key. |
5173 | QMap<QByteArray, int> inputLocations; // sorted by key when iterating |
5174 | for (const QShaderDescription::InOutVariable &var : inputVars) |
5175 | inputLocations.insert(key: var.name, value: var.location); |
5176 | source += QByteArrayLiteral("\n // "); // just to be nice; treated as an arbitrary string regardless |
5177 | for (auto it = inputLocations.cbegin(), end = inputLocations.cend(); it != end; ++it) { |
5178 | source += it.key(); |
5179 | source += QByteArray::number(it.value()); |
5180 | } |
5181 | source += QByteArrayLiteral("\n"); |
5182 | } |
5183 | |
5184 | binaryProgram.shaders.append(t: QOpenGLProgramBinaryCache::ShaderDesc(toShaderStage(type: stage.type()), source)); |
5185 | } |
5186 | |
5187 | *cacheKey = binaryProgram.cacheKey(); |
5188 | |
5189 | // Try our pipeline cache simulation first, if it got seeded with |
5190 | // setPipelineCacheData and there's a hit, then no need to go to the |
5191 | // filesystem at all. |
5192 | if (pipelineCacheEnabled) { |
5193 | auto it = m_pipelineCache.constFind(key: *cacheKey); |
5194 | if (it != m_pipelineCache.constEnd()) { |
5195 | GLenum err; |
5196 | for ( ; ; ) { |
5197 | err = f->glGetError(); |
5198 | if (err == GL_NO_ERROR || err == GL_CONTEXT_LOST) |
5199 | break; |
5200 | } |
5201 | f->glProgramBinary(program, binaryFormat: it->format, binary: it->data.constData(), length: it->data.size()); |
5202 | err = f->glGetError(); |
5203 | if (err == GL_NO_ERROR) { |
5204 | GLint linkStatus = 0; |
5205 | f->glGetProgramiv(program, GL_LINK_STATUS, params: &linkStatus); |
5206 | if (linkStatus == GL_TRUE) |
5207 | return QRhiGles2::ProgramCacheHit; |
5208 | } |
5209 | } |
5210 | } |
5211 | |
5212 | if (legacyDiskCacheEnabled && qrhi_programBinaryCache()->load(cacheKey: *cacheKey, programId: program)) { |
5213 | // use the logging category QOpenGLShaderProgram would |
5214 | qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s", |
5215 | program, cacheKey->constData()); |
5216 | return QRhiGles2::ProgramCacheHit; |
5217 | } |
5218 | } |
5219 | |
5220 | return QRhiGles2::ProgramCacheMiss; |
5221 | } |
5222 | |
5223 | void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey) |
5224 | { |
5225 | // This is only for the traditional QOpenGL disk cache since Qt 5.9. |
5226 | |
5227 | if (isProgramBinaryDiskCacheEnabled()) { |
5228 | // use the logging category QOpenGLShaderProgram would |
5229 | qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s", |
5230 | program, cacheKey.constData()); |
5231 | qrhi_programBinaryCache()->save(cacheKey, programId: program); |
5232 | } |
5233 | } |
5234 | |
5235 | void QRhiGles2::trySaveToPipelineCache(GLuint program, const QByteArray &cacheKey, bool force) |
5236 | { |
5237 | // This handles our own simulated "pipeline cache". (specific to QRhi, not |
5238 | // shared with legacy QOpenGL* stuff) |
5239 | |
5240 | if (caps.programBinary && (force || !m_pipelineCache.contains(key: cacheKey))) { |
5241 | GLint blobSize = 0; |
5242 | f->glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, params: &blobSize); |
5243 | QByteArray blob(blobSize, Qt::Uninitialized); |
5244 | GLint outSize = 0; |
5245 | GLenum binaryFormat = 0; |
5246 | f->glGetProgramBinary(program, bufSize: blobSize, length: &outSize, binaryFormat: &binaryFormat, binary: blob.data()); |
5247 | if (blobSize == outSize) |
5248 | m_pipelineCache.insert(key: cacheKey, value: { .format: binaryFormat, .data: blob }); |
5249 | } |
5250 | } |
5251 | |
5252 | QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size) |
5253 | : QRhiBuffer(rhi, type, usage, size) |
5254 | { |
5255 | } |
5256 | |
5257 | QGles2Buffer::~QGles2Buffer() |
5258 | { |
5259 | destroy(); |
5260 | } |
5261 | |
5262 | void QGles2Buffer::destroy() |
5263 | { |
5264 | data.clear(); |
5265 | if (!buffer) |
5266 | return; |
5267 | |
5268 | QRhiGles2::DeferredReleaseEntry e; |
5269 | e.type = QRhiGles2::DeferredReleaseEntry::Buffer; |
5270 | |
5271 | e.buffer.buffer = buffer; |
5272 | buffer = 0; |
5273 | |
5274 | QRHI_RES_RHI(QRhiGles2); |
5275 | if (rhiD) { |
5276 | rhiD->releaseQueue.append(t: e); |
5277 | rhiD->unregisterResource(res: this); |
5278 | } |
5279 | } |
5280 | |
5281 | bool QGles2Buffer::create() |
5282 | { |
5283 | if (buffer) |
5284 | destroy(); |
5285 | |
5286 | QRHI_RES_RHI(QRhiGles2); |
5287 | |
5288 | nonZeroSize = m_size <= 0 ? 256 : m_size; |
5289 | |
5290 | if (m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) { |
5291 | if (int(m_usage) != QRhiBuffer::UniformBuffer) { |
5292 | qWarning(msg: "Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend"); |
5293 | return false; |
5294 | } |
5295 | data.resize(size: nonZeroSize); |
5296 | return true; |
5297 | } |
5298 | |
5299 | if (!rhiD->ensureContext()) |
5300 | return false; |
5301 | |
5302 | targetForDataOps = GL_ARRAY_BUFFER; |
5303 | if (m_usage.testFlag(flag: QRhiBuffer::IndexBuffer)) |
5304 | targetForDataOps = GL_ELEMENT_ARRAY_BUFFER; |
5305 | else if (m_usage.testFlag(flag: QRhiBuffer::StorageBuffer)) |
5306 | targetForDataOps = GL_SHADER_STORAGE_BUFFER; |
5307 | |
5308 | rhiD->f->glGenBuffers(n: 1, buffers: &buffer); |
5309 | rhiD->f->glBindBuffer(target: targetForDataOps, buffer); |
5310 | rhiD->f->glBufferData(target: targetForDataOps, size: nonZeroSize, data: nullptr, usage: m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); |
5311 | |
5312 | if (rhiD->glObjectLabel) |
5313 | rhiD->glObjectLabel(GL_BUFFER, buffer, -1, m_objectName.constData()); |
5314 | |
5315 | usageState.access = AccessNone; |
5316 | |
5317 | rhiD->registerResource(res: this); |
5318 | return true; |
5319 | } |
5320 | |
5321 | QRhiBuffer::NativeBuffer QGles2Buffer::nativeBuffer() |
5322 | { |
5323 | if (m_usage.testFlag(flag: QRhiBuffer::UniformBuffer)) |
5324 | return { .objects: {}, .slotCount: 0 }; |
5325 | |
5326 | return { .objects: { &buffer }, .slotCount: 1 }; |
5327 | } |
5328 | |
5329 | char *QGles2Buffer::beginFullDynamicBufferUpdateForCurrentFrame() |
5330 | { |
5331 | Q_ASSERT(m_type == Dynamic); |
5332 | if (!m_usage.testFlag(flag: UniformBuffer)) { |
5333 | QRHI_RES_RHI(QRhiGles2); |
5334 | rhiD->f->glBindBuffer(target: targetForDataOps, buffer); |
5335 | if (rhiD->caps.properMapBuffer) { |
5336 | return static_cast<char *>(rhiD->f->glMapBufferRange(target: targetForDataOps, offset: 0, length: nonZeroSize, |
5337 | GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)); |
5338 | } else { |
5339 | // Need some storage for the data, use the otherwise unused 'data' member. |
5340 | if (data.isEmpty()) |
5341 | data.resize(size: nonZeroSize); |
5342 | } |
5343 | } |
5344 | return data.data(); |
5345 | } |
5346 | |
5347 | void QGles2Buffer::endFullDynamicBufferUpdateForCurrentFrame() |
5348 | { |
5349 | if (!m_usage.testFlag(flag: UniformBuffer)) { |
5350 | QRHI_RES_RHI(QRhiGles2); |
5351 | rhiD->f->glBindBuffer(target: targetForDataOps, buffer); |
5352 | if (rhiD->caps.properMapBuffer) |
5353 | rhiD->f->glUnmapBuffer(target: targetForDataOps); |
5354 | else |
5355 | rhiD->f->glBufferSubData(target: targetForDataOps, offset: 0, size: nonZeroSize, data: data.data()); |
5356 | } |
5357 | } |
5358 | |
5359 | void QGles2Buffer::fullDynamicBufferUpdateForCurrentFrame(const void *bufferData, quint32 size) |
5360 | { |
5361 | const quint32 copySize = size > 0 ? size : m_size; |
5362 | if (!m_usage.testFlag(flag: UniformBuffer)) { |
5363 | QRHI_RES_RHI(QRhiGles2); |
5364 | rhiD->f->glBindBuffer(target: targetForDataOps, buffer); |
5365 | rhiD->f->glBufferSubData(target: targetForDataOps, offset: 0, size: copySize, data: bufferData); |
5366 | } else { |
5367 | memcpy(dest: data.data(), src: bufferData, n: copySize); |
5368 | } |
5369 | } |
5370 | |
5371 | QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, |
5372 | int sampleCount, QRhiRenderBuffer::Flags flags, |
5373 | QRhiTexture::Format backingFormatHint) |
5374 | : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint) |
5375 | { |
5376 | } |
5377 | |
5378 | QGles2RenderBuffer::~QGles2RenderBuffer() |
5379 | { |
5380 | destroy(); |
5381 | } |
5382 | |
5383 | void QGles2RenderBuffer::destroy() |
5384 | { |
5385 | if (!renderbuffer) |
5386 | return; |
5387 | |
5388 | QRhiGles2::DeferredReleaseEntry e; |
5389 | e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer; |
5390 | |
5391 | e.renderbuffer.renderbuffer = renderbuffer; |
5392 | e.renderbuffer.renderbuffer2 = stencilRenderbuffer; |
5393 | |
5394 | renderbuffer = 0; |
5395 | stencilRenderbuffer = 0; |
5396 | |
5397 | QRHI_RES_RHI(QRhiGles2); |
5398 | if (rhiD) { |
5399 | if (owns) |
5400 | rhiD->releaseQueue.append(t: e); |
5401 | rhiD->unregisterResource(res: this); |
5402 | } |
5403 | } |
5404 | |
5405 | bool QGles2RenderBuffer::create() |
5406 | { |
5407 | if (renderbuffer) |
5408 | destroy(); |
5409 | |
5410 | QRHI_RES_RHI(QRhiGles2); |
5411 | samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
5412 | |
5413 | if (m_flags.testFlag(flag: UsedWithSwapChainOnly)) { |
5414 | if (m_type == DepthStencil) |
5415 | return true; |
5416 | |
5417 | qWarning(msg: "RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color"); |
5418 | } |
5419 | |
5420 | if (!rhiD->ensureContext()) |
5421 | return false; |
5422 | |
5423 | rhiD->f->glGenRenderbuffers(n: 1, renderbuffers: &renderbuffer); |
5424 | rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); |
5425 | |
5426 | const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; |
5427 | |
5428 | switch (m_type) { |
5429 | case QRhiRenderBuffer::DepthStencil: |
5430 | if (rhiD->caps.msaaRenderBuffer && samples > 1) { |
5431 | rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, |
5432 | width: size.width(), height: size.height()); |
5433 | stencilRenderbuffer = 0; |
5434 | } else if (rhiD->caps.packedDepthStencil || rhiD->caps.needsDepthStencilCombinedAttach) { |
5435 | const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8; |
5436 | rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: storage, |
5437 | width: size.width(), height: size.height()); |
5438 | stencilRenderbuffer = 0; |
5439 | } else { |
5440 | GLenum depthStorage = GL_DEPTH_COMPONENT; |
5441 | if (rhiD->caps.gles) { |
5442 | if (rhiD->caps.depth24) |
5443 | depthStorage = GL_DEPTH_COMPONENT24; |
5444 | else |
5445 | depthStorage = GL_DEPTH_COMPONENT16; // plain ES 2.0 only has this |
5446 | } |
5447 | const GLenum stencilStorage = rhiD->caps.gles ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX; |
5448 | rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: depthStorage, |
5449 | width: size.width(), height: size.height()); |
5450 | rhiD->f->glGenRenderbuffers(n: 1, renderbuffers: &stencilRenderbuffer); |
5451 | rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer: stencilRenderbuffer); |
5452 | rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: stencilStorage, |
5453 | width: size.width(), height: size.height()); |
5454 | } |
5455 | break; |
5456 | case QRhiRenderBuffer::Color: |
5457 | { |
5458 | GLenum internalFormat = GL_RGBA4; // ES 2.0 |
5459 | if (rhiD->caps.rgba8Format) { |
5460 | internalFormat = GL_RGBA8; |
5461 | if (m_backingFormatHint != QRhiTexture::UnknownFormat) { |
5462 | GLenum glintformat, glformat, gltype; |
5463 | // only care about the sized internal format, the rest is not used here |
5464 | toGlTextureFormat(format: m_backingFormatHint, caps: rhiD->caps, |
5465 | glintformat: &glintformat, glsizedintformat: &internalFormat, glformat: &glformat, gltype: &gltype); |
5466 | } |
5467 | } |
5468 | if (rhiD->caps.msaaRenderBuffer && samples > 1) { |
5469 | rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat: internalFormat, |
5470 | width: size.width(), height: size.height()); |
5471 | } else { |
5472 | rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, internalformat: internalFormat, |
5473 | width: size.width(), height: size.height()); |
5474 | } |
5475 | } |
5476 | break; |
5477 | default: |
5478 | Q_UNREACHABLE(); |
5479 | break; |
5480 | } |
5481 | |
5482 | if (rhiD->glObjectLabel) |
5483 | rhiD->glObjectLabel(GL_RENDERBUFFER, renderbuffer, -1, m_objectName.constData()); |
5484 | |
5485 | owns = true; |
5486 | generation += 1; |
5487 | rhiD->registerResource(res: this); |
5488 | return true; |
5489 | } |
5490 | |
5491 | bool QGles2RenderBuffer::createFrom(NativeRenderBuffer src) |
5492 | { |
5493 | if (!src.object) |
5494 | return false; |
5495 | |
5496 | if (renderbuffer) |
5497 | destroy(); |
5498 | |
5499 | QRHI_RES_RHI(QRhiGles2); |
5500 | samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
5501 | |
5502 | if (m_flags.testFlag(flag: UsedWithSwapChainOnly)) |
5503 | qWarning(msg: "RenderBuffer: UsedWithSwapChainOnly is meaningless when importing an existing native object"); |
5504 | |
5505 | if (!rhiD->ensureContext()) |
5506 | return false; |
5507 | |
5508 | renderbuffer = src.object; |
5509 | |
5510 | owns = false; |
5511 | generation += 1; |
5512 | rhiD->registerResource(res: this); |
5513 | return true; |
5514 | } |
5515 | |
5516 | QRhiTexture::Format QGles2RenderBuffer::backingFormat() const |
5517 | { |
5518 | if (m_backingFormatHint != QRhiTexture::UnknownFormat) |
5519 | return m_backingFormatHint; |
5520 | else |
5521 | return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; |
5522 | } |
5523 | |
5524 | QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, |
5525 | int arraySize, int sampleCount, Flags flags) |
5526 | : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags) |
5527 | { |
5528 | } |
5529 | |
5530 | QGles2Texture::~QGles2Texture() |
5531 | { |
5532 | destroy(); |
5533 | } |
5534 | |
5535 | void QGles2Texture::destroy() |
5536 | { |
5537 | if (!texture) |
5538 | return; |
5539 | |
5540 | QRhiGles2::DeferredReleaseEntry e; |
5541 | e.type = QRhiGles2::DeferredReleaseEntry::Texture; |
5542 | |
5543 | e.texture.texture = texture; |
5544 | |
5545 | texture = 0; |
5546 | specified = false; |
5547 | zeroInitialized = false; |
5548 | |
5549 | QRHI_RES_RHI(QRhiGles2); |
5550 | if (rhiD) { |
5551 | if (owns) |
5552 | rhiD->releaseQueue.append(t: e); |
5553 | rhiD->unregisterResource(res: this); |
5554 | } |
5555 | } |
5556 | |
5557 | bool QGles2Texture::prepareCreate(QSize *adjustedSize) |
5558 | { |
5559 | if (texture) |
5560 | destroy(); |
5561 | |
5562 | QRHI_RES_RHI(QRhiGles2); |
5563 | if (!rhiD->ensureContext()) |
5564 | return false; |
5565 | |
5566 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5567 | const bool isArray = m_flags.testFlag(flag: QRhiTexture::TextureArray); |
5568 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
5569 | const bool hasMipMaps = m_flags.testFlag(flag: MipMapped); |
5570 | const bool isCompressed = rhiD->isCompressedFormat(format: m_format); |
5571 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
5572 | |
5573 | const QSize size = is1D ? QSize(qMax(a: 1, b: m_pixelSize.width()), 1) |
5574 | : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize); |
5575 | |
5576 | if (is3D && !rhiD->caps.texture3D) { |
5577 | qWarning(msg: "3D textures are not supported"); |
5578 | return false; |
5579 | } |
5580 | if (isCube && is3D) { |
5581 | qWarning(msg: "Texture cannot be both cube and 3D"); |
5582 | return false; |
5583 | } |
5584 | if (isArray && is3D) { |
5585 | qWarning(msg: "Texture cannot be both array and 3D"); |
5586 | return false; |
5587 | } |
5588 | if (is1D && !rhiD->caps.texture1D) { |
5589 | qWarning(msg: "1D textures are not supported"); |
5590 | return false; |
5591 | } |
5592 | if (is1D && is3D) { |
5593 | qWarning(msg: "Texture cannot be both 1D and 3D"); |
5594 | return false; |
5595 | } |
5596 | if (is1D && isCube) { |
5597 | qWarning(msg: "Texture cannot be both 1D and cube"); |
5598 | return false; |
5599 | } |
5600 | |
5601 | if (m_depth > 1 && !is3D) { |
5602 | qWarning(msg: "Texture cannot have a depth of %d when it is not 3D", m_depth); |
5603 | return false; |
5604 | } |
5605 | if (m_arraySize > 0 && !isArray) { |
5606 | qWarning(msg: "Texture cannot have an array size of %d when it is not an array", m_arraySize); |
5607 | return false; |
5608 | } |
5609 | if (m_arraySize < 1 && isArray) { |
5610 | qWarning(msg: "Texture is an array but array size is %d", m_arraySize); |
5611 | return false; |
5612 | } |
5613 | |
5614 | target = isCube ? GL_TEXTURE_CUBE_MAP |
5615 | : m_sampleCount > 1 ? (isArray ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_MULTISAMPLE) |
5616 | : (is3D ? GL_TEXTURE_3D |
5617 | : (is1D ? (isArray ? GL_TEXTURE_1D_ARRAY : GL_TEXTURE_1D) |
5618 | : (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D))); |
5619 | |
5620 | if (m_flags.testFlag(flag: ExternalOES)) |
5621 | target = GL_TEXTURE_EXTERNAL_OES; |
5622 | else if (m_flags.testFlag(flag: TextureRectangleGL)) |
5623 | target = GL_TEXTURE_RECTANGLE; |
5624 | |
5625 | mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; |
5626 | gltype = GL_UNSIGNED_BYTE; |
5627 | |
5628 | if (isCompressed) { |
5629 | if (m_flags.testFlag(flag: UsedWithLoadStore)) { |
5630 | qWarning(msg: "Compressed texture cannot be used with image load/store"); |
5631 | return false; |
5632 | } |
5633 | glintformat = toGlCompressedTextureFormat(format: m_format, flags: m_flags); |
5634 | if (!glintformat) { |
5635 | qWarning(msg: "Compressed format %d not mappable to GL compressed format", m_format); |
5636 | return false; |
5637 | } |
5638 | glsizedintformat = glintformat; |
5639 | glformat = GL_RGBA; |
5640 | } else { |
5641 | toGlTextureFormat(format: m_format, caps: rhiD->caps, |
5642 | glintformat: &glintformat, glsizedintformat: &glsizedintformat, glformat: &glformat, gltype: &gltype); |
5643 | } |
5644 | |
5645 | samplerState = QGles2SamplerData(); |
5646 | |
5647 | usageState.access = AccessNone; |
5648 | |
5649 | if (adjustedSize) |
5650 | *adjustedSize = size; |
5651 | |
5652 | return true; |
5653 | } |
5654 | |
5655 | bool QGles2Texture::create() |
5656 | { |
5657 | QSize size; |
5658 | if (!prepareCreate(adjustedSize: &size)) |
5659 | return false; |
5660 | |
5661 | QRHI_RES_RHI(QRhiGles2); |
5662 | rhiD->f->glGenTextures(n: 1, textures: &texture); |
5663 | |
5664 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5665 | const bool isArray = m_flags.testFlag(flag: QRhiTexture::TextureArray); |
5666 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
5667 | const bool hasMipMaps = m_flags.testFlag(flag: MipMapped); |
5668 | const bool isCompressed = rhiD->isCompressedFormat(format: m_format); |
5669 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
5670 | |
5671 | if (!isCompressed) { |
5672 | rhiD->f->glBindTexture(target, texture); |
5673 | if (!m_flags.testFlag(flag: UsedWithLoadStore)) { |
5674 | if (is1D) { |
5675 | for (int level = 0; level < mipLevelCount; ++level) { |
5676 | const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size); |
5677 | if (isArray) |
5678 | rhiD->f->glTexImage2D(target, level, internalformat: GLint(glintformat), width: mipSize.width(), |
5679 | height: qMax(a: 0, b: m_arraySize), border: 0, format: glformat, type: gltype, pixels: nullptr); |
5680 | else |
5681 | rhiD->glTexImage1D(target, level, GLint(glintformat), mipSize.width(), 0, |
5682 | glformat, gltype, nullptr); |
5683 | } |
5684 | } else if (is3D || isArray) { |
5685 | const int layerCount = is3D ? qMax(a: 1, b: m_depth) : qMax(a: 0, b: m_arraySize); |
5686 | if (hasMipMaps) { |
5687 | for (int level = 0; level != mipLevelCount; ++level) { |
5688 | const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size); |
5689 | rhiD->f->glTexImage3D(target, level, internalformat: GLint(glintformat), width: mipSize.width(), height: mipSize.height(), depth: layerCount, |
5690 | border: 0, format: glformat, type: gltype, pixels: nullptr); |
5691 | } |
5692 | } else { |
5693 | rhiD->f->glTexImage3D(target, level: 0, internalformat: GLint(glintformat), width: size.width(), height: size.height(), depth: layerCount, |
5694 | border: 0, format: glformat, type: gltype, pixels: nullptr); |
5695 | } |
5696 | } else if (hasMipMaps || isCube) { |
5697 | const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target; |
5698 | for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) { |
5699 | for (int level = 0; level != mipLevelCount; ++level) { |
5700 | const QSize mipSize = rhiD->q->sizeForMipLevel(mipLevel: level, baseLevelSize: size); |
5701 | rhiD->f->glTexImage2D(target: faceTargetBase + uint(layer), level, internalformat: GLint(glintformat), |
5702 | width: mipSize.width(), height: mipSize.height(), border: 0, |
5703 | format: glformat, type: gltype, pixels: nullptr); |
5704 | } |
5705 | } |
5706 | } else { |
5707 | // 2D texture. For multisample textures the GLES 3.1 |
5708 | // glStorage2DMultisample must be used for portability. |
5709 | if (m_sampleCount > 1 && rhiD->caps.multisampledTexture) { |
5710 | // internal format must be sized |
5711 | rhiD->f->glTexStorage2DMultisample(target, samples: m_sampleCount, internalformat: glsizedintformat, |
5712 | width: size.width(), height: size.height(), GL_TRUE); |
5713 | } else { |
5714 | rhiD->f->glTexImage2D(target, level: 0, internalformat: GLint(glintformat), width: size.width(), height: size.height(), |
5715 | border: 0, format: glformat, type: gltype, pixels: nullptr); |
5716 | } |
5717 | } |
5718 | } else { |
5719 | // Must be specified with immutable storage functions otherwise |
5720 | // bindImageTexture may fail. Also, the internal format must be a |
5721 | // sized format here. |
5722 | if (is1D && !isArray) |
5723 | rhiD->glTexStorage1D(target, mipLevelCount, glsizedintformat, size.width()); |
5724 | else if (!is1D && (is3D || isArray)) |
5725 | rhiD->f->glTexStorage3D(target, levels: mipLevelCount, internalformat: glsizedintformat, width: size.width(), height: size.height(), |
5726 | depth: is3D ? qMax(a: 1, b: m_depth) : qMax(a: 0, b: m_arraySize)); |
5727 | else if (m_sampleCount > 1) |
5728 | rhiD->f->glTexStorage2DMultisample(target, samples: m_sampleCount, internalformat: glsizedintformat, |
5729 | width: size.width(), height: size.height(), GL_TRUE); |
5730 | else |
5731 | rhiD->f->glTexStorage2D(target, levels: mipLevelCount, internalformat: glsizedintformat, width: size.width(), |
5732 | height: is1D ? qMax(a: 0, b: m_arraySize) : size.height()); |
5733 | } |
5734 | // Make sure the min filter is set to something non-mipmap-based already |
5735 | // here, given the ridiculous default of GL. It is changed based on |
5736 | // the sampler later, but there could be cases when one pulls the native |
5737 | // object out via nativeTexture() right away. |
5738 | rhiD->f->glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
5739 | specified = true; |
5740 | } else { |
5741 | // Cannot use glCompressedTexImage2D without valid data, so defer. |
5742 | // Compressed textures will not be used as render targets so this is |
5743 | // not an issue. |
5744 | specified = false; |
5745 | } |
5746 | |
5747 | if (rhiD->glObjectLabel) |
5748 | rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData()); |
5749 | |
5750 | owns = true; |
5751 | |
5752 | generation += 1; |
5753 | rhiD->registerResource(res: this); |
5754 | return true; |
5755 | } |
5756 | |
5757 | bool QGles2Texture::createFrom(QRhiTexture::NativeTexture src) |
5758 | { |
5759 | const uint textureId = uint(src.object); |
5760 | if (textureId == 0) |
5761 | return false; |
5762 | |
5763 | if (!prepareCreate()) |
5764 | return false; |
5765 | |
5766 | texture = textureId; |
5767 | specified = true; |
5768 | zeroInitialized = true; |
5769 | |
5770 | owns = false; |
5771 | |
5772 | generation += 1; |
5773 | QRHI_RES_RHI(QRhiGles2); |
5774 | rhiD->registerResource(res: this); |
5775 | return true; |
5776 | } |
5777 | |
5778 | QRhiTexture::NativeTexture QGles2Texture::nativeTexture() |
5779 | { |
5780 | return {.object: texture, .layout: 0}; |
5781 | } |
5782 | |
5783 | QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, |
5784 | AddressMode u, AddressMode v, AddressMode w) |
5785 | : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) |
5786 | { |
5787 | } |
5788 | |
5789 | QGles2Sampler::~QGles2Sampler() |
5790 | { |
5791 | destroy(); |
5792 | } |
5793 | |
5794 | void QGles2Sampler::destroy() |
5795 | { |
5796 | QRHI_RES_RHI(QRhiGles2); |
5797 | if (rhiD) |
5798 | rhiD->unregisterResource(res: this); |
5799 | } |
5800 | |
5801 | bool QGles2Sampler::create() |
5802 | { |
5803 | d.glminfilter = toGlMinFilter(f: m_minFilter, m: m_mipmapMode); |
5804 | d.glmagfilter = toGlMagFilter(f: m_magFilter); |
5805 | d.glwraps = toGlWrapMode(m: m_addressU); |
5806 | d.glwrapt = toGlWrapMode(m: m_addressV); |
5807 | d.glwrapr = toGlWrapMode(m: m_addressW); |
5808 | d.gltexcomparefunc = toGlTextureCompareFunc(op: m_compareOp); |
5809 | |
5810 | generation += 1; |
5811 | QRHI_RES_RHI(QRhiGles2); |
5812 | rhiD->registerResource(res: this, ownsNativeResources: false); |
5813 | return true; |
5814 | } |
5815 | |
5816 | // dummy, no Vulkan-style RenderPass+Framebuffer concept here |
5817 | QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi) |
5818 | : QRhiRenderPassDescriptor(rhi) |
5819 | { |
5820 | } |
5821 | |
5822 | QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor() |
5823 | { |
5824 | destroy(); |
5825 | } |
5826 | |
5827 | void QGles2RenderPassDescriptor::destroy() |
5828 | { |
5829 | QRHI_RES_RHI(QRhiGles2); |
5830 | if (rhiD) |
5831 | rhiD->unregisterResource(res: this); |
5832 | } |
5833 | |
5834 | bool QGles2RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const |
5835 | { |
5836 | Q_UNUSED(other); |
5837 | return true; |
5838 | } |
5839 | |
5840 | QRhiRenderPassDescriptor *QGles2RenderPassDescriptor::newCompatibleRenderPassDescriptor() const |
5841 | { |
5842 | QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); |
5843 | QRHI_RES_RHI(QRhiGles2); |
5844 | rhiD->registerResource(res: rpD, ownsNativeResources: false); |
5845 | return rpD; |
5846 | } |
5847 | |
5848 | QVector<quint32> QGles2RenderPassDescriptor::serializedFormat() const |
5849 | { |
5850 | return {}; |
5851 | } |
5852 | |
5853 | QGles2SwapChainRenderTarget::QGles2SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) |
5854 | : QRhiSwapChainRenderTarget(rhi, swapchain), |
5855 | d(rhi) |
5856 | { |
5857 | } |
5858 | |
5859 | QGles2SwapChainRenderTarget::~QGles2SwapChainRenderTarget() |
5860 | { |
5861 | destroy(); |
5862 | } |
5863 | |
5864 | void QGles2SwapChainRenderTarget::destroy() |
5865 | { |
5866 | // nothing to do here |
5867 | } |
5868 | |
5869 | QSize QGles2SwapChainRenderTarget::pixelSize() const |
5870 | { |
5871 | return d.pixelSize; |
5872 | } |
5873 | |
5874 | float QGles2SwapChainRenderTarget::devicePixelRatio() const |
5875 | { |
5876 | return d.dpr; |
5877 | } |
5878 | |
5879 | int QGles2SwapChainRenderTarget::sampleCount() const |
5880 | { |
5881 | return d.sampleCount; |
5882 | } |
5883 | |
5884 | QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi, |
5885 | const QRhiTextureRenderTargetDescription &desc, |
5886 | Flags flags) |
5887 | : QRhiTextureRenderTarget(rhi, desc, flags), |
5888 | d(rhi) |
5889 | { |
5890 | } |
5891 | |
5892 | QGles2TextureRenderTarget::~QGles2TextureRenderTarget() |
5893 | { |
5894 | destroy(); |
5895 | } |
5896 | |
5897 | void QGles2TextureRenderTarget::destroy() |
5898 | { |
5899 | if (!framebuffer) |
5900 | return; |
5901 | |
5902 | QRhiGles2::DeferredReleaseEntry e; |
5903 | e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget; |
5904 | |
5905 | e.textureRenderTarget.framebuffer = framebuffer; |
5906 | e.textureRenderTarget.nonMsaaThrowawayDepthTexture = nonMsaaThrowawayDepthTexture; |
5907 | |
5908 | framebuffer = 0; |
5909 | nonMsaaThrowawayDepthTexture = 0; |
5910 | |
5911 | QRHI_RES_RHI(QRhiGles2); |
5912 | if (rhiD) { |
5913 | rhiD->releaseQueue.append(t: e); |
5914 | rhiD->unregisterResource(res: this); |
5915 | } |
5916 | } |
5917 | |
5918 | QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor() |
5919 | { |
5920 | QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); |
5921 | QRHI_RES_RHI(QRhiGles2); |
5922 | rhiD->registerResource(res: rpD, ownsNativeResources: false); |
5923 | return rpD; |
5924 | } |
5925 | |
5926 | bool QGles2TextureRenderTarget::create() |
5927 | { |
5928 | QRHI_RES_RHI(QRhiGles2); |
5929 | |
5930 | if (framebuffer) |
5931 | destroy(); |
5932 | |
5933 | const bool hasColorAttachments = m_desc.colorAttachmentCount() > 0; |
5934 | Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); |
5935 | Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); |
5936 | const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); |
5937 | |
5938 | if (hasColorAttachments) { |
5939 | const int count = int(m_desc.colorAttachmentCount()); |
5940 | if (count > rhiD->caps.maxDrawBuffers) { |
5941 | qWarning(msg: "QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)", |
5942 | count, rhiD->caps.maxDrawBuffers); |
5943 | } |
5944 | } |
5945 | if (m_desc.depthTexture() && !rhiD->caps.depthTexture) |
5946 | qWarning(msg: "QGles2TextureRenderTarget: Depth texture is not supported and will be ignored"); |
5947 | |
5948 | if (!rhiD->ensureContext()) |
5949 | return false; |
5950 | |
5951 | rhiD->f->glGenFramebuffers(n: 1, framebuffers: &framebuffer); |
5952 | rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
5953 | |
5954 | d.colorAttCount = 0; |
5955 | int attIndex = 0; |
5956 | int multiViewCount = 0; |
5957 | for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { |
5958 | d.colorAttCount += 1; |
5959 | const QRhiColorAttachment &colorAtt(*it); |
5960 | QRhiTexture *texture = colorAtt.texture(); |
5961 | QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer(); |
5962 | Q_ASSERT(texture || renderBuffer); |
5963 | if (texture) { |
5964 | QGles2Texture *texD = QRHI_RES(QGles2Texture, texture); |
5965 | Q_ASSERT(texD->texture && texD->specified); |
5966 | if (texD->flags().testFlag(flag: QRhiTexture::ThreeDimensional) || texD->flags().testFlag(flag: QRhiTexture::TextureArray)) { |
5967 | if (colorAtt.multiViewCount() < 2) { |
5968 | rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texture: texD->texture, |
5969 | level: colorAtt.level(), layer: colorAtt.layer()); |
5970 | } else { |
5971 | multiViewCount = colorAtt.multiViewCount(); |
5972 | if (texD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture && colorAtt.resolveTexture()) { |
5973 | // Special path for GLES and GL_OVR_multiview_multisampled_render_to_texture: |
5974 | // ignore the color attachment's (multisample) texture |
5975 | // array and give the resolve texture array to GL. (no |
5976 | // explicit resolving is needed by us later on) |
5977 | QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); |
5978 | rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, |
5979 | GL_COLOR_ATTACHMENT0 + uint(attIndex), |
5980 | resolveTexD->texture, |
5981 | colorAtt.resolveLevel(), |
5982 | texD->sampleCount(), |
5983 | colorAtt.resolveLayer(), |
5984 | multiViewCount); |
5985 | } else { |
5986 | rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, |
5987 | GL_COLOR_ATTACHMENT0 + uint(attIndex), |
5988 | texD->texture, |
5989 | colorAtt.level(), |
5990 | colorAtt.layer(), |
5991 | multiViewCount); |
5992 | } |
5993 | } |
5994 | } else if (texD->flags().testFlag(flag: QRhiTexture::OneDimensional)) { |
5995 | rhiD->glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), |
5996 | texD->target + uint(colorAtt.layer()), texD->texture, |
5997 | colorAtt.level()); |
5998 | } else { |
5999 | if (texD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && colorAtt.resolveTexture()) { |
6000 | // Special path for GLES and GL_EXT_multisampled_render_to_texture: |
6001 | // ignore the color attachment's (multisample) texture and |
6002 | // give the resolve texture to GL. (no explicit resolving is |
6003 | // needed by us later on) |
6004 | QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture()); |
6005 | const GLenum faceTargetBase = resolveTexD->flags().testFlag(flag: QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : resolveTexD->target; |
6006 | rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.resolveLayer()), |
6007 | resolveTexD->texture, colorAtt.level(), texD->sampleCount()); |
6008 | } else { |
6009 | const GLenum faceTargetBase = texD->flags().testFlag(flag: QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; |
6010 | rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), textarget: faceTargetBase + uint(colorAtt.layer()), |
6011 | texture: texD->texture, level: colorAtt.level()); |
6012 | } |
6013 | } |
6014 | if (attIndex == 0) { |
6015 | d.pixelSize = rhiD->q->sizeForMipLevel(mipLevel: colorAtt.level(), baseLevelSize: texD->pixelSize()); |
6016 | d.sampleCount = texD->sampleCount(); |
6017 | } |
6018 | } else if (renderBuffer) { |
6019 | QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer); |
6020 | rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), GL_RENDERBUFFER, renderbuffer: rbD->renderbuffer); |
6021 | if (attIndex == 0) { |
6022 | d.pixelSize = rbD->pixelSize(); |
6023 | d.sampleCount = rbD->samples; |
6024 | } |
6025 | } |
6026 | } |
6027 | |
6028 | if (hasDepthStencil) { |
6029 | if (m_desc.depthStencilBuffer()) { |
6030 | QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer()); |
6031 | if (rhiD->caps.needsDepthStencilCombinedAttach) { |
6032 | rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
6033 | renderbuffer: depthRbD->renderbuffer); |
6034 | } else { |
6035 | rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, |
6036 | renderbuffer: depthRbD->renderbuffer); |
6037 | if (depthRbD->stencilRenderbuffer) { |
6038 | rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
6039 | renderbuffer: depthRbD->stencilRenderbuffer); |
6040 | } else { |
6041 | // packed depth-stencil |
6042 | rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, |
6043 | renderbuffer: depthRbD->renderbuffer); |
6044 | } |
6045 | } |
6046 | if (d.colorAttCount == 0) { |
6047 | d.pixelSize = depthRbD->pixelSize(); |
6048 | d.sampleCount = depthRbD->samples; |
6049 | } |
6050 | } else { |
6051 | QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture()); |
6052 | if (multiViewCount < 2) { |
6053 | if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && m_desc.depthResolveTexture()) { |
6054 | // Special path for GLES and |
6055 | // GL_EXT_multisampled_render_to_texture, for depth-stencil. |
6056 | // Relevant only when depthResolveTexture is set. |
6057 | QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture()); |
6058 | rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthResolveTexD->target, |
6059 | depthResolveTexD->texture, 0, depthTexD->sampleCount()); |
6060 | if (rhiD->isStencilSupportingFormat(format: depthResolveTexD->format())) { |
6061 | rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthResolveTexD->target, |
6062 | depthResolveTexD->texture, 0, depthTexD->sampleCount()); |
6063 | } |
6064 | } else { |
6065 | rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textarget: depthTexD->target, |
6066 | texture: depthTexD->texture, level: 0); |
6067 | if (rhiD->isStencilSupportingFormat(format: depthTexD->format())) { |
6068 | rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, textarget: depthTexD->target, |
6069 | texture: depthTexD->texture, level: 0); |
6070 | } |
6071 | } |
6072 | } else { |
6073 | if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture) { |
6074 | // And so it turns out |
6075 | // https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt |
6076 | // does not work with multisample 2D texture arrays. (at least |
6077 | // that's what Issue 30 in the extension spec seems to imply) |
6078 | // |
6079 | // There is https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multiview_texture_multisample.txt |
6080 | // that seems to resolve that, but that does not seem to |
6081 | // work (or not available) on GLES devices such as the Quest 3. |
6082 | // |
6083 | // So instead, on GLES we can use the |
6084 | // multisample-multiview-auto-resolving version (which in |
6085 | // turn is not supported on desktop GL e.g. by NVIDIA), too |
6086 | // bad we have a multisample depth texture array here as |
6087 | // every other API out there requires that. So, in absence |
6088 | // of a depthResolveTexture, create a temporary one ignoring |
6089 | // what the user has already created. |
6090 | // |
6091 | if (!m_flags.testFlag(flag: DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture()) { |
6092 | qWarning(msg: "Attempted to create a multiview+multisample QRhiTextureRenderTarget, but DoNotStoreDepthStencilContents was not set." |
6093 | " This path has no choice but to behave as if DoNotStoreDepthStencilContents was set, because QRhi is forced to create" |
6094 | " a throwaway non-multisample depth texture here. Set the flag to silence this warning, or set a depthResolveTexture."); |
6095 | } |
6096 | if (m_desc.depthResolveTexture()) { |
6097 | QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture()); |
6098 | rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, |
6099 | GL_DEPTH_ATTACHMENT, |
6100 | depthResolveTexD->texture, |
6101 | 0, |
6102 | depthTexD->sampleCount(), |
6103 | 0, |
6104 | multiViewCount); |
6105 | if (rhiD->isStencilSupportingFormat(format: depthResolveTexD->format())) { |
6106 | rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, |
6107 | GL_STENCIL_ATTACHMENT, |
6108 | depthResolveTexD->texture, |
6109 | 0, |
6110 | depthTexD->sampleCount(), |
6111 | 0, |
6112 | multiViewCount); |
6113 | } |
6114 | } else { |
6115 | if (!nonMsaaThrowawayDepthTexture) { |
6116 | rhiD->f->glGenTextures(n: 1, textures: &nonMsaaThrowawayDepthTexture); |
6117 | rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, texture: nonMsaaThrowawayDepthTexture); |
6118 | rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, levels: 1, GL_DEPTH24_STENCIL8, |
6119 | width: depthTexD->pixelSize().width(), height: depthTexD->pixelSize().height(), depth: multiViewCount); |
6120 | } |
6121 | rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, |
6122 | GL_DEPTH_ATTACHMENT, |
6123 | nonMsaaThrowawayDepthTexture, |
6124 | 0, |
6125 | depthTexD->sampleCount(), |
6126 | 0, |
6127 | multiViewCount); |
6128 | rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, |
6129 | GL_STENCIL_ATTACHMENT, |
6130 | nonMsaaThrowawayDepthTexture, |
6131 | 0, |
6132 | depthTexD->sampleCount(), |
6133 | 0, |
6134 | multiViewCount); |
6135 | } |
6136 | } else { |
6137 | // The depth texture here must be an array with at least |
6138 | // multiViewCount elements, and the format should be D24 or D32F |
6139 | // for depth only, or D24S8 for depth and stencil. |
6140 | rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->texture, |
6141 | 0, 0, multiViewCount); |
6142 | if (rhiD->isStencilSupportingFormat(format: depthTexD->format())) { |
6143 | rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->texture, |
6144 | 0, 0, multiViewCount); |
6145 | } |
6146 | } |
6147 | } |
6148 | if (d.colorAttCount == 0) { |
6149 | d.pixelSize = depthTexD->pixelSize(); |
6150 | d.sampleCount = depthTexD->sampleCount(); |
6151 | } |
6152 | } |
6153 | d.dsAttCount = 1; |
6154 | } else { |
6155 | d.dsAttCount = 0; |
6156 | } |
6157 | |
6158 | d.dpr = 1; |
6159 | d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); |
6160 | |
6161 | GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER); |
6162 | if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) { |
6163 | qWarning(msg: "Framebuffer incomplete: 0x%x", status); |
6164 | return false; |
6165 | } |
6166 | |
6167 | if (rhiD->glObjectLabel) |
6168 | rhiD->glObjectLabel(GL_FRAMEBUFFER, framebuffer, -1, m_objectName.constData()); |
6169 | |
6170 | QRhiRenderTargetAttachmentTracker::updateResIdList<QGles2Texture, QGles2RenderBuffer>(desc: m_desc, dst: &d.currentResIdList); |
6171 | |
6172 | rhiD->registerResource(res: this); |
6173 | return true; |
6174 | } |
6175 | |
6176 | QSize QGles2TextureRenderTarget::pixelSize() const |
6177 | { |
6178 | if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QGles2Texture, QGles2RenderBuffer>(desc: m_desc, currentResIdList: d.currentResIdList)) |
6179 | const_cast<QGles2TextureRenderTarget *>(this)->create(); |
6180 | |
6181 | return d.pixelSize; |
6182 | } |
6183 | |
6184 | float QGles2TextureRenderTarget::devicePixelRatio() const |
6185 | { |
6186 | return d.dpr; |
6187 | } |
6188 | |
6189 | int QGles2TextureRenderTarget::sampleCount() const |
6190 | { |
6191 | return d.sampleCount; |
6192 | } |
6193 | |
6194 | QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi) |
6195 | : QRhiShaderResourceBindings(rhi) |
6196 | { |
6197 | } |
6198 | |
6199 | QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings() |
6200 | { |
6201 | destroy(); |
6202 | } |
6203 | |
6204 | void QGles2ShaderResourceBindings::destroy() |
6205 | { |
6206 | QRHI_RES_RHI(QRhiGles2); |
6207 | if (rhiD) |
6208 | rhiD->unregisterResource(res: this); |
6209 | } |
6210 | |
6211 | bool QGles2ShaderResourceBindings::create() |
6212 | { |
6213 | QRHI_RES_RHI(QRhiGles2); |
6214 | if (!rhiD->sanityCheckShaderResourceBindings(srb: this)) |
6215 | return false; |
6216 | |
6217 | hasDynamicOffset = false; |
6218 | for (int i = 0, ie = m_bindings.size(); i != ie; ++i) { |
6219 | const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding: m_bindings.at(idx: i)); |
6220 | if (b->type == QRhiShaderResourceBinding::UniformBuffer) { |
6221 | if (b->u.ubuf.hasDynamicOffset) { |
6222 | hasDynamicOffset = true; |
6223 | break; |
6224 | } |
6225 | } |
6226 | } |
6227 | |
6228 | rhiD->updateLayoutDesc(srb: this); |
6229 | |
6230 | generation += 1; |
6231 | rhiD->registerResource(res: this, ownsNativeResources: false); |
6232 | return true; |
6233 | } |
6234 | |
6235 | void QGles2ShaderResourceBindings::updateResources(UpdateFlags flags) |
6236 | { |
6237 | Q_UNUSED(flags); |
6238 | generation += 1; |
6239 | } |
6240 | |
6241 | QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi) |
6242 | : QRhiGraphicsPipeline(rhi) |
6243 | { |
6244 | } |
6245 | |
6246 | QGles2GraphicsPipeline::~QGles2GraphicsPipeline() |
6247 | { |
6248 | destroy(); |
6249 | } |
6250 | |
6251 | void QGles2GraphicsPipeline::destroy() |
6252 | { |
6253 | if (!program) |
6254 | return; |
6255 | |
6256 | QRhiGles2::DeferredReleaseEntry e; |
6257 | e.type = QRhiGles2::DeferredReleaseEntry::Pipeline; |
6258 | |
6259 | e.pipeline.program = program; |
6260 | |
6261 | program = 0; |
6262 | uniforms.clear(); |
6263 | samplers.clear(); |
6264 | |
6265 | QRHI_RES_RHI(QRhiGles2); |
6266 | if (rhiD) { |
6267 | rhiD->releaseQueue.append(t: e); |
6268 | rhiD->unregisterResource(res: this); |
6269 | } |
6270 | } |
6271 | |
6272 | static inline bool isGraphicsStage(const QRhiShaderStage &shaderStage) |
6273 | { |
6274 | const QRhiShaderStage::Type t = shaderStage.type(); |
6275 | return t == QRhiShaderStage::Vertex |
6276 | || t == QRhiShaderStage::TessellationControl |
6277 | || t == QRhiShaderStage::TessellationEvaluation |
6278 | || t == QRhiShaderStage::Geometry |
6279 | || t == QRhiShaderStage::Fragment; |
6280 | } |
6281 | |
6282 | bool QGles2GraphicsPipeline::create() |
6283 | { |
6284 | QRHI_RES_RHI(QRhiGles2); |
6285 | |
6286 | if (program) |
6287 | destroy(); |
6288 | |
6289 | if (!rhiD->ensureContext()) |
6290 | return false; |
6291 | |
6292 | rhiD->pipelineCreationStart(); |
6293 | if (!rhiD->sanityCheckGraphicsPipeline(ps: this)) |
6294 | return false; |
6295 | |
6296 | drawMode = toGlTopology(t: m_topology); |
6297 | |
6298 | program = rhiD->f->glCreateProgram(); |
6299 | |
6300 | enum { |
6301 | VtxIdx = 0, |
6302 | TCIdx, |
6303 | TEIdx, |
6304 | GeomIdx, |
6305 | FragIdx, |
6306 | LastIdx |
6307 | }; |
6308 | const auto descIdxForStage = [](const QRhiShaderStage &shaderStage) { |
6309 | switch (shaderStage.type()) { |
6310 | case QRhiShaderStage::Vertex: |
6311 | return VtxIdx; |
6312 | case QRhiShaderStage::TessellationControl: |
6313 | return TCIdx; |
6314 | case QRhiShaderStage::TessellationEvaluation: |
6315 | return TEIdx; |
6316 | case QRhiShaderStage::Geometry: |
6317 | return GeomIdx; |
6318 | case QRhiShaderStage::Fragment: |
6319 | return FragIdx; |
6320 | default: |
6321 | break; |
6322 | } |
6323 | Q_UNREACHABLE_RETURN(VtxIdx); |
6324 | }; |
6325 | QShaderDescription desc[LastIdx]; |
6326 | QShader::SeparateToCombinedImageSamplerMappingList samplerMappingList[LastIdx]; |
6327 | bool vertexFragmentOnly = true; |
6328 | for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) { |
6329 | if (isGraphicsStage(shaderStage)) { |
6330 | const int idx = descIdxForStage(shaderStage); |
6331 | if (idx != VtxIdx && idx != FragIdx) |
6332 | vertexFragmentOnly = false; |
6333 | QShader shader = shaderStage.shader(); |
6334 | QShaderVersion shaderVersion; |
6335 | desc[idx] = shader.description(); |
6336 | if (!rhiD->shaderSource(shaderStage, shaderVersion: &shaderVersion).isEmpty()) { |
6337 | samplerMappingList[idx] = shader.separateToCombinedImageSamplerMappingList( |
6338 | key: { QShader::GlslShader, shaderVersion, shaderStage.shaderVariant() }); |
6339 | } |
6340 | } |
6341 | } |
6342 | |
6343 | QByteArray cacheKey; |
6344 | QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(stages: m_shaderStages.constData(), |
6345 | stageCount: m_shaderStages.size(), |
6346 | program, |
6347 | inputVars: desc[VtxIdx].inputVariables(), |
6348 | cacheKey: &cacheKey); |
6349 | if (cacheResult == QRhiGles2::ProgramCacheError) |
6350 | return false; |
6351 | |
6352 | if (cacheResult == QRhiGles2::ProgramCacheMiss) { |
6353 | for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) { |
6354 | if (isGraphicsStage(shaderStage)) { |
6355 | if (!rhiD->compileShader(program, shaderStage, shaderVersion: nullptr)) |
6356 | return false; |
6357 | } |
6358 | } |
6359 | |
6360 | // important when GLSL <= 150 is used that does not have location qualifiers |
6361 | for (const QShaderDescription::InOutVariable &inVar : desc[VtxIdx].inputVariables()) |
6362 | rhiD->f->glBindAttribLocation(program, index: GLuint(inVar.location), name: inVar.name); |
6363 | |
6364 | if (vertexFragmentOnly) |
6365 | rhiD->sanityCheckVertexFragmentInterface(vsDesc: desc[VtxIdx], fsDesc: desc[FragIdx]); |
6366 | |
6367 | if (!rhiD->linkProgram(program)) |
6368 | return false; |
6369 | |
6370 | if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) { |
6371 | // force replacing existing cache entry (if there is one, then |
6372 | // something is wrong with it, as there was no hit) |
6373 | rhiD->trySaveToPipelineCache(program, cacheKey, force: true); |
6374 | } else { |
6375 | // legacy QOpenGLShaderProgram style behavior: the "pipeline cache" |
6376 | // was not enabled, so instead store to the Qt 5 disk cache |
6377 | rhiD->trySaveToDiskCache(program, cacheKey); |
6378 | } |
6379 | } else { |
6380 | Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit); |
6381 | if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) { |
6382 | // just so that it ends up in the pipeline cache also when the hit was |
6383 | // from the disk cache |
6384 | rhiD->trySaveToPipelineCache(program, cacheKey); |
6385 | } |
6386 | } |
6387 | |
6388 | // Use the same work area for the vertex & fragment stages, thus ensuring |
6389 | // that we will not do superfluous glUniform calls for uniforms that are |
6390 | // present in both shaders. |
6391 | QRhiGles2::ActiveUniformLocationTracker activeUniformLocations; |
6392 | |
6393 | for (const QRhiShaderStage &shaderStage : std::as_const(t&: m_shaderStages)) { |
6394 | if (isGraphicsStage(shaderStage)) { |
6395 | const int idx = descIdxForStage(shaderStage); |
6396 | for (const QShaderDescription::UniformBlock &ub : desc[idx].uniformBlocks()) |
6397 | rhiD->gatherUniforms(program, ub, activeUniformLocations: &activeUniformLocations, dst: &uniforms); |
6398 | for (const QShaderDescription::InOutVariable &v : desc[idx].combinedImageSamplers()) |
6399 | rhiD->gatherSamplers(program, v, dst: &samplers); |
6400 | for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : samplerMappingList[idx]) |
6401 | rhiD->gatherGeneratedSamplers(program, mapping, dst: &samplers); |
6402 | } |
6403 | } |
6404 | |
6405 | std::sort(first: uniforms.begin(), last: uniforms.end(), |
6406 | comp: [](const QGles2UniformDescription &a, const QGles2UniformDescription &b) |
6407 | { |
6408 | return a.offset < b.offset; |
6409 | }); |
6410 | |
6411 | memset(s: uniformState, c: 0, n: sizeof(uniformState)); |
6412 | |
6413 | currentSrb = nullptr; |
6414 | currentSrbGeneration = 0; |
6415 | |
6416 | if (rhiD->glObjectLabel) |
6417 | rhiD->glObjectLabel(GL_PROGRAM, program, -1, m_objectName.constData()); |
6418 | |
6419 | rhiD->pipelineCreationEnd(); |
6420 | generation += 1; |
6421 | rhiD->registerResource(res: this); |
6422 | return true; |
6423 | } |
6424 | |
6425 | QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi) |
6426 | : QRhiComputePipeline(rhi) |
6427 | { |
6428 | } |
6429 | |
6430 | QGles2ComputePipeline::~QGles2ComputePipeline() |
6431 | { |
6432 | destroy(); |
6433 | } |
6434 | |
6435 | void QGles2ComputePipeline::destroy() |
6436 | { |
6437 | if (!program) |
6438 | return; |
6439 | |
6440 | QRhiGles2::DeferredReleaseEntry e; |
6441 | e.type = QRhiGles2::DeferredReleaseEntry::Pipeline; |
6442 | |
6443 | e.pipeline.program = program; |
6444 | |
6445 | program = 0; |
6446 | uniforms.clear(); |
6447 | samplers.clear(); |
6448 | |
6449 | QRHI_RES_RHI(QRhiGles2); |
6450 | if (rhiD) { |
6451 | rhiD->releaseQueue.append(t: e); |
6452 | rhiD->unregisterResource(res: this); |
6453 | } |
6454 | } |
6455 | |
6456 | bool QGles2ComputePipeline::create() |
6457 | { |
6458 | QRHI_RES_RHI(QRhiGles2); |
6459 | |
6460 | if (program) |
6461 | destroy(); |
6462 | |
6463 | if (!rhiD->ensureContext()) |
6464 | return false; |
6465 | |
6466 | rhiD->pipelineCreationStart(); |
6467 | |
6468 | const QShaderDescription csDesc = m_shaderStage.shader().description(); |
6469 | QShader::SeparateToCombinedImageSamplerMappingList csSamplerMappingList; |
6470 | QShaderVersion shaderVersion; |
6471 | if (!rhiD->shaderSource(shaderStage: m_shaderStage, shaderVersion: &shaderVersion).isEmpty()) { |
6472 | csSamplerMappingList = m_shaderStage.shader().separateToCombinedImageSamplerMappingList( |
6473 | key: { QShader::GlslShader, shaderVersion, m_shaderStage.shaderVariant() }); |
6474 | } |
6475 | |
6476 | program = rhiD->f->glCreateProgram(); |
6477 | |
6478 | QByteArray cacheKey; |
6479 | QRhiGles2::ProgramCacheResult cacheResult = rhiD->tryLoadFromDiskOrPipelineCache(stages: &m_shaderStage, stageCount: 1, program, inputVars: {}, cacheKey: &cacheKey); |
6480 | if (cacheResult == QRhiGles2::ProgramCacheError) |
6481 | return false; |
6482 | |
6483 | if (cacheResult == QRhiGles2::ProgramCacheMiss) { |
6484 | if (!rhiD->compileShader(program, shaderStage: m_shaderStage, shaderVersion: nullptr)) |
6485 | return false; |
6486 | |
6487 | if (!rhiD->linkProgram(program)) |
6488 | return false; |
6489 | |
6490 | if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) { |
6491 | // force replacing existing cache entry (if there is one, then |
6492 | // something is wrong with it, as there was no hit) |
6493 | rhiD->trySaveToPipelineCache(program, cacheKey, force: true); |
6494 | } else { |
6495 | // legacy QOpenGLShaderProgram style behavior: the "pipeline cache" |
6496 | // was not enabled, so instead store to the Qt 5 disk cache |
6497 | rhiD->trySaveToDiskCache(program, cacheKey); |
6498 | } |
6499 | } else { |
6500 | Q_ASSERT(cacheResult == QRhiGles2::ProgramCacheHit); |
6501 | if (rhiD->rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) { |
6502 | // just so that it ends up in the pipeline cache also when the hit was |
6503 | // from the disk cache |
6504 | rhiD->trySaveToPipelineCache(program, cacheKey); |
6505 | } |
6506 | } |
6507 | |
6508 | QRhiGles2::ActiveUniformLocationTracker activeUniformLocations; |
6509 | for (const QShaderDescription::UniformBlock &ub : csDesc.uniformBlocks()) |
6510 | rhiD->gatherUniforms(program, ub, activeUniformLocations: &activeUniformLocations, dst: &uniforms); |
6511 | for (const QShaderDescription::InOutVariable &v : csDesc.combinedImageSamplers()) |
6512 | rhiD->gatherSamplers(program, v, dst: &samplers); |
6513 | for (const QShader::SeparateToCombinedImageSamplerMapping &mapping : csSamplerMappingList) |
6514 | rhiD->gatherGeneratedSamplers(program, mapping, dst: &samplers); |
6515 | |
6516 | // storage images and buffers need no special steps here |
6517 | |
6518 | memset(s: uniformState, c: 0, n: sizeof(uniformState)); |
6519 | |
6520 | currentSrb = nullptr; |
6521 | currentSrbGeneration = 0; |
6522 | |
6523 | rhiD->pipelineCreationEnd(); |
6524 | generation += 1; |
6525 | rhiD->registerResource(res: this); |
6526 | return true; |
6527 | } |
6528 | |
6529 | QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi) |
6530 | : QRhiCommandBuffer(rhi) |
6531 | { |
6532 | resetState(); |
6533 | } |
6534 | |
6535 | QGles2CommandBuffer::~QGles2CommandBuffer() |
6536 | { |
6537 | destroy(); |
6538 | } |
6539 | |
6540 | void QGles2CommandBuffer::destroy() |
6541 | { |
6542 | // nothing to do here |
6543 | } |
6544 | |
6545 | QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi) |
6546 | : QRhiSwapChain(rhi), |
6547 | rt(rhi, this), |
6548 | rtLeft(rhi, this), |
6549 | rtRight(rhi, this), |
6550 | cb(rhi) |
6551 | { |
6552 | } |
6553 | |
6554 | QGles2SwapChain::~QGles2SwapChain() |
6555 | { |
6556 | destroy(); |
6557 | } |
6558 | |
6559 | void QGles2SwapChain::destroy() |
6560 | { |
6561 | QRHI_RES_RHI(QRhiGles2); |
6562 | if (rhiD) |
6563 | rhiD->unregisterResource(res: this); |
6564 | } |
6565 | |
6566 | QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer() |
6567 | { |
6568 | return &cb; |
6569 | } |
6570 | |
6571 | QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget() |
6572 | { |
6573 | return &rt; |
6574 | } |
6575 | |
6576 | QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer) |
6577 | { |
6578 | if (targetBuffer == LeftBuffer) |
6579 | return rtLeft.d.isValid() ? &rtLeft : &rt; |
6580 | else if (targetBuffer == RightBuffer) |
6581 | return rtRight.d.isValid() ? &rtRight : &rt; |
6582 | else |
6583 | Q_UNREACHABLE_RETURN(nullptr); |
6584 | } |
6585 | |
6586 | QSize QGles2SwapChain::surfacePixelSize() |
6587 | { |
6588 | Q_ASSERT(m_window); |
6589 | if (QPlatformWindow *platformWindow = m_window->handle()) |
6590 | // Prefer using QPlatformWindow geometry and DPR in order to avoid |
6591 | // errors due to rounded QWindow geometry. |
6592 | return platformWindow->geometry().size() * platformWindow->devicePixelRatio(); |
6593 | else |
6594 | return m_window->size() * m_window->devicePixelRatio(); |
6595 | } |
6596 | |
6597 | bool QGles2SwapChain::isFormatSupported(Format f) |
6598 | { |
6599 | return f == SDR; |
6600 | } |
6601 | |
6602 | QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor() |
6603 | { |
6604 | QGles2RenderPassDescriptor *rpD = new QGles2RenderPassDescriptor(m_rhi); |
6605 | QRHI_RES_RHI(QRhiGles2); |
6606 | rhiD->registerResource(res: rpD, ownsNativeResources: false); |
6607 | return rpD; |
6608 | } |
6609 | |
6610 | void QGles2SwapChain::initSwapChainRenderTarget(QGles2SwapChainRenderTarget *rt) |
6611 | { |
6612 | rt->setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget |
6613 | rt->d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc); |
6614 | rt->d.pixelSize = pixelSize; |
6615 | rt->d.dpr = float(m_window->devicePixelRatio()); |
6616 | rt->d.sampleCount = qBound(min: 1, val: m_sampleCount, max: 64); |
6617 | rt->d.colorAttCount = 1; |
6618 | rt->d.dsAttCount = m_depthStencil ? 1 : 0; |
6619 | rt->d.srgbUpdateAndBlend = m_flags.testFlag(flag: QRhiSwapChain::sRGB); |
6620 | } |
6621 | |
6622 | bool QGles2SwapChain::createOrResize() |
6623 | { |
6624 | // can be called multiple times due to window resizes |
6625 | const bool needsRegistration = !surface || surface != m_window; |
6626 | if (surface && surface != m_window) |
6627 | destroy(); |
6628 | |
6629 | surface = m_window; |
6630 | m_currentPixelSize = surfacePixelSize(); |
6631 | pixelSize = m_currentPixelSize; |
6632 | |
6633 | if (m_depthStencil && m_depthStencil->flags().testFlag(flag: QRhiRenderBuffer::UsedWithSwapChainOnly) |
6634 | && m_depthStencil->pixelSize() != pixelSize) |
6635 | { |
6636 | m_depthStencil->setPixelSize(pixelSize); |
6637 | m_depthStencil->create(); |
6638 | } |
6639 | |
6640 | initSwapChainRenderTarget(rt: &rt); |
6641 | |
6642 | if (m_window->format().stereo()) { |
6643 | initSwapChainRenderTarget(rt: &rtLeft); |
6644 | rtLeft.d.stereoTarget = QRhiSwapChain::LeftBuffer; |
6645 | initSwapChainRenderTarget(rt: &rtRight); |
6646 | rtRight.d.stereoTarget = QRhiSwapChain::RightBuffer; |
6647 | } |
6648 | |
6649 | frameCount = 0; |
6650 | |
6651 | QRHI_RES_RHI(QRhiGles2); |
6652 | if (rhiD->rhiFlags.testFlag(flag: QRhi::EnableTimestamps) && rhiD->caps.timestamps) |
6653 | timestamps.prepare(rhiD); |
6654 | |
6655 | // The only reason to register this fairly fake gl swapchain |
6656 | // object with no native resources underneath is to be able to |
6657 | // implement a safe destroy(). |
6658 | if (needsRegistration) |
6659 | rhiD->registerResource(res: this, ownsNativeResources: false); |
6660 | |
6661 | return true; |
6662 | } |
6663 | |
6664 | void QGles2SwapChainTimestamps::prepare(QRhiGles2 *rhiD) |
6665 | { |
6666 | if (!query[0]) |
6667 | rhiD->f->glGenQueries(n: TIMESTAMP_PAIRS * 2, ids: query); |
6668 | } |
6669 | |
6670 | void QGles2SwapChainTimestamps::destroy(QRhiGles2 *rhiD) |
6671 | { |
6672 | rhiD->f->glDeleteQueries(n: TIMESTAMP_PAIRS * 2, ids: query); |
6673 | memset(s: active, c: 0, n: sizeof(active)); |
6674 | memset(s: query, c: 0, n: sizeof(query)); |
6675 | } |
6676 | |
6677 | bool QGles2SwapChainTimestamps::tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec) |
6678 | { |
6679 | if (!active[pairIndex]) |
6680 | return false; |
6681 | |
6682 | GLuint tsStart = query[pairIndex * 2]; |
6683 | GLuint tsEnd = query[pairIndex * 2 + 1]; |
6684 | |
6685 | GLuint ready = GL_FALSE; |
6686 | rhiD->f->glGetQueryObjectuiv(id: tsEnd, GL_QUERY_RESULT_AVAILABLE, params: &ready); |
6687 | |
6688 | if (!ready) |
6689 | return false; |
6690 | |
6691 | bool result = false; |
6692 | quint64 timestamps[2]; |
6693 | rhiD->glGetQueryObjectui64v(tsStart, GL_QUERY_RESULT, ×tamps[0]); |
6694 | rhiD->glGetQueryObjectui64v(tsEnd, GL_QUERY_RESULT, ×tamps[1]); |
6695 | |
6696 | if (timestamps[1] >= timestamps[0]) { |
6697 | const quint64 nanoseconds = timestamps[1] - timestamps[0]; |
6698 | *elapsedSec = nanoseconds / 1000000000.0; |
6699 | result = true; |
6700 | } |
6701 | |
6702 | active[pairIndex] = false; |
6703 | return result; |
6704 | } |
6705 | |
6706 | QT_END_NAMESPACE |
6707 |
Definitions
- QRhiGles2InitParams
- newFallbackSurface
- QRhiGles2
- currentSurfaceForCurrentContext
- evaluateFallbackSurface
- ensureContext
- toGlCompressedTextureFormat
- create
- destroy
- executeDeferredReleases
- supportedSampleCounts
- createSwapChain
- createBuffer
- ubufAlignment
- isYUpInFramebuffer
- isYUpInNDC
- isClipDepthZeroToOne
- clipSpaceCorrMatrix
- toGlTextureFormat
- isTextureFormatSupported
- isFeatureSupported
- resourceLimit
- nativeHandles
- driverInfo
- statistics
- makeThreadLocalNativeContextCurrent
- releaseCachedResources
- isDeviceLost
- QGles2PipelineCacheDataHeader
- pipelineCacheData
- setPipelineCacheData
- createRenderBuffer
- createTexture
- createSampler
- createTextureRenderTarget
- createGraphicsPipeline
- createShaderResourceBindings
- createComputePipeline
- setGraphicsPipeline
- setShaderResources
- setVertexInput
- setViewport
- setScissor
- setBlendConstants
- setStencilRef
- draw
- drawIndexed
- debugMarkBegin
- debugMarkEnd
- debugMarkMsg
- nativeHandles
- addBoundaryCommand
- beginExternal
- endExternal
- lastCompletedGpuTime
- beginFrame
- endFrame
- beginOffscreenFrame
- endOffscreenFrame
- finish
- bufferAccessIsWrite
- textureAccessIsWrite
- barriersForBuffer
- barriersForTexture
- trackedBufferBarrier
- trackedImageBarrier
- enqueueSubresUpload
- enqueueResourceUpdates
- toGlTopology
- toGlCullMode
- toGlFrontFace
- toGlBlendFactor
- toGlBlendOp
- toGlCompareOp
- toGlStencilOp
- toGlPolygonMode
- toGlMinFilter
- toGlMagFilter
- toGlWrapMode
- toGlTextureCompareFunc
- toGlAccess
- toPassTrackerUsageState
- toGlAccess
- toPassTrackerUsageState
- trackedRegisterBuffer
- trackedRegisterTexture
- CommandBufferExecTrackedState
- bindVertexIndexBufferWithStateReset
- executeCommandBuffer
- executeBindGraphicsPipeline
- qrhi_std140_to_packed
- bindCombinedSampler
- bindShaderResources
- resourceUpdate
- enqueueBindFramebuffer
- enqueueBarriersForPass
- beginPass
- endPass
- beginComputePass
- endComputePass
- setComputePipeline
- qrhigl_accumulateComputeResource
- dispatch
- toGlShaderType
- shaderSource
- compileShader
- linkProgram
- registerUniformIfActive
- gatherUniforms
- gatherSamplers
- gatherGeneratedSamplers
- sanityCheckVertexFragmentInterface
- isProgramBinaryDiskCacheEnabled
- qrhi_programBinaryCache
- toShaderStage
- tryLoadFromDiskOrPipelineCache
- trySaveToDiskCache
- trySaveToPipelineCache
- QGles2Buffer
- ~QGles2Buffer
- destroy
- create
- nativeBuffer
- beginFullDynamicBufferUpdateForCurrentFrame
- endFullDynamicBufferUpdateForCurrentFrame
- fullDynamicBufferUpdateForCurrentFrame
- QGles2RenderBuffer
- ~QGles2RenderBuffer
- destroy
- create
- createFrom
- backingFormat
- QGles2Texture
- ~QGles2Texture
- destroy
- prepareCreate
- create
- createFrom
- nativeTexture
- QGles2Sampler
- ~QGles2Sampler
- destroy
- create
- QGles2RenderPassDescriptor
- ~QGles2RenderPassDescriptor
- destroy
- isCompatible
- newCompatibleRenderPassDescriptor
- serializedFormat
- QGles2SwapChainRenderTarget
- ~QGles2SwapChainRenderTarget
- destroy
- pixelSize
- devicePixelRatio
- sampleCount
- QGles2TextureRenderTarget
- ~QGles2TextureRenderTarget
- destroy
- newCompatibleRenderPassDescriptor
- create
- pixelSize
- devicePixelRatio
- sampleCount
- QGles2ShaderResourceBindings
- ~QGles2ShaderResourceBindings
- destroy
- create
- updateResources
- QGles2GraphicsPipeline
- ~QGles2GraphicsPipeline
- destroy
- isGraphicsStage
- create
- QGles2ComputePipeline
- ~QGles2ComputePipeline
- destroy
- create
- QGles2CommandBuffer
- ~QGles2CommandBuffer
- destroy
- QGles2SwapChain
- ~QGles2SwapChain
- destroy
- currentFrameCommandBuffer
- currentFrameRenderTarget
- currentFrameRenderTarget
- surfacePixelSize
- isFormatSupported
- newCompatibleRenderPassDescriptor
- initSwapChainRenderTarget
- createOrResize
- prepare
- destroy
Learn Advanced QML with KDAB
Find out more