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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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