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

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