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