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

source code of qtbase/src/gui/rhi/qrhigles2.cpp